Skip to content

Commit

Permalink
fix!: Include kind in StructDefinition::generics and fix derivation…
Browse files Browse the repository at this point in the history
… of Eq in structs with numeric generics (#7076)
  • Loading branch information
sirasistant authored Jan 16, 2025
1 parent 9fa36e6 commit f17b7cc
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 18 deletions.
42 changes: 36 additions & 6 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,9 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"struct_def_fields_as_written" => {
struct_def_fields_as_written(interner, arguments, location)
}
"struct_def_generics" => struct_def_generics(interner, arguments, location),
"struct_def_generics" => {
struct_def_generics(interner, arguments, return_type, location)
}
"struct_def_has_named_attribute" => {
struct_def_has_named_attribute(interner, arguments, location)
}
Expand Down Expand Up @@ -445,22 +447,50 @@ fn struct_def_as_type(
Ok(Value::Type(Type::Struct(struct_def_rc, generics)))
}

/// fn generics(self) -> [Type]
/// fn generics(self) -> [(Type, Option<Type>)]
fn struct_def_generics(
interner: &NodeInterner,
arguments: Vec<(Value, Location)>,
return_type: Type,
location: Location,
) -> IResult<Value> {
let argument = check_one_argument(arguments, location)?;
let struct_id = get_struct(argument)?;
let struct_def = interner.get_struct(struct_id);
let struct_def = struct_def.borrow();

let generics =
struct_def.generics.iter().map(|generic| Value::Type(generic.clone().as_named_generic()));
let expected = Type::Slice(Box::new(Type::Tuple(vec![
Type::Quoted(QuotedType::Type),
interner.next_type_variable(), // Option
])));

let actual = return_type.clone();

let slice_item_type = match return_type {
Type::Slice(item_type) => *item_type,
_ => return Err(InterpreterError::TypeMismatch { expected, actual, location }),
};

let option_typ = match &slice_item_type {
Type::Tuple(types) if types.len() == 2 => types[1].clone(),
_ => return Err(InterpreterError::TypeMismatch { expected, actual, location }),
};

let generics: IResult<_> = struct_def
.generics
.iter()
.map(|generic| -> IResult<Value> {
let generic_as_named = generic.clone().as_named_generic();
let numeric_type = match generic_as_named.kind() {
Kind::Numeric(numeric_type) => Some(Value::Type(*numeric_type)),
_ => None,
};
let numeric_type = option(option_typ.clone(), numeric_type, location.span)?;
Ok(Value::Tuple(vec![Value::Type(generic_as_named), numeric_type]))
})
.collect();

let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Type)));
Ok(Value::Slice(generics.collect(), typ))
Ok(Value::Slice(generics?, slice_item_type))
}

fn struct_def_hash(arguments: Vec<(Value, Location)>, location: Location) -> IResult<Value> {
Expand Down
15 changes: 10 additions & 5 deletions docs/docs/noir/standard_library/meta/struct_def.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,28 @@ any generics, the generics are also included as-is.

#include_code generics noir_stdlib/src/meta/struct_def.nr rust

Returns each generic on this struct.
Returns each generic on this struct. Each generic is represented as a tuple containing the type,
and an optional containing the numeric type if it's a numeric generic.

Example:

```
#[example]
struct Foo<T, U> {
bar: [T; 2],
struct Foo<T, U, let K: u32> {
bar: [T; K],
baz: Baz<U, U>,
}
comptime fn example(foo: StructDefinition) {
assert_eq(foo.generics().len(), 2);
assert_eq(foo.generics().len(), 3);
// Fails because `T` isn't in scope
// let t = quote { T }.as_type();
// assert_eq(foo.generics()[0], t);
// assert_eq(foo.generics()[0].0, t);
assert(foo.generics()[0].1.is_none());
// Last generic is numeric, so we have the numeric type available to us
assert(foo.generics()[2].1.is_some());
}
```

Expand Down
6 changes: 4 additions & 2 deletions docs/docs/noir/standard_library/meta/trait_impl.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ comptime {
let generics = my_impl.trait_generic_args();
assert_eq(generics.len(), 2);

assert_eq(generics[0], quote { i32 }.as_type());
assert_eq(generics[1], quote { Field }.as_type());
assert_eq(generics[0].0, quote { i32 }.as_type());
assert(generics[0].1.is_none());
assert_eq(generics[1].0, quote { Field }.as_type());
assert(generics[1].1.is_none());
}
```

Expand Down
15 changes: 13 additions & 2 deletions noir_stdlib/src/meta/mod.nr
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,19 @@ pub comptime fn make_trait_impl<Env1, Env2>(
) -> Quoted {
// docs:end:make_trait_impl
let typ = s.as_type();
let impl_generics = s.generics().map(|g| quote { $g }).join(quote {,});
let where_clause = s.generics().map(|name| quote { $name: $trait_name }).join(quote {,});

let mut impl_generics = &[];
let mut where_clause = &[];
for g in s.generics() {
let (typ, numeric_type) = g;
impl_generics = impl_generics.push_back(quote { $typ });
if numeric_type.is_none() {
where_clause = where_clause.push_back(quote { $typ: $trait_name });
}
}

let impl_generics = impl_generics.join(quote {, });
let where_clause = where_clause.join(quote {, });

// `for_each_field(field1) $join_fields_with for_each_field(field2) $join_fields_with ...`
let fields = s.fields_as_written().map(|f: (Quoted, Type)| {
Expand Down
7 changes: 5 additions & 2 deletions noir_stdlib/src/meta/struct_def.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::option::Option;

impl StructDefinition {
#[builtin(struct_def_add_attribute)]
// docs:start:add_attribute
Expand All @@ -21,10 +23,11 @@ impl StructDefinition {
pub comptime fn has_named_attribute<let N: u32>(self, name: str<N>) -> bool {}
// docs:end:has_named_attribute

/// Return each generic on this struct.
/// Return (type, option<type>) pairs of each generic in this struct definition.
/// If a generic is numeric, the second element of the pair will contain the numeric type.
#[builtin(struct_def_generics)]
// docs:start:generics
pub comptime fn generics(self) -> [Type] {}
pub comptime fn generics(self) -> [(Type, Option<Type>)] {}
// docs:end:generics

/// Returns (name, type) pairs of each field in this struct.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ mod foo {

let generics = s.generics();
assert_eq(generics.len(), 1);
assert_eq(generics[0], new_generic);
let (typ, numeric) = generics[0];
assert_eq(typ, new_generic);
assert(numeric.is_none());
}
// docs:end:add-generic-example
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "eq_derivation_with_numeric_generics"
type = "bin"
authors = [""]

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#[derive(Eq)]
struct Foo<let T: u32> {
a: [Field; T],
b: u32,
}

fn main() {
let foo = Foo { a: [0; 10], b: 27 };
let bar = Foo { a: [0; 10], b: 28 };
assert(foo != bar);
}

0 comments on commit f17b7cc

Please sign in to comment.