diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_declaration.nr b/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_declaration.nr new file mode 100644 index 00000000000..f4c246c786a --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_declaration.nr @@ -0,0 +1,26 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field,y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +// Duplicate trait declarations should not compile +trait Default { + fn default(x: Field) -> Self; +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_implementation.nr b/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_implementation.nr new file mode 100644 index 00000000000..6bb6cedefb5 --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/dup_trait_implementation.nr @@ -0,0 +1,30 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +// Duplicate trait implementations should not compile +impl Default for Foo { + fn default(x: Field,y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +// Duplicate trait implementations should not compile +impl Default for Foo { + fn default(x: Field, y: Field) -> Self { + Self { bar: y, array: [y,x] } + } +} + + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/impl_struct_not_trait.nr b/crates/nargo_cli/tests/compile_tests_data/fail/impl_struct_not_trait.nr new file mode 100644 index 00000000000..e25465378b1 --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/impl_struct_not_trait.nr @@ -0,0 +1,23 @@ +use dep::std; + +struct Foo { + bar: Field, + array: [Field; 2], +} + +struct Default { + x: Field, + z: Field, +} + +// Default is struct not a trait +impl Default for Foo { + fn default(x: Field, y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_implementation_wrong_parameter.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_implementation_wrong_parameter.nr new file mode 100644 index 00000000000..92469ae8fdb --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_implementation_wrong_parameter.nr @@ -0,0 +1,21 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field) -> Self { + Self { bar: x, array: [x, x] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_not_exists.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_not_exists.nr new file mode 100644 index 00000000000..9dc57ee395f --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_not_exists.nr @@ -0,0 +1,18 @@ +use dep::std; + +struct Foo { + bar: Field, + array: [Field; 2], +} + +// Default trait does not exist +impl Default for Foo { + fn default(x: Field, y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_name.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_name.nr new file mode 100644 index 00000000000..0ba10815efa --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_name.nr @@ -0,0 +1,22 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +// wrong trait name method should not compile +impl Default for Foo { + fn default_wrong_name(x: Field, y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default_wrong_name(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_return_type.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_return_type.nr new file mode 100644 index 00000000000..acd930a6d49 --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_method_return_type.nr @@ -0,0 +1,21 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field, y: Field) -> Field { + x + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameter.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameter.nr new file mode 100644 index 00000000000..2975aa6b1dd --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameter.nr @@ -0,0 +1,21 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field, y: Foo) -> Self { + Self { bar: x, array: [x, y.bar] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameters_count.nr b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameters_count.nr new file mode 100644 index 00000000000..92469ae8fdb --- /dev/null +++ b/crates/nargo_cli/tests/compile_tests_data/fail/trait_wrong_parameters_count.nr @@ -0,0 +1,21 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field) -> Self { + Self { bar: x, array: [x, x] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/test_data/traits/Nargo.toml b/crates/nargo_cli/tests/test_data/traits/Nargo.toml new file mode 100644 index 00000000000..629f55ef064 --- /dev/null +++ b/crates/nargo_cli/tests/test_data/traits/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "traits" +authors = [""] +compiler_version = "0.1" + +[dependencies] diff --git a/crates/nargo_cli/tests/test_data/traits/Prover.toml b/crates/nargo_cli/tests/test_data/traits/Prover.toml new file mode 100644 index 00000000000..71805e71e8e --- /dev/null +++ b/crates/nargo_cli/tests/test_data/traits/Prover.toml @@ -0,0 +1,2 @@ +x = "5" +y = "1" \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data/traits/src/main.nr b/crates/nargo_cli/tests/test_data/traits/src/main.nr new file mode 100644 index 00000000000..2333c5da244 --- /dev/null +++ b/crates/nargo_cli/tests/test_data/traits/src/main.nr @@ -0,0 +1,21 @@ +use dep::std; + +trait Default { + fn default(x: Field, y: Field) -> Self; +} + +struct Foo { + bar: Field, + array: [Field; 2], +} + +impl Default for Foo { + fn default(x: Field,y: Field) -> Self { + Self { bar: x, array: [x,y] } + } +} + +fn main(x: Field, y: Field) { + let first = Foo::default(x,y); + assert(first.bar == x); +} diff --git a/crates/nargo_cli/tests/test_data/traits/target/main.json b/crates/nargo_cli/tests/test_data/traits/target/main.json new file mode 100644 index 00000000000..ed15fc6c618 --- /dev/null +++ b/crates/nargo_cli/tests/test_data/traits/target/main.json @@ -0,0 +1 @@ +{"backend":"acvm-backend-barretenberg","abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"param_witnesses":{"x":[1],"y":[2]},"return_type":null,"return_witnesses":[]},"bytecode":[155,194,60,97,194,4,0],"proving_key":null,"verification_key":null} \ No newline at end of file diff --git a/crates/noirc_frontend/src/ast/traits.rs b/crates/noirc_frontend/src/ast/traits.rs index 1dacb73fc5e..d615e2ec796 100644 --- a/crates/noirc_frontend/src/ast/traits.rs +++ b/crates/noirc_frontend/src/ast/traits.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use iter_extended::vecmap; use noirc_errors::Span; -use crate::{Ident, NoirFunction, UnresolvedGenerics, UnresolvedType}; +use crate::{BlockExpression, Expression, Ident, NoirFunction, UnresolvedGenerics, UnresolvedType}; /// AST node for trait definitions: /// `trait name { ... items ... }` @@ -11,6 +11,8 @@ use crate::{Ident, NoirFunction, UnresolvedGenerics, UnresolvedType}; pub struct NoirTrait { pub name: Ident, pub generics: Vec, + pub where_clause: Vec, + pub span: Span, pub items: Vec, } @@ -24,6 +26,12 @@ pub enum TraitItem { parameters: Vec<(Ident, UnresolvedType)>, return_type: UnresolvedType, where_clause: Vec, + body: Option, + }, + Constant { + name: Ident, + typ: UnresolvedType, + default_value: Option, }, Type { name: Ident, @@ -68,6 +76,7 @@ pub struct TraitConstraint { #[derive(Clone, Debug)] pub enum TraitImplItem { Function(NoirFunction), + Constant(Ident, UnresolvedType, Expression), Type { name: Ident, alias: UnresolvedType }, } @@ -110,7 +119,7 @@ impl Display for NoirTrait { impl Display for TraitItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - TraitItem::Function { name, generics, parameters, return_type, where_clause } => { + TraitItem::Function { name, generics, parameters, return_type, where_clause, body } => { let generics = vecmap(generics, |generic| generic.to_string()); let parameters = vecmap(parameters, |(name, typ)| format!("{name}: {typ}")); let where_clause = vecmap(where_clause, ToString::to_string); @@ -121,9 +130,25 @@ impl Display for TraitItem { write!( f, - "fn {name}<{}>({}) -> {} where {};", + "fn {name}<{}>({}) -> {} where {}", generics, parameters, return_type, where_clause - ) + )?; + + if let Some(body) = body { + write!(f, "{}", body) + } else { + write!(f, ";") + } + } + TraitItem::Constant { name, typ, default_value } => { + // TODO: Shall we use `comptime` or `const` here? + write!(f, "comptime {}: {}", name, typ)?; + + if let Some(default_value) = default_value { + write!(f, "{};", default_value) + } else { + write!(f, ";") + } } TraitItem::Type { name } => write!(f, "type {name};"), } @@ -159,7 +184,11 @@ impl Display for TraitImplItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TraitImplItem::Function(function) => function.fmt(f), - TraitImplItem::Type { name, alias } => write!(f, "type {name} = {alias}"), + TraitImplItem::Type { name, alias } => write!(f, "type {name} = {alias};"), + // TODO: Should we use `comptime` or `const` here? + TraitImplItem::Constant(name, typ, value) => { + write!(f, "comptime {}: {} = {};", name, typ, value) + } } } } diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs index e974961a405..df246978f48 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -10,10 +10,10 @@ use crate::hir::resolution::{ }; use crate::hir::type_check::{type_check_func, TypeChecker}; use crate::hir::Context; -use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TypeAliasId}; +use crate::node_interner::{FuncId, NodeInterner, StmtId, StructId, TraitId, TypeAliasId}; use crate::{ - ExpressionKind, Generics, Ident, LetStatement, NoirFunction, NoirStruct, NoirTypeAlias, - ParsedModule, Shared, Type, TypeBinding, UnresolvedGenerics, UnresolvedType, + ExpressionKind, Generics, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, + NoirTypeAlias, ParsedModule, Shared, Type, TypeBinding, UnresolvedGenerics, UnresolvedType, }; use fm::FileId; use iter_extended::vecmap; @@ -40,6 +40,12 @@ pub struct UnresolvedStruct { pub struct_def: NoirStruct, } +pub struct UnresolvedTrait { + pub file_id: FileId, + pub module_id: LocalModuleId, + pub trait_def: NoirTrait, +} + #[derive(Clone)] pub struct UnresolvedTypeAlias { pub file_id: FileId, @@ -62,6 +68,7 @@ pub struct DefCollector { pub(crate) collected_functions: Vec, pub(crate) collected_types: HashMap, pub(crate) collected_type_aliases: HashMap, + pub(crate) collected_traits: HashMap, pub(crate) collected_globals: Vec, pub(crate) collected_impls: ImplMap, } @@ -80,6 +87,7 @@ impl DefCollector { collected_functions: vec![], collected_types: HashMap::new(), collected_type_aliases: HashMap::new(), + collected_traits: HashMap::new(), collected_impls: HashMap::new(), collected_globals: vec![], } @@ -150,7 +158,11 @@ impl DefCollector { .import(name.clone(), ns); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::DuplicateImport { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "import".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(root_file_id)); } } @@ -169,6 +181,7 @@ impl DefCollector { resolve_type_aliases(context, def_collector.collected_type_aliases, crate_id, errors); // Must resolve structs before we resolve globals. + resolve_traits(context, def_collector.collected_traits, crate_id, errors); resolve_structs(context, def_collector.collected_types, crate_id, errors); // We must wait to resolve non-integer globals until after we resolve structs since structs @@ -243,8 +256,11 @@ fn collect_impls( let result = module.declare_function(method.name_ident().clone(), *method_id); if let Err((first_def, second_def)) = result { - let err = - DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "function".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(unresolved.file_id)); } } @@ -350,6 +366,19 @@ fn resolve_structs( } } +/// Create the mappings from TypeId -> TraitType +/// so that expressions can access the elements of traits +fn resolve_traits( + context: &mut Context, + traits: HashMap, + _crate_id: CrateId, + _errors: &mut [FileDiagnostic], +) { + for (type_id, typ) in &traits { + context.def_interner.push_empty_trait(*type_id, typ); + } +} + fn resolve_struct_fields( context: &mut Context, krate: CrateId, @@ -420,7 +449,6 @@ fn resolve_impls( generics, errors, ); - if self_type != Type::Error { for (file_id, method_id) in &file_func_ids { let method_name = interner.function_name(method_id).to_owned(); diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs index 37c017ecb96..ef55c6cdb7b 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -2,16 +2,21 @@ use fm::FileId; use noirc_errors::FileDiagnostic; use crate::{ - graph::CrateId, hir::def_collector::dc_crate::UnresolvedStruct, node_interner::StructId, - parser::SubModule, Ident, LetStatement, NoirFunction, NoirStruct, NoirTypeAlias, ParsedModule, - TypeImpl, + graph::CrateId, + hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, + node_interner::{StructId, TraitId}, + parser::SubModule, + Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, ParsedModule, + TraitConstraint, TraitImpl, TraitImplItem, TraitItem, TypeImpl, UnresolvedType, }; use super::{ dc_crate::{DefCollector, UnresolvedFunctions, UnresolvedGlobal, UnresolvedTypeAlias}, errors::DefCollectorErrorKind, }; -use crate::hir::def_map::{parse_file, LocalModuleId, ModuleData, ModuleId, ModuleOrigin}; +use crate::hir::def_map::{ + parse_file, LocalModuleId, ModuleData, ModuleDefId, ModuleId, ModuleOrigin, +}; use crate::hir::resolution::import::ImportDirective; use crate::hir::Context; @@ -54,15 +59,142 @@ pub fn collect_defs( collector.collect_globals(context, ast.globals, errors); + collector.collect_traits(ast.traits, crate_id, errors); + collector.collect_structs(ast.types, crate_id, errors); collector.collect_type_aliases(context, ast.type_aliases, errors); collector.collect_functions(context, ast.functions, errors); + collector.collect_trait_impls(context, ast.trait_impls, errors); + collector.collect_impls(context, ast.impls); } +fn check_trait_method_implementation_generics( + _generics: &[Ident], + _noir_function: &NoirFunction, + _trait_name: &str, +) -> Result<(), DefCollectorErrorKind> { + // TODO + Ok(()) +} + +fn check_trait_method_implementation_parameters( + parameters: &Vec<(Ident, UnresolvedType)>, + noir_function: &NoirFunction, + trait_name: &String, +) -> Result<(), DefCollectorErrorKind> { + if noir_function.def.parameters.len() != parameters.len() { + return Err(DefCollectorErrorKind::GenericError { + primary_message: format!("Mismatch signature [Number of parameters] of method with name `{}` that implemetns trait `{}`", noir_function.name(), trait_name), + secondary_message: "".to_string(), + span: noir_function.name_ident().span(), + }); + } + for (count, (pattern, typ, _abi_vis)) in noir_function.def.parameters.iter().enumerate() { + let (_expected_name, expected_type) = ¶meters[count]; + if typ != expected_type { + return Err(DefCollectorErrorKind::GenericError { + primary_message: format!( + "Mismatch signature of method {} that implemtns trait {}", + noir_function.name(), + trait_name, + ), + secondary_message: format!( + "`{}: {}` expected", + pattern.name_ident().0.contents, + expected_type, + ), + span: pattern.name_ident().span(), + }); + } + } + Ok(()) +} + +fn check_trait_method_implementation_trait_constains( + _where_clause: &[TraitConstraint], + _noir_function: &NoirFunction, + _trait_name: &str, +) -> Result<(), DefCollectorErrorKind> { + // TODO + Ok(()) +} + +fn check_trait_method_implementation_return_type( + return_type: &UnresolvedType, + noir_function: &NoirFunction, + trait_name: &String, +) -> Result<(), DefCollectorErrorKind> { + if !(return_type == &noir_function.return_type()) { + Err(DefCollectorErrorKind::GenericError { + primary_message: format!( + "mismatch return type of method with name {} that implemetns trait {}", + noir_function.name(), + trait_name + ), + secondary_message: "".to_string(), + span: noir_function.name_ident().span(), + }) + } else { + Ok(()) + } +} + +fn check_trait_method_implementation( + r#trait: &NoirTrait, + noir_function: &NoirFunction, +) -> Result<(), DefCollectorErrorKind> { + for item in &r#trait.items { + if let TraitItem::Function { + name, + generics, + parameters, + return_type, + where_clause, + body: _, + } = item + { + if name.0.contents == noir_function.def.name.0.contents { + // name matches, check for parameters, return type and where clause + check_trait_method_implementation_generics( + generics, + noir_function, + &r#trait.name.0.contents, + )?; + check_trait_method_implementation_parameters( + parameters, + noir_function, + &r#trait.name.0.contents, + )?; + check_trait_method_implementation_trait_constains( + where_clause, + noir_function, + &r#trait.name.0.contents, + )?; + check_trait_method_implementation_return_type( + return_type, + noir_function, + &r#trait.name.0.contents, + )?; + return Ok(()); + } + } + } + + Err(DefCollectorErrorKind::GenericError { + primary_message: format!( + "method with name {} is not part of trait {}, therefore it can't be implemented", + noir_function.name(), + r#trait.name.0.contents + ), + secondary_message: "".to_string(), + span: noir_function.name_ident().span(), + }) +} + impl<'a> ModCollector<'a> { fn collect_globals( &mut self, @@ -82,7 +214,11 @@ impl<'a> ModCollector<'a> { self.def_collector.def_map.modules[self.module_id.0].declare_global(name, stmt_id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::DuplicateGlobal { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "global".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(self.file_id)); } @@ -112,6 +248,91 @@ impl<'a> ModCollector<'a> { } } + fn collect_trait_impls( + &mut self, + context: &mut Context, + impls: Vec, + errors: &mut Vec, + ) { + for r#impl in impls { + let mut unresolved_functions = + UnresolvedFunctions { file_id: self.file_id, functions: Vec::new() }; + let trait_name = r#impl.trait_name.clone(); + let module = &self.def_collector.def_map.modules[self.module_id.0]; + match module.find_name(&trait_name).types { + Some((module_def_id, _visibility)) => match module_def_id { + ModuleDefId::TraitId(trait_id) => { + for item in r#impl.items { + match item { + TraitImplItem::Function(noir_function) => { + if let Some(unresolved_trait) = + self.def_collector.collected_traits.get(&trait_id) + { + match check_trait_method_implementation( + &unresolved_trait.trait_def, + &noir_function, + ) { + Ok(()) => { + let func_id = context.def_interner.push_empty_fn(); + context.def_interner.push_function_definition( + noir_function.name().to_owned(), + func_id, + ); + unresolved_functions.push_fn( + self.module_id, + func_id, + noir_function, + ); + } + Err(error) => { + errors + .push(error.into_file_diagnostic(self.file_id)); + } + } + } else { + // ?? + } + } + TraitImplItem::Constant(_name, _typ, _value) => { + // TODO: Implement this + } + TraitImplItem::Type { name: _, alias: _ } => { + // TODO: Implement this + } + } + } + let key = (r#impl.object_type, self.module_id); + let methods = self.def_collector.collected_impls.entry(key).or_default(); + methods.push(( + r#impl.impl_generics, + r#impl.object_type_span, + unresolved_functions, + )); + } + _ => { + let error = DefCollectorErrorKind::GenericError { + primary_message: format!( + "{} is not a trait, therefore it can't be implemented", + trait_name + ), + secondary_message: "".to_string(), + span: trait_name.span(), + }; + errors.push(error.into_file_diagnostic(self.file_id)); + } + }, + None => { + let error = DefCollectorErrorKind::GenericError { + primary_message: format!("Trait {} not found", trait_name), + secondary_message: "".to_string(), + span: trait_name.span(), + }; + errors.push(error.into_file_diagnostic(self.file_id)); + } + } + } + } + fn collect_functions( &mut self, context: &mut Context, @@ -142,7 +363,11 @@ impl<'a> ModCollector<'a> { .declare_function(name, func_id); if let Err((first_def, second_def)) = result { - let error = DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; + let error = DefCollectorErrorKind::Duplicate { + typ: "function".to_string(), + first_def, + second_def, + }; errors.push(error.into_file_diagnostic(self.file_id)); } } @@ -172,7 +397,11 @@ impl<'a> ModCollector<'a> { self.def_collector.def_map.modules[self.module_id.0].declare_struct(name, id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "type definition".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(self.file_id)); } @@ -211,7 +440,11 @@ impl<'a> ModCollector<'a> { .declare_type_alias(name, type_alias_id); if let Err((first_def, second_def)) = result { - let err = DefCollectorErrorKind::DuplicateFunction { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "function".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(self.file_id)); } @@ -219,6 +452,46 @@ impl<'a> ModCollector<'a> { } } + /// Collect any traits definitions declared within the ast. + /// Returns a vector of errors if any traits were already defined. + fn collect_traits( + &mut self, + traits: Vec, + krate: CrateId, + errors: &mut Vec, + ) { + for trait_definition in traits { + let name = trait_definition.name.clone(); + + // Create the corresponding module for the trait namespace + let id = match self.push_child_module(&name, self.file_id, false, false, errors) { + Some(local_id) => TraitId(ModuleId { krate, local_id }), + None => continue, + }; + + // Add the trait to scope so its path can be looked up later + let result = + self.def_collector.def_map.modules[self.module_id.0].declare_trait(name, id); + + if let Err((first_def, second_def)) = result { + let err = DefCollectorErrorKind::Duplicate { + typ: "trait definition".to_string(), + first_def, + second_def, + }; + errors.push(err.into_file_diagnostic(self.file_id)); + } + + // And store the TraitId -> TraitType mapping somewhere it is reachable + let unresolved = UnresolvedTrait { + file_id: self.file_id, + module_id: self.module_id, + trait_def: trait_definition, + }; + self.def_collector.collected_traits.insert(id, unresolved); + } + } + fn collect_submodules( &mut self, context: &mut Context, @@ -323,7 +596,11 @@ impl<'a> ModCollector<'a> { if let Err((first_def, second_def)) = modules[self.module_id.0].declare_child_module(mod_name.to_owned(), mod_id) { - let err = DefCollectorErrorKind::DuplicateModuleDecl { first_def, second_def }; + let err = DefCollectorErrorKind::Duplicate { + typ: "module".to_string(), + first_def, + second_def, + }; errors.push(err.into_file_diagnostic(self.file_id)); return None; } diff --git a/crates/noirc_frontend/src/hir/def_collector/errors.rs b/crates/noirc_frontend/src/hir/def_collector/errors.rs index d18df58d64d..0b0a1bcb727 100644 --- a/crates/noirc_frontend/src/hir/def_collector/errors.rs +++ b/crates/noirc_frontend/src/hir/def_collector/errors.rs @@ -8,20 +8,16 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum DefCollectorErrorKind { - #[error("duplicate function found in namespace")] - DuplicateFunction { first_def: Ident, second_def: Ident }, - #[error("duplicate function found in namespace")] - DuplicateModuleDecl { first_def: Ident, second_def: Ident }, - #[error("duplicate import")] - DuplicateImport { first_def: Ident, second_def: Ident }, - #[error("duplicate global found in namespace")] - DuplicateGlobal { first_def: Ident, second_def: Ident }, + #[error("duplicate {typ} found in namespace")] + Duplicate { typ: String, first_def: Ident, second_def: Ident }, #[error("unresolved import")] UnresolvedModuleDecl { mod_name: Ident }, #[error("path resolution error")] PathResolutionError(PathResolutionError), #[error("Non-struct type used in impl")] NonStructTypeInImpl { span: Span }, + #[error("Generic error")] + GenericError { primary_message: String, secondary_message: String, span: Span }, } impl DefCollectorErrorKind { @@ -33,57 +29,24 @@ impl DefCollectorErrorKind { impl From for Diagnostic { fn from(error: DefCollectorErrorKind) -> Diagnostic { match error { - DefCollectorErrorKind::DuplicateFunction { first_def, second_def } => { - let first_span = first_def.0.span(); - let second_span = second_def.0.span(); - let func_name = &first_def.0.contents; - - let mut diag = Diagnostic::simple_error( - format!("duplicate definitions of {func_name} function found"), - "first definition found here".to_string(), - first_span, - ); - diag.add_secondary("second definition found here".to_string(), second_span); - diag - } - DefCollectorErrorKind::DuplicateModuleDecl { first_def, second_def } => { - let first_span = first_def.0.span(); - let second_span = second_def.0.span(); - let mod_name = &first_def.0.contents; - - let mut diag = Diagnostic::simple_error( - format!("module {mod_name} has been declared twice"), - "first declaration found here".to_string(), - first_span, - ); - diag.add_secondary("second declaration found here".to_string(), second_span); - diag - } - DefCollectorErrorKind::DuplicateImport { first_def, second_def } => { - let first_span = first_def.0.span(); - let second_span = second_def.0.span(); - let import_name = &first_def.0.contents; - - let mut diag = Diagnostic::simple_error( - format!("the name `{import_name}` is defined multiple times"), - "first import found here".to_string(), - first_span, - ); - diag.add_secondary("second import found here".to_string(), second_span); - diag - } - DefCollectorErrorKind::DuplicateGlobal { first_def, second_def } => { - let first_span = first_def.0.span(); - let second_span = second_def.0.span(); - let import_name = &first_def.0.contents; - - let mut diag = Diagnostic::simple_error( - format!("the name `{import_name}` is defined multiple times"), - "first global declaration found here".to_string(), - first_span, - ); - diag.add_secondary("second global declaration found here".to_string(), second_span); - diag + DefCollectorErrorKind::Duplicate { typ, first_def, second_def } => { + let primary_message = + format!("duplicate definitions of {} function found", &first_def.0.contents); + { + let duplicate_type: &str = &typ; + let first_span = first_def.0.span(); + let second_span = second_def.0.span(); + let mut diag = Diagnostic::simple_error( + primary_message, + format!("first {} found here", duplicate_type), + first_span, + ); + diag.add_secondary( + format!("second {} found here", duplicate_type), + second_span, + ); + diag + } } DefCollectorErrorKind::UnresolvedModuleDecl { mod_name } => { let span = mod_name.0.span(); @@ -101,6 +64,9 @@ impl From for Diagnostic { "Only struct types may have implementation methods".into(), span, ), + DefCollectorErrorKind::GenericError { primary_message, secondary_message, span } => { + Diagnostic::simple_error(primary_message, secondary_message, span) + } } } } diff --git a/crates/noirc_frontend/src/hir/def_map/item_scope.rs b/crates/noirc_frontend/src/hir/def_map/item_scope.rs index 760088a3b7e..7dcc5051a0c 100644 --- a/crates/noirc_frontend/src/hir/def_map/item_scope.rs +++ b/crates/noirc_frontend/src/hir/def_map/item_scope.rs @@ -49,6 +49,7 @@ impl ItemScope { ModuleDefId::FunctionId(_) => add_item(&mut self.values), ModuleDefId::TypeId(_) => add_item(&mut self.types), ModuleDefId::TypeAliasId(_) => add_item(&mut self.types), + ModuleDefId::TraitId(_) => add_item(&mut self.types), ModuleDefId::GlobalId(_) => add_item(&mut self.values), } } diff --git a/crates/noirc_frontend/src/hir/def_map/module_data.rs b/crates/noirc_frontend/src/hir/def_map/module_data.rs index 5b93d04fea7..0b6c6ebd77c 100644 --- a/crates/noirc_frontend/src/hir/def_map/module_data.rs +++ b/crates/noirc_frontend/src/hir/def_map/module_data.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use fm::FileId; use crate::{ - node_interner::{FuncId, StmtId, StructId, TypeAliasId}, + node_interner::{FuncId, StmtId, StructId, TraitId, TypeAliasId}, Ident, }; @@ -73,6 +73,10 @@ impl ModuleData { self.declare(name, id.into()) } + pub fn declare_trait(&mut self, name: Ident, id: TraitId) -> Result<(), (Ident, Ident)> { + self.declare(name, ModuleDefId::TraitId(id)) + } + pub fn declare_child_module( &mut self, name: Ident, diff --git a/crates/noirc_frontend/src/hir/def_map/module_def.rs b/crates/noirc_frontend/src/hir/def_map/module_def.rs index b64ced78772..ade0fcaf7aa 100644 --- a/crates/noirc_frontend/src/hir/def_map/module_def.rs +++ b/crates/noirc_frontend/src/hir/def_map/module_def.rs @@ -1,14 +1,15 @@ -use crate::node_interner::{FuncId, StmtId, StructId, TypeAliasId}; +use crate::node_interner::{FuncId, StmtId, StructId, TraitId, TypeAliasId}; use super::ModuleId; -/// A generic ID that references either a module, function, type, or global +/// A generic ID that references either a module, function, type, interface or global #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ModuleDefId { ModuleId(ModuleId), FunctionId(FuncId), TypeId(StructId), TypeAliasId(TypeAliasId), + TraitId(TraitId), GlobalId(StmtId), } @@ -34,6 +35,13 @@ impl ModuleDefId { } } + pub fn as_trait(&self) -> Option { + match self { + ModuleDefId::TraitId(trait_id) => Some(*trait_id), + _ => None, + } + } + pub fn as_global(&self) -> Option { match self { ModuleDefId::GlobalId(stmt_id) => Some(*stmt_id), @@ -48,6 +56,7 @@ impl ModuleDefId { ModuleDefId::FunctionId(_) => "function", ModuleDefId::TypeId(_) => "type", ModuleDefId::TypeAliasId(_) => "type alias", + ModuleDefId::TraitId(_) => "trait", ModuleDefId::ModuleId(_) => "module", ModuleDefId::GlobalId(_) => "global", } @@ -126,6 +135,20 @@ impl TryFromModuleDefId for TypeAliasId { } } +impl TryFromModuleDefId for TraitId { + fn try_from(id: ModuleDefId) -> Option { + id.as_trait() + } + + fn dummy_id() -> Self { + TraitId::dummy_id() + } + + fn description() -> String { + "trait".to_string() + } +} + impl TryFromModuleDefId for StmtId { fn try_from(id: ModuleDefId) -> Option { id.as_global() diff --git a/crates/noirc_frontend/src/hir/resolution/import.rs b/crates/noirc_frontend/src/hir/resolution/import.rs index 9a6ef9b1b8b..c70f383f245 100644 --- a/crates/noirc_frontend/src/hir/resolution/import.rs +++ b/crates/noirc_frontend/src/hir/resolution/import.rs @@ -64,7 +64,6 @@ pub fn resolve_imports( let resolved_namespace = resolve_path_to_ns(&import_directive, def_map, def_maps, allow_contracts) .map_err(|error| (error, module_scope))?; - let name = resolve_path_name(&import_directive); Ok(ResolvedImport { name, resolved_namespace, module_scope }) }) @@ -139,7 +138,6 @@ fn resolve_name_in_module( if current_ns.is_none() { return Err(PathResolutionError::Unresolved(first_segment.clone())); } - for segment in import_path { let typ = match current_ns.take_types() { None => return Err(PathResolutionError::Unresolved(segment.clone())), @@ -153,6 +151,7 @@ fn resolve_name_in_module( // TODO: If impls are ever implemented, types can be used in a path ModuleDefId::TypeId(id) => id.0, ModuleDefId::TypeAliasId(_) => panic!("type aliases cannot be used in type namespace"), + ModuleDefId::TraitId(id) => id.0, ModuleDefId::GlobalId(_) => panic!("globals cannot be in the type namespace"), }; @@ -160,15 +159,14 @@ fn resolve_name_in_module( // Check if namespace let found_ns = current_mod.find_name(segment); + if found_ns.is_none() { return Err(PathResolutionError::Unresolved(segment.clone())); } - // Check if it is a contract and we're calling from a non-contract context if current_mod.is_contract && !allow_contracts { return Err(PathResolutionError::ExternalContractUsed(segment.clone())); } - current_ns = found_ns; } diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 8b4f97dbd8e..7a29d828509 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -26,7 +26,7 @@ use crate::graph::CrateId; use crate::hir::def_map::{ModuleDefId, ModuleId, TryFromModuleDefId, MAIN_FUNCTION}; use crate::hir_def::stmt::{HirAssignStatement, HirLValue, HirPattern}; use crate::node_interner::{ - DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId, StructId, + DefinitionId, DefinitionKind, ExprId, FuncId, NodeInterner, StmtId, StructId, TraitId, }; use crate::{ hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}, @@ -35,7 +35,7 @@ use crate::{ }; use crate::{ ArrayLiteral, ContractFunctionType, Generics, LValue, NoirStruct, NoirTypeAlias, Path, Pattern, - Shared, StructType, Type, TypeAliasType, TypeBinding, TypeVariable, UnaryOp, + Shared, StructType, Trait, Type, TypeAliasType, TypeBinding, TypeVariable, UnaryOp, UnresolvedGenerics, UnresolvedType, UnresolvedTypeExpression, ERROR_IDENT, }; use fm::FileId; @@ -823,6 +823,7 @@ impl<'a> Resolver<'a> { | Type::Constant(_) | Type::NamedGeneric(_, _) | Type::NotConstant + | Type::Trait(..) | Type::Forall(_, _) => (), Type::Array(length, element_type) => { @@ -1227,6 +1228,10 @@ impl<'a> Resolver<'a> { self.interner.get_struct(type_id) } + pub fn get_trait(&self, type_id: TraitId) -> Shared { + self.interner.get_trait(type_id) + } + fn lookup(&mut self, path: Path) -> Result { let span = path.span(); let id = self.resolve_path(path)?; diff --git a/crates/noirc_frontend/src/hir_def/types.rs b/crates/noirc_frontend/src/hir_def/types.rs index df4c2f6c229..ab5bb19c1b1 100644 --- a/crates/noirc_frontend/src/hir_def/types.rs +++ b/crates/noirc_frontend/src/hir_def/types.rs @@ -13,7 +13,7 @@ use iter_extended::vecmap; use noirc_abi::AbiType; use noirc_errors::Span; -use crate::{node_interner::StructId, Ident, Signedness}; +use crate::{node_interner::StructId, node_interner::TraitId, Ident, Signedness}; use super::expr::{HirCallExpression, HirExpression, HirIdent}; @@ -51,6 +51,11 @@ pub enum Type { /// represents the generic arguments (if any) to this struct type. Struct(Shared, Vec), + /// A user-defined trait type. The `Shared` field here refers to + /// the shared definition for each instance of this trait type. The `Vec` + /// represents the generic arguments (if any) to this trait type. + Trait(Shared, Vec), + /// A tuple type with the given list of fields in the order they appear in source code. Tuple(Vec), @@ -124,6 +129,39 @@ pub struct StructType { pub span: Span, } +#[derive(Debug, PartialEq, Eq)] +pub enum TraitItemType { + /// A function declaration in a trait. + Function { + name: Ident, + generics: Generics, + arguments: Vec, + return_type: Type, + span: Span, + }, + + /// A constant declaration in a trait. + Constant { name: Ident, ty: Type, span: Span }, + + /// A type declaration in a trait. + Type { name: Ident, ty: Type, span: Span }, +} +/// Represents a trait type in the type system. Each instance of this +/// rust struct will be shared across all Type::Trait variants that represent +/// the same trait type. +#[derive(Debug, Eq)] +pub struct Trait { + /// A unique id representing this trait type. Used to check if two + /// struct traits are equal. + pub id: TraitId, + + pub items: Vec, + + pub name: Ident, + pub generics: Generics, + pub span: Span, +} + /// Corresponds to generic lists such as `` in the source /// program. The `TypeVariableId` portion is used to match two /// type variables to check for equality, while the `TypeVariable` is @@ -142,6 +180,36 @@ impl PartialEq for StructType { } } +impl std::hash::Hash for Trait { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for Trait { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Trait { + pub fn new( + id: TraitId, + name: Ident, + span: Span, + items: Vec, + generics: Generics, + ) -> Trait { + Trait { id, name, span, items, generics } + } +} + +impl std::fmt::Display for Trait { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + impl StructType { pub fn new( id: StructId, @@ -684,6 +752,7 @@ impl Type { | Type::Constant(_) | Type::NamedGeneric(_, _) | Type::NotConstant + | Type::Trait(..) | Type::Forall(_, _) => false, Type::Array(length, elem) => { @@ -772,6 +841,15 @@ impl std::fmt::Display for Type { write!(f, "{}<{}>", s.borrow(), args.join(", ")) } } + Type::Trait(s, args) => { + // TODO: Extract this into a shared helper for traits and structs + let args = vecmap(args, |arg| arg.to_string()); + if args.is_empty() { + write!(f, "{}", s.borrow()) + } else { + write!(f, "{}<{}>", s.borrow(), args.join(", ")) + } + } Type::Tuple(elements) => { let elements = vecmap(elements, ToString::to_string); write!(f, "({})", elements.join(", ")) @@ -1497,6 +1575,7 @@ impl Type { let fields = vecmap(fields, |(name, typ)| (name, typ.as_abi_type())); AbiType::Struct { fields } } + Type::Trait(_, _) => unreachable!("traits cannot be used in the abi"), Type::Tuple(_) => todo!("as_abi_type not yet implemented for tuple types"), Type::TypeVariable(_, _) => unreachable!(), Type::NamedGeneric(..) => unreachable!(), @@ -1603,6 +1682,12 @@ impl Type { let args = vecmap(args, |arg| arg.substitute(type_bindings)); Type::Struct(fields.clone(), args) } + Type::Trait(items, args) => { + let args = vecmap(args, |arg| arg.substitute(type_bindings)); + // TODO: Don't we need to substitute any reference to the generic parameters within the + // items as well here? How does it work for structs? + Type::Trait(items.clone(), args) + } Type::Tuple(fields) => { let fields = vecmap(fields, |field| field.substitute(type_bindings)); Type::Tuple(fields) @@ -1646,6 +1731,7 @@ impl Type { len_occurs || field_occurs } Type::Struct(_, generic_args) => generic_args.iter().any(|arg| arg.occurs(target_id)), + Type::Trait(_, generic_args) => generic_args.iter().any(|arg| arg.occurs(target_id)), Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { match &*binding.borrow() { @@ -1710,7 +1796,7 @@ impl Type { MutableReference(element) => MutableReference(Box::new(element.follow_bindings())), // Expect that this function should only be called on instantiated types - Forall(..) => unreachable!(), + Forall(..) | Trait(..) => unreachable!(), FieldElement(_) | Integer(_, _, _) diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index dbe2ee080bf..fa80c1673ce 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -671,6 +671,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::Forall(_, _) | HirType::Constant(_) | HirType::NotConstant + | HirType::Trait(..) | HirType::Error => { unreachable!("Unexpected type {} found", typ) } diff --git a/crates/noirc_frontend/src/node_interner.rs b/crates/noirc_frontend/src/node_interner.rs index f5fea5c1ea7..ad63ca8fc74 100644 --- a/crates/noirc_frontend/src/node_interner.rs +++ b/crates/noirc_frontend/src/node_interner.rs @@ -7,11 +7,11 @@ use noirc_errors::{Location, Span, Spanned}; use crate::ast::Ident; use crate::graph::CrateId; -use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTypeAlias}; +use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::{LocalModuleId, ModuleId}; use crate::hir::StorageSlot; use crate::hir_def::stmt::HirLetStatement; -use crate::hir_def::types::{StructType, Type}; +use crate::hir_def::types::{StructType, Trait, Type}; use crate::hir_def::{ expr::HirExpression, function::{FuncMeta, HirFunction}, @@ -61,6 +61,16 @@ pub struct NodeInterner { // When resolving types, check against this map to see if a type alias is defined. type_aliases: Vec, + // Trait map. + // + // Each trait definition is possibly shared across multiple type nodes. + // It is also mutated through the RefCell during name resolution to append + // methods from impls to the type. + // + // TODO: We may be able to remove the Shared wrapper once traits are no longer types. + // We'd just lookup their methods as needed through the NodeInterner. + traits: HashMap>, + /// Map from ExprId (referring to a Function/Method call) to its corresponding TypeBindings, /// filled out during type checking from instantiated variables. Used during monomorphization /// to map call site types back onto function parameter types, and undo this binding as needed. @@ -150,6 +160,18 @@ impl TypeAliasId { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct TraitId(pub ModuleId); + +impl TraitId { + // dummy id for error reporting + // This can be anything, as the program will ultimately fail + // after resolution + pub fn dummy_id() -> TraitId { + TraitId(ModuleId { krate: CrateId::dummy_id(), local_id: LocalModuleId::dummy_id() }) + } +} + macro_rules! into_index { ($id_type:ty) => { impl From<$id_type> for Index { @@ -262,6 +284,7 @@ impl Default for NodeInterner { id_to_type: HashMap::new(), structs: HashMap::new(), type_aliases: Vec::new(), + traits: HashMap::new(), instantiation_bindings: HashMap::new(), field_indices: HashMap::new(), next_type_variable_id: 0, @@ -304,6 +327,26 @@ impl NodeInterner { self.id_to_type.insert(expr_id.into(), typ); } + pub fn push_empty_trait(&mut self, type_id: TraitId, typ: &UnresolvedTrait) { + self.traits.insert( + type_id, + Shared::new(Trait::new( + type_id, + typ.trait_def.name.clone(), + typ.trait_def.span, + Vec::new(), + vecmap(&typ.trait_def.generics, |_| { + // Temporary type variable ids before the trait is resolved to its actual ids. + // This lets us record how many arguments the type expects so that other types + // can refer to it with generic arguments before the generic parameters themselves + // are resolved. + let id = TypeVariableId(0); + (id, Shared::new(TypeBinding::Unbound(id))) + }), + )), + ); + } + pub fn push_empty_struct(&mut self, type_id: StructId, typ: &UnresolvedStruct) { self.structs.insert( type_id, @@ -551,6 +594,10 @@ impl NodeInterner { &self.type_aliases[id.0] } + pub fn get_trait(&self, id: TraitId) -> Shared { + self.traits[&id].clone() + } + pub fn get_global(&self, stmt_id: &StmtId) -> Option { self.globals.get(stmt_id).cloned() } @@ -683,6 +730,7 @@ fn get_type_method_key(typ: &Type) -> Option { | Type::Error | Type::NotConstant | Type::Struct(_, _) - | Type::FmtString(_, _) => None, + | Type::FmtString(_, _) + | Type::Trait(_, _) => None, } } diff --git a/crates/noirc_frontend/src/parser/errors.rs b/crates/noirc_frontend/src/parser/errors.rs index 9b072d83b09..abd50263d0f 100644 --- a/crates/noirc_frontend/src/parser/errors.rs +++ b/crates/noirc_frontend/src/parser/errors.rs @@ -27,6 +27,8 @@ pub enum ParserErrorReason { PatternInTraitFunctionParameter, #[error("Traits are an experimental feature that are not yet in a working state")] TraitsAreExperimental, + #[error("Where clauses are allowed only on functions with generic parameters")] + WhereClauseOnNonGenericFunction, } /// Represents a parsing error, or a parsing error in the making. diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 6445205eae6..d8aa5342183 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/noirc_frontend/src/parser/parser.rs @@ -168,9 +168,9 @@ fn function_definition(allow_self: bool) -> impl NoirParser { .then(function_return_type()) .then(where_clause()) .then(block(expression())) - .map(|(((args, ret), where_clause), body)| { + .validate(|(((args, ret), where_clause), body), span, emit| { let ((((attribute, modifiers), name), generics), parameters) = args; - + validate_where_clause(&generics, &where_clause, span, emit); FunctionDefinition { span: name.0.span(), name, @@ -364,22 +364,44 @@ fn trait_definition() -> impl NoirParser { keyword(Keyword::Trait) .ignore_then(ident()) .then(generics()) + .then(where_clause()) .then_ignore(just(Token::LeftBrace)) .then(trait_body()) .then_ignore(just(Token::RightBrace)) - .validate(|((name, generics), items), span, emit| { - emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); - TopLevelStatement::Trait(NoirTrait { name, generics, items }) + .validate(|(((name, generics), where_clause), items), span, emit| { + validate_where_clause(&generics, &where_clause, span, emit); + if !generics.is_empty() || !where_clause.is_empty() { + emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); + } + TopLevelStatement::Trait(NoirTrait { name, generics, where_clause, span, items }) }) } fn trait_body() -> impl NoirParser> { trait_function_declaration() .or(trait_type_declaration()) + .or(trait_constant_declaration()) .separated_by(just(Token::Semicolon)) .allow_trailing() } +fn optional_default_value() -> impl NoirParser> { + ignore_then_commit(just(Token::Assign), expression()).or_not() +} + +fn trait_constant_declaration() -> impl NoirParser { + keyword(Keyword::Let) + .ignore_then(ident()) + .then_ignore(just(Token::Colon)) + .then(parse_type()) + .then(optional_default_value()) + .validate(|((name, typ), default_value), _span, _emit| TraitItem::Constant { + name, + typ, + default_value, + }) +} + /// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type fn trait_function_declaration() -> impl NoirParser { keyword(Keyword::Fn) @@ -388,13 +410,31 @@ fn trait_function_declaration() -> impl NoirParser { .then(parenthesized(function_declaration_parameters())) .then(function_return_type().map(|(_, typ)| typ)) .then(where_clause()) - .map(|((((name, generics), parameters), return_type), where_clause)| TraitItem::Function { - name, - generics, - parameters, - return_type, - where_clause, - }) + .then(block(expression()).or_not()) + .validate( + |(((((name, generics), parameters), return_type), where_clause), body), span, emit| { + validate_where_clause(&generics, &where_clause, span, emit); + TraitItem::Function { name, generics, parameters, return_type, where_clause, body } + }, + ) +} + +fn validate_where_clause( + generics: &Vec, + where_clause: &Vec, + span: Span, + emit: &mut dyn FnMut(ParserError), +) { + if !where_clause.is_empty() && generics.is_empty() { + emit(ParserError::with_reason(ParserErrorReason::WhereClauseOnNonGenericFunction, span)); + } + + // TODO(GenericParameterNotFoundInFunction): + // Even though Rust supports where clauses that don't mention any of the generic + // parameters, these are of dubious value and can be accidentally produced by + // typos in the code, so we can consider producing compile-time errors for them. + // + // https://doc.rust-lang.org/reference/items/generics.html#where-clauses } /// Function declaration parameters differ from other parameters in that parameter @@ -403,9 +443,8 @@ fn function_declaration_parameters() -> impl NoirParser (ident, param.1), other => { @@ -418,7 +457,9 @@ fn function_declaration_parameters() -> impl NoirParser impl NoirParser { let (((impl_generics, trait_name), trait_generics), (object_type, object_type_span)) = other_args; - emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); + if !where_clause.is_empty() || !impl_generics.is_empty() { + emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); + } + TopLevelStatement::TraitImpl(TraitImpl { impl_generics, trait_name, @@ -504,7 +548,7 @@ fn where_clause() -> impl NoirParser> { }); keyword(Keyword::Where) - .ignore_then(constraints.repeated()) + .ignore_then(constraints.separated_by(just(Token::Comma))) .or_not() .map(|option| option.unwrap_or_default()) } @@ -1748,7 +1792,8 @@ mod test { "fn func_name() {}", "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", "fn f(f: pub Field, y : Field, z : comptime Field) -> u8 { x + a }", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", + "fn f(f: pub Field, y : T, z : comptime Field) -> u8 { x + a }", + "fn func_name(f: Field, y : T) where T: SomeTrait {}", "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", "fn main(x: pub u8, y: pub u8) -> distinct pub [u8; 2] { [x, y] }", ], @@ -1756,7 +1801,52 @@ mod test { parse_all_failing( function_definition(false), - vec!["fn x2( f: []Field,,) {}", "fn ( f: []Field) {}", "fn ( f: []Field) {}"], + vec![ + "fn x2( f: []Field,,) {}", + "fn ( f: []Field) {}", + "fn ( f: []Field) {}", + // TODO: Check for more specific error messages + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: {}", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where SomeTrait {}", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) SomeTrait {}", + "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: SomeTrait {}", + // TODO(GenericParameterNotFoundInFunction) + // Consider making this a compilation error: + // "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: SomeTrait {}", + ], + ); + } + + #[test] + fn parse_trait() { + parse_all( + trait_definition(), + vec![ + // Empty traits are legal in Rust and sometimes used as a way to whitelist certain types + // for a particular operation. Also known as `tag` or `marker` traits: + // https://stackoverflow.com/questions/71895489/what-is-the-purpose-of-defining-empty-impl-in-rust + "trait Empty {}", + "trait TraitWithDefaultBody { fn foo(self) {}; }", + "trait TraitAcceptingMutableRef { fn foo(&mut self); }", + "trait TraitWithTypeBoundOperation { fn identity() -> Self; }", + "trait TraitWithAssociatedType { type Element; fn item(self, index: Field) -> Self::Element; }", + "trait TraitWithAssociatedConstant { let Size: Field; }", + "trait TraitWithAssociatedConstantWithDefaultValue { let Size: Field = 10; }", + ], + ); + + parse_all_failing( + trait_definition(), + vec![ + "trait MissingBody", + "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }", + "trait WhereClauseWithoutGenerics where A: SomeTrait { }", + // TODO: when implemnt generics in traits the following 3 should pass + "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", + "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", + "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { comptime Size: Field; fn zero() -> Self; }", + + ], ); }