Skip to content

Commit

Permalink
feat: trait aliases (#6431)
Browse files Browse the repository at this point in the history
Co-authored-by: Tom French <[email protected]>
  • Loading branch information
michaeljklein and TomAFrench authored Nov 20, 2024
1 parent 9db5937 commit 68c32b4
Show file tree
Hide file tree
Showing 14 changed files with 886 additions and 56 deletions.
16 changes: 16 additions & 0 deletions compiler/noirc_frontend/src/ast/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct NoirTrait {
pub items: Vec<Documented<TraitItem>>,
pub attributes: Vec<SecondaryAttribute>,
pub visibility: ItemVisibility,
pub is_alias: bool,
}

/// Any declaration inside the body of a trait that a user is required to
Expand Down Expand Up @@ -77,6 +78,9 @@ pub struct NoirTraitImpl {
pub where_clause: Vec<UnresolvedTraitConstraint>,

pub items: Vec<Documented<TraitImplItem>>,

/// true if generated at compile-time, e.g. from a trait alias
pub is_synthetic: bool,
}

/// Represents a simple trait constraint such as `where Foo: TraitY<U, V>`
Expand Down Expand Up @@ -130,12 +134,19 @@ impl Display for TypeImpl {
}
}

// TODO: display where clauses (follow-up issue)
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(", ") };

write!(f, "trait {}{}", self.name, generics)?;

if self.is_alias {
let bounds = vecmap(&self.bounds, |bound| bound.to_string()).join(" + ");
return write!(f, " = {};", bounds);
}

