Skip to content

Commit

Permalink
chore: Add parser support for <MyType as Trait>::ident (#5688)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

## Summary\*

In the interest of smaller PRs I'll be breaking up the associated
constants & types work into multiple smaller PRs.
This PR adds parser support for `<Type as Trait>::ident` syntax since we
already parse both associated constants & types in traits & impls.

## Additional Context

## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[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.
  • Loading branch information
jfecher authored Aug 8, 2024
1 parent eefd69b commit a6ce7f4
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 19 deletions.
10 changes: 10 additions & 0 deletions aztec_macros/src/utils/parse_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,6 +36,7 @@ pub enum ExpressionKind {
Quote(Tokens),
Unquote(Box<Expression>),
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
Expand Down Expand Up @@ -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}"),
}
}
}
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,13 @@ pub enum UnresolvedTypeData {
/*env:*/ Box<UnresolvedType>,
),

// 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. `<Foo as Trait>::Bar`
AsTraitPath(Box<crate::ast::AsTraitPath>),

/// 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),
Expand Down Expand Up @@ -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}"),
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions compiler/noirc_frontend/src/ast/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,20 @@ impl UseTree {
}
}

/// A special kind of path in the form `<MyType as Trait>::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
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
16 changes: 13 additions & 3 deletions compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -518,16 +519,24 @@ where
.map(|(block, span)| ExpressionKind::Comptime(block, span))
}

fn declaration<'a, P>(expr_parser: P) -> impl NoirParser<StatementKind> + 'a
fn let_statement<'a, P>(
expr_parser: P,
) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a
where
P: ExprParser + 'a,
{
let p =
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<StatementKind> + 'a
where
P: ExprParser + 'a,
{
let_statement(expr_parser).map(StatementKind::new_let)
}

fn pattern() -> impl NoirParser<Pattern> {
Expand Down Expand Up @@ -1076,6 +1085,7 @@ where
unquote(expr_parser.clone()),
variable(),
literal(),
as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath),
macro_quote_marker(),
))
.map_with_span(Expression::new)
Expand Down
25 changes: 22 additions & 3 deletions compiler/noirc_frontend/src/parser/parser/path.rs
Original file line number Diff line number Diff line change
@@ -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<UnresolvedType> + 'a,
Expand Down Expand Up @@ -34,6 +34,25 @@ fn path_inner<'a>(segment: impl NoirParser<PathSegment> + 'a) -> impl NoirParser
))
}

/// Parses `<MyType as Trait>::path_segment`
/// These paths only support exactly two segments.
pub(super) fn as_trait_path<'a>(
type_parser: impl NoirParser<UnresolvedType> + 'a,
) -> impl NoirParser<AsTraitPath> + '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<Path> {
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));
Expand Down
25 changes: 16 additions & 9 deletions compiler/noirc_frontend/src/parser/parser/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -59,13 +62,7 @@ fn trait_constant_declaration() -> impl NoirParser<TraitItem> {
.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
Expand Down Expand Up @@ -146,7 +143,17 @@ fn trait_implementation_body() -> impl NoirParser<Vec<TraitImplItem>> {
.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<Vec<UnresolvedTraitConstraint>> {
Expand Down
12 changes: 10 additions & 2 deletions compiler/noirc_frontend/src/parser/parser/types.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<UnresolvedType> + 'a,
) -> impl NoirParser<UnresolvedType> + '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<UnresolvedType>,
) -> impl NoirParser<UnresolvedType> {
Expand Down
4 changes: 4 additions & 0 deletions tooling/lsp/src/requests/inlay_hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,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(..)
Expand Down Expand Up @@ -671,6 +674,7 @@ fn get_expression_name(expression: &Expression) -> Option<String> {
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(..)
Expand Down
4 changes: 4 additions & 0 deletions tooling/nargo_fmt/src/rewrite/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions tooling/nargo_fmt/src/rewrite/typ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit a6ce7f4

Please sign in to comment.