From 14a7e37d7f18f1d6e9148ecc77059999d5f9b14b Mon Sep 17 00:00:00 2001 From: jfecher Date: Tue, 14 Jan 2025 15:12:16 -0600 Subject: [PATCH] feat!: Handle generic fields in `StructDefinition::fields` and move old functionality to `StructDefinition::fields_as_written` (#7067) Co-authored-by: Michael J Klein --- .../src/hir/comptime/interpreter/builtin.rs | 54 +++++++++++++++++-- .../noir/standard_library/meta/struct_def.md | 13 ++++- noir_stdlib/src/cmp.nr | 2 +- noir_stdlib/src/meta/mod.nr | 6 +-- noir_stdlib/src/meta/struct_def.nr | 16 ++++-- .../comptime_struct_definition/src/main.nr | 22 +++++++- .../comptime_type/src/main.nr | 2 +- .../derive_impl/src/main.nr | 2 +- 8 files changed, 102 insertions(+), 15 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 3d3d0721c65..ace2d29431c 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -181,7 +181,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), "struct_def_eq" => struct_def_eq(arguments, location), - "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_fields" => struct_def_fields(interner, arguments, location, call_stack), + "struct_def_fields_as_written" => { + struct_def_fields_as_written(interner, arguments, location) + } "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_has_named_attribute" => { struct_def_has_named_attribute(interner, arguments, location) @@ -482,12 +485,57 @@ fn struct_def_has_named_attribute( Ok(Value::Bool(has_named_attribute(&name, interner.struct_attributes(&struct_id)))) } -/// fn fields(self) -> [(Quoted, Type)] -/// Returns (name, type) pairs of each field of this StructDefinition +/// fn fields(self, generic_args: [Type]) -> [(Quoted, Type)] +/// Returns (name, type) pairs of each field of this StructDefinition. +/// Applies the given generic arguments to each field. fn struct_def_fields( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, + call_stack: &im::Vector, +) -> IResult { + let (typ, generic_args) = check_two_arguments(arguments, location)?; + let struct_id = get_struct(typ)?; + let struct_def = interner.get_struct(struct_id); + let struct_def = struct_def.borrow(); + + let args_location = generic_args.1; + let generic_args = get_slice(interner, generic_args)?.0; + let generic_args = try_vecmap(generic_args, |arg| get_type((arg, args_location)))?; + + let actual = generic_args.len(); + let expected = struct_def.generics.len(); + if actual != expected { + let s = if expected == 1 { "" } else { "s" }; + let was_were = if actual == 1 { "was" } else { "were" }; + let message = Some(format!("`StructDefinition::fields` expected {expected} generic{s} for `{}` but {actual} {was_were} given", struct_def.name)); + let location = args_location; + let call_stack = call_stack.clone(); + return Err(InterpreterError::FailingConstraint { message, location, call_stack }); + } + + let mut fields = im::Vector::new(); + + for (field_name, field_type) in struct_def.get_fields(&generic_args) { + let name = Value::Quoted(Rc::new(vec![Token::Ident(field_name)])); + fields.push_back(Value::Tuple(vec![name, Value::Type(field_type)])); + } + + let typ = Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Type), + ]))); + Ok(Value::Slice(fields, typ)) +} + +/// fn fields_as_written(self) -> [(Quoted, Type)] +/// Returns (name, type) pairs of each field of this StructDefinition. +/// +/// Note that any generic arguments won't be applied: if you need them to be, use `fields`. +fn struct_def_fields_as_written( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; let struct_id = get_struct(argument)?; diff --git a/docs/docs/noir/standard_library/meta/struct_def.md b/docs/docs/noir/standard_library/meta/struct_def.md index 535708e0353..8c6e50ba23a 100644 --- a/docs/docs/noir/standard_library/meta/struct_def.md +++ b/docs/docs/noir/standard_library/meta/struct_def.md @@ -66,7 +66,18 @@ comptime fn example(foo: StructDefinition) { #include_code fields noir_stdlib/src/meta/struct_def.nr rust -Returns each field of this struct as a pair of (field name, field type). +Returns (name, type) pairs of each field in this struct. +Any generic types used in each field type is automatically substituted with the +provided generic arguments. + +### fields_as_written + +#include_code fields_as_written noir_stdlib/src/meta/struct_def.nr rust + +Returns (name, type) pairs of each field in this struct. Each type is as-is +with any generic arguments unchanged. Unless the field types are not needed, +users should generally prefer to use `StructDefinition::fields` over this +function if possible. ### has_named_attribute diff --git a/noir_stdlib/src/cmp.nr b/noir_stdlib/src/cmp.nr index ae515150a4d..7f19594c30e 100644 --- a/noir_stdlib/src/cmp.nr +++ b/noir_stdlib/src/cmp.nr @@ -12,7 +12,7 @@ comptime fn derive_eq(s: StructDefinition) -> Quoted { let signature = quote { fn eq(_self: Self, _other: Self) -> bool }; let for_each_field = |name| quote { (_self.$name == _other.$name) }; let body = |fields| { - if s.fields().len() == 0 { + if s.fields_as_written().len() == 0 { quote { true } } else { fields diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index f6a1f4838ee..21c1b14cc96 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -101,7 +101,7 @@ pub comptime fn make_trait_impl( let where_clause = s.generics().map(|name| quote { $name: $trait_name }).join(quote {,}); // `for_each_field(field1) $join_fields_with for_each_field(field2) $join_fields_with ...` - let fields = s.fields().map(|f: (Quoted, Type)| { + let fields = s.fields_as_written().map(|f: (Quoted, Type)| { let name = f.0; for_each_field(name) }); @@ -155,7 +155,7 @@ mod tests { comptime fn derive_field_count(s: StructDefinition) -> Quoted { let typ = s.as_type(); - let field_count = s.fields().len(); + let field_count = s.fields_as_written().len(); quote { impl FieldCount for $typ { fn field_count() -> u32 { @@ -174,7 +174,7 @@ mod tests { comptime fn assert_field_is_type(s: StructDefinition, typ: Type) { // Assert the first field in `s` has type `typ` - let fields = s.fields(); + let fields = s.fields([]); assert_eq(fields[0].1, typ); } // docs:end:annotation-arguments-example diff --git a/noir_stdlib/src/meta/struct_def.nr b/noir_stdlib/src/meta/struct_def.nr index ba5d0289e73..0e64a537f1f 100644 --- a/noir_stdlib/src/meta/struct_def.nr +++ b/noir_stdlib/src/meta/struct_def.nr @@ -27,13 +27,23 @@ impl StructDefinition { pub comptime fn generics(self) -> [Type] {} // docs:end:generics - /// Returns (name, type) pairs of each field in this struct. Each type is as-is - /// with any generic arguments unchanged. + /// Returns (name, type) pairs of each field in this struct. + /// Any generic types used in each field type is automatically substituted with the + /// provided generic arguments. #[builtin(struct_def_fields)] // docs:start:fields - pub comptime fn fields(self) -> [(Quoted, Type)] {} + pub comptime fn fields(self, generic_args: [Type]) -> [(Quoted, Type)] {} // docs:end:fields + /// Returns (name, type) pairs of each field in this struct. Each type is as-is + /// with any generic arguments unchanged. Unless the field types are not needed, + /// users should generally prefer to use `StructDefinition::fields` over this + /// function if possible. + #[builtin(struct_def_fields_as_written)] + // docs:start:fields_as_written + pub comptime fn fields_as_written(self) -> [(Quoted, Type)] {} + // docs:end:fields_as_written + #[builtin(struct_def_module)] // docs:start:module pub comptime fn module(self) -> Module {} diff --git a/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr index 686ac26025d..75d48c6f1dd 100644 --- a/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_struct_definition/src/main.nr @@ -12,7 +12,7 @@ pub struct I32AndField { comptime fn my_comptime_fn(typ: StructDefinition) { let _ = typ.as_type(); assert_eq(typ.generics().len(), 3); - assert_eq(typ.fields().len(), 2); + assert_eq(typ.fields_as_written().len(), 2); assert_eq(typ.name(), quote { MyType }); } @@ -44,4 +44,22 @@ mod foo { // docs:end:add-generic-example } -fn main() {} +fn main() { + comptime { + let typ = quote { MyType }.as_type(); + let (struct_def, generics) = typ.as_struct().unwrap(); + + let fields = struct_def.fields(generics); + assert_eq(fields.len(), 2); + + let (field1_name, field1_type) = fields[0]; + let (field2_name, field2_type) = fields[1]; + + assert_eq(field1_name, quote { field1 }); + assert_eq(field2_name, quote { field2 }); + + // Ensure .fields(generics) actually performs substitutions on generics + assert_eq(field1_type, quote { [i8; 10] }.as_type()); + assert_eq(field2_type, quote { (i16, i32) }.as_type()); + } +} diff --git a/test_programs/compile_success_empty/comptime_type/src/main.nr b/test_programs/compile_success_empty/comptime_type/src/main.nr index 887690cb6cb..6a2453ff0f2 100644 --- a/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -100,7 +100,7 @@ fn main() { let foo = Foo { x: 0 }; let foo_type = type_of(foo); let (struct_definition, generics) = foo_type.as_struct().unwrap(); - let fields = struct_definition.fields(); + let fields = struct_definition.fields(generics); assert_eq(fields.len(), 1); assert_eq(generics.len(), 1); diff --git a/test_programs/compile_success_empty/derive_impl/src/main.nr b/test_programs/compile_success_empty/derive_impl/src/main.nr index 4396169235d..fe7a7140280 100644 --- a/test_programs/compile_success_empty/derive_impl/src/main.nr +++ b/test_programs/compile_success_empty/derive_impl/src/main.nr @@ -7,7 +7,7 @@ comptime fn derive_default(typ: StructDefinition) -> Quoted { ); let type_name = typ.as_type(); - let fields = typ.fields(); + let fields = typ.fields_as_written(); let fields = join(make_field_exprs(fields));