From a33cafcb7e175ad8b3b80b8c9419a32e009ec702 Mon Sep 17 00:00:00 2001 From: jfecher Date: Thu, 11 Jul 2024 15:11:33 -0500 Subject: [PATCH] feat: Allow arguments to attribute functions (#5494) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/5473 Resolves https://github.com/noir-lang/noir/issues/5476 ## Summary\* Allows arguments to attribute functions. - These arguments are all quoted by default. This is to replicate something like Rust's `#[derive(Foo, Bar)]` where `Foo` and `Bar` are meant to be traits rather than values in scope. - Varargs is also possible if the attribute accepts a slice of Quoted values rather than individual Quoted arguments. ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_frontend/src/elaborator/mod.rs | 67 ++++++++++++++++++- .../src/hir/def_collector/dc_crate.rs | 2 +- cspell.json | 2 + .../attribute_args/Nargo.toml | 7 ++ .../attribute_args/src/main.nr | 20 ++++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 test_programs/compile_success_empty/attribute_args/Nargo.toml create mode 100644 test_programs/compile_success_empty/attribute_args/src/main.nr diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 7fa05c9612e..f736ad76970 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -26,6 +26,7 @@ use crate::{ traits::TraitConstraint, types::{Generics, Kind, ResolvedGeneric}, }, + lexer::Lexer, macros_api::{ BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern, SecondaryAttribute, StructId, @@ -35,6 +36,7 @@ use crate::{ TypeAliasId, }, parser::TopLevelStatement, + token::Tokens, Shared, Type, TypeBindings, TypeVariable, }; use crate::{ @@ -1284,18 +1286,24 @@ impl<'context> Elaborator<'context> { span: Span, generated_items: &mut CollectedItems, ) -> Result<(), (CompilationError, FileId)> { + let location = Location::new(span, self.file); + let (function_name, mut arguments) = + Self::parse_attribute(&attribute, location).unwrap_or((attribute, Vec::new())); + let id = self - .lookup_global(Path::from_single(attribute, span)) + .lookup_global(Path::from_single(function_name, span)) .map_err(|_| (ResolverError::UnknownAnnotation { span }.into(), self.file))?; let definition = self.interner.definition(id); let DefinitionKind::Function(function) = definition.kind else { return Err((ResolverError::NonFunctionInAnnotation { span }.into(), self.file)); }; - let location = Location::new(span, self.file); + + self.handle_varargs_attribute(function, &mut arguments, location); + arguments.insert(0, (Value::StructDefinition(struct_id), location)); + let mut interpreter_errors = vec![]; let mut interpreter = self.setup_interpreter(&mut interpreter_errors); - let arguments = vec![(Value::StructDefinition(struct_id), location)]; let value = interpreter .call_function(function, arguments, TypeBindings::new(), location) @@ -1313,6 +1321,59 @@ impl<'context> Elaborator<'context> { Ok(()) } + /// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into + /// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`) + fn parse_attribute( + annotation: &str, + location: Location, + ) -> Option<(String, Vec<(Value, Location)>)> { + let (tokens, errors) = Lexer::lex(annotation); + if !errors.is_empty() { + return None; + } + + let mut tokens = tokens.0; + if tokens.len() >= 4 { + // Remove the outer `ident ( )` wrapping the function arguments + let first = tokens.remove(0).into_token(); + let second = tokens.remove(0).into_token(); + + // Last token is always an EndOfInput + let _ = tokens.pop().unwrap().into_token(); + let last = tokens.pop().unwrap().into_token(); + + use crate::lexer::token::Token::*; + if let (Ident(name), LeftParen, RightParen) = (first, second, last) { + let args = tokens.split(|token| *token.token() == Comma); + let args = + vecmap(args, |arg| (Value::Code(Rc::new(Tokens(arg.to_vec()))), location)); + return Some((name, args)); + } + } + + None + } + + /// Checks if the given attribute function is a varargs function. + /// If so, we should pass its arguments in one slice rather than as separate arguments. + fn handle_varargs_attribute( + &mut self, + function: FuncId, + arguments: &mut Vec<(Value, Location)>, + location: Location, + ) { + let meta = self.interner.function_meta(&function); + let parameters = &meta.parameters.0; + + // If the last parameter is a slice, this is a varargs function. + if parameters.last().map_or(false, |(_, typ, _)| matches!(typ, Type::Slice(_))) { + let typ = Type::Slice(Box::new(Type::Quoted(crate::QuotedType::Quoted))); + let slice_elements = arguments.drain(..).map(|(value, _)| value); + let slice = Value::Slice(slice_elements.collect(), typ); + arguments.push((slice, location)); + } + } + pub fn resolve_struct_fields( &mut self, unresolved: NoirStruct, diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index ba93eb87ef8..8b926c8107f 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -202,7 +202,7 @@ pub enum CompilationError { } impl CompilationError { - fn is_error(&self) -> bool { + pub fn is_error(&self) -> bool { let diagnostic = CustomDiagnostic::from(self); diagnostic.is_error() } diff --git a/cspell.json b/cspell.json index 2a9bfb4b544..689b72435ef 100644 --- a/cspell.json +++ b/cspell.json @@ -206,6 +206,8 @@ "unoptimized", "urem", "USERPROFILE", + "vararg", + "varargs", "vecmap", "vitkov", "wasi", diff --git a/test_programs/compile_success_empty/attribute_args/Nargo.toml b/test_programs/compile_success_empty/attribute_args/Nargo.toml new file mode 100644 index 00000000000..8efe5d203d1 --- /dev/null +++ b/test_programs/compile_success_empty/attribute_args/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "attribute_args" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/attribute_args/src/main.nr b/test_programs/compile_success_empty/attribute_args/src/main.nr new file mode 100644 index 00000000000..44b9c20460f --- /dev/null +++ b/test_programs/compile_success_empty/attribute_args/src/main.nr @@ -0,0 +1,20 @@ +#[attr_with_args(a b, c d)] +#[varargs(one, two)] +#[varargs(one, two, three, four)] +struct Foo {} + +comptime fn attr_with_args(s: StructDefinition, a: Quoted, b: Quoted) { + // Ensure all variables are in scope. + // We can't print them since that breaks the test runner. + let _ = s; + let _ = a; + let _ = b; +} + +comptime fn varargs(s: StructDefinition, t: [Quoted]) { + let _ = s; + for _ in t {} + assert(t.len() < 5); +} + +fn main() {}