Skip to content

Commit a950195

Browse files
asteritejfecher
andauthored
feat: add FunctionDef::has_named_attribute (#5870)
# Description ## Problem Part of #5668 ## Summary Adds `FunctionDef::has_named_attributes` and also changes `Expr::resolve` to take an optional `FunctionDefinition` so you can resolve it relative to another function. ## Additional Context Now the `inject_context` macro works like it needs to for Aztec-Packages!... with the caveat that not all expressions are currently handled in `Expr::mutate` yet. ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[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. --------- Co-authored-by: jfecher <jake@aztecprotocol.com>
1 parent be9c9e1 commit a950195

12 files changed

Lines changed: 140 additions & 34 deletions

File tree

compiler/noirc_frontend/src/elaborator/comptime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl<'context> Elaborator<'context> {
168168
/// Parses an attribute in the form of a function call (e.g. `#[foo(a b, c d)]`) into
169169
/// the function and quoted arguments called (e.g. `("foo", vec![(a b, location), (c d, location)])`)
170170
#[allow(clippy::type_complexity)]
171-
fn parse_attribute(
171+
pub(crate) fn parse_attribute(
172172
annotation: &str,
173173
file: FileId,
174174
) -> Result<Option<(Expression, Vec<Expression>)>, (CompilationError, FileId)> {

compiler/noirc_frontend/src/elaborator/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,11 @@ impl<'context> Elaborator<'context> {
816816
None
817817
};
818818

819+
let attributes = func.secondary_attributes().iter();
820+
let attributes =
821+
attributes.filter_map(|secondary_attribute| secondary_attribute.as_custom());
822+
let attributes = attributes.map(|str| str.to_string()).collect();
823+
819824
let meta = FuncMeta {
820825
name: name_ident,
821826
kind: func.kind,
@@ -839,6 +844,7 @@ impl<'context> Elaborator<'context> {
839844
function_body: FunctionBody::Unresolved(func.kind, body, func.def.span),
840845
self_type: self.self_type.clone(),
841846
source_file: self.file,
847+
custom_attributes: attributes,
842848
};
843849

844850
self.interner.push_fn_meta(meta, func_id);

compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::{
2525
FunctionReturnType, IntegerBitSize, LValue, Literal, Statement, StatementKind, UnaryOp,
2626
UnresolvedType, UnresolvedTypeData, Visibility,
2727
},
28+
elaborator::Elaborator,
2829
hir::comptime::{
2930
errors::IResult,
3031
value::{ExprValue, TypedExpr},
@@ -94,6 +95,9 @@ impl<'local, 'context> Interpreter<'local, 'context> {
9495
"expr_resolve" => expr_resolve(self, arguments, location),
9596
"is_unconstrained" => Ok(Value::Bool(true)),
9697
"function_def_body" => function_def_body(interner, arguments, location),
98+
"function_def_has_named_attribute" => {
99+
function_def_has_named_attribute(interner, arguments, location)
100+
}
97101
"function_def_name" => function_def_name(interner, arguments, location),
98102
"function_def_parameters" => function_def_parameters(interner, arguments, location),
99103
"function_def_return_type" => function_def_return_type(interner, arguments, location),
@@ -1412,36 +1416,53 @@ where
14121416
option(return_type, option_value)
14131417
}
14141418

1415-
// fn resolve(self) -> TypedExpr
1419+
// fn resolve(self, in_function: Option<FunctionDefinition>) -> TypedExpr
14161420
fn expr_resolve(
14171421
interpreter: &mut Interpreter,
14181422
arguments: Vec<(Value, Location)>,
14191423
location: Location,
14201424
) -> IResult<Value> {
1421-
let self_argument = check_one_argument(arguments, location)?;
1425+
let (self_argument, func) = check_two_arguments(arguments, location)?;
14221426
let self_argument_location = self_argument.1;
14231427
let expr_value = get_expr(interpreter.elaborator.interner, self_argument)?;
14241428
let expr_value = unwrap_expr_value(interpreter.elaborator.interner, expr_value);
14251429

1426-
let value =
1427-
interpreter.elaborate_item(interpreter.current_function, |elaborator| match expr_value {
1428-
ExprValue::Expression(expression_kind) => {
1429-
let expr = Expression { kind: expression_kind, span: self_argument_location.span };
1430-
let (expr_id, _) = elaborator.elaborate_expression(expr);
1431-
Value::TypedExpr(TypedExpr::ExprId(expr_id))
1432-
}
1433-
ExprValue::Statement(statement_kind) => {
1434-
let statement =
1435-
Statement { kind: statement_kind, span: self_argument_location.span };
1436-
let (stmt_id, _) = elaborator.elaborate_statement(statement);
1437-
Value::TypedExpr(TypedExpr::StmtId(stmt_id))
1438-
}
1439-
ExprValue::LValue(lvalue) => {
1440-
let expr = lvalue.as_expression();
1441-
let (expr_id, _) = elaborator.elaborate_expression(expr);
1442-
Value::TypedExpr(TypedExpr::ExprId(expr_id))
1443-
}
1444-
});
1430+
let Value::Struct(fields, _) = func.0 else {
1431+
panic!("Expected second argument to be a struct");
1432+
};
1433+
1434+
let is_some = fields.get(&Rc::new("_is_some".to_string())).unwrap();
1435+
let Value::Bool(is_some) = is_some else {
1436+
panic!("Expected is_some to be a boolean");
1437+
};
1438+
1439+
let function_to_resolve_in = if *is_some {
1440+
let value = fields.get(&Rc::new("_value".to_string())).unwrap();
1441+
let Value::FunctionDefinition(func_id) = value else {
1442+
panic!("Expected option value to be a FunctionDefinition");
1443+
};
1444+
Some(*func_id)
1445+
} else {
1446+
interpreter.current_function
1447+
};
1448+
1449+
let value = interpreter.elaborate_item(function_to_resolve_in, |elaborator| match expr_value {
1450+
ExprValue::Expression(expression_kind) => {
1451+
let expr = Expression { kind: expression_kind, span: self_argument_location.span };
1452+
let (expr_id, _) = elaborator.elaborate_expression(expr);
1453+
Value::TypedExpr(TypedExpr::ExprId(expr_id))
1454+
}
1455+
ExprValue::Statement(statement_kind) => {
1456+
let statement = Statement { kind: statement_kind, span: self_argument_location.span };
1457+
let (stmt_id, _) = elaborator.elaborate_statement(statement);
1458+
Value::TypedExpr(TypedExpr::StmtId(stmt_id))
1459+
}
1460+
ExprValue::LValue(lvalue) => {
1461+
let expr = lvalue.as_expression();
1462+
let (expr_id, _) = elaborator.elaborate_expression(expr);
1463+
Value::TypedExpr(TypedExpr::ExprId(expr_id))
1464+
}
1465+
});
14451466

14461467
Ok(value)
14471468
}
@@ -1487,6 +1508,41 @@ fn function_def_body(
14871508
}
14881509
}
14891510

1511+
// fn has_named_attribute(self, name: Quoted) -> bool
1512+
fn function_def_has_named_attribute(
1513+
interner: &NodeInterner,
1514+
arguments: Vec<(Value, Location)>,
1515+
location: Location,
1516+
) -> IResult<Value> {
1517+
let (self_argument, name) = check_two_arguments(arguments, location)?;
1518+
let func_id = get_function_def(self_argument)?;
1519+
let name = get_quoted(name)?;
1520+
let func_meta = interner.function_meta(&func_id);
1521+
let attributes = &func_meta.custom_attributes;
1522+
if attributes.is_empty() {
1523+
return Ok(Value::Bool(false));
1524+
};
1525+
1526+
let name = name.iter().map(|token| token.to_string()).collect::<Vec<_>>().join("");
1527+
1528+
for attribute in attributes {
1529+
let parse_result = Elaborator::parse_attribute(attribute, location.file);
1530+
let Ok(Some((function, _arguments))) = parse_result else {
1531+
continue;
1532+
};
1533+
1534+
let ExpressionKind::Variable(path) = function.kind else {
1535+
continue;
1536+
};
1537+
1538+
if path.last_name() == name {
1539+
return Ok(Value::Bool(true));
1540+
}
1541+
}
1542+
1543+
Ok(Value::Bool(false))
1544+
}
1545+
14901546
// fn name(self) -> Quoted
14911547
fn function_def_name(
14921548
interner: &NodeInterner,

compiler/noirc_frontend/src/hir_def/function.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ pub struct FuncMeta {
164164
/// If this function is from an impl (trait or regular impl), this
165165
/// is the object type of the impl. Otherwise this is None.
166166
pub self_type: Option<Type>,
167+
168+
/// Custom attributes attached to this function.
169+
pub custom_attributes: Vec<String>,
167170
}
168171

169172
#[derive(Debug, Clone)]

compiler/noirc_frontend/src/lexer/token.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,16 @@ pub enum SecondaryAttribute {
870870
Varargs,
871871
}
872872

873+
impl SecondaryAttribute {
874+
pub(crate) fn as_custom(&self) -> Option<&str> {
875+
if let Self::Custom(str) = self {
876+
Some(str)
877+
} else {
878+
None
879+
}
880+
}
881+
}
882+
873883
impl fmt::Display for SecondaryAttribute {
874884
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
875885
match self {

docs/docs/noir/standard_library/meta/expr.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,12 @@ Returns this expression as a `Quoted` value. It's the same as `quote { $self }`.
190190

191191
#include_code resolve noir_stdlib/src/meta/expr.nr rust
192192

193-
Resolves and type-checks this expression and returns the result as a `TypedExpr`. If any names used by this expression are not in scope or if there are any type errors, this will give compiler errors as if the expression was written directly into the current `comptime` function.
193+
Resolves and type-checks this expression and returns the result as a `TypedExpr`.
194+
195+
The `in_function` argument specifies where the expression is resolved:
196+
- If it's `none`, the expression is resolved in the function where `resolve` was called
197+
- If it's `some`, the expression is resolved in the given function
198+
199+
If any names used by this expression are not in scope or if there are any type errors,
200+
this will give compiler errors as if the expression was written directly into
201+
the current `comptime` function.

docs/docs/noir/standard_library/meta/function_def.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Returns the body of the function as an expression. This is only valid
1515
on functions in the current crate which have not yet been resolved.
1616
This means any functions called at compile-time are invalid targets for this method.
1717

18+
### has_named_attribute
19+
20+
#include_code has_named_attribute noir_stdlib/src/meta/function_def.nr rust
21+
22+
Returns true if this function has a custom attribute with the given name.
23+
1824
### name
1925

2026
#include_code name noir_stdlib/src/meta/function_def.nr rust

noir_stdlib/src/meta/expr.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl Expr {
154154

155155
#[builtin(expr_resolve)]
156156
// docs:start:resolve
157-
fn resolve(self) -> TypedExpr {}
157+
fn resolve(self, in_function: Option<FunctionDefinition>) -> TypedExpr {}
158158
// docs:end:resolve
159159
}
160160

noir_stdlib/src/meta/function_def.nr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ impl FunctionDefinition {
44
fn body(self) -> Expr {}
55
// docs:end:body
66

7+
#[builtin(function_def_has_named_attribute)]
8+
// docs:start:has_named_attribute
9+
fn has_named_attribute(self, name: Quoted) -> bool {}
10+
// docs:end:has_named_attribute
11+
712
#[builtin(function_def_name)]
813
// docs:start:name
914
fn name(self) -> Quoted {}

test_programs/compile_success_empty/comptime_function_definition/src/main.nr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ comptime fn function_attr(f: FunctionDefinition) {
3232

3333
// Check FunctionDefinition::name
3434
assert_eq(f.name(), quote { foo });
35+
36+
assert(f.has_named_attribute(quote { function_attr }));
3537
}
3638

3739
#[mutate_add_one]

0 commit comments

Comments
 (0)