Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow arguments to attribute functions #5494

Merged
merged 5 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
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
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
}

/// Checks if the given attribute function is a varargs function.
jfecher marked this conversation as resolved.
Show resolved Hide resolved
/// 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(_))) {
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
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() {}
Loading