Skip to content

Commit

Permalink
feat: Allow arguments to attribute functions (noir-lang#5494)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves noir-lang#5473
Resolves noir-lang#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.
  • Loading branch information
jfecher authored Jul 11, 2024
1 parent 90b636e commit a33cafc
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 4 deletions.
67 changes: 64 additions & 3 deletions compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
traits::TraitConstraint,
types::{Generics, Kind, ResolvedGeneric},
},
lexer::Lexer,
macros_api::{
BlockExpression, Ident, NodeInterner, NoirFunction, NoirStruct, Pattern,
SecondaryAttribute, StructId,
Expand All @@ -35,6 +36,7 @@ use crate::{
TypeAliasId,
},
parser::TopLevelStatement,
token::Tokens,
Shared, Type, TypeBindings, TypeVariable,
};
use crate::{
Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@
"unoptimized",
"urem",
"USERPROFILE",
"vararg",
"varargs",
"vecmap",
"vitkov",
"wasi",
Expand Down
7 changes: 7 additions & 0 deletions test_programs/compile_success_empty/attribute_args/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "attribute_args"
type = "bin"
authors = [""]
compiler_version = ">=0.31.0"

[dependencies]
20 changes: 20 additions & 0 deletions test_programs/compile_success_empty/attribute_args/src/main.nr
Original file line number Diff line number Diff line change
@@ -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() {}

0 comments on commit a33cafc

Please sign in to comment.