diff --git a/aztec_macros/src/utils/parse_utils.rs b/aztec_macros/src/utils/parse_utils.rs index a2c177026c4..3b3813da6ee 100644 --- a/aztec_macros/src/utils/parse_utils.rs +++ b/aztec_macros/src/utils/parse_utils.rs @@ -268,6 +268,11 @@ fn empty_expression(expression: &mut Expression) { empty_block_expression(block_expression); } ExpressionKind::Quote(..) | ExpressionKind::Resolved(_) | ExpressionKind::Error => (), + ExpressionKind::AsTraitPath(path) => { + empty_unresolved_type(&mut path.typ); + empty_path(&mut path.trait_path); + empty_ident(&mut path.impl_item); + } } } @@ -324,6 +329,11 @@ fn empty_unresolved_type(unresolved_type: &mut UnresolvedType) { empty_unresolved_types(args); empty_unresolved_type(ret); } + UnresolvedTypeData::AsTraitPath(path) => { + empty_unresolved_type(&mut path.typ); + empty_path(&mut path.trait_path); + empty_ident(&mut path.impl_item); + } UnresolvedTypeData::FieldElement | UnresolvedTypeData::Integer(_, _) | UnresolvedTypeData::Bool diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 7a324eb2600..aab995c49a1 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -14,7 +14,7 @@ use acvm::{acir::AcirField, FieldElement}; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; -use super::UnaryRhsMemberAccess; +use super::{AsTraitPath, UnaryRhsMemberAccess}; #[derive(Debug, PartialEq, Eq, Clone)] pub enum ExpressionKind { @@ -36,6 +36,7 @@ pub enum ExpressionKind { Quote(Tokens), Unquote(Box), Comptime(BlockExpression, Span), + AsTraitPath(AsTraitPath), // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while @@ -593,6 +594,7 @@ impl Display for ExpressionKind { let tokens = vecmap(&tokens.0, ToString::to_string); write!(f, "quote {{ {} }}", tokens.join(" ")) } + AsTraitPath(path) => write!(f, "{path}"), } } } @@ -752,6 +754,12 @@ impl Display for Lambda { } } +impl Display for AsTraitPath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "<{} as {}>::{}", self.typ, self.trait_path, self.impl_item) + } +} + impl FunctionDefinition { pub fn normal( name: &Ident, diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index f59d316950c..8e27f0bdda9 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -129,9 +129,13 @@ pub enum UnresolvedTypeData { /*env:*/ Box, ), - // The type of quoted code for metaprogramming + /// The type of quoted code for metaprogramming Quoted(crate::QuotedType), + /// An "as Trait" path leading to an associated type. + /// E.g. `::Bar` + AsTraitPath(Box), + /// An already resolved type. These can only be parsed if they were present in the token stream /// as a result of being spliced into a macro's token stream input. Resolved(QuotedTypeId), @@ -239,6 +243,7 @@ impl std::fmt::Display for UnresolvedTypeData { Unspecified => write!(f, "unspecified"), Parenthesized(typ) => write!(f, "({typ})"), Resolved(_) => write!(f, "(resolved type)"), + AsTraitPath(path) => write!(f, "{path}"), } } } diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 8ce2e1a41c0..5d9a97fa6cf 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -358,6 +358,20 @@ impl UseTree { } } +/// A special kind of path in the form `::ident`. +/// Note that this path must consist of exactly two segments. +/// +/// An AsTraitPath may be used in either a type context where `ident` +/// refers to an associated type of a particular impl, or in a value +/// context where `ident` may refer to an associated constant or a +/// function within the impl. +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct AsTraitPath { + pub typ: UnresolvedType, + pub trait_path: Path, + pub impl_item: Ident, +} + // Note: Path deliberately doesn't implement Recoverable. // No matter which default value we could give in Recoverable::error, // it would most likely cause further errors during name resolution diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 6e2756f0301..5ba448f890e 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -64,6 +64,7 @@ impl<'context> Elaborator<'context> { self.push_err(ResolverError::UnquoteUsedOutsideQuote { span: expr.span }); (HirExpression::Error, Type::Error) } + ExpressionKind::AsTraitPath(_) => todo!("Implement AsTraitPath"), }; let id = self.interner.push_expr(hir_expr); self.interner.push_expr_location(id, expr.span, self.file); diff --git a/compiler/noirc_frontend/src/elaborator/types.rs b/compiler/noirc_frontend/src/elaborator/types.rs index 7448ccaa42b..f8ba994f66b 100644 --- a/compiler/noirc_frontend/src/elaborator/types.rs +++ b/compiler/noirc_frontend/src/elaborator/types.rs @@ -155,6 +155,7 @@ impl<'context> Elaborator<'context> { } Parenthesized(typ) => self.resolve_type_inner(*typ, kind), Resolved(id) => self.interner.get_quoted_type(id).clone(), + AsTraitPath(_) => todo!("Resolve AsTraitPath"), }; if let Some(unresolved_span) = typ.span { diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 80adb01dc9a..390afbefcda 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -26,6 +26,8 @@ pub enum ParserErrorReason { EarlyReturn, #[error("Patterns aren't allowed in a trait's function declarations")] PatternInTraitFunctionParameter, + #[error("Patterns aren't allowed in a trait impl's associated constants")] + PatternInAssociatedConstant, #[error("Modifiers are ignored on a trait impl method")] TraitImplFunctionModifiers, #[error("comptime keyword is deprecated")] diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 9772814027f..fac52545ab4 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -23,6 +23,7 @@ //! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should //! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the //! current parser to try alternative parsers in a `choice` expression. +use self::path::as_trait_path; use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable}; use self::types::{generic_type_args, maybe_comp_time}; pub use types::parse_type; @@ -522,7 +523,9 @@ where .map(|(block, span)| ExpressionKind::Comptime(block, span)) } -fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a +fn let_statement<'a, P>( + expr_parser: P, +) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a where P: ExprParser + 'a, { @@ -530,8 +533,14 @@ where ignore_then_commit(keyword(Keyword::Let).labelled(ParsingRuleLabel::Statement), pattern()); let p = p.then(optional_type_annotation()); let p = then_commit_ignore(p, just(Token::Assign)); - let p = then_commit(p, expr_parser); - p.map(StatementKind::new_let) + then_commit(p, expr_parser) +} + +fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a +where + P: ExprParser + 'a, +{ + let_statement(expr_parser).map(StatementKind::new_let) } fn pattern() -> impl NoirParser { @@ -1080,6 +1089,7 @@ where unquote(expr_parser.clone()), variable(), literal(), + as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), macro_quote_marker(), )) .map_with_span(Expression::new) diff --git a/compiler/noirc_frontend/src/parser/parser/path.rs b/compiler/noirc_frontend/src/parser/parser/path.rs index 140650af1a2..091ac33a4b2 100644 --- a/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,12 +1,12 @@ -use crate::ast::{Path, PathKind, PathSegment, UnresolvedType}; -use crate::parser::NoirParser; +use crate::ast::{AsTraitPath, Path, PathKind, PathSegment, UnresolvedType}; +use crate::parser::{NoirParser, ParserError, ParserErrorReason}; use crate::token::{Keyword, Token}; use chumsky::prelude::*; use super::keyword; -use super::primitives::{path_segment, path_segment_no_turbofish}; +use super::primitives::{ident, path_segment, path_segment_no_turbofish}; pub(super) fn path<'a>( type_parser: impl NoirParser + 'a, @@ -34,6 +34,25 @@ fn path_inner<'a>(segment: impl NoirParser + 'a) -> impl NoirParser )) } +/// Parses `::path_segment` +/// These paths only support exactly two segments. +pub(super) fn as_trait_path<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + just(Token::Less) + .ignore_then(type_parser.clone()) + .then_ignore(keyword(Keyword::As)) + .then(path(type_parser)) + .then_ignore(just(Token::Greater)) + .then_ignore(just(Token::DoubleColon)) + .then(ident()) + .validate(|((typ, trait_path), impl_item), span, emit| { + let reason = ParserErrorReason::ExperimentalFeature("Fully qualified trait impl paths"); + emit(ParserError::with_reason(reason, span)); + AsTraitPath { typ, trait_path, impl_item } + }) +} + fn empty_path() -> impl NoirParser { let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index ffcf7e07629..4f1594c8801 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -3,12 +3,15 @@ use chumsky::prelude::*; use super::attributes::{attributes, validate_secondary_attributes}; use super::function::function_return_type; use super::path::path_no_turbofish; -use super::{block, expression, fresh_statement, function, function_declaration_parameters}; +use super::{ + block, expression, fresh_statement, function, function_declaration_parameters, let_statement, +}; use crate::ast::{ Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, UnresolvedTraitConstraint, UnresolvedType, }; +use crate::macros_api::Pattern; use crate::{ parser::{ ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, @@ -59,13 +62,7 @@ fn trait_constant_declaration() -> impl NoirParser { .then(parse_type()) .then(optional_default_value()) .then_ignore(just(Token::Semicolon)) - .validate(|((name, typ), default_value), span, emit| { - emit(ParserError::with_reason( - ParserErrorReason::ExperimentalFeature("Associated constants"), - span, - )); - TraitItem::Constant { name, typ, default_value } - }) + .map(|((name, typ), default_value)| TraitItem::Constant { name, typ, default_value }) } /// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type @@ -146,7 +143,17 @@ fn trait_implementation_body() -> impl NoirParser> { .then_ignore(just(Token::Semicolon)) .map(|(name, alias)| TraitImplItem::Type { name, alias }); - function.or(alias).repeated() + let let_statement = let_statement(expression()).then_ignore(just(Token::Semicolon)).try_map( + |((pattern, typ), expr), span| match pattern { + Pattern::Identifier(ident) => Ok(TraitImplItem::Constant(ident, typ, expr)), + _ => Err(ParserError::with_reason( + ParserErrorReason::PatternInTraitFunctionParameter, + span, + )), + }, + ); + + choice((function, alias, let_statement)).repeated() } pub(super) fn where_clause() -> impl NoirParser> { diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index 7c2bdcb9fa3..7c551ca96d1 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,4 +1,4 @@ -use super::path::path_no_turbofish; +use super::path::{as_trait_path, path_no_turbofish}; use super::primitives::token_kind; use super::{ expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, @@ -45,10 +45,18 @@ pub(super) fn parse_type_inner<'a>( parenthesized_type(recursive_type_parser.clone()), tuple_type(recursive_type_parser.clone()), function_type(recursive_type_parser.clone()), - mutable_reference_type(recursive_type_parser), + mutable_reference_type(recursive_type_parser.clone()), + as_trait_path_type(recursive_type_parser), )) } +fn as_trait_path_type<'a>( + type_parser: impl NoirParser + 'a, +) -> impl NoirParser + 'a { + as_trait_path(type_parser) + .map_with_span(|path, span| UnresolvedTypeData::AsTraitPath(Box::new(path)).with_span(span)) +} + pub(super) fn parenthesized_type( recursive_type_parser: impl NoirParser, ) -> impl NoirParser { diff --git a/tooling/lsp/src/requests/inlay_hint.rs b/tooling/lsp/src/requests/inlay_hint.rs index 2ed441c623e..1f30d8f9ac1 100644 --- a/tooling/lsp/src/requests/inlay_hint.rs +++ b/tooling/lsp/src/requests/inlay_hint.rs @@ -269,6 +269,9 @@ impl<'a> InlayHintCollector<'a> { ExpressionKind::Comptime(block_expression, _span) => { self.collect_in_block_expression(block_expression); } + ExpressionKind::AsTraitPath(path) => { + self.collect_in_ident(&path.impl_item, true); + } ExpressionKind::Literal(..) | ExpressionKind::Variable(..) | ExpressionKind::Quote(..) @@ -632,6 +635,7 @@ fn get_expression_name(expression: &Expression) -> Option { ExpressionKind::MethodCall(method_call) => Some(method_call.method_name.to_string()), ExpressionKind::Cast(cast) => get_expression_name(&cast.lhs), ExpressionKind::Parenthesized(expr) => get_expression_name(expr), + ExpressionKind::AsTraitPath(path) => Some(path.impl_item.to_string()), ExpressionKind::Constructor(..) | ExpressionKind::Infix(..) | ExpressionKind::Index(..) diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 5673baf2893..41b15069546 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -179,6 +179,10 @@ pub(crate) fn rewrite( format!("$({})", rewrite_sub_expr(visitor, shape, *expr)) } } + ExpressionKind::AsTraitPath(path) => { + let trait_path = rewrite_path(visitor, shape, path.trait_path); + format!("<{} as {}>::{}", path.typ, trait_path, path.impl_item) + } } } diff --git a/tooling/nargo_fmt/src/rewrite/typ.rs b/tooling/nargo_fmt/src/rewrite/typ.rs index 3298ed8ae73..b586f32a6fe 100644 --- a/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/tooling/nargo_fmt/src/rewrite/typ.rs @@ -58,6 +58,7 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) UnresolvedTypeData::Resolved(_) => { unreachable!("Unexpected macro expansion of a type in nargo fmt input") } + UnresolvedTypeData::AsTraitPath(path) => path.to_string(), UnresolvedTypeData::Unspecified => todo!(), UnresolvedTypeData::FieldElement