diff --git a/crates/noirc_frontend/src/ast/expression.rs b/crates/noirc_frontend/src/ast/expression.rs index 88fff617284..0b8cc9b575f 100644 --- a/crates/noirc_frontend/src/ast/expression.rs +++ b/crates/noirc_frontend/src/ast/expression.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use crate::token::{Attribute, Token}; -use crate::{Ident, Path, Pattern, Recoverable, Statement, UnresolvedType}; +use crate::{Ident, Path, Pattern, Recoverable, Statement, TraitConstraint, UnresolvedType}; use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -337,6 +337,7 @@ pub struct FunctionDefinition { pub parameters: Vec<(Pattern, UnresolvedType, noirc_abi::AbiVisibility)>, pub body: BlockExpression, pub span: Span, + pub where_clause: Vec, pub return_type: UnresolvedType, pub return_visibility: noirc_abi::AbiVisibility, pub return_distinctness: noirc_abi::AbiDistinctness, diff --git a/crates/noirc_frontend/src/ast/mod.rs b/crates/noirc_frontend/src/ast/mod.rs index 339dc346e64..ed73cce486a 100644 --- a/crates/noirc_frontend/src/ast/mod.rs +++ b/crates/noirc_frontend/src/ast/mod.rs @@ -8,6 +8,7 @@ mod expression; mod function; mod statement; mod structure; +mod traits; pub use expression::*; pub use function::*; @@ -15,6 +16,7 @@ pub use function::*; use noirc_errors::Span; pub use statement::*; pub use structure::*; +pub use traits::*; use crate::{ parser::{ParserError, ParserErrorReason}, diff --git a/crates/noirc_frontend/src/ast/statement.rs b/crates/noirc_frontend/src/ast/statement.rs index f8093221cf8..7292d227c3e 100644 --- a/crates/noirc_frontend/src/ast/statement.rs +++ b/crates/noirc_frontend/src/ast/statement.rs @@ -424,6 +424,14 @@ impl Pattern { _ => panic!("only the identifier pattern can return a name"), } } + + pub(crate) fn into_ident(self) -> Ident { + match self { + Pattern::Identifier(ident) => ident, + Pattern::Mutable(pattern, _) => pattern.into_ident(), + other => panic!("Pattern::into_ident called on {other} pattern with no identifier"), + } + } } impl Recoverable for Pattern { diff --git a/crates/noirc_frontend/src/ast/structure.rs b/crates/noirc_frontend/src/ast/structure.rs index 47dd432e739..9b4c0153f52 100644 --- a/crates/noirc_frontend/src/ast/structure.rs +++ b/crates/noirc_frontend/src/ast/structure.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::{Ident, NoirFunction, UnresolvedGenerics, UnresolvedType}; +use crate::{Ident, UnresolvedGenerics, UnresolvedType}; use iter_extended::vecmap; use noirc_errors::Span; @@ -24,15 +24,6 @@ impl NoirStruct { } } -/// Ast node for an impl -#[derive(Clone, Debug)] -pub struct NoirImpl { - pub object_type: UnresolvedType, - pub type_span: Span, - pub generics: UnresolvedGenerics, - pub methods: Vec, -} - impl Display for NoirStruct { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let generics = vecmap(&self.generics, |generic| generic.to_string()); @@ -47,21 +38,3 @@ impl Display for NoirStruct { write!(f, "}}") } } - -impl Display for NoirImpl { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let generics = vecmap(&self.generics, |generic| generic.to_string()); - let generics = if generics.is_empty() { "".into() } else { generics.join(", ") }; - - writeln!(f, "impl{} {} {{", generics, self.object_type)?; - - for method in self.methods.iter() { - let method = method.to_string(); - for line in method.lines() { - writeln!(f, " {line}")?; - } - } - - write!(f, "}}") - } -} diff --git a/crates/noirc_frontend/src/ast/traits.rs b/crates/noirc_frontend/src/ast/traits.rs new file mode 100644 index 00000000000..1dacb73fc5e --- /dev/null +++ b/crates/noirc_frontend/src/ast/traits.rs @@ -0,0 +1,165 @@ +use std::fmt::Display; + +use iter_extended::vecmap; +use noirc_errors::Span; + +use crate::{Ident, NoirFunction, UnresolvedGenerics, UnresolvedType}; + +/// AST node for trait definitions: +/// `trait name { ... items ... }` +#[derive(Clone, Debug)] +pub struct NoirTrait { + pub name: Ident, + pub generics: Vec, + pub items: Vec, +} + +/// Any declaration inside the body of a trait that a user is required to +/// specify when implementing the trait. +#[derive(Clone, Debug)] +pub enum TraitItem { + Function { + name: Ident, + generics: Vec, + parameters: Vec<(Ident, UnresolvedType)>, + return_type: UnresolvedType, + where_clause: Vec, + }, + Type { + name: Ident, + }, +} + +/// Ast node for an impl of a concrete type +/// `impl object_type { ... methods ... }` +#[derive(Clone, Debug)] +pub struct TypeImpl { + pub object_type: UnresolvedType, + pub type_span: Span, + pub generics: UnresolvedGenerics, + pub methods: Vec, +} + +/// Ast node for an implementation of a trait for a particular type +/// `impl trait_name for object_type where where_clauses { ... items ... }` +#[derive(Clone, Debug)] +pub struct TraitImpl { + pub impl_generics: UnresolvedGenerics, + + pub trait_name: Ident, + pub trait_generics: Vec, + + pub object_type: UnresolvedType, + pub object_type_span: Span, + + pub where_clause: Vec, + + pub items: Vec, +} + +/// Represents a trait constraint such as `where Foo: Display` +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TraitConstraint { + pub typ: UnresolvedType, + pub trait_name: Ident, + pub trait_generics: Vec, +} + +#[derive(Clone, Debug)] +pub enum TraitImplItem { + Function(NoirFunction), + Type { name: Ident, alias: UnresolvedType }, +} + +impl Display for TypeImpl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let generics = vecmap(&self.generics, |generic| generic.to_string()); + let generics = if generics.is_empty() { "".into() } else { generics.join(", ") }; + + writeln!(f, "impl{} {} {{", generics, self.object_type)?; + + for method in self.methods.iter() { + let method = method.to_string(); + for line in method.lines() { + writeln!(f, " {line}")?; + } + } + + write!(f, "}}") + } +} + +impl Display for NoirTrait { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let generics = vecmap(&self.generics, |generic| generic.to_string()); + let generics = if generics.is_empty() { "".into() } else { generics.join(", ") }; + + writeln!(f, "trait {}{} {{", self.name, generics)?; + + for item in self.items.iter() { + let item = item.to_string(); + for line in item.lines() { + writeln!(f, " {line}")?; + } + } + + write!(f, "}}") + } +} + +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 } => { + 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); + + let generics = generics.join(", "); + let parameters = parameters.join(", "); + let where_clause = where_clause.join(", "); + + write!( + f, + "fn {name}<{}>({}) -> {} where {};", + generics, parameters, return_type, where_clause + ) + } + TraitItem::Type { name } => write!(f, "type {name};"), + } + } +} + +impl Display for TraitConstraint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let generics = vecmap(&self.trait_generics, |generic| generic.to_string()); + write!(f, "{}: {}<{}>", self.typ, self.trait_name, generics.join(", ")) + } +} + +impl Display for TraitImpl { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let generics = vecmap(&self.trait_generics, |generic| generic.to_string()); + let generics = generics.join(", "); + + writeln!(f, "impl {}<{}> for {} {{", self.trait_name, generics, self.object_type)?; + + for item in self.items.iter() { + let item = item.to_string(); + for line in item.lines() { + writeln!(f, " {line}")?; + } + } + + write!(f, "}}") + } +} + +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}"), + } + } +} 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 989d87e0720..2e478b6c040 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -3,7 +3,7 @@ use noirc_errors::FileDiagnostic; use crate::{ graph::CrateId, hir::def_collector::dc_crate::UnresolvedStruct, node_interner::StructId, - parser::SubModule, Ident, LetStatement, NoirFunction, NoirImpl, NoirStruct, ParsedModule, + parser::SubModule, Ident, LetStatement, NoirFunction, NoirStruct, ParsedModule, TypeImpl, }; use super::{ @@ -92,7 +92,7 @@ impl<'a> ModCollector<'a> { } } - fn collect_impls(&mut self, context: &mut Context, impls: Vec) { + fn collect_impls(&mut self, context: &mut Context, impls: Vec) { for r#impl in impls { let mut unresolved_functions = UnresolvedFunctions { file_id: self.file_id, functions: Vec::new() }; diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index 16f8cfd3544..90bc957600c 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -428,8 +428,8 @@ pub enum Keyword { Fn, For, Global, - Impl, If, + Impl, In, Internal, Let, @@ -437,11 +437,14 @@ pub enum Keyword { Mut, Open, Pub, - String, Return, + String, Struct, + Trait, + Type, Unconstrained, Use, + Where, While, } @@ -463,8 +466,8 @@ impl fmt::Display for Keyword { Keyword::Fn => write!(f, "fn"), Keyword::For => write!(f, "for"), Keyword::Global => write!(f, "global"), - Keyword::Impl => write!(f, "impl"), Keyword::If => write!(f, "if"), + Keyword::Impl => write!(f, "impl"), Keyword::In => write!(f, "in"), Keyword::Internal => write!(f, "internal"), Keyword::Let => write!(f, "let"), @@ -472,11 +475,14 @@ impl fmt::Display for Keyword { Keyword::Mut => write!(f, "mut"), Keyword::Open => write!(f, "open"), Keyword::Pub => write!(f, "pub"), - Keyword::String => write!(f, "str"), Keyword::Return => write!(f, "return"), + Keyword::String => write!(f, "str"), Keyword::Struct => write!(f, "struct"), + Keyword::Trait => write!(f, "trait"), + Keyword::Type => write!(f, "type"), Keyword::Unconstrained => write!(f, "unconstrained"), Keyword::Use => write!(f, "use"), + Keyword::Where => write!(f, "where"), Keyword::While => write!(f, "while"), } } @@ -501,8 +507,8 @@ impl Keyword { "fn" => Keyword::Fn, "for" => Keyword::For, "global" => Keyword::Global, - "impl" => Keyword::Impl, "if" => Keyword::If, + "impl" => Keyword::Impl, "in" => Keyword::In, "internal" => Keyword::Internal, "let" => Keyword::Let, @@ -510,11 +516,14 @@ impl Keyword { "mut" => Keyword::Mut, "open" => Keyword::Open, "pub" => Keyword::Pub, - "str" => Keyword::String, "return" => Keyword::Return, + "str" => Keyword::String, "struct" => Keyword::Struct, + "trait" => Keyword::Trait, + "type" => Keyword::Type, "unconstrained" => Keyword::Unconstrained, "use" => Keyword::Use, + "where" => Keyword::Where, "while" => Keyword::While, "true" => return Some(Token::Bool(true)), diff --git a/crates/noirc_frontend/src/parser/errors.rs b/crates/noirc_frontend/src/parser/errors.rs index 50b84235d58..0f0d8e15809 100644 --- a/crates/noirc_frontend/src/parser/errors.rs +++ b/crates/noirc_frontend/src/parser/errors.rs @@ -21,6 +21,10 @@ pub enum ParserErrorReason { InvalidArrayLengthExpression(Expression), #[error("Early 'return' is unsupported")] EarlyReturn, + #[error("Patterns aren't allowed in a trait's function declarations")] + PatternInTraitFunctionParameter, + #[error("Traits are an experimental feature that are not yet in a working state")] + TraitsAreExperimental, } /// Represents a parsing error, or a parsing error in the making. diff --git a/crates/noirc_frontend/src/parser/mod.rs b/crates/noirc_frontend/src/parser/mod.rs index e1aec84d563..9cf9f1e9869 100644 --- a/crates/noirc_frontend/src/parser/mod.rs +++ b/crates/noirc_frontend/src/parser/mod.rs @@ -17,8 +17,8 @@ use crate::token::{Keyword, Token}; use crate::{ast::ImportStatement, Expression, NoirStruct}; use crate::{ BlockExpression, ExpressionKind, ForExpression, Ident, IndexExpression, LetStatement, - MethodCallExpression, NoirFunction, NoirImpl, Path, PathKind, Pattern, Recoverable, Statement, - UnresolvedType, UseTree, + MethodCallExpression, NoirFunction, NoirTrait, Path, PathKind, Pattern, Recoverable, Statement, + TraitImpl, TypeImpl, UnresolvedType, UseTree, }; use acvm::FieldElement; @@ -40,7 +40,9 @@ pub(crate) enum TopLevelStatement { Module(Ident), Import(UseTree), Struct(NoirStruct), - Impl(NoirImpl), + Trait(NoirTrait), + TraitImpl(TraitImpl), + Impl(TypeImpl), SubModule(SubModule), Global(LetStatement), Error, @@ -220,7 +222,9 @@ pub struct ParsedModule { pub imports: Vec, pub functions: Vec, pub types: Vec, - pub impls: Vec, + pub traits: Vec, + pub trait_impls: Vec, + pub impls: Vec, pub globals: Vec, /// Module declarations like `mod foo;` @@ -248,7 +252,15 @@ impl ParsedModule { self.types.push(typ); } - fn push_impl(&mut self, r#impl: NoirImpl) { + fn push_trait(&mut self, noir_trait: NoirTrait) { + self.traits.push(noir_trait); + } + + fn push_trait_impl(&mut self, trait_impl: TraitImpl) { + self.trait_impls.push(trait_impl); + } + + fn push_impl(&mut self, r#impl: TypeImpl) { self.impls.push(r#impl); } @@ -447,6 +459,8 @@ impl std::fmt::Display for TopLevelStatement { TopLevelStatement::Function(fun) => fun.fmt(f), TopLevelStatement::Module(m) => write!(f, "mod {m}"), TopLevelStatement::Import(tree) => write!(f, "use {tree}"), + TopLevelStatement::Trait(t) => t.fmt(f), + TopLevelStatement::TraitImpl(i) => i.fmt(f), TopLevelStatement::Struct(s) => s.fmt(f), TopLevelStatement::Impl(i) => i.fmt(f), TopLevelStatement::SubModule(s) => s.fmt(f), diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 8bc232cae3f..6e565ac9207 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/noirc_frontend/src/parser/parser.rs @@ -35,8 +35,9 @@ use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Attribute, Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, CompTime, ConstrainStatement, FunctionDefinition, - Ident, IfExpression, InfixExpression, LValue, Lambda, NoirFunction, NoirImpl, NoirStruct, Path, - PathKind, Pattern, Recoverable, UnaryOp, UnresolvedTypeExpression, UseTree, UseTreeKind, + Ident, IfExpression, InfixExpression, LValue, Lambda, NoirFunction, NoirStruct, NoirTrait, + Path, PathKind, Pattern, Recoverable, TraitConstraint, TraitImpl, TraitImplItem, TraitItem, + TypeImpl, UnaryOp, UnresolvedTypeExpression, UseTree, UseTreeKind, }; use chumsky::prelude::*; @@ -78,6 +79,8 @@ fn module() -> impl NoirParser { TopLevelStatement::Module(m) => program.push_module_decl(m), TopLevelStatement::Import(i) => program.push_import(i), TopLevelStatement::Struct(s) => program.push_type(s), + TopLevelStatement::Trait(t) => program.push_trait(t), + TopLevelStatement::TraitImpl(t) => program.push_trait_impl(t), TopLevelStatement::Impl(i) => program.push_impl(i), TopLevelStatement::SubModule(s) => program.push_submodule(s), TopLevelStatement::Global(c) => program.push_global(c), @@ -90,6 +93,7 @@ fn module() -> impl NoirParser { /// top_level_statement: function_definition /// | struct_definition +/// | trait_definition /// | implementation /// | submodule /// | module_declaration @@ -101,6 +105,8 @@ fn top_level_statement( choice(( function_definition(false).map(TopLevelStatement::Function), struct_definition(), + trait_definition(), + trait_implementation(), implementation(), submodule(module_parser.clone()), contract(module_parser), @@ -158,38 +164,32 @@ fn function_definition(allow_self: bool) -> impl NoirParser { .then(generics()) .then(parenthesized(function_parameters(allow_self))) .then(function_return_type()) + .then(where_clause()) .then(block(expression())) - .map( - |( - ( - ( - (((attribute, (is_unconstrained, is_open, is_internal)), name), generics), - parameters, - ), - ((return_distinctness, return_visibility), return_type), - ), + .map(|(((args, ret), where_clause), body)| { + let ((((attribute, modifiers), name), generics), parameters) = args; + + FunctionDefinition { + span: name.0.span(), + name, + attribute, // XXX: Currently we only have one attribute defined. If more attributes are needed per function, we can make this a vector and make attribute definition more expressive + is_unconstrained: modifiers.0, + is_open: modifiers.1, + is_internal: modifiers.2, + generics, + parameters, body, - )| { - FunctionDefinition { - span: name.0.span(), - name, - attribute, // XXX: Currently we only have one attribute defined. If more attributes are needed per function, we can make this a vector and make attribute definition more expressive - is_open, - is_internal, - is_unconstrained, - generics, - parameters, - body, - return_type, - return_visibility, - return_distinctness, - } - .into() - }, - ) + where_clause, + return_type: ret.1, + return_visibility: ret.0 .1, + return_distinctness: ret.0 .0, + } + .into() + }) } -/// function_modifiers: 'open internal' | 'internal' | 'unconstrained' | 'open' | %empty +/// function_modifiers: 'unconstrained'? 'open'? 'internal'? +/// /// returns (is_unconstrained, is_open, is_internal) for whether each keyword was present fn function_modifiers() -> impl NoirParser<(bool, bool, bool)> { keyword(Keyword::Unconstrained) @@ -345,6 +345,80 @@ fn self_parameter() -> impl NoirParser<(Pattern, UnresolvedType, AbiVisibility)> }) } +fn trait_definition() -> impl NoirParser { + keyword(Keyword::Trait) + .ignore_then(ident()) + .then(generics()) + .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 }) + }) +} + +fn trait_body() -> impl NoirParser> { + trait_function_declaration() + .or(trait_type_declaration()) + .separated_by(just(Token::Semicolon)) + .allow_trailing() +} + +/// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type +fn trait_function_declaration() -> impl NoirParser { + keyword(Keyword::Fn) + .ignore_then(ident()) + .then(generics()) + .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, + }) +} + +/// Function declaration parameters differ from other parameters in that parameter +/// patterns are not allowed in declarations. All parameters must be identifiers. +fn function_declaration_parameters() -> impl NoirParser> { + let typ = parse_type().recover_via(parameter_recovery()); + let typ = just(Token::Colon).ignore_then(typ); + + let parameter = ident().recover_via(parameter_name_recovery()).then(typ); + + let parameter = parameter.or(self_parameter().validate(|param, span, emit| { + match param.0 { + Pattern::Identifier(ident) => (ident, param.1), + other => { + emit(ParserError::with_reason( + ParserErrorReason::PatternInTraitFunctionParameter, + span, + )); + // into_ident panics on tuple or struct patterns but should be fine to call here + // since the `self` parser can only parse `self`, `mut self` or `&mut self`. + (other.into_ident(), param.1) + } + } + })); + + parameter + .separated_by(just(Token::Comma)) + .allow_trailing() + .labelled(ParsingRuleLabel::Parameter) +} + +/// trait_type_declaration: 'type' ident generics +fn trait_type_declaration() -> impl NoirParser { + keyword(Keyword::Type).ignore_then(ident()).map(|name| TraitItem::Type { name }) +} + +/// Parses a non-trait implementation, adding a set of methods to a type. +/// +/// implementation: 'impl' generics type '{' function_definition ... '}' fn implementation() -> impl NoirParser { keyword(Keyword::Impl) .ignore_then(generics()) @@ -353,10 +427,73 @@ fn implementation() -> impl NoirParser { .then(function_definition(true).repeated()) .then_ignore(just(Token::RightBrace)) .map(|((generics, (object_type, type_span)), methods)| { - TopLevelStatement::Impl(NoirImpl { generics, object_type, type_span, methods }) + TopLevelStatement::Impl(TypeImpl { generics, object_type, type_span, methods }) + }) +} + +/// Parses a trait implementation, implementing a particular trait for a type. +/// This has a similar syntax to `implementation`, but the `for type` clause is required, +/// and an optional `where` clause is also useable. +/// +/// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' +fn trait_implementation() -> impl NoirParser { + keyword(Keyword::Impl) + .ignore_then(generics()) + .then(ident()) + .then(generic_type_args(parse_type())) + .then_ignore(keyword(Keyword::For)) + .then(parse_type().map_with_span(|typ, span| (typ, span))) + .then(where_clause()) + .then_ignore(just(Token::LeftBrace)) + .then(trait_implementation_body()) + .then_ignore(just(Token::RightBrace)) + .validate(|args, span, emit| { + let ((other_args, where_clause), items) = args; + let (((impl_generics, trait_name), trait_generics), (object_type, object_type_span)) = + other_args; + + emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); + TopLevelStatement::TraitImpl(TraitImpl { + impl_generics, + trait_name, + trait_generics, + object_type, + object_type_span, + items, + where_clause, + }) }) } +fn trait_implementation_body() -> impl NoirParser> { + let function = function_definition(true).map(TraitImplItem::Function); + + let alias = keyword(Keyword::Type) + .ignore_then(ident()) + .then_ignore(just(Token::Assign)) + .then(parse_type()) + .then_ignore(just(Token::Semicolon)) + .map(|(name, alias)| TraitImplItem::Type { name, alias }); + + function.or(alias).repeated() +} + +fn where_clause() -> impl NoirParser> { + let constraints = parse_type() + .then_ignore(just(Token::Colon)) + .then(ident()) + .then(generic_type_args(parse_type())) + .validate(|((typ, trait_name), trait_generics), span, emit| { + emit(ParserError::with_reason(ParserErrorReason::TraitsAreExperimental, span)); + TraitConstraint { typ, trait_name, trait_generics } + }); + + keyword(Keyword::Where) + .ignore_then(constraints.repeated()) + .or_not() + .map(|option| option.unwrap_or_default()) +} + fn block_expr<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a,