Skip to content

Commit 982380e

Browse files
authored
feat: Add aztec selectors for event structs (#2983)
1 parent a3593c0 commit 982380e

File tree

3 files changed

+244
-30
lines changed

3 files changed

+244
-30
lines changed

compiler/noirc_frontend/src/hir/def_map/aztec_library.rs

Lines changed: 228 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use acvm::FieldElement;
2+
use iter_extended::vecmap;
23
use noirc_errors::Span;
34

45
use crate::graph::CrateId;
56
use crate::hir::def_collector::errors::DefCollectorErrorKind;
7+
use crate::hir_def::expr::{HirExpression, HirLiteral};
8+
use crate::hir_def::stmt::HirStatement;
9+
use crate::node_interner::{NodeInterner, StructId};
610
use crate::token::SecondaryAttribute;
711
use crate::{
812
hir::Context, BlockExpression, CallExpression, CastExpression, Distinctness, Expression,
@@ -11,9 +15,14 @@ use crate::{
1115
ParsedModule, Path, PathKind, Pattern, Statement, UnresolvedType, UnresolvedTypeData,
1216
Visibility,
1317
};
14-
use crate::{PrefixExpression, UnaryOp};
18+
use crate::{
19+
FunctionDefinition, NoirStruct, PrefixExpression, Shared, Signedness, StructType, Type,
20+
TypeBinding, TypeImpl, TypeVariableKind, UnaryOp,
21+
};
1522
use fm::FileId;
1623

24+
use super::ModuleDefId;
25+
1726
//
1827
// Helper macros for creating noir ast nodes
1928
//
@@ -163,7 +172,7 @@ pub(crate) fn transform(
163172
for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) {
164173
let storage_defined = check_for_storage_definition(&submodule.contents);
165174

166-
if transform_module(&mut submodule.contents.functions, storage_defined) {
175+
if transform_module(&mut submodule.contents, storage_defined) {
167176
match check_for_aztec_dependency(crate_id, context) {
168177
Ok(()) => include_relevant_imports(&mut submodule.contents),
169178
Err(file_id) => {
@@ -175,6 +184,15 @@ pub(crate) fn transform(
175184
Ok(ast)
176185
}
177186

187+
//
188+
// Transform Hir Nodes for Aztec
189+
//
190+
191+
/// Completes the Hir with data gathered from type resolution
192+
pub(crate) fn transform_hir(crate_id: &CrateId, context: &mut Context) {
193+
transform_events(crate_id, context);
194+
}
195+
178196
/// Includes an import to the aztec library if it has not been included yet
179197
fn include_relevant_imports(ast: &mut ParsedModule) {
180198
// Create the aztec import path using the assumed chained_dep! macro
@@ -206,34 +224,45 @@ fn check_for_storage_definition(module: &ParsedModule) -> bool {
206224
module.types.iter().any(|function| function.name.0.contents == "Storage")
207225
}
208226

209-
/// Determines if the function is annotated with `aztec(private)` or `aztec(public)`
210-
/// If it is, it calls the `transform` function which will perform the required transformations.
211-
/// Returns true if an annotated function is found, false otherwise
212-
fn transform_module(functions: &mut [NoirFunction], storage_defined: bool) -> bool {
213-
let mut has_annotated_functions = false;
214-
for func in functions.iter_mut() {
227+
/// Checks if an attribute is a custom attribute with a specific name
228+
fn is_custom_attribute(attr: &SecondaryAttribute, attribute_name: &str) -> bool {
229+
if let SecondaryAttribute::Custom(custom_attr) = attr {
230+
custom_attr.as_str() == attribute_name
231+
} else {
232+
false
233+
}
234+
}
235+
236+
/// Determines if ast nodes are annotated with aztec attributes.
237+
/// For annotated functions it calls the `transform` function which will perform the required transformations.
238+
/// Returns true if an annotated node is found, false otherwise
239+
fn transform_module(module: &mut ParsedModule, storage_defined: bool) -> bool {
240+
let mut has_transformed_module = false;
241+
242+
for structure in module.types.iter_mut() {
243+
if structure.attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Event)) {
244+
module.impls.push(generate_selector_impl(structure));
245+
has_transformed_module = true;
246+
}
247+
}
248+
249+
for func in module.functions.iter_mut() {
215250
for secondary_attribute in func.def.attributes.secondary.clone() {
216-
if let SecondaryAttribute::Custom(custom_attribute) = secondary_attribute {
217-
match custom_attribute.as_str() {
218-
"aztec(private)" => {
219-
transform_function("Private", func, storage_defined);
220-
has_annotated_functions = true;
221-
}
222-
"aztec(public)" => {
223-
transform_function("Public", func, storage_defined);
224-
has_annotated_functions = true;
225-
}
226-
_ => continue,
227-
}
251+
if is_custom_attribute(&secondary_attribute, "aztec(private)") {
252+
transform_function("Private", func, storage_defined);
253+
has_transformed_module = true;
254+
} else if is_custom_attribute(&secondary_attribute, "aztec(public)") {
255+
transform_function("Public", func, storage_defined);
256+
has_transformed_module = true;
228257
}
229258
}
230259
// Add the storage struct to the beginning of the function if it is unconstrained in an aztec contract
231260
if storage_defined && func.def.is_unconstrained {
232261
transform_unconstrained(func);
233-
has_annotated_functions = true;
262+
has_transformed_module = true;
234263
}
235264
}
236-
has_annotated_functions
265+
has_transformed_module
237266
}
238267

239268
/// If it does, it will insert the following things:
@@ -293,6 +322,141 @@ fn transform_unconstrained(func: &mut NoirFunction) {
293322
func.def.body.0.insert(0, abstract_storage("Unconstrained", true));
294323
}
295324

325+
fn collect_crate_structs(crate_id: &CrateId, context: &Context) -> Vec<StructId> {
326+
context
327+
.def_map(crate_id)
328+
.expect("ICE: Missing crate in def_map")
329+
.modules()
330+
.iter()
331+
.flat_map(|(_, module)| {
332+
module.type_definitions().filter_map(|typ| {
333+
if let ModuleDefId::TypeId(struct_id) = typ {
334+
Some(struct_id)
335+
} else {
336+
None
337+
}
338+
})
339+
})
340+
.collect()
341+
}
342+
343+
/// Substitutes the signature literal that was introduced in the selector method previously with the actual signature.
344+
fn transform_event(struct_id: StructId, interner: &mut NodeInterner) {
345+
let selector_id =
346+
interner.lookup_method(struct_id, "selector").expect("Selector method not found");
347+
let selector_function = interner.function(&selector_id);
348+
349+
let compute_selector_statement = interner.statement(
350+
selector_function
351+
.block(interner)
352+
.statements()
353+
.first()
354+
.expect("Compute selector statement not found"),
355+
);
356+
357+
let compute_selector_expression = match compute_selector_statement {
358+
HirStatement::Expression(expression_id) => match interner.expression(&expression_id) {
359+
HirExpression::Call(hir_call_expression) => Some(hir_call_expression),
360+
_ => None,
361+
},
362+
_ => None,
363+
}
364+
.expect("Compute selector statement is not a call expression");
365+
366+
let first_arg_id = compute_selector_expression
367+
.arguments
368+
.first()
369+
.expect("Missing argument for compute selector");
370+
371+
match interner.expression(first_arg_id) {
372+
HirExpression::Literal(HirLiteral::Str(signature))
373+
if signature == SIGNATURE_PLACEHOLDER =>
374+
{
375+
let selector_literal_id = first_arg_id;
376+
let compute_selector_call_id = compute_selector_expression.func;
377+
378+
let structure = interner.get_struct(struct_id);
379+
let signature = event_signature(&structure.borrow());
380+
interner.update_expression(*selector_literal_id, |expr| {
381+
*expr = HirExpression::Literal(HirLiteral::Str(signature.clone()));
382+
});
383+
384+
// Also update the type! It might have a different length now than the placeholder.
385+
interner.push_expr_type(
386+
selector_literal_id,
387+
Type::String(Box::new(Type::Constant(signature.len() as u64))),
388+
);
389+
interner.push_expr_type(
390+
&compute_selector_call_id,
391+
Type::Function(
392+
vec![Type::String(Box::new(Type::TypeVariable(
393+
Shared::new(TypeBinding::Bound(Type::Constant(signature.len() as u64))),
394+
TypeVariableKind::Normal,
395+
)))],
396+
Box::new(Type::FieldElement),
397+
Box::new(Type::Unit),
398+
),
399+
);
400+
}
401+
_ => unreachable!("Signature placeholder literal does not match"),
402+
}
403+
}
404+
405+
fn transform_events(crate_id: &CrateId, context: &mut Context) {
406+
for struct_id in collect_crate_structs(crate_id, context) {
407+
let attributes = context.def_interner.struct_attributes(&struct_id);
408+
if attributes.iter().any(|attr| matches!(attr, SecondaryAttribute::Event)) {
409+
transform_event(struct_id, &mut context.def_interner);
410+
}
411+
}
412+
}
413+
414+
const SIGNATURE_PLACEHOLDER: &str = "SIGNATURE_PLACEHOLDER";
415+
416+
/// Generates the impl for an event selector
417+
///
418+
/// Inserts the following code:
419+
/// ```noir
420+
/// impl SomeStruct {
421+
/// fn selector() -> Field {
422+
/// aztec::oracle::compute_selector::compute_selector("SIGNATURE_PLACEHOLDER")
423+
/// }
424+
/// }
425+
/// ```
426+
///
427+
/// This allows developers to emit events without having to write the signature of the event every time they emit it.
428+
/// The signature cannot be known at this point since types are not resolved yet, so we use a signature placeholder.
429+
/// It'll get resolved after by transforming the HIR.
430+
fn generate_selector_impl(structure: &mut NoirStruct) -> TypeImpl {
431+
let struct_type = make_type(UnresolvedTypeData::Named(path(structure.name.clone()), vec![]));
432+
433+
let selector_fun_body = BlockExpression(vec![Statement::Expression(call(
434+
variable_path(chained_path!("aztec", "oracle", "compute_selector", "compute_selector")),
435+
vec![expression(ExpressionKind::Literal(Literal::Str(SIGNATURE_PLACEHOLDER.to_string())))],
436+
))]);
437+
438+
let mut selector_fn_def = FunctionDefinition::normal(
439+
&ident("selector"),
440+
&vec![],
441+
&[],
442+
&selector_fun_body,
443+
&[],
444+
&FunctionReturnType::Ty(make_type(UnresolvedTypeData::FieldElement)),
445+
);
446+
447+
selector_fn_def.is_public = true;
448+
449+
// Seems to be necessary on contract modules
450+
selector_fn_def.return_visibility = Visibility::Public;
451+
452+
TypeImpl {
453+
object_type: struct_type,
454+
type_span: structure.span,
455+
generics: vec![],
456+
methods: vec![NoirFunction::normal(selector_fn_def)],
457+
}
458+
}
459+
296460
/// Helper function that returns what the private context would look like in the ast
297461
/// This should make it available to be consumed within aztec private annotated functions.
298462
///
@@ -537,9 +701,9 @@ fn make_return_push_array(push_value: Expression) -> Statement {
537701
/// `context.return_values.push_array({push_value}.serialize())`
538702
fn make_struct_return_type(expression: Expression) -> Statement {
539703
let serialized_call = method_call(
540-
expression.clone(), // variable
541-
"serialize", // method name
542-
vec![], // args
704+
expression, // variable
705+
"serialize", // method name
706+
vec![], // args
543707
);
544708
make_return_push_array(serialized_call)
545709
}
@@ -561,7 +725,7 @@ fn make_array_return_type(expression: Expression) -> Statement {
561725
vec![inner_cast_expression],
562726
));
563727

564-
create_loop_over(expression.clone(), vec![assignment])
728+
create_loop_over(expression, vec![assignment])
565729
}
566730

567731
/// Castable return type
@@ -572,7 +736,7 @@ fn make_array_return_type(expression: Expression) -> Statement {
572736
/// ```
573737
fn make_castable_return_type(expression: Expression) -> Statement {
574738
// Cast these types to a field before pushing
575-
let cast_expression = cast(expression.clone(), UnresolvedTypeData::FieldElement);
739+
let cast_expression = cast(expression, UnresolvedTypeData::FieldElement);
576740
make_return_push(cast_expression)
577741
}
578742

@@ -657,9 +821,9 @@ fn create_loop_over(var: Expression, loop_body: Vec<Statement>) -> Statement {
657821

658822
// `array.len()`
659823
let end_range_expression = method_call(
660-
var.clone(), // variable
661-
"len", // method name
662-
vec![], // args
824+
var, // variable
825+
"len", // method name
826+
vec![], // args
663827
);
664828

665829
// What will be looped over
@@ -721,3 +885,37 @@ fn add_cast_to_hasher(identifier: &Ident) -> Statement {
721885
vec![cast_operation], // args
722886
))
723887
}
888+
889+
/// Computes the aztec signature for a resolved type.
890+
fn signature_of_type(typ: &Type) -> String {
891+
match typ {
892+
Type::Integer(Signedness::Signed, bit_size) => format!("i{}", bit_size),
893+
Type::Integer(Signedness::Unsigned, bit_size) => format!("u{}", bit_size),
894+
Type::FieldElement => "Field".to_owned(),
895+
Type::Bool => "bool".to_owned(),
896+
Type::Array(len, typ) => {
897+
if let Type::Constant(len) = **len {
898+
format!("[{};{len}]", signature_of_type(typ))
899+
} else {
900+
unimplemented!("Cannot generate signature for array with length type {:?}", typ)
901+
}
902+
}
903+
Type::Struct(def, args) => {
904+
let fields = def.borrow().get_fields(args);
905+
let fields = vecmap(fields, |(_, typ)| signature_of_type(&typ));
906+
format!("({})", fields.join(","))
907+
}
908+
Type::Tuple(types) => {
909+
let fields = vecmap(types, signature_of_type);
910+
format!("({})", fields.join(","))
911+
}
912+
_ => unimplemented!("Cannot generate signature for type {:?}", typ),
913+
}
914+
}
915+
916+
/// Computes the signature for a resolved event type.
917+
/// It has the form 'EventName(Field,(Field),[u8;2])'
918+
fn event_signature(event: &StructType) -> String {
919+
let fields = vecmap(event.get_fields(&[]), |(_, typ)| signature_of_type(&typ));
920+
format!("{}({})", event.name.0.contents, fields.join(","))
921+
}

compiler/noirc_frontend/src/hir/def_map/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ impl CrateDefMap {
110110

111111
// Now we want to populate the CrateDefMap using the DefCollector
112112
errors.extend(DefCollector::collect(def_map, context, ast, root_file_id));
113+
#[cfg(feature = "aztec")]
114+
aztec_library::transform_hir(&crate_id, context);
115+
113116
errors.extend(
114117
parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::<Vec<_>>(),
115118
);

compiler/noirc_frontend/src/node_interner.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,19 @@ impl NodeInterner {
512512
}
513513
}
514514

515+
/// Updates the interned expression corresponding to `expr_id`
516+
pub fn update_expression(&mut self, expr_id: ExprId, f: impl FnOnce(&mut HirExpression)) {
517+
let def =
518+
self.nodes.get_mut(expr_id.0).expect("ice: all expression ids should have definitions");
519+
520+
match def {
521+
Node::Expression(expr) => f(expr),
522+
_ => {
523+
panic!("ice: all expression ids should correspond to a expression in the interner")
524+
}
525+
}
526+
}
527+
515528
/// Store the type for an interned Identifier
516529
pub fn push_definition_type(&mut self, definition_id: DefinitionId, typ: Type) {
517530
self.id_to_type.insert(definition_id.into(), typ);

0 commit comments

Comments
 (0)