From 6004067e42572c34dd6465e66d36410826e2fd90 Mon Sep 17 00:00:00 2001 From: jfecher Date: Fri, 6 Sep 2024 12:04:13 -0500 Subject: [PATCH] feat: Add `StructDefinition::add_generic` (#5961) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/5957 ## Summary\* Adds a generic to an existing struct. ## Additional Context Due to the same reasoning in https://github.com/noir-lang/noir/issues/5926, existing code referring to these structs won't see the generics on them. For now I've documented to try to avoid this use case. ## 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 | 41 +++++++++++- .../src/hir/comptime/interpreter/builtin.rs | 66 +++++++++++++++++-- .../noir/standard_library/meta/struct_def.md | 18 +++++ noir_stdlib/src/meta/struct_def.nr | 11 +++- .../comptime_struct_definition/src/main.nr | 17 ++++- 5 files changed, 141 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 5d4d814f3ee..70589973745 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -194,7 +194,6 @@ pub enum InterpreterError { candidates: Vec, location: Location, }, - Unimplemented { item: String, location: Location, @@ -211,6 +210,16 @@ pub enum InterpreterError { attribute: String, location: Location, }, + GenericNameShouldBeAnIdent { + name: Rc, + location: Location, + }, + DuplicateGeneric { + name: Rc, + struct_name: String, + duplicate_location: Location, + existing_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. @@ -279,8 +288,10 @@ impl InterpreterError { | InterpreterError::FunctionAlreadyResolved { location, .. } | InterpreterError::MultipleMatchingImpls { location, .. } | InterpreterError::ExpectedIdentForStructField { location, .. } + | InterpreterError::InvalidAttribute { location, .. } + | InterpreterError::GenericNameShouldBeAnIdent { location, .. } + | InterpreterError::DuplicateGeneric { duplicate_location: location, .. } | InterpreterError::TypeAnnotationsNeededForMethodCall { location } => *location, - InterpreterError::InvalidAttribute { location, .. } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -589,6 +600,32 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let secondary = "Note that this method expects attribute contents, without the leading `#[` or trailing `]`".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::GenericNameShouldBeAnIdent { name, location } => { + let msg = + "Generic name needs to be a valid identifer (one word beginning with a letter)" + .to_string(); + let secondary = format!("`{name}` is not a valid identifier"); + CustomDiagnostic::simple_error(msg, secondary, location.span) + } + InterpreterError::DuplicateGeneric { + name, + struct_name, + duplicate_location, + existing_location, + } => { + let msg = format!("`{struct_name}` already has a generic named `{name}`"); + let secondary = format!("`{name}` added here a second time"); + let mut error = + CustomDiagnostic::simple_error(msg, secondary, duplicate_location.span); + + let existing_msg = format!("`{name}` was previously defined here"); + error.add_secondary_with_file( + existing_msg, + existing_location.span, + existing_location.file, + ); + error + } } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index e6ef685e278..7321f09dd75 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -39,7 +39,7 @@ use crate::{ node_interner::{DefinitionKind, TraitImplKind}, parser::{self}, token::{Attribute, SecondaryAttribute, Token}, - QuotedType, Shared, Type, + Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; use self::builtin_helpers::{get_array, get_str, get_u8}; @@ -139,7 +139,8 @@ impl<'local, 'context> Interpreter<'local, 'context> { "slice_push_front" => slice_push_front(interner, arguments, location), "slice_remove" => slice_remove(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), - "struct_def_add_attribute" => struct_def_add_attribute(self, arguments, location), + "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), + "struct_def_add_generic" => struct_def_add_generic(interner, arguments, location), "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), @@ -280,13 +281,13 @@ fn str_as_bytes( // fn add_attribute(self, attribute: str) fn struct_def_add_attribute( - interpreter: &mut Interpreter, + interner: &mut NodeInterner, arguments: Vec<(Value, Location)>, location: Location, ) -> IResult { let (self_argument, attribute) = check_two_arguments(arguments, location)?; let attribute_location = attribute.1; - let attribute = get_str(interpreter.elaborator.interner, attribute)?; + let attribute = get_str(interner, attribute)?; let mut tokens = Lexer::lex(&format!("#[{}]", attribute)).0 .0; if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { @@ -315,13 +316,68 @@ fn struct_def_add_attribute( }; let struct_id = get_struct(self_argument)?; - interpreter.elaborator.interner.update_struct_attributes(struct_id, |attributes| { + interner.update_struct_attributes(struct_id, |attributes| { attributes.push(attribute.clone()); }); Ok(Value::Unit) } +// fn add_generic(self, generic_name: str) +fn struct_def_add_generic( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (self_argument, generic) = check_two_arguments(arguments, location)?; + let generic_location = generic.1; + let generic = get_str(interner, generic)?; + + let mut tokens = Lexer::lex(&generic).0 .0; + if let Some(Token::EOF) = tokens.last().map(|token| token.token()) { + tokens.pop(); + } + + if tokens.len() != 1 { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + } + + let Token::Ident(generic_name) = tokens.pop().unwrap().into_token() else { + return Err(InterpreterError::GenericNameShouldBeAnIdent { + name: generic, + location: generic_location, + }); + }; + + let struct_id = get_struct(self_argument)?; + let the_struct = interner.get_struct(struct_id); + let mut the_struct = the_struct.borrow_mut(); + let name = Rc::new(generic_name); + + for generic in &the_struct.generics { + if generic.name == name { + return Err(InterpreterError::DuplicateGeneric { + name, + struct_name: the_struct.name.to_string(), + existing_location: Location::new(generic.span, the_struct.location.file), + duplicate_location: generic_location, + }); + } + } + + let type_var = TypeVariable::unbound(interner.next_type_variable_id()); + let span = generic_location.span; + let kind = Kind::Normal; + let typ = Type::NamedGeneric(type_var.clone(), name.clone(), kind.clone()); + let new_generic = ResolvedGeneric { name, type_var, span, kind }; + the_struct.generics.push(new_generic); + + Ok(Value::Type(typ)) +} + /// fn as_type(self) -> Type fn struct_def_as_type( interner: &NodeInterner, diff --git a/docs/docs/noir/standard_library/meta/struct_def.md b/docs/docs/noir/standard_library/meta/struct_def.md index 425f74a7484..c088e538fc9 100644 --- a/docs/docs/noir/standard_library/meta/struct_def.md +++ b/docs/docs/noir/standard_library/meta/struct_def.md @@ -13,6 +13,24 @@ This type corresponds to `struct Name { field1: Type1, ... }` items in the sourc Adds an attribute to the struct. +### add_generic + +#include_code add_generic noir_stdlib/src/meta/struct_def.nr rust + +Adds an generic to the struct. Returns the new generic type. +Errors if the given generic name isn't a single identifier or if +the struct already has a generic with the same name. + +This method should be used carefully, if there is existing code referring +to the struct type it may be checked before this function is called and +see the struct with the original number of generics. This method should +thus be preferred to use on code generated from other macros and structs +that are not used in function signatures. + +Example: + +#include_code add-generic-example test_programs/compile_success_empty/comptime_struct_definition/src/main.nr rust + ### as_type #include_code as_type noir_stdlib/src/meta/struct_def.nr rust diff --git a/noir_stdlib/src/meta/struct_def.nr b/noir_stdlib/src/meta/struct_def.nr index 85832b00770..5db720b91d3 100644 --- a/noir_stdlib/src/meta/struct_def.nr +++ b/noir_stdlib/src/meta/struct_def.nr @@ -4,10 +4,15 @@ impl StructDefinition { fn add_attribute(self, attribute: str) {} // docs:end:add_attribute + #[builtin(struct_def_add_generic)] + // docs:start:add_generic + fn add_generic(self, generic_name: str) -> Type {} + // docs:end:add_generic + /// Return a syntactic version of this struct definition as a type. /// For example, `as_type(quote { type Foo { ... } })` would return `Foo` #[builtin(struct_def_as_type)] -// docs:start:as_type + // docs:start:as_type fn as_type(self) -> Type {} // docs:end:as_type @@ -18,14 +23,14 @@ impl StructDefinition { /// Return each generic on this struct. #[builtin(struct_def_generics)] -// docs:start:generics + // docs:start:generics 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. #[builtin(struct_def_fields)] -// docs:start:fields + // docs:start:fields fn fields(self) -> [(Quoted, Type)] {} // docs:end:fields 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 758330cf7b7..da2871a253d 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 @@ -28,10 +28,23 @@ mod foo { #[attr] struct Foo {} - fn attr(s: StructDefinition) { + comptime fn attr(s: StructDefinition) { assert_eq(s.module().name(), quote { foo }); } + + #[add_generic] + struct Bar {} + + // docs:start:add-generic-example + comptime fn add_generic(s: StructDefinition) { + assert_eq(s.generics().len(), 0); + let new_generic = s.add_generic("T"); + + let generics = s.generics(); + assert_eq(generics.len(), 1); + assert_eq(generics[0], new_generic); + } + // docs:end:add-generic-example } fn main() {} -