diff --git a/crates/noirc_errors/src/reporter.rs b/crates/noirc_errors/src/reporter.rs index a1bd239b6b1..23b6970a913 100644 --- a/crates/noirc_errors/src/reporter.rs +++ b/crates/noirc_errors/src/reporter.rs @@ -74,6 +74,10 @@ impl CustomDiagnostic { pub fn is_error(&self) -> bool { matches!(self.kind, DiagnosticKind::Error) } + + pub fn is_warrning(&self) -> bool { + matches!(self.kind, DiagnosticKind::Warning) + } } impl std::fmt::Display for CustomDiagnostic { diff --git a/crates/noirc_frontend/src/ast/traits.rs b/crates/noirc_frontend/src/ast/traits.rs index 1dacb73fc5e..bb5d2117037 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,24 @@ 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 } => { + write!(f, "let {}: {}", 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 +183,10 @@ 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};"), + TraitImplItem::Constant(name, typ, value) => { + write!(f, "let {}: {} = {};", name, typ, value) + } } } } diff --git a/crates/noirc_frontend/src/parser/errors.rs b/crates/noirc_frontend/src/parser/errors.rs index 7c048f2a0c5..ec3095a989d 100644 --- a/crates/noirc_frontend/src/parser/errors.rs +++ b/crates/noirc_frontend/src/parser/errors.rs @@ -29,6 +29,8 @@ pub enum ParserErrorReason { ComptimeDeprecated, #[error("{0} are experimental and aren't fully supported yet")] ExperimentalFeature(&'static str), + #[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 232aff618b0..029f692cc3a 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,38 @@ 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| { + .validate(|(((name, generics), where_clause), items), span, emit| { + validate_where_clause(&generics, &where_clause, span, emit); emit(ParserError::with_reason(ParserErrorReason::ExperimentalFeature("Traits"), span)); - TopLevelStatement::Trait(NoirTrait { name, generics, items }) + 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()) + .map(|((name, typ), default_value)| 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 +404,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 +437,8 @@ fn function_declaration_parameters() -> impl NoirParser (ident, param.1), other => { @@ -418,7 +451,9 @@ fn function_declaration_parameters() -> impl NoirParser 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()) } @@ -1444,6 +1479,22 @@ mod test { }) } + fn parse_all_ignore_warnings(parser: P, programs: Vec<&str>) -> Vec + where + P: NoirParser, + { + vecmap(programs, move |program| { + let message = format!("Failed to parse:\n{}", program); + let (op_t, errors) = parse_recover(&parser, program); + for e in errors { + if !e.is_warrning() { + panic!("{}", &message); + } + } + op_t.expect(&message) + }) + } + fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec where P: NoirParser, @@ -1743,14 +1794,70 @@ mod test { "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", + "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", + "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", "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] }", ], ); + parse_all_ignore_warnings( + function_definition(false), + vec![ + "fn f(f: pub Field, y : Field, z : comptime Field) -> u8 { x + a }", + "fn f(f: pub Field, y : T, z : comptime Field) -> u8 { x + a }", + "fn func_name(f: Field, y : T) where T: SomeTrait {}", + ], + ); + 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_ignore_warnings( + 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; }", + + ], ); }