Skip to content

Commit

Permalink
feat!: Handle generic fields in StructDefinition::fields and move o…
Browse files Browse the repository at this point in the history
…ld functionality to `StructDefinition::fields_as_written` (#7067)

Co-authored-by: Michael J Klein <[email protected]>
  • Loading branch information
jfecher and michaeljklein authored Jan 14, 2025
1 parent 51b7c07 commit 14a7e37
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 15 deletions.
54 changes: 51 additions & 3 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<Location>,
) -> IResult<Value> {
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<Value> {
let argument = check_one_argument(arguments, location)?;
let struct_id = get_struct(argument)?;
Expand Down
13 changes: 12 additions & 1 deletion docs/docs/noir/standard_library/meta/struct_def.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion noir_stdlib/src/cmp.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions noir_stdlib/src/meta/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub comptime fn make_trait_impl<Env1, Env2>(
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)
});
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
16 changes: 13 additions & 3 deletions noir_stdlib/src/meta/struct_def.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}

Expand Down Expand Up @@ -44,4 +44,22 @@ mod foo {
// docs:end:add-generic-example
}

fn main() {}
fn main() {
comptime {
let typ = quote { MyType<i8, i16, i32> }.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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down

0 comments on commit 14a7e37

Please sign in to comment.