diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index cfc2e34c520..e8dbf2ec775 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -385,14 +385,30 @@ impl<'context> Elaborator<'context> { self.errors.push(error); } } + TopLevelStatement::Struct(struct_def) => { + if let Some((type_id, the_struct)) = dc_mod::collect_struct( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + struct_def, + self.file, + self.local_module, + self.crate_id, + &mut self.errors, + ) { + generated_items.types.insert(type_id, the_struct); + } + } + TopLevelStatement::Impl(r#impl) => { + let module = self.module_id(); + dc_mod::collect_impl(self.interner, generated_items, r#impl, self.file, module); + } + // Assume that an error has already been issued TopLevelStatement::Error => (), TopLevelStatement::Module(_) | TopLevelStatement::Import(..) - | TopLevelStatement::Struct(_) | TopLevelStatement::Trait(_) - | TopLevelStatement::Impl(_) | TopLevelStatement::TypeAlias(_) | TopLevelStatement::SubModule(_) | TopLevelStatement::InnerAttribute(_) => { diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index cfee6bcedac..48efc08f463 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -488,7 +488,7 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { InterpreterError::UnsupportedTopLevelItemUnquote { item, location } => { let msg = "Unsupported statement type to unquote".into(); let secondary = - "Only functions, globals, and trait impls can be unquoted here".into(); + "Only functions, structs, globals, and impls can be unquoted here".into(); let mut error = CustomDiagnostic::simple_error(msg, secondary, location.span); error.add_note(format!("Unquoted item was:\n{item}")); error diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 520cccf7580..d6432b0ca56 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -15,7 +15,7 @@ use crate::ast::{ TypeImpl, }; use crate::hir::resolution::errors::ResolverError; -use crate::macros_api::{Expression, NodeInterner, UnresolvedType, UnresolvedTypeData}; +use crate::macros_api::{Expression, NodeInterner, StructId, UnresolvedType, UnresolvedTypeData}; use crate::node_interner::ModuleAttributes; use crate::token::SecondaryAttribute; use crate::{ @@ -27,6 +27,7 @@ use crate::{ }; use crate::{Generics, Kind, ResolvedGeneric, Type, TypeVariable}; +use super::dc_crate::CollectedItems; use super::dc_crate::ModuleAttribute; use super::{ dc_crate::{ @@ -171,24 +172,13 @@ impl<'a> ModCollector<'a> { let module_id = ModuleId { krate, local_id: self.module_id }; for r#impl in impls { - let mut unresolved_functions = UnresolvedFunctions { - file_id: self.file_id, - functions: Vec::new(), - trait_id: None, - self_type: None, - }; - - for (mut method, _) in r#impl.methods { - let func_id = context.def_interner.push_empty_fn(); - method.def.where_clause.extend(r#impl.where_clause.clone()); - let location = Location::new(method.span(), self.file_id); - context.def_interner.push_function(func_id, &method.def, module_id, location); - unresolved_functions.push_fn(self.module_id, func_id, method); - } - - let key = (r#impl.object_type, self.module_id); - let methods = self.def_collector.items.impls.entry(key).or_default(); - methods.push((r#impl.generics, r#impl.type_span, unresolved_functions)); + collect_impl( + &mut context.def_interner, + &mut self.def_collector.items, + r#impl, + self.file_id, + module_id, + ); } } @@ -315,92 +305,21 @@ impl<'a> ModCollector<'a> { ) -> Vec<(CompilationError, FileId)> { let mut definition_errors = vec![]; for struct_definition in types { - self.check_duplicate_field_names(&struct_definition, &mut definition_errors); - - let name = struct_definition.name.clone(); - - let unresolved = UnresolvedStruct { - file_id: self.file_id, - module_id: self.module_id, - struct_def: struct_definition, - }; - - let resolved_generics = context.resolve_generics( - &unresolved.struct_def.generics, - &mut definition_errors, + if let Some((id, the_struct)) = collect_struct( + &mut context.def_interner, + &mut self.def_collector.def_map, + struct_definition, self.file_id, - ); - - // Create the corresponding module for the struct namespace - let id = match self.push_child_module( - context, - &name, - Location::new(name.span(), self.file_id), - Vec::new(), - Vec::new(), - false, - false, + self.module_id, + krate, + &mut definition_errors, ) { - Ok(module_id) => context.def_interner.new_struct( - &unresolved, - resolved_generics, - krate, - module_id.local_id, - self.file_id, - ), - Err(error) => { - definition_errors.push((error.into(), self.file_id)); - continue; - } - }; - - // Add the struct to scope so its path can be looked up later - let result = self.def_collector.def_map.modules[self.module_id.0] - .declare_struct(name.clone(), id); - - if let Err((first_def, second_def)) = result { - let error = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::TypeDefinition, - first_def, - second_def, - }; - definition_errors.push((error.into(), self.file_id)); - } - - // And store the TypeId -> StructType mapping somewhere it is reachable - self.def_collector.items.types.insert(id, unresolved); - - if context.def_interner.is_in_lsp_mode() { - let parent_module_id = ModuleId { krate, local_id: self.module_id }; - context.def_interner.register_struct(id, name.to_string(), parent_module_id); + self.def_collector.items.types.insert(id, the_struct); } } definition_errors } - fn check_duplicate_field_names( - &self, - struct_definition: &NoirStruct, - definition_errors: &mut Vec<(CompilationError, FileId)>, - ) { - let mut seen_field_names = std::collections::HashSet::new(); - for (field_name, _) in &struct_definition.fields { - if seen_field_names.insert(field_name) { - continue; - } - - let previous_field_name = *seen_field_names.get(field_name).unwrap(); - definition_errors.push(( - DefCollectorErrorKind::DuplicateField { - first_def: previous_field_name.clone(), - second_def: field_name.clone(), - } - .into(), - self.file_id, - )); - } - } - /// Collect any type aliases definitions declared within the ast. /// Returns a vector of errors if any type aliases were already defined. fn collect_type_aliases( @@ -420,7 +339,8 @@ impl<'a> ModCollector<'a> { type_alias_def: type_alias, }; - let resolved_generics = context.resolve_generics( + let resolved_generics = Context::resolve_generics( + &context.def_interner, &unresolved.type_alias_def.generics, &mut errors, self.file_id, @@ -623,8 +543,12 @@ impl<'a> ModCollector<'a> { } } - let resolved_generics = - context.resolve_generics(&trait_definition.generics, &mut errors, self.file_id); + let resolved_generics = Context::resolve_generics( + &context.def_interner, + &trait_definition.generics, + &mut errors, + self.file_id, + ); let unresolved = UnresolvedTrait { file_id: self.file_id, @@ -818,64 +742,17 @@ impl<'a> ModCollector<'a> { add_to_parent_scope: bool, is_contract: bool, ) -> Result { - let parent = Some(self.module_id); - - // Note: the difference between `location` and `mod_location` is: - // - `mod_location` will point to either the token "foo" in `mod foo { ... }` - // if it's an inline module, or the first char of a the file if it's an external module. - // - `location` will always point to the token "foo" in `mod foo` regardless of whether - // it's inline or external. - // Eventually the location put in `ModuleData` is used for codelenses about `contract`s, - // so we keep using `location` so that it continues to work as usual. - let location = Location::new(mod_name.span(), mod_location.file); - let new_module = - ModuleData::new(parent, location, outer_attributes, inner_attributes, is_contract); - let module_id = self.def_collector.def_map.modules.insert(new_module); - - let modules = &mut self.def_collector.def_map.modules; - - // Update the parent module to reference the child - modules[self.module_id.0].children.insert(mod_name.clone(), LocalModuleId(module_id)); - - let mod_id = ModuleId { - krate: self.def_collector.def_map.krate, - local_id: LocalModuleId(module_id), - }; - - // Add this child module into the scope of the parent module as a module definition - // module definitions are definitions which can only exist at the module level. - // ModuleDefinitionIds can be used across crates since they contain the CrateId - // - // We do not want to do this in the case of struct modules (each struct type corresponds - // to a child module containing its methods) since the module name should not shadow - // the struct name. - if add_to_parent_scope { - if let Err((first_def, second_def)) = - modules[self.module_id.0].declare_child_module(mod_name.to_owned(), mod_id) - { - let err = DefCollectorErrorKind::Duplicate { - typ: DuplicateType::Module, - first_def, - second_def, - }; - return Err(err); - } - - context.def_interner.add_module_attributes( - mod_id, - ModuleAttributes { - name: mod_name.0.contents.clone(), - location: mod_location, - parent: Some(self.module_id), - }, - ); - - if context.def_interner.is_in_lsp_mode() { - context.def_interner.register_module(mod_id, mod_name.0.contents.clone()); - } - } - - Ok(mod_id) + push_child_module( + &mut context.def_interner, + &mut self.def_collector.def_map, + self.module_id, + mod_name, + mod_location, + outer_attributes, + inner_attributes, + add_to_parent_scope, + is_contract, + ) } fn resolve_associated_constant_type( @@ -896,6 +773,162 @@ impl<'a> ModCollector<'a> { } } +/// Add a child module to the current def_map. +/// On error this returns None and pushes to `errors` +#[allow(clippy::too_many_arguments)] +fn push_child_module( + interner: &mut NodeInterner, + def_map: &mut CrateDefMap, + parent: LocalModuleId, + mod_name: &Ident, + mod_location: Location, + outer_attributes: Vec, + inner_attributes: Vec, + add_to_parent_scope: bool, + is_contract: bool, +) -> Result { + // Note: the difference between `location` and `mod_location` is: + // - `mod_location` will point to either the token "foo" in `mod foo { ... }` + // if it's an inline module, or the first char of a the file if it's an external module. + // - `location` will always point to the token "foo" in `mod foo` regardless of whether + // it's inline or external. + // Eventually the location put in `ModuleData` is used for codelenses about `contract`s, + // so we keep using `location` so that it continues to work as usual. + let location = Location::new(mod_name.span(), mod_location.file); + let new_module = + ModuleData::new(Some(parent), location, outer_attributes, inner_attributes, is_contract); + + let module_id = def_map.modules.insert(new_module); + let modules = &mut def_map.modules; + + // Update the parent module to reference the child + modules[parent.0].children.insert(mod_name.clone(), LocalModuleId(module_id)); + + let mod_id = ModuleId { krate: def_map.krate, local_id: LocalModuleId(module_id) }; + + // Add this child module into the scope of the parent module as a module definition + // module definitions are definitions which can only exist at the module level. + // ModuleDefinitionIds can be used across crates since they contain the CrateId + // + // We do not want to do this in the case of struct modules (each struct type corresponds + // to a child module containing its methods) since the module name should not shadow + // the struct name. + if add_to_parent_scope { + if let Err((first_def, second_def)) = + modules[parent.0].declare_child_module(mod_name.to_owned(), mod_id) + { + let err = DefCollectorErrorKind::Duplicate { + typ: DuplicateType::Module, + first_def, + second_def, + }; + return Err(err); + } + + interner.add_module_attributes( + mod_id, + ModuleAttributes { + name: mod_name.0.contents.clone(), + location: mod_location, + parent: Some(parent), + }, + ); + + if interner.is_in_lsp_mode() { + interner.register_module(mod_id, mod_name.0.contents.clone()); + } + } + + Ok(mod_id) +} + +pub fn collect_struct( + interner: &mut NodeInterner, + def_map: &mut CrateDefMap, + struct_definition: NoirStruct, + file_id: FileId, + module_id: LocalModuleId, + krate: CrateId, + definition_errors: &mut Vec<(CompilationError, FileId)>, +) -> Option<(StructId, UnresolvedStruct)> { + check_duplicate_field_names(&struct_definition, file_id, definition_errors); + + let name = struct_definition.name.clone(); + + let unresolved = UnresolvedStruct { file_id, module_id, struct_def: struct_definition }; + + let resolved_generics = Context::resolve_generics( + interner, + &unresolved.struct_def.generics, + definition_errors, + file_id, + ); + + // Create the corresponding module for the struct namespace + let location = Location::new(name.span(), file_id); + let id = match push_child_module( + interner, + def_map, + module_id, + &name, + location, + Vec::new(), + Vec::new(), + false, + false, + ) { + Ok(module_id) => { + interner.new_struct(&unresolved, resolved_generics, krate, module_id.local_id, file_id) + } + Err(error) => { + definition_errors.push((error.into(), file_id)); + return None; + } + }; + + // Add the struct to scope so its path can be looked up later + let result = def_map.modules[module_id.0].declare_struct(name.clone(), id); + + if let Err((first_def, second_def)) = result { + let error = DefCollectorErrorKind::Duplicate { + typ: DuplicateType::TypeDefinition, + first_def, + second_def, + }; + definition_errors.push((error.into(), file_id)); + } + + if interner.is_in_lsp_mode() { + let parent_module_id = ModuleId { krate, local_id: module_id }; + interner.register_struct(id, name.to_string(), parent_module_id); + } + + Some((id, unresolved)) +} + +pub fn collect_impl( + interner: &mut NodeInterner, + items: &mut CollectedItems, + r#impl: TypeImpl, + file_id: FileId, + module_id: ModuleId, +) { + let mut unresolved_functions = + UnresolvedFunctions { file_id, functions: Vec::new(), trait_id: None, self_type: None }; + + for (mut method, _) in r#impl.methods { + let func_id = interner.push_empty_fn(); + method.def.where_clause.extend(r#impl.where_clause.clone()); + let location = Location::new(method.span(), file_id); + interner.push_function(func_id, &method.def, module_id, location); + unresolved_functions.push_fn(module_id.local_id, func_id, method); + } + + let key = (r#impl.object_type, module_id.local_id); + let methods = items.impls.entry(key).or_default(); + methods.push((r#impl.generics, r#impl.type_span, unresolved_functions)); +} + fn find_module( file_manager: &FileManager, anchor: FileId, @@ -1054,6 +1087,29 @@ pub(crate) fn collect_global( (global, error) } +fn check_duplicate_field_names( + struct_definition: &NoirStruct, + file: FileId, + definition_errors: &mut Vec<(CompilationError, FileId)>, +) { + let mut seen_field_names = std::collections::HashSet::new(); + for (field_name, _) in &struct_definition.fields { + if seen_field_names.insert(field_name) { + continue; + } + + let previous_field_name = *seen_field_names.get(field_name).unwrap(); + definition_errors.push(( + DefCollectorErrorKind::DuplicateField { + first_def: previous_field_name.clone(), + second_def: field_name.clone(), + } + .into(), + file, + )); + } +} + #[cfg(test)] mod find_module_tests { use super::*; diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index e4f000778d1..c631edfa889 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -272,14 +272,14 @@ impl Context<'_, '_> { /// Each result is returned in a list rather than returned as a single result as to allow /// definition collection to provide an error for each ill-formed numeric generic. pub(crate) fn resolve_generics( - &mut self, + interner: &NodeInterner, generics: &UnresolvedGenerics, errors: &mut Vec<(CompilationError, FileId)>, file_id: FileId, ) -> Generics { vecmap(generics, |generic| { // Map the generic to a fresh type variable - let id = self.def_interner.next_type_variable_id(); + let id = interner.next_type_variable_id(); let type_var = TypeVariable::unbound(id); let ident = generic.ident(); let span = ident.0.span(); diff --git a/docs/docs/noir/concepts/comptime.md b/docs/docs/noir/concepts/comptime.md index ed55a541fbd..ba078c763d0 100644 --- a/docs/docs/noir/concepts/comptime.md +++ b/docs/docs/noir/concepts/comptime.md @@ -183,7 +183,7 @@ comptime fn my_function_annotation(f: FunctionDefinition) { Anything returned from one of these functions will be inserted at top-level along with the original item. Note that expressions are not valid at top-level so you'll get an error trying to return `3` or similar just as if you tried to write a program containing `3; struct Foo {}`. -You can insert other top-level items such as traits, structs, or functions this way though. +You can insert other top-level items such as trait impls, structs, or functions this way though. For example, this is the mechanism used to insert additional trait implementations into the program when deriving a trait impl from a struct: #include_code derive-field-count-example noir_stdlib/src/meta/mod.nr rust diff --git a/test_programs/compile_success_empty/unquote_struct/Nargo.toml b/test_programs/compile_success_empty/unquote_struct/Nargo.toml new file mode 100644 index 00000000000..c40d6a07093 --- /dev/null +++ b/test_programs/compile_success_empty/unquote_struct/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "unquote_struct" +type = "bin" +authors = [""] +compiler_version = ">=0.33.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/unquote_struct/src/main.nr b/test_programs/compile_success_empty/unquote_struct/src/main.nr new file mode 100644 index 00000000000..e90711dd710 --- /dev/null +++ b/test_programs/compile_success_empty/unquote_struct/src/main.nr @@ -0,0 +1,28 @@ +fn main() { + let foo = Foo { x: 4, y: 4 }; + foo.assert_equal(); +} + +#[output_struct] +fn foo(x: Field, y: u32) -> u32 { + x as u32 + y +} + +// Given a function, wrap its parameters in a struct definition +comptime fn output_struct(f: FunctionDefinition) -> Quoted { + let fields = f.parameters().map(|param: (Quoted, Type)| { + let name = param.0; + let typ = param.1; + quote { $name: $typ, } + }).join(quote {}); + + quote { + struct Foo { $fields } + + impl Foo { + fn assert_equal(self) { + assert_eq(self.x as u32, self.y); + } + } + } +}