if !self.bounds.is_empty() {
let bounds = vecmap(&self.bounds, |bound| bound.to_string()).join(" + ");
write!(f, ": {}", bounds)?;
Expand Down Expand Up @@ -222,6 +233,11 @@ impl Display for TraitBound {

impl Display for NoirTraitImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Synthetic NoirTraitImpl's don't get printed
if self.is_synthetic {
return Ok(());
}

write!(f, "impl")?;
if !self.impl_generics.is_empty() {
write!(
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/type_check/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ pub enum TypeCheckError {

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NoMatchingImplFoundError {
constraints: Vec<(Type, String)>,
pub(crate) constraints: Vec<(Type, String)>,
pub span: 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 @@ -95,6 +95,8 @@ pub enum ParserErrorReason {
AssociatedTypesNotAllowedInPaths,
#[error("Associated types are not allowed on a method call")]
AssociatedTypesNotAllowedInMethodCalls,
#[error("Empty trait alias")]
EmptyTraitAlias,
#[error(
"Wrong number of arguments for attribute `{}`. Expected {}, found {}",
name,
Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_frontend/src/parser/parser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ impl<'a> Parser<'a> {
let object_type = self.parse_type_or_error();
let where_clause = self.parse_where_clause();
let items = self.parse_trait_impl_body();
let is_synthetic = false;

NoirTraitImpl {
impl_generics,
Expand All @@ -121,6 +122,7 @@ impl<'a> Parser<'a> {
object_type,
where_clause,
items,
is_synthetic,
}
}

Expand Down
72 changes: 41 additions & 31 deletions compiler/noirc_frontend/src/parser/parser/item.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use iter_extended::vecmap;

use crate::{
parser::{labels::ParsingRuleLabel, Item, ItemKind},
token::{Keyword, Token},
Expand All @@ -13,31 +15,32 @@ impl<'a> Parser<'a> {
}

pub(crate) fn parse_module_items(&mut self, nested: bool) -> Vec<Item> {
self.parse_many("items", without_separator(), |parser| {
self.parse_many_to_many("items", without_separator(), |parser| {
parser.parse_module_item_in_list(nested)
})
}

fn parse_module_item_in_list(&mut self, nested: bool) -> Option<Item> {
fn parse_module_item_in_list(&mut self, nested: bool) -> Vec<Item> {
loop {
// We only break out of the loop on `}` if we are inside a `mod { ..`
if nested && self.at(Token::RightBrace) {
return None;
return vec![];
}

// We always break on EOF (we don't error because if we are inside `mod { ..`
// the outer parsing logic will error instead)
if self.at_eof() {
return None;
return vec![];
}

let Some(item) = self.parse_item() else {
let parsed_items = self.parse_item();
if parsed_items.is_empty() {
// If we couldn't parse an item we check which token we got
match self.token.token() {
Token::RightBrace if nested => {
return None;
return vec![];
}
Token::EOF => return None,
Token::EOF => return vec![],
_ => (),
}

Expand All @@ -47,7 +50,7 @@ impl<'a> Parser<'a> {
continue;
};

return Some(item);
return parsed_items;
}
}

Expand Down Expand Up @@ -85,15 +88,19 @@ impl<'a> Parser<'a> {
}

/// Item = OuterDocComments ItemKind
fn parse_item(&mut self) -> Option<Item> {
fn parse_item(&mut self) -> Vec<Item> {
let start_span = self.current_token_span;
let doc_comments = self.parse_outer_doc_comments();
let kind = self.parse_item_kind()?;
let kinds = self.parse_item_kind();
let span = self.span_since(start_span);

Some(Item { kind, span, doc_comments })
vecmap(kinds, |kind| Item { kind, span, doc_comments: doc_comments.clone() })
}

/// This method returns one 'ItemKind' in the majority of cases.
/// The current exception is when parsing a trait alias,
/// which returns both the trait and the impl.
///
/// ItemKind
/// = InnerAttribute
/// | Attributes Modifiers
Expand All @@ -106,9 +113,9 @@ impl<'a> Parser<'a> {
/// | TypeAlias
/// | Function
/// )
fn parse_item_kind(&mut self) -> Option<ItemKind> {
fn parse_item_kind(&mut self) -> Vec<ItemKind> {
if let Some(kind) = self.parse_inner_attribute() {
return Some(ItemKind::InnerAttribute(kind));
return vec![ItemKind::InnerAttribute(kind)];
}

let start_span = self.current_token_span;
Expand All @@ -122,78 +129,81 @@ impl<'a> Parser<'a> {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

let use_tree = self.parse_use_tree();
return Some(ItemKind::Import(use_tree, modifiers.visibility));
return vec![ItemKind::Import(use_tree, modifiers.visibility)];
}

if let Some(is_contract) = self.eat_mod_or_contract() {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

return Some(self.parse_mod_or_contract(attributes, is_contract, modifiers.visibility));
return vec![self.parse_mod_or_contract(attributes, is_contract, modifiers.visibility)];
}

if self.eat_keyword(Keyword::Struct) {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

return Some(ItemKind::Struct(self.parse_struct(
return vec![ItemKind::Struct(self.parse_struct(
attributes,
modifiers.visibility,
start_span,
)));
))];
}

if self.eat_keyword(Keyword::Impl) {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

return Some(match self.parse_impl() {
return vec![match self.parse_impl() {
Impl::Impl(type_impl) => ItemKind::Impl(type_impl),
Impl::TraitImpl(noir_trait_impl) => ItemKind::TraitImpl(noir_trait_impl),
});
}];
}

if self.eat_keyword(Keyword::Trait) {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

return Some(ItemKind::Trait(self.parse_trait(
attributes,
modifiers.visibility,
start_span,
)));
let (noir_trait, noir_impl) =
self.parse_trait(attributes, modifiers.visibility, start_span);
let mut output = vec![ItemKind::Trait(noir_trait)];
if let Some(noir_impl) = noir_impl {
output.push(ItemKind::TraitImpl(noir_impl));
}

return output;
}

if self.eat_keyword(Keyword::Global) {
self.unconstrained_not_applicable(modifiers);

return Some(ItemKind::Global(
return vec![ItemKind::Global(
self.parse_global(
attributes,
modifiers.comptime.is_some(),
modifiers.mutable.is_some(),
),
modifiers.visibility,
));
)];
}

if self.eat_keyword(Keyword::Type) {
self.comptime_mutable_and_unconstrained_not_applicable(modifiers);

return Some(ItemKind::TypeAlias(
return vec![ItemKind::TypeAlias(
self.parse_type_alias(modifiers.visibility, start_span),
));
)];
}

if self.eat_keyword(Keyword::Fn) {
self.mutable_not_applicable(modifiers);

return Some(ItemKind::Function(self.parse_function(
return vec![ItemKind::Function(self.parse_function(
attributes,
modifiers.visibility,
modifiers.comptime.is_some(),
modifiers.unconstrained.is_some(),
false, // allow_self
)));
))];
}

None
vec![]
}

fn eat_mod_or_contract(&mut self) -> Option<bool> {
Expand Down
40 changes: 37 additions & 3 deletions compiler/noirc_frontend/src/parser/parser/parse_many.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ impl<'a> Parser<'a> {
self.parse_many_return_trailing_separator_if_any(items, separated_by, f).0
}

/// parse_many, where the given function `f` may return multiple results
pub(super) fn parse_many_to_many<T, F>(
&mut self,
items: &'static str,
separated_by: SeparatedBy,
f: F,
) -> Vec<T>
where
F: FnMut(&mut Parser<'a>) -> Vec<T>,
{
self.parse_many_to_many_return_trailing_separator_if_any(items, separated_by, f).0
}

/// Same as parse_many, but returns a bool indicating whether a trailing separator was found.
pub(super) fn parse_many_return_trailing_separator_if_any<T, F>(
&mut self,
Expand All @@ -27,6 +40,26 @@ impl<'a> Parser<'a> {
) -> (Vec<T>, bool)
where
F: FnMut(&mut Parser<'a>) -> Option<T>,
{
let f = |x: &mut Parser<'a>| {
if let Some(result) = f(x) {
vec![result]
} else {
vec![]
}
};
self.parse_many_to_many_return_trailing_separator_if_any(items, separated_by, f)
}

/// Same as parse_many, but returns a bool indicating whether a trailing separator was found.
fn parse_many_to_many_return_trailing_separator_if_any<T, F>(
&mut self,
items: &'static str,
separated_by: SeparatedBy,
mut f: F,
) -> (Vec<T>, bool)
where
F: FnMut(&mut Parser<'a>) -> Vec<T>,
{
let mut elements: Vec<T> = Vec::new();
let mut trailing_separator = false;
Expand All @@ -38,20 +71,21 @@ impl<'a> Parser<'a> {
}

let start_span = self.current_token_span;
let Some(element) = f(self) else {
let mut new_elements = f(self);
if new_elements.is_empty() {
if let Some(end) = &separated_by.until {
self.eat(end.clone());
}
break;
};
}

if let Some(separator) = &separated_by.token {
if !trailing_separator && !elements.is_empty() {
self.expected_token_separating_items(separator.clone(), items, start_span);
}
}

elements.push(element);
elements.append(&mut new_elements);

trailing_separator = if let Some(separator) = &separated_by.token {
self.eat(separator.clone())
Expand Down
Loading

0 comments on commit 68c32b4

Please sign in to comment.