From 9d2629dd1bb28a8c2ecb4c33d26119da75d626c2 Mon Sep 17 00:00:00 2001 From: jfecher Date: Thu, 5 Sep 2024 06:17:16 -0500 Subject: [PATCH] feat: Add `StructDefinition::set_fields` (#5931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/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 --- .../noirc_frontend/src/hir/comptime/errors.rs | 13 ++++ .../src/hir/comptime/interpreter/builtin.rs | 61 ++++++++++++++++++- compiler/noirc_frontend/src/hir_def/types.rs | 1 - .../noir/standard_library/meta/struct_def.md | 29 +++++++++ noir_stdlib/src/meta/struct_def.nr | 9 +++ .../comptime_type_definition/src/main.nr | 13 ++++ 6 files changed, 124 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 48efc08f463..f6585786eeb 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -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. @@ -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, .. } => { @@ -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) + } } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index d2c9e4ffc0c..65c9c3f018d 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -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, @@ -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), @@ -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 { + 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::, _>>()?; + + struct_def.set_fields(new_fields); + Ok(Value::Unit) +} + fn slice_remove( interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 7b3d0d7a205..113a4fb3888 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -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; } diff --git a/docs/docs/noir/standard_library/meta/struct_def.md b/docs/docs/noir/standard_library/meta/struct_def.md index ab3ea4e0698..95e377dffd4 100644 --- a/docs/docs/noir/standard_library/meta/struct_def.md +++ b/docs/docs/noir/standard_library/meta/struct_def.md @@ -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()), + ]); +} +``` diff --git a/noir_stdlib/src/meta/struct_def.nr b/noir_stdlib/src/meta/struct_def.nr index 60fdeba21aa..1ca1b6a3925 100644 --- a/noir_stdlib/src/meta/struct_def.nr +++ b/noir_stdlib/src/meta/struct_def.nr @@ -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 } diff --git a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr index cdfc9bd6b75..aca8d067dde 100644 --- a/test_programs/compile_success_empty/comptime_type_definition/src/main.nr +++ b/test_programs/compile_success_empty/comptime_type_definition/src/main.nr @@ -6,8 +6,21 @@ struct MyType { 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); +}