From 30cb65a12668d192f8da940c32961210a05a962f Mon Sep 17 00:00:00 2001 From: jfecher Date: Wed, 17 Jul 2024 10:35:10 -0500 Subject: [PATCH] feat: Add TraitConstraint type (#5499) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/5480 Resolves https://github.com/noir-lang/noir/issues/5481 ## Summary\* Adds: - The `TraitConstraint` type - `impl Eq for TraitConstraint` - `impl Hash for TraitConstraint` - `Quoted::as_trait_constraint` ## Additional Context Ran into the type error when calling trait impls issue again while working on this. Hence why it is a draft. ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[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. --- aztec_macros/src/transforms/note_interface.rs | 1 + aztec_macros/src/transforms/storage.rs | 1 + compiler/noirc_frontend/src/ast/structure.rs | 13 +- compiler/noirc_frontend/src/ast/traits.rs | 5 +- compiler/noirc_frontend/src/elaborator/mod.rs | 152 ++++++++++-------- .../src/hir/comptime/interpreter/builtin.rs | 93 ++++++++++- .../noirc_frontend/src/hir/comptime/value.rs | 7 +- .../src/hir/def_collector/dc_crate.rs | 3 +- .../src/hir/def_collector/dc_mod.rs | 1 + .../noirc_frontend/src/hir/def_map/mod.rs | 2 +- compiler/noirc_frontend/src/hir_def/types.rs | 2 + compiler/noirc_frontend/src/lexer/token.rs | 3 + compiler/noirc_frontend/src/parser/mod.rs | 20 ++- compiler/noirc_frontend/src/parser/parser.rs | 32 ++-- .../src/parser/parser/structs.rs | 15 +- .../src/parser/parser/traits.rs | 11 +- .../noirc_frontend/src/parser/parser/types.rs | 8 + compiler/noirc_frontend/src/tests.rs | 2 +- noir_stdlib/src/meta/mod.nr | 2 + noir_stdlib/src/meta/quoted.nr | 4 + noir_stdlib/src/meta/trait_constraint.nr | 20 +++ .../comptime_trait_constraint/Nargo.toml | 7 + .../comptime_trait_constraint/src/main.nr | 39 +++++ 23 files changed, 323 insertions(+), 120 deletions(-) create mode 100644 noir_stdlib/src/meta/quoted.nr create mode 100644 noir_stdlib/src/meta/trait_constraint.nr create mode 100644 test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml create mode 100644 test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr diff --git a/aztec_macros/src/transforms/note_interface.rs b/aztec_macros/src/transforms/note_interface.rs index 49525fc2ae1..1a25950e6c8 100644 --- a/aztec_macros/src/transforms/note_interface.rs +++ b/aztec_macros/src/transforms/note_interface.rs @@ -73,6 +73,7 @@ pub fn generate_note_interface_impl(module: &mut SortedModule) -> Result<(), Azt generics: vec![], methods: vec![], where_clause: vec![], + is_comptime: false, }; module.impls.push(default_impl.clone()); module.impls.last_mut().unwrap() diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index c302dd87aa5..1c6ef634070 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -248,6 +248,7 @@ pub fn generate_storage_implementation( methods: vec![(init, Span::default())], where_clause: vec![], + is_comptime: false, }; module.impls.push(storage_impl); diff --git a/compiler/noirc_frontend/src/ast/structure.rs b/compiler/noirc_frontend/src/ast/structure.rs index bb2d89841b9..112747e09fb 100644 --- a/compiler/noirc_frontend/src/ast/structure.rs +++ b/compiler/noirc_frontend/src/ast/structure.rs @@ -14,18 +14,7 @@ pub struct NoirStruct { pub generics: UnresolvedGenerics, pub fields: Vec<(Ident, UnresolvedType)>, pub span: Span, -} - -impl NoirStruct { - pub fn new( - name: Ident, - attributes: Vec, - generics: UnresolvedGenerics, - fields: Vec<(Ident, UnresolvedType)>, - span: Span, - ) -> NoirStruct { - NoirStruct { name, attributes, generics, fields, span } - } + pub is_comptime: bool, } impl Display for NoirStruct { diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 064ea8ba9d9..b23fbaede61 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -53,6 +53,7 @@ pub struct TypeImpl { pub generics: UnresolvedGenerics, pub where_clause: Vec, pub methods: Vec<(NoirFunction, Span)>, + pub is_comptime: bool, } /// Ast node for an implementation of a trait for a particular type @@ -69,6 +70,8 @@ pub struct NoirTraitImpl { pub where_clause: Vec, pub items: Vec, + + pub is_comptime: bool, } /// Represents a simple trait constraint such as `where Foo: TraitY` @@ -84,7 +87,7 @@ pub struct UnresolvedTraitConstraint { } /// Represents a single trait bound, such as `TraitX` or `TraitY` -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TraitBound { pub trait_path: Path, pub trait_id: Option, // initially None, gets assigned during DC diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 431a0b9a2da..f573947ffb2 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -1636,17 +1636,25 @@ impl<'context> Elaborator<'context> { function_sets.push(UnresolvedFunctions { functions, file_id, trait_id, self_type }); } + let (comptime_trait_impls, trait_impls) = + items.trait_impls.into_iter().partition(|trait_impl| trait_impl.is_comptime); + + let (comptime_structs, structs) = + items.types.into_iter().partition(|typ| typ.1.struct_def.is_comptime); + let comptime = CollectedItems { functions: comptime_function_sets, - types: BTreeMap::new(), + types: comptime_structs, type_aliases: BTreeMap::new(), traits: BTreeMap::new(), - trait_impls: Vec::new(), + trait_impls: comptime_trait_impls, globals: Vec::new(), impls: rustc_hash::FxHashMap::default(), }; items.functions = function_sets; + items.trait_impls = trait_impls; + items.types = structs; (comptime, items) } @@ -1657,75 +1665,85 @@ impl<'context> Elaborator<'context> { location: Location, ) { for item in items { - match item { - TopLevelStatement::Function(function) => { - let id = self.interner.push_empty_fn(); - let module = self.module_id(); - self.interner.push_function(id, &function.def, module, location); - let functions = vec![(self.local_module, id, function)]; - generated_items.functions.push(UnresolvedFunctions { - file_id: self.file, - functions, - trait_id: None, - self_type: None, - }); - } - TopLevelStatement::TraitImpl(mut trait_impl) => { - let methods = dc_mod::collect_trait_impl_functions( - self.interner, - &mut trait_impl, - self.crate_id, - self.file, - self.local_module, - ); + self.add_item(item, generated_items, location); + } + } - generated_items.trait_impls.push(UnresolvedTraitImpl { - file_id: self.file, - module_id: self.local_module, - trait_generics: trait_impl.trait_generics, - trait_path: trait_impl.trait_name, - object_type: trait_impl.object_type, - methods, - generics: trait_impl.impl_generics, - where_clause: trait_impl.where_clause, - - // These last fields are filled in later - trait_id: None, - impl_id: None, - resolved_object_type: None, - resolved_generics: Vec::new(), - resolved_trait_generics: Vec::new(), - }); - } - TopLevelStatement::Global(global) => { - let (global, error) = dc_mod::collect_global( - self.interner, - self.def_maps.get_mut(&self.crate_id).unwrap(), - global, - self.file, - self.local_module, - ); + fn add_item( + &mut self, + item: TopLevelStatement, + generated_items: &mut CollectedItems, + location: Location, + ) { + match item { + TopLevelStatement::Function(function) => { + let id = self.interner.push_empty_fn(); + let module = self.module_id(); + self.interner.push_function(id, &function.def, module, location); + let functions = vec![(self.local_module, id, function)]; + generated_items.functions.push(UnresolvedFunctions { + file_id: self.file, + functions, + trait_id: None, + self_type: None, + }); + } + TopLevelStatement::TraitImpl(mut trait_impl) => { + let methods = dc_mod::collect_trait_impl_functions( + self.interner, + &mut trait_impl, + self.crate_id, + self.file, + self.local_module, + ); - generated_items.globals.push(global); - if let Some(error) = error { - self.errors.push(error); - } - } - // Assume that an error has already been issued - TopLevelStatement::Error => (), - - TopLevelStatement::Module(_) - | TopLevelStatement::Import(_) - | TopLevelStatement::Struct(_) - | TopLevelStatement::Trait(_) - | TopLevelStatement::Impl(_) - | TopLevelStatement::TypeAlias(_) - | TopLevelStatement::SubModule(_) => { - let item = item.to_string(); - let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; - self.errors.push(error.into_compilation_error_pair()); + generated_items.trait_impls.push(UnresolvedTraitImpl { + file_id: self.file, + module_id: self.local_module, + trait_generics: trait_impl.trait_generics, + trait_path: trait_impl.trait_name, + object_type: trait_impl.object_type, + methods, + generics: trait_impl.impl_generics, + where_clause: trait_impl.where_clause, + is_comptime: trait_impl.is_comptime, + + // These last fields are filled in later + trait_id: None, + impl_id: None, + resolved_object_type: None, + resolved_generics: Vec::new(), + resolved_trait_generics: Vec::new(), + }); + } + TopLevelStatement::Global(global) => { + let (global, error) = dc_mod::collect_global( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + global, + self.file, + self.local_module, + ); + + generated_items.globals.push(global); + if let Some(error) = error { + self.errors.push(error); } } + // Assume that an error has already been issued + TopLevelStatement::Error => (), + + TopLevelStatement::Module(_) + | TopLevelStatement::Import(_) + | TopLevelStatement::Struct(_) + | TopLevelStatement::Trait(_) + | TopLevelStatement::Impl(_) + | TopLevelStatement::TypeAlias(_) + | TopLevelStatement::SubModule(_) => { + let item = item.to_string(); + let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; + self.errors.push(error.into_compilation_error_pair()); + } } } diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 399d9905269..eea5e2747e5 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -1,11 +1,16 @@ -use std::rc::Rc; +use std::{ + hash::{Hash, Hasher}, + rc::Rc, +}; +use chumsky::Parser; use noirc_errors::Location; use crate::{ - ast::IntegerBitSize, + ast::{IntegerBitSize, TraitBound}, hir::comptime::{errors::IResult, InterpreterError, Value}, macros_api::{NodeInterner, Signedness}, + parser, token::{SpannedToken, Token, Tokens}, QuotedType, Type, }; @@ -29,6 +34,9 @@ pub(super) fn call_builtin( "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), + "trait_constraint_eq" => trait_constraint_eq(interner, arguments, location), + "trait_constraint_hash" => trait_constraint_hash(interner, arguments, location), + "quoted_as_trait_constraint" => quoted_as_trait_constraint(interner, arguments, location), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -79,6 +87,26 @@ fn get_u32(value: Value, location: Location) -> IResult { } } +fn get_trait_constraint(value: Value, location: Location) -> IResult { + match value { + Value::TraitConstraint(bound) => Ok(bound), + value => { + let expected = Type::Quoted(QuotedType::TraitConstraint); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn get_quoted(value: Value, location: Location) -> IResult> { + match value { + Value::Code(tokens) => Ok(tokens), + value => { + let expected = Type::Quoted(QuotedType::Quoted); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + fn array_len( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, @@ -231,7 +259,7 @@ fn slice_remove( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(2, &arguments, location)?; let index = get_u32(arguments.pop().unwrap().0, location)? as usize; @@ -257,7 +285,7 @@ fn slice_push_front( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(2, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); @@ -270,7 +298,7 @@ fn slice_pop_front( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(1, &arguments, location)?; let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; @@ -284,7 +312,7 @@ fn slice_pop_back( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(1, &arguments, location)?; let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; @@ -298,7 +326,7 @@ fn slice_insert( interner: &mut NodeInterner, mut arguments: Vec<(Value, Location)>, location: Location, -) -> Result { +) -> IResult { check_argument_count(3, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); @@ -307,3 +335,54 @@ fn slice_insert( values.insert(index as usize, element); Ok(Value::Slice(values, typ)) } + +// fn as_trait_constraint(quoted: Quoted) -> TraitConstraint +fn quoted_as_trait_constraint( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let tokens = get_quoted(arguments.pop().unwrap().0, location)?; + let quoted = tokens.as_ref().clone(); + + let trait_bound = parser::trait_bound().parse(quoted).map_err(|mut errors| { + let error = errors.swap_remove(0); + let rule = "a trait constraint"; + InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } + })?; + + Ok(Value::TraitConstraint(trait_bound)) +} + +// fn constraint_hash(constraint: TraitConstraint) -> Field +fn trait_constraint_hash( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(1, &arguments, location)?; + + let bound = get_trait_constraint(arguments.pop().unwrap().0, location)?; + + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + bound.hash(&mut hasher); + let hash = hasher.finish(); + + Ok(Value::Field((hash as u128).into())) +} + +// fn constraint_eq(constraint_a: TraitConstraint, constraint_b: TraitConstraint) -> bool +fn trait_constraint_eq( + _interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(2, &arguments, location)?; + + let constraint_b = get_trait_constraint(arguments.pop().unwrap().0, location)?; + let constraint_a = get_trait_constraint(arguments.pop().unwrap().0, location)?; + + Ok(Value::Bool(constraint_a == constraint_b)) +} diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 6f83f9bf23e..9eeb323d664 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -7,7 +7,7 @@ use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; use crate::{ - ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness}, + ast::{ArrayLiteral, ConstructorExpression, Ident, IntegerBitSize, Signedness, TraitBound}, hir::def_map::ModuleId, hir_def::expr::{HirArrayLiteral, HirConstructorExpression, HirIdent, HirLambda, ImplKind}, macros_api::{ @@ -46,6 +46,7 @@ pub enum Value { Slice(Vector, Type), Code(Rc), StructDefinition(StructId), + TraitConstraint(TraitBound), TraitDefinition(TraitId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), @@ -83,6 +84,7 @@ impl Value { let element = element.borrow().get_type().into_owned(); Type::MutableReference(Box::new(element)) } + Value::TraitConstraint { .. } => Type::Quoted(QuotedType::TraitConstraint), Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition), Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition), Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module), @@ -201,6 +203,7 @@ impl Value { } Value::Pointer(..) | Value::StructDefinition(_) + | Value::TraitConstraint(_) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::ModuleDefinition(_) => { @@ -311,6 +314,7 @@ impl Value { Value::Code(block) => HirExpression::Unquote(unwrap_rc(block)), Value::Pointer(..) | Value::StructDefinition(_) + | Value::TraitConstraint(_) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) | Value::ModuleDefinition(_) => { @@ -417,6 +421,7 @@ impl Display for Value { write!(f, " }}") } Value::StructDefinition(_) => write!(f, "(struct definition)"), + Value::TraitConstraint { .. } => write!(f, "(trait constraint)"), Value::TraitDefinition(_) => write!(f, "(trait definition)"), Value::FunctionDefinition(_) => write!(f, "(function definition)"), Value::ModuleDefinition(_) => write!(f, "(module)"), diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 200e8cbb22f..ad8832b3f68 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -79,6 +79,7 @@ pub struct UnresolvedTraitImpl { pub methods: UnresolvedFunctions, pub generics: UnresolvedGenerics, pub where_clause: Vec, + pub is_comptime: bool, // Every field after this line is filled in later in the elaborator pub trait_id: Option, @@ -240,7 +241,7 @@ impl DefCollector { /// Collect all of the definitions in a given crate into a CrateDefMap /// Modules which are not a part of the module hierarchy starting with /// the root module, will be ignored. - pub fn collect( + pub fn collect_crate_and_dependencies( mut def_map: CrateDefMap, context: &mut Context, ast: SortedModule, 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 1f30b4388b6..a9213a8c09a 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -185,6 +185,7 @@ impl<'a> ModCollector<'a> { generics: trait_impl.impl_generics, where_clause: trait_impl.where_clause, trait_generics: trait_impl.trait_generics, + is_comptime: trait_impl.is_comptime, // These last fields are filled later on trait_id: None, diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 143d29971bf..9de96ab06e8 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -117,7 +117,7 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( + errors.extend(DefCollector::collect_crate_and_dependencies( def_map, context, ast, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 91a374a0787..677915776e9 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -142,6 +142,7 @@ pub enum QuotedType { TopLevelItem, Type, StructDefinition, + TraitConstraint, TraitDefinition, FunctionDefinition, Module, @@ -681,6 +682,7 @@ impl std::fmt::Display for QuotedType { QuotedType::Type => write!(f, "Type"), QuotedType::StructDefinition => write!(f, "StructDefinition"), QuotedType::TraitDefinition => write!(f, "TraitDefinition"), + QuotedType::TraitConstraint => write!(f, "TraitConstraint"), QuotedType::FunctionDefinition => write!(f, "FunctionDefinition"), QuotedType::Module => write!(f, "Module"), } diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 59ae3d8fd7a..c6a1d44f26b 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -914,6 +914,7 @@ pub enum Keyword { Super, TopLevelItem, Trait, + TraitConstraint, TraitDefinition, Type, TypeType, @@ -965,6 +966,7 @@ impl fmt::Display for Keyword { Keyword::Super => write!(f, "super"), Keyword::TopLevelItem => write!(f, "TopLevelItem"), Keyword::Trait => write!(f, "trait"), + Keyword::TraitConstraint => write!(f, "TraitConstraint"), Keyword::TraitDefinition => write!(f, "TraitDefinition"), Keyword::Type => write!(f, "type"), Keyword::TypeType => write!(f, "Type"), @@ -1018,6 +1020,7 @@ impl Keyword { "super" => Keyword::Super, "TopLevelItem" => Keyword::TopLevelItem, "trait" => Keyword::Trait, + "TraitConstraint" => Keyword::TraitConstraint, "TraitDefinition" => Keyword::TraitDefinition, "type" => Keyword::Type, "Type" => Keyword::TypeType, diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index c4aa0654ecd..c62d66769ac 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -22,7 +22,7 @@ use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::{expression, parse_program, top_level_items}; +pub use parser::{expression, parse_program, top_level_items, trait_bound}; #[derive(Debug, Clone)] pub enum TopLevelStatement { @@ -39,6 +39,24 @@ pub enum TopLevelStatement { Error, } +impl TopLevelStatement { + pub fn into_item_kind(self) -> Option { + match self { + TopLevelStatement::Function(f) => Some(ItemKind::Function(f)), + TopLevelStatement::Module(m) => Some(ItemKind::ModuleDecl(m)), + TopLevelStatement::Import(i) => Some(ItemKind::Import(i)), + TopLevelStatement::Struct(s) => Some(ItemKind::Struct(s)), + TopLevelStatement::Trait(t) => Some(ItemKind::Trait(t)), + TopLevelStatement::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), + TopLevelStatement::Impl(i) => Some(ItemKind::Impl(i)), + TopLevelStatement::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), + TopLevelStatement::SubModule(s) => Some(ItemKind::Submodules(s)), + TopLevelStatement::Global(c) => Some(ItemKind::Global(c)), + TopLevelStatement::Error => None, + } + } +} + // Helper trait that gives us simpler type signatures for return types: // e.g. impl Parser versus impl Parser> pub trait NoirParser: Parser + Sized + Clone {} diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index de9095aaff2..7f3e0e68bbc 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -171,20 +171,8 @@ fn module() -> impl NoirParser { .to(ParsedModule::default()) .then(spanned(top_level_statement(module_parser)).repeated()) .foldl(|mut program, (statement, span)| { - let mut push_item = |kind| program.items.push(Item { kind, span }); - - match statement { - TopLevelStatement::Function(f) => push_item(ItemKind::Function(f)), - TopLevelStatement::Module(m) => push_item(ItemKind::ModuleDecl(m)), - TopLevelStatement::Import(i) => push_item(ItemKind::Import(i)), - TopLevelStatement::Struct(s) => push_item(ItemKind::Struct(s)), - TopLevelStatement::Trait(t) => push_item(ItemKind::Trait(t)), - TopLevelStatement::TraitImpl(t) => push_item(ItemKind::TraitImpl(t)), - TopLevelStatement::Impl(i) => push_item(ItemKind::Impl(i)), - TopLevelStatement::TypeAlias(t) => push_item(ItemKind::TypeAlias(t)), - TopLevelStatement::SubModule(s) => push_item(ItemKind::Submodules(s)), - TopLevelStatement::Global(c) => push_item(ItemKind::Global(c)), - TopLevelStatement::Error => (), + if let Some(kind) = statement.into_item_kind() { + program.items.push(Item { kind, span }); } program }) @@ -204,9 +192,9 @@ pub fn top_level_items() -> impl NoirParser> { /// | module_declaration /// | use_statement /// | global_declaration -fn top_level_statement( - module_parser: impl NoirParser, -) -> impl NoirParser { +fn top_level_statement<'a>( + module_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { choice(( function::function_definition(false).map(TopLevelStatement::Function), structs::struct_definition(), @@ -227,8 +215,9 @@ fn top_level_statement( /// /// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) + maybe_comp_time() + .then_ignore(keyword(Keyword::Impl)) + .then(function::generics()) .then(parse_type().map_with_span(|typ, span| (typ, span))) .then(where_clause()) .then_ignore(just(Token::LeftBrace)) @@ -236,13 +225,14 @@ fn implementation() -> impl NoirParser { .then_ignore(just(Token::RightBrace)) .map(|args| { let ((other_args, where_clause), methods) = args; - let (generics, (object_type, type_span)) = other_args; + let ((is_comptime, generics), (object_type, type_span)) = other_args; TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, where_clause, methods, + is_comptime, }) }) } @@ -408,7 +398,7 @@ fn trait_bounds() -> impl NoirParser> { trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() } -fn trait_bound() -> impl NoirParser { +pub fn trait_bound() -> impl NoirParser { path().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| TraitBound { trait_path, trait_generics, diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index 7da956bdfea..9a3adf74d7f 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,6 +1,7 @@ use chumsky::prelude::*; use crate::ast::{Ident, NoirStruct, UnresolvedType}; +use crate::parser::parser::types::maybe_comp_time; use crate::{ parser::{ parser::{ @@ -28,13 +29,21 @@ pub(super) fn struct_definition() -> impl NoirParser { .or(just(Semicolon).to(Vec::new())); attributes() + .then(maybe_comp_time()) .then_ignore(keyword(Struct)) .then(ident()) .then(function::generics()) .then(fields) - .validate(|(((raw_attributes, name), generics), fields), span, emit| { - let attributes = validate_secondary_attributes(raw_attributes, span, emit); - TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) + .validate(|((((attributes, is_comptime), name), generics), fields), span, emit| { + let attributes = validate_secondary_attributes(attributes, span, emit); + TopLevelStatement::Struct(NoirStruct { + name, + attributes, + generics, + fields, + span, + is_comptime, + }) }) } diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index e64de584da4..4e4c9d5c0db 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -2,6 +2,7 @@ use chumsky::prelude::*; use super::attributes::{attributes, validate_secondary_attributes}; use super::function::function_return_type; +use super::types::maybe_comp_time; use super::{block, expression, fresh_statement, function, function_declaration_parameters}; use crate::ast::{ @@ -103,8 +104,9 @@ fn trait_type_declaration() -> impl NoirParser { /// /// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' pub(super) fn trait_implementation() -> impl NoirParser { - keyword(Keyword::Impl) - .ignore_then(function::generics()) + maybe_comp_time() + .then_ignore(keyword(Keyword::Impl)) + .then(function::generics()) .then(path()) .then(generic_type_args(parse_type())) .then_ignore(keyword(Keyword::For)) @@ -114,8 +116,8 @@ pub(super) fn trait_implementation() -> impl NoirParser { .then(trait_implementation_body()) .then_ignore(just(Token::RightBrace)) .map(|args| { - let ((other_args, where_clause), items) = args; - let (((impl_generics, trait_name), trait_generics), object_type) = other_args; + let (((other_args, object_type), where_clause), items) = args; + let (((is_comptime, impl_generics), trait_name), trait_generics) = other_args; TopLevelStatement::TraitImpl(NoirTraitImpl { impl_generics, @@ -124,6 +126,7 @@ pub(super) fn trait_implementation() -> impl NoirParser { object_type, items, where_clause, + is_comptime, }) }) } diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index 7b69b309bae..3bd59608b12 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -26,6 +26,7 @@ pub(super) fn parse_type_inner<'a>( string_type(), expr_type(), struct_definition_type(), + trait_constraint_type(), trait_definition_type(), function_definition_type(), module_type(), @@ -82,6 +83,13 @@ pub(super) fn struct_definition_type() -> impl NoirParser { }) } +/// This is the type `TraitConstraint` - the type of a quoted trait constraint +pub(super) fn trait_constraint_type() -> impl NoirParser { + keyword(Keyword::TraitConstraint).map_with_span(|_, span| { + UnresolvedTypeData::Quoted(QuotedType::TraitConstraint).with_span(span) + }) +} + pub(super) fn trait_definition_type() -> impl NoirParser { keyword(Keyword::TraitDefinition).map_with_span(|_, span| { UnresolvedTypeData::Quoted(QuotedType::TraitDefinition).with_span(span) diff --git a/compiler/noirc_frontend/src/tests.rs b/compiler/noirc_frontend/src/tests.rs index 956b1c57de7..eb4631385ed 100644 --- a/compiler/noirc_frontend/src/tests.rs +++ b/compiler/noirc_frontend/src/tests.rs @@ -77,7 +77,7 @@ pub(crate) fn get_program(src: &str) -> (ParsedModule, Context, Vec<(Compilation }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( + errors.extend(DefCollector::collect_crate_and_dependencies( def_map, &mut context, program.clone().into_sorted(), diff --git a/noir_stdlib/src/meta/mod.nr b/noir_stdlib/src/meta/mod.nr index ad8ee4f8586..ed3365d755c 100644 --- a/noir_stdlib/src/meta/mod.nr +++ b/noir_stdlib/src/meta/mod.nr @@ -1,4 +1,6 @@ mod type_def; +mod trait_constraint; +mod quoted; /// Calling unquote as a macro (via `unquote!(arg)`) will unquote /// its argument. Since this is the effect `!` already does, `unquote` diff --git a/noir_stdlib/src/meta/quoted.nr b/noir_stdlib/src/meta/quoted.nr new file mode 100644 index 00000000000..6273d64b10c --- /dev/null +++ b/noir_stdlib/src/meta/quoted.nr @@ -0,0 +1,4 @@ +impl Quoted { + #[builtin(quoted_as_trait_constraint)] + fn as_trait_constraint(self) -> TraitConstraint {} +} diff --git a/noir_stdlib/src/meta/trait_constraint.nr b/noir_stdlib/src/meta/trait_constraint.nr new file mode 100644 index 00000000000..f0276608974 --- /dev/null +++ b/noir_stdlib/src/meta/trait_constraint.nr @@ -0,0 +1,20 @@ +use crate::hash::{Hash, Hasher}; +use crate::cmp::Eq; + +impl Eq for TraitConstraint { + fn eq(self, other: Self) -> bool { + constraint_eq(self, other) + } +} + +impl Hash for TraitConstraint { + fn hash(self, state: &mut H) where H: Hasher { + state.write(constraint_hash(self)); + } +} + +#[builtin(trait_constraint_eq)] +fn constraint_eq(_first: TraitConstraint, _second: TraitConstraint) -> bool {} + +#[builtin(trait_constraint_hash)] +fn constraint_hash(_constraint: TraitConstraint) -> Field {} diff --git a/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml b/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml new file mode 100644 index 00000000000..c7e28c053a7 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_trait_constraint/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_trait_constraint" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr b/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr new file mode 100644 index 00000000000..5c99f8c587e --- /dev/null +++ b/test_programs/compile_success_empty/comptime_trait_constraint/src/main.nr @@ -0,0 +1,39 @@ +use std::hash::{Hash, Hasher}; + +trait TraitWithGenerics { + fn foo(self) -> (A, B); +} + +fn main() { + comptime + { + let constraint1 = quote { Default }.as_trait_constraint(); + let constraint2 = quote { TraitWithGenerics }.as_trait_constraint(); + + assert(constraint1 != constraint2); + + let mut hasher = TestHasher { result: 0 }; + constraint1.hash(&mut hasher); + let hash1 = hasher.finish(); + + let mut hasher = TestHasher { result: 0 }; + constraint2.hash(&mut hasher); + let hash2 = hasher.finish(); + + assert(hash1 != hash2); + } +} + +comptime struct TestHasher { + result: Field, +} + +comptime impl Hasher for TestHasher { + comptime fn finish(self) -> Field { + self.result + } + + comptime fn write(&mut self, input: Field) { + self.result += input; + } +}