Skip to content

Commit

Permalink
feat: Add StructDefinition::set_fields (#5931)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves #5911

## Summary\*

Adds a function to set the fields on a struct type

## Additional Context

Hyper-specific error messages can be fun. Here's the error issued when
one of the field names isn't a valid identifier:

```
error: Quoted value in index 1 of this slice is not a valid field name
   ┌─ src/main.nr:25:18
   │
25 │     s.set_fields(fields);
   │                  ------ `quote { foo bar }` is not a valid field name for `set_fields`
   │
```

## 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: Ary Borenszweig <asterite@gmail.com>
  • Loading branch information
jfecher and asterite authored Sep 5, 2024
1 parent 8b60bbc commit 9d2629d
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 2 deletions.
13 changes: 13 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ pub enum InterpreterError {
TypeAnnotationsNeededForMethodCall {
location: Location,
},
ExpectedIdentForStructField {
value: String,
index: usize,
location: Location,
},

// These cases are not errors, they are just used to prevent us from running more code
// until the loop can be resumed properly. These cases will never be displayed to users.
Expand Down Expand Up @@ -269,6 +274,7 @@ impl InterpreterError {
| InterpreterError::FailedToResolveTraitBound { location, .. }
| InterpreterError::FunctionAlreadyResolved { location, .. }
| InterpreterError::MultipleMatchingImpls { location, .. }
| InterpreterError::ExpectedIdentForStructField { location, .. }
| InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location,

InterpreterError::FailedToParseMacro { error, file, .. } => {
Expand Down Expand Up @@ -566,6 +572,13 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
error.add_note(message.to_string());
error
}
InterpreterError::ExpectedIdentForStructField { value, index, location } => {
let msg = format!(
"Quoted value in index {index} of this slice is not a valid field name"
);
let secondary = format!("`{value}` is not a valid field name for `set_fields`");
CustomDiagnostic::simple_error(msg, secondary, location.span)
}
}
}
}
61 changes: 60 additions & 1 deletion compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::{
},
hir_def::function::FunctionBody,
lexer::Lexer,
macros_api::{HirExpression, HirLiteral, ModuleDefId, NodeInterner, Signedness},
macros_api::{HirExpression, HirLiteral, Ident, ModuleDefId, NodeInterner, Signedness},
node_interner::{DefinitionKind, TraitImplKind},
parser::{self},
token::Token,
Expand Down Expand Up @@ -133,6 +133,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"struct_def_as_type" => struct_def_as_type(interner, arguments, location),
"struct_def_fields" => struct_def_fields(interner, arguments, location),
"struct_def_generics" => struct_def_generics(interner, arguments, location),
"struct_def_set_fields" => struct_def_set_fields(interner, arguments, location),
"to_le_radix" => to_le_radix(arguments, return_type, location),
"trait_constraint_eq" => trait_constraint_eq(interner, arguments, location),
"trait_constraint_hash" => trait_constraint_hash(interner, arguments, location),
Expand Down Expand Up @@ -326,6 +327,64 @@ fn struct_def_fields(
Ok(Value::Slice(fields, typ))
}

/// fn set_fields(self, new_fields: [(Quoted, Type)]) {}
/// Returns (name, type) pairs of each field of this StructDefinition
fn struct_def_set_fields(
interner: &mut NodeInterner,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
let (the_struct, fields) = check_two_arguments(arguments, location)?;
let struct_id = get_struct(the_struct)?;

let struct_def = interner.get_struct(struct_id);
let mut struct_def = struct_def.borrow_mut();

let field_location = fields.1;
let fields = get_slice(interner, fields)?.0;

let new_fields = fields
.into_iter()
.flat_map(|field_pair| get_tuple(interner, (field_pair, field_location)))
.enumerate()
.map(|(index, mut field_pair)| {
if field_pair.len() == 2 {
let typ = field_pair.pop().unwrap();
let name_value = field_pair.pop().unwrap();

let name_tokens = get_quoted((name_value.clone(), field_location))?;
let typ = get_type((typ, field_location))?;

match name_tokens.first() {
Some(Token::Ident(name)) if name_tokens.len() == 1 => {
Ok((Ident::new(name.clone(), field_location.span), typ))
}
_ => {
let value = name_value.display(interner).to_string();
let location = field_location;
Err(InterpreterError::ExpectedIdentForStructField {
value,
index,
location,
})
}
}
} else {
let type_var = interner.next_type_variable();
let expected = Type::Tuple(vec![type_var.clone(), type_var]);

let actual =
Type::Tuple(vecmap(&field_pair, |value| value.get_type().into_owned()));

Err(InterpreterError::TypeMismatch { expected, actual, location })
}
})
.collect::<Result<Vec<_>, _>>()?;

struct_def.set_fields(new_fields);
Ok(Value::Unit)
}

fn slice_remove(
interner: &mut NodeInterner,
arguments: Vec<(Value, Location)>,
Expand Down
1 change: 0 additions & 1 deletion compiler/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ impl StructType {
/// created. Therefore, this method is used to set the fields once they
/// become known.
pub fn set_fields(&mut self, fields: Vec<(Ident, Type)>) {
assert!(self.fields.is_empty());
self.fields = fields;
}

Expand Down
29 changes: 29 additions & 0 deletions docs/docs/noir/standard_library/meta/struct_def.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,32 @@ 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).

### set_fields

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

Sets the fields of this struct to the given fields list where each element
is a pair of the field's name and the field's type. Expects each field name
to be a single identifier. Note that this will override any previous fields
on this struct. If those should be preserved, use `.fields()` to retrieve the
current fields on the struct type and append the new fields from there.

Example:

```rust
// Change this struct to:
// struct Foo {
// a: u32,
// b: i8,
// }
#[mangle_fields]
struct Foo { x: Field }

comptime fn mangle_fields(s: StructDefinition) {
s.set_fields(&[
(quote { a }, quote { u32 }.as_type()),
(quote { b }, quote { i8 }.as_type()),
]);
}
```
9 changes: 9 additions & 0 deletions noir_stdlib/src/meta/struct_def.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ impl StructDefinition {
// docs:start:fields
fn fields(self) -> [(Quoted, Type)] {}
// docs:end:fields

/// Sets the fields of this struct to the given fields list.
/// All existing fields of the struct will be overridden with the given fields.
/// Each element of the fields list corresponds to the name and type of a field.
/// Each name is expected to be a single identifier.
#[builtin(struct_def_set_fields)]
// docs:start:set_fields
fn set_fields(self, new_fields: [(Quoted, Type)]) {}
// docs:end:set_fields
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@ struct MyType<A, B, C> {
field2: (B, C),
}

#[mutate_struct_fields]
struct I32AndField {
z: i8,
}

comptime fn my_comptime_fn(typ: StructDefinition) {
let _ = typ.as_type();
assert_eq(typ.generics().len(), 3);
assert_eq(typ.fields().len(), 2);
}

comptime fn mutate_struct_fields(s: StructDefinition) {
let fields = &[
(quote[x], quote[i32].as_type()),
(quote[y], quote[Field].as_type())
];
s.set_fields(fields);
}

0 comments on commit 9d2629d

Please sign in to comment.