From 6886b5a11ab042c2f4b2ae5df0121b4f4906aaf4 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" Date: Fri, 1 Nov 2024 14:54:31 -0400 Subject: [PATCH 1/5] wip adding parsing support for trait aliases, adding tests, debugging tests --- compiler/noirc_frontend/src/ast/traits.rs | 8 + .../src/parser/parser/traits.rs | 150 +++++++++++++++++- 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/traits.rs b/compiler/noirc_frontend/src/ast/traits.rs index 723df775b1e..876188a7067 100644 --- a/compiler/noirc_frontend/src/ast/traits.rs +++ b/compiler/noirc_frontend/src/ast/traits.rs @@ -24,6 +24,7 @@ pub struct NoirTrait { pub items: Vec>, pub attributes: Vec, pub visibility: ItemVisibility, + pub is_alias: bool, } /// Any declaration inside the body of a trait that a user is required to @@ -130,12 +131,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)?; diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index fead6a34c82..de7c60333e2 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -26,9 +26,32 @@ impl<'a> Parser<'a> { }; let generics = self.parse_generics(); - let bounds = if self.eat_colon() { self.parse_trait_bounds() } else { Vec::new() }; - let where_clause = self.parse_where_clause(); - let items = self.parse_trait_body(); + + + // Trait aliases: + // trait Foo<..> = A + B + E where ..; + let (bounds, where_clause, items, is_alias) = if self.eat_assign() { + // TODO: need to add default impl as well in this case + // TODO: add (is_alias: bool) to NoirTrait and add during later pass? + let bounds = self.parse_trait_bounds(); + let where_clause = self.parse_where_clause(); + let items = Vec::new(); + if !self.eat_semicolon() { + self.expected_token(Token::Semicolon); + } + let is_alias = true; + (bounds, where_clause, items, is_alias) + } else { + let bounds = if self.eat_colon() { self.parse_trait_bounds() } else { Vec::new() }; + let where_clause = self.parse_where_clause(); + let items = self.parse_trait_body(); + let is_alias = false; + (bounds, where_clause, items, is_alias) + }; + + // let bounds = if self.eat_colon() { self.parse_trait_bounds() } else { Vec::new() }; + // let where_clause = self.parse_where_clause(); + // let items = self.parse_trait_body(); NoirTrait { name, @@ -39,6 +62,7 @@ impl<'a> Parser<'a> { items, attributes, visibility, + is_alias, } } @@ -188,6 +212,7 @@ fn empty_trait( items: Vec::new(), attributes, visibility, + is_alias: false, } } @@ -220,6 +245,20 @@ mod tests { assert!(noir_trait.generics.is_empty()); assert!(noir_trait.where_clause.is_empty()); assert!(noir_trait.items.is_empty()); + assert!(!noir_trait.is_alias); + } + + // TODO error on this case + #[test] + fn parse_empty_trait_alias() { + let src = "trait Foo = ;"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert!(noir_trait.generics.is_empty()); + assert!(noir_trait.bounds.is_empty()); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); + assert!(noir_trait.is_alias); } #[test] @@ -230,6 +269,44 @@ mod tests { assert_eq!(noir_trait.generics.len(), 2); assert!(noir_trait.where_clause.is_empty()); assert!(noir_trait.items.is_empty()); + assert!(!noir_trait.is_alias); + } + + #[test] + fn parse_trait_alias_with_generics() { + let src = "trait Foo = Bar + Baz;"; + let noir_trait_alias = parse_trait_no_errors(src); + assert_eq!(noir_trait_alias.name.to_string(), "Foo"); + assert_eq!(noir_trait_alias.generics.len(), 2); + assert_eq!(noir_trait_alias.bounds.len(), 1); + assert_eq!(noir_trait_alias.bounds[0].to_string(), "Baz"); + assert!(noir_trait_alias.where_clause.is_empty()); + assert!(noir_trait_alias.items.is_empty()); + assert!(noir_trait_alias.is_alias); + + // Equivalent to + let src = "trait Foo: Bar + Baz {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), noir_trait_alias.name.to_string()); + assert_eq!(noir_trait.generics.len(), noir_trait_alias.generics.len()); + assert_eq!(noir_trait.bounds.len(), noir_trait_alias.bounds.len()); + assert_eq!(noir_trait.bounds[0].to_string(), noir_trait_alias.bounds[0].to_string()); + assert_eq!(noir_trait.where_clause.is_empty(), noir_trait_alias.where_clause.is_empty()); + assert_eq!(noir_trait.items.is_empty(), noir_trait_alias.items.is_empty()); + assert!(!noir_trait.is_alias); + } + + // TODO error on this case + #[test] + fn parse_empty_trait_alias_with_generics() { + let src = "trait Foo = ;"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert!(noir_trait.bounds.is_empty()); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); + assert!(noir_trait.is_alias); } #[test] @@ -240,6 +317,44 @@ mod tests { assert_eq!(noir_trait.generics.len(), 2); assert_eq!(noir_trait.where_clause.len(), 1); assert!(noir_trait.items.is_empty()); + assert!(!noir_trait.is_alias); + } + + #[test] + fn parse_trait_alias_with_where_clause() { + let src = "trait Foo = Bar where A: Z;"; + let noir_trait_alias = parse_trait_no_errors(src); + assert_eq!(noir_trait_alias.name.to_string(), "Foo"); + assert_eq!(noir_trait_alias.generics.len(), 2); + assert_eq!(noir_trait_alias.bounds.len(), 1); + assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait_alias.where_clause.len(), 1); + assert!(noir_trait_alias.items.is_empty()); + assert!(noir_trait_alias.is_alias); + + // Equivalent to + let src = "trait Foo: Bar + Baz {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), noir_trait_alias.name.to_string()); + assert_eq!(noir_trait.generics.len(), noir_trait_alias.generics.len()); + assert_eq!(noir_trait.bounds.len(), noir_trait_alias.bounds.len()); + assert_eq!(noir_trait.bounds[0].to_string(), noir_trait_alias.bounds[0].to_string()); + assert_eq!(noir_trait.where_clause.is_empty(), noir_trait_alias.where_clause.is_empty()); + assert_eq!(noir_trait.items.is_empty(), noir_trait_alias.items.is_empty()); + assert!(!noir_trait.is_alias); + } + + // TODO: error on this case + #[test] + fn parse_empty_trait_alias_with_where_clause() { + let src = "trait Foo = where A: Z;"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert!(noir_trait.bounds.is_empty()); + assert_eq!(noir_trait.where_clause.len(), 1); + assert!(noir_trait.items.is_empty()); + assert!(noir_trait.is_alias); } #[test] @@ -253,6 +368,7 @@ mod tests { panic!("Expected type"); }; assert_eq!(name.to_string(), "Elem"); + assert!(!noir_trait.is_alias); } #[test] @@ -268,6 +384,7 @@ mod tests { assert_eq!(name.to_string(), "x"); assert_eq!(typ.to_string(), "Field"); assert_eq!(default_value.unwrap().to_string(), "1"); + assert!(!noir_trait.is_alias); } #[test] @@ -281,6 +398,7 @@ mod tests { panic!("Expected function"); }; assert!(body.is_none()); + assert!(!noir_trait.is_alias); } #[test] @@ -294,6 +412,7 @@ mod tests { panic!("Expected function"); }; assert!(body.is_some()); + assert!(!noir_trait.is_alias); } #[test] @@ -306,5 +425,30 @@ mod tests { assert_eq!(noir_trait.bounds[1].to_string(), "Baz"); assert_eq!(noir_trait.to_string(), "trait Foo: Bar + Baz {\n}"); + assert!(!noir_trait.is_alias); + } + + #[test] + fn parse_trait_alias() { + let src = "trait Foo = Bar + Baz;"; + let noir_trait_alias = parse_trait_no_errors(src); + assert_eq!(noir_trait_alias.bounds.len(), 2); + + assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait_alias.bounds[1].to_string(), "Baz"); + + assert_eq!(noir_trait_alias.to_string(), "trait Foo: Bar + Baz {\n}"); + assert!(noir_trait_alias.is_alias); + + // Equivalent to + let src = "trait Foo: Bar + Baz {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), noir_trait_alias.name.to_string()); + assert_eq!(noir_trait.generics.len(), noir_trait_alias.generics.len()); + assert_eq!(noir_trait.bounds.len(), noir_trait_alias.bounds.len()); + assert_eq!(noir_trait.bounds[0].to_string(), noir_trait_alias.bounds[0].to_string()); + assert_eq!(noir_trait.where_clause.is_empty(), noir_trait_alias.where_clause.is_empty()); + assert_eq!(noir_trait.items.is_empty(), noir_trait_alias.items.is_empty()); + assert!(!noir_trait.is_alias); } } From a271d8b64f1cb4a8f1066f58dc0d6dc17e81cbae Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" Date: Fri, 1 Nov 2024 15:50:33 -0400 Subject: [PATCH 2/5] finished debugging parsing tests, added method stub to generate the impl's for trait aliases, wip implementing impl generator --- .../noirc_frontend/src/elaborator/traits.rs | 10 +++ .../src/hir/def_collector/dc_crate.rs | 54 ++++++++++++++- compiler/noirc_frontend/src/parser/errors.rs | 2 + .../src/parser/parser/traits.rs | 65 ++++++++----------- 4 files changed, 91 insertions(+), 40 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index ae278616e03..35d026245d4 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -55,6 +55,16 @@ impl<'context> Elaborator<'context> { }); }); + // Given a trait alias: + // trait Foo<..> = Bar + Baz; + // + // Generate a trait impl: + // impl Foo<..> for T where T: Bar, T: Baz {} + if unresolved_trait.trait_def.is_alias { + let mut trait_impl = unresolved_trait.alias_impl(); + self.collect_trait_impl(&mut trait_impl); + } + // This check needs to be after the trait's methods are set since // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 9917207d217..019259b2745 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -23,7 +23,7 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, UnsupportedNumericGenericType, + UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnsupportedNumericGenericType, }; use crate::parser::{ParserError, SortedModule}; @@ -74,6 +74,58 @@ pub struct UnresolvedTrait { pub fns_with_default_impl: UnresolvedFunctions, } +impl UnresolvedTrait { + // impl Foo<..> for T where .. + pub fn alias_impl(&self) -> UnresolvedTraitImpl { + // TODO: 'T' + let is_synthesized = true; + + let object_type_ident = Ident::new("#T".to_string(), self.trait_def.span); + let object_type_path = Path::from_ident(object_type_ident); + let object_type_generic = UnresolvedGeneric::Variable(object_type_ident); + + let object_type = UnresolvedType { + typ: UnresolvedTypeData::Named(object_type_path, var![], is_synthesized), + span: self.trait_def.span, + }; + + let mut trait_impl_generics = self.trait_def.generics; + trait_impl_generics.push(object_type_generic); + + let mut trait_impl_where_clause = self.trait_def.where_clause; + for bound in self.trait_def.bounds { + // TODO: add where clause for each Bar, Baz in bounds + trait_impl_where_clause.push((object_type, bound)) + } + + UnresolvedTraitImpl { + file_id: self.file_id, + module_id: self.module_id, + trait_generics: self.trait_def.generics, + + // TODO + trait_path: self.trait_def.path, + object_type, + methods: vec![], + generics: trait_impl_generics, + where_clause: trait_impl_where_clause, + + associated_types: vec![], + associated_constants: vec![], + + // Every field after this line is filled in later in the elaborator + trait_id: None, + impl_id: None, + resolved_object_type: None, + resolved_generics: vec![], + + // The resolved generic on the trait itself. E.g. it is the `` in + // `impl Foo for Bar { ... }` + resolved_trait_generics: vec![], + } + } +} + pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 3315b38a351..ab602b087d0 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -95,6 +95,8 @@ pub enum ParserErrorReason { AssociatedTypesNotAllowedInPaths, #[error("Associated types are not allowed on a method call")] AssociatedTypesNotAllowedInMethodCalls, + #[error("Empty trait alias")] + EmptyTraitAlias, } /// Represents a parsing error, or a parsing error in the making. diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index de7c60333e2..34db65eac4f 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -27,18 +27,21 @@ impl<'a> Parser<'a> { let generics = self.parse_generics(); - // Trait aliases: // trait Foo<..> = A + B + E where ..; let (bounds, where_clause, items, is_alias) = if self.eat_assign() { - // TODO: need to add default impl as well in this case - // TODO: add (is_alias: bool) to NoirTrait and add during later pass? let bounds = self.parse_trait_bounds(); + + if bounds.is_empty() { + self.push_error(ParserErrorReason::EmptyTraitAlias, self.previous_token_span); + } + let where_clause = self.parse_where_clause(); let items = Vec::new(); if !self.eat_semicolon() { self.expected_token(Token::Semicolon); } + let is_alias = true; (bounds, where_clause, items, is_alias) } else { @@ -49,10 +52,6 @@ impl<'a> Parser<'a> { (bounds, where_clause, items, is_alias) }; - // let bounds = if self.eat_colon() { self.parse_trait_bounds() } else { Vec::new() }; - // let where_clause = self.parse_where_clause(); - // let items = self.parse_trait_body(); - NoirTrait { name, generics, @@ -221,7 +220,7 @@ mod tests { use crate::{ ast::{NoirTrait, TraitItem}, parser::{ - parser::{parse_program, tests::expect_no_errors}, + parser::{parse_program, ParserErrorReason, tests::expect_no_errors}, ItemKind, }, }; @@ -248,17 +247,12 @@ mod tests { assert!(!noir_trait.is_alias); } - // TODO error on this case #[test] fn parse_empty_trait_alias() { let src = "trait Foo = ;"; - let noir_trait = parse_trait_no_errors(src); - assert_eq!(noir_trait.name.to_string(), "Foo"); - assert!(noir_trait.generics.is_empty()); - assert!(noir_trait.bounds.is_empty()); - assert!(noir_trait.where_clause.is_empty()); - assert!(noir_trait.items.is_empty()); - assert!(noir_trait.is_alias); + let (_module, errors) = parse_program(src); + assert_eq!(errors.len(), 2); + assert_eq!(errors[1].reason(), Some(ParserErrorReason::EmptyTraitAlias).as_ref()); } #[test] @@ -278,8 +272,9 @@ mod tests { let noir_trait_alias = parse_trait_no_errors(src); assert_eq!(noir_trait_alias.name.to_string(), "Foo"); assert_eq!(noir_trait_alias.generics.len(), 2); - assert_eq!(noir_trait_alias.bounds.len(), 1); - assert_eq!(noir_trait_alias.bounds[0].to_string(), "Baz"); + assert_eq!(noir_trait_alias.bounds.len(), 2); + assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait_alias.bounds[1].to_string(), "Baz"); assert!(noir_trait_alias.where_clause.is_empty()); assert!(noir_trait_alias.items.is_empty()); assert!(noir_trait_alias.is_alias); @@ -296,17 +291,12 @@ mod tests { assert!(!noir_trait.is_alias); } - // TODO error on this case #[test] fn parse_empty_trait_alias_with_generics() { let src = "trait Foo = ;"; - let noir_trait = parse_trait_no_errors(src); - assert_eq!(noir_trait.name.to_string(), "Foo"); - assert_eq!(noir_trait.generics.len(), 2); - assert!(noir_trait.bounds.is_empty()); - assert!(noir_trait.where_clause.is_empty()); - assert!(noir_trait.items.is_empty()); - assert!(noir_trait.is_alias); + let (_module, errors) = parse_program(src); + assert_eq!(errors.len(), 2); + assert_eq!(errors[1].reason(), Some(ParserErrorReason::EmptyTraitAlias).as_ref()); } #[test] @@ -322,39 +312,36 @@ mod tests { #[test] fn parse_trait_alias_with_where_clause() { - let src = "trait Foo = Bar where A: Z;"; + let src = "trait Foo = Bar + Baz where A: Z;"; let noir_trait_alias = parse_trait_no_errors(src); assert_eq!(noir_trait_alias.name.to_string(), "Foo"); assert_eq!(noir_trait_alias.generics.len(), 2); - assert_eq!(noir_trait_alias.bounds.len(), 1); + assert_eq!(noir_trait_alias.bounds.len(), 2); assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait_alias.bounds[1].to_string(), "Baz"); assert_eq!(noir_trait_alias.where_clause.len(), 1); assert!(noir_trait_alias.items.is_empty()); assert!(noir_trait_alias.is_alias); // Equivalent to - let src = "trait Foo: Bar + Baz {}"; + let src = "trait Foo: Bar + Baz where A: Z {}"; let noir_trait = parse_trait_no_errors(src); assert_eq!(noir_trait.name.to_string(), noir_trait_alias.name.to_string()); assert_eq!(noir_trait.generics.len(), noir_trait_alias.generics.len()); assert_eq!(noir_trait.bounds.len(), noir_trait_alias.bounds.len()); assert_eq!(noir_trait.bounds[0].to_string(), noir_trait_alias.bounds[0].to_string()); - assert_eq!(noir_trait.where_clause.is_empty(), noir_trait_alias.where_clause.is_empty()); + assert_eq!(noir_trait.where_clause.len(), noir_trait_alias.where_clause.len()); + assert_eq!(noir_trait.where_clause[0].to_string(), noir_trait_alias.where_clause[0].to_string()); assert_eq!(noir_trait.items.is_empty(), noir_trait_alias.items.is_empty()); assert!(!noir_trait.is_alias); } - // TODO: error on this case #[test] fn parse_empty_trait_alias_with_where_clause() { let src = "trait Foo = where A: Z;"; - let noir_trait = parse_trait_no_errors(src); - assert_eq!(noir_trait.name.to_string(), "Foo"); - assert_eq!(noir_trait.generics.len(), 2); - assert!(noir_trait.bounds.is_empty()); - assert_eq!(noir_trait.where_clause.len(), 1); - assert!(noir_trait.items.is_empty()); - assert!(noir_trait.is_alias); + let (_module, errors) = parse_program(src); + assert_eq!(errors.len(), 2); + assert_eq!(errors[1].reason(), Some(ParserErrorReason::EmptyTraitAlias).as_ref()); } #[test] @@ -437,7 +424,7 @@ mod tests { assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); assert_eq!(noir_trait_alias.bounds[1].to_string(), "Baz"); - assert_eq!(noir_trait_alias.to_string(), "trait Foo: Bar + Baz {\n}"); + assert_eq!(noir_trait_alias.to_string(), "trait Foo = Bar + Baz;"); assert!(noir_trait_alias.is_alias); // Equivalent to From 54845ae125a274db25683ddd161b2c9e4f1e7695 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" Date: Tue, 5 Nov 2024 17:13:19 -0500 Subject: [PATCH 3/5] debugging failing tests, cleanup, modify parse_many to accept a many-to-many case, modify parse_item_kind to return multiple ItemKind's, generate a trait impl from the trait parser (when the trait alias is parsed), test generated impl's, test several cases in unit tests --- .../noirc_frontend/src/elaborator/traits.rs | 10 -- .../src/hir/def_collector/dc_crate.rs | 54 +------ .../noirc_frontend/src/parser/parser/impls.rs | 2 +- .../noirc_frontend/src/parser/parser/item.rs | 65 +++++---- .../src/parser/parser/parse_many.rs | 44 +++++- .../src/parser/parser/traits.rs | 133 ++++++++++++++++-- compiler/noirc_frontend/src/tests/traits.rs | 97 +++++++++++++ 7 files changed, 295 insertions(+), 110 deletions(-) diff --git a/compiler/noirc_frontend/src/elaborator/traits.rs b/compiler/noirc_frontend/src/elaborator/traits.rs index 35d026245d4..ae278616e03 100644 --- a/compiler/noirc_frontend/src/elaborator/traits.rs +++ b/compiler/noirc_frontend/src/elaborator/traits.rs @@ -55,16 +55,6 @@ impl<'context> Elaborator<'context> { }); }); - // Given a trait alias: - // trait Foo<..> = Bar + Baz; - // - // Generate a trait impl: - // impl Foo<..> for T where T: Bar, T: Baz {} - if unresolved_trait.trait_def.is_alias { - let mut trait_impl = unresolved_trait.alias_impl(); - self.collect_trait_impl(&mut trait_impl); - } - // This check needs to be after the trait's methods are set since // the interner may set `interner.ordering_type` based on the result type // of the Cmp trait, if this is it. diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 019259b2745..9917207d217 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -23,7 +23,7 @@ use crate::node_interner::{ use crate::ast::{ ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, PathSegment, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnsupportedNumericGenericType, + UnresolvedTraitConstraint, UnresolvedType, UnsupportedNumericGenericType, }; use crate::parser::{ParserError, SortedModule}; @@ -74,58 +74,6 @@ pub struct UnresolvedTrait { pub fns_with_default_impl: UnresolvedFunctions, } -impl UnresolvedTrait { - // impl Foo<..> for T where .. - pub fn alias_impl(&self) -> UnresolvedTraitImpl { - // TODO: 'T' - let is_synthesized = true; - - let object_type_ident = Ident::new("#T".to_string(), self.trait_def.span); - let object_type_path = Path::from_ident(object_type_ident); - let object_type_generic = UnresolvedGeneric::Variable(object_type_ident); - - let object_type = UnresolvedType { - typ: UnresolvedTypeData::Named(object_type_path, var![], is_synthesized), - span: self.trait_def.span, - }; - - let mut trait_impl_generics = self.trait_def.generics; - trait_impl_generics.push(object_type_generic); - - let mut trait_impl_where_clause = self.trait_def.where_clause; - for bound in self.trait_def.bounds { - // TODO: add where clause for each Bar, Baz in bounds - trait_impl_where_clause.push((object_type, bound)) - } - - UnresolvedTraitImpl { - file_id: self.file_id, - module_id: self.module_id, - trait_generics: self.trait_def.generics, - - // TODO - trait_path: self.trait_def.path, - object_type, - methods: vec![], - generics: trait_impl_generics, - where_clause: trait_impl_where_clause, - - associated_types: vec![], - associated_constants: vec![], - - // Every field after this line is filled in later in the elaborator - trait_id: None, - impl_id: None, - resolved_object_type: None, - resolved_generics: vec![], - - // The resolved generic on the trait itself. E.g. it is the `` in - // `impl Foo for Bar { ... }` - resolved_trait_generics: vec![], - } - } -} - pub struct UnresolvedTraitImpl { pub file_id: FileId, pub module_id: LocalModuleId, diff --git a/compiler/noirc_frontend/src/parser/parser/impls.rs b/compiler/noirc_frontend/src/parser/parser/impls.rs index 9215aec2742..ebe6e56d3ca 100644 --- a/compiler/noirc_frontend/src/parser/parser/impls.rs +++ b/compiler/noirc_frontend/src/parser/parser/impls.rs @@ -58,7 +58,7 @@ impl<'a> Parser<'a> { ) -> Impl { let where_clause = self.parse_where_clause(); let methods = self.parse_type_impl_body(); - + Impl::Impl(TypeImpl { object_type, type_span, generics, where_clause, methods }) } diff --git a/compiler/noirc_frontend/src/parser/parser/item.rs b/compiler/noirc_frontend/src/parser/parser/item.rs index 4fbcd7abac5..ff21fe60a61 100644 --- a/compiler/noirc_frontend/src/parser/parser/item.rs +++ b/compiler/noirc_frontend/src/parser/parser/item.rs @@ -1,3 +1,5 @@ +use iter_extended::vecmap; + use crate::{ parser::{labels::ParsingRuleLabel, Item, ItemKind}, token::{Keyword, Token}, @@ -13,31 +15,32 @@ impl<'a> Parser<'a> { } pub(crate) fn parse_module_items(&mut self, nested: bool) -> Vec { - 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 { + fn parse_module_item_in_list(&mut self, nested: bool) -> Vec { 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![], _ => (), } @@ -47,7 +50,7 @@ impl<'a> Parser<'a> { continue; }; - return Some(item); + return parsed_items } } @@ -85,13 +88,13 @@ impl<'a> Parser<'a> { } /// Item = OuterDocComments ItemKind - fn parse_item(&mut self) -> Option { + fn parse_item(&mut self) -> Vec { 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() }) } /// ItemKind @@ -106,9 +109,9 @@ impl<'a> Parser<'a> { /// | TypeAlias /// | Function /// ) - fn parse_item_kind(&mut self) -> Option { + fn parse_item_kind(&mut self) -> Vec { 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; @@ -122,78 +125,84 @@ 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( + 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 { diff --git a/compiler/noirc_frontend/src/parser/parser/parse_many.rs b/compiler/noirc_frontend/src/parser/parser/parse_many.rs index ea4dfe97122..3ba965de6be 100644 --- a/compiler/noirc_frontend/src/parser/parser/parse_many.rs +++ b/compiler/noirc_frontend/src/parser/parser/parse_many.rs @@ -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( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + f: F, + ) -> Vec + where + F: FnMut(&mut Parser<'a>) -> Vec, + { + 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( &mut self, @@ -27,6 +40,30 @@ impl<'a> Parser<'a> { ) -> (Vec, bool) where F: FnMut(&mut Parser<'a>) -> Option, + { + 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. + pub(super) fn parse_many_to_many_return_trailing_separator_if_any( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + mut f: F, + ) -> (Vec, bool) + where + F: FnMut(&mut Parser<'a>) -> Vec, { let mut elements: Vec = Vec::new(); let mut trailing_separator = false; @@ -38,12 +75,13 @@ 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() { @@ -51,7 +89,7 @@ impl<'a> Parser<'a> { } } - elements.push(element); + elements.append(&mut new_elements); trailing_separator = if let Some(separator) = &separated_by.token { self.eat(separator.clone()) diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index 34db65eac4f..5363fd2d352 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -1,9 +1,11 @@ +use iter_extended::vecmap; + use noirc_errors::Span; -use crate::ast::{Documented, ItemVisibility, NoirTrait, Pattern, TraitItem, UnresolvedType}; +use crate::ast::{Documented, GenericTypeArg, GenericTypeArgs, ItemVisibility, NoirTrait, Path, Pattern, TraitItem, UnresolvedGeneric, UnresolvedTraitConstraint, UnresolvedType}; use crate::{ ast::{Ident, UnresolvedTypeData}, - parser::{labels::ParsingRuleLabel, ParserErrorReason}, + parser::{labels::ParsingRuleLabel, NoirTraitImpl, ParserErrorReason}, token::{Attribute, Keyword, SecondaryAttribute, Token}, }; @@ -12,17 +14,20 @@ use super::Parser; impl<'a> Parser<'a> { /// Trait = 'trait' identifier Generics ( ':' TraitBounds )? WhereClause TraitBody + /// | 'trait' identifier Generics '=' TraitBounds WhereClause ';' pub(crate) fn parse_trait( &mut self, attributes: Vec<(Attribute, Span)>, visibility: ItemVisibility, start_span: Span, - ) -> NoirTrait { + ) -> (NoirTrait, Option) { let attributes = self.validate_secondary_attributes(attributes); let Some(name) = self.eat_ident() else { self.expected_identifier(); - return empty_trait(attributes, visibility, self.span_since(start_span)); + let noir_trait = empty_trait(attributes, visibility, self.span_since(start_span)); + let no_implicit_impl = None; + return (noir_trait, no_implicit_impl); }; let generics = self.parse_generics(); @@ -52,17 +57,67 @@ impl<'a> Parser<'a> { (bounds, where_clause, items, is_alias) }; - NoirTrait { + let span = self.span_since(start_span); + + let noir_impl = is_alias.then(|| { + let object_type_ident = Ident::new("#T".to_string(), span); + let object_type_path = Path::from_ident(object_type_ident.clone()); + let object_type_generic = UnresolvedGeneric::Variable(object_type_ident); + + let is_synthesized = true; + let object_type = UnresolvedType { + typ: UnresolvedTypeData::Named(object_type_path, vec![].into(), is_synthesized), + span, + }; + + let mut impl_generics = generics.clone(); + impl_generics.push(object_type_generic); + + let trait_name = Path::from_ident(name.clone()); + let trait_generics: GenericTypeArgs = vecmap(generics.clone(), |generic| { + let is_synthesized = true; + let generic_type = UnresolvedType { + typ: UnresolvedTypeData::Named(Path::from_ident(generic.ident().clone()), vec![].into(), is_synthesized), + span, + }; + + GenericTypeArg::Ordered(generic_type) + }).into(); + + // bounds from trait + let mut where_clause = where_clause.clone(); + for bound in bounds.clone() { + where_clause.push(UnresolvedTraitConstraint { + typ: object_type.clone(), + trait_bound: bound, + }); + } + + let items = vec![]; + + NoirTraitImpl { + impl_generics, + trait_name, + trait_generics, + object_type, + where_clause, + items, + } + }); + + let noir_trait = NoirTrait { name, generics, bounds, where_clause, - span: self.span_since(start_span), + span, items, attributes, visibility, is_alias, - } + }; + + (noir_trait, noir_impl) } /// TraitBody = '{' ( OuterDocComments TraitItem )* '}' @@ -218,22 +273,44 @@ fn empty_trait( #[cfg(test)] mod tests { use crate::{ - ast::{NoirTrait, TraitItem}, + ast::{NoirTrait, NoirTraitImpl, TraitItem}, parser::{ parser::{parse_program, ParserErrorReason, tests::expect_no_errors}, ItemKind, }, }; - fn parse_trait_no_errors(src: &str) -> NoirTrait { + fn parse_trait_opt_impl_no_errors(src: &str) -> (NoirTrait, Option) { let (mut module, errors) = parse_program(src); expect_no_errors(&errors); - assert_eq!(module.items.len(), 1); - let item = module.items.remove(0); + let (item, impl_item) = if module.items.len() == 2 { + let item = module.items.remove(0); + let impl_item = module.items.remove(0); + (item, Some(impl_item)) + } else { + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + (item, None) + }; let ItemKind::Trait(noir_trait) = item.kind else { panic!("Expected trait"); }; - noir_trait + let noir_trait_impl = impl_item.map(|impl_item| { + let ItemKind::TraitImpl(noir_trait_impl) = impl_item.kind else { + panic!("Expected impl"); + }; + noir_trait_impl + }); + (noir_trait, noir_trait_impl) + } + + fn parse_trait_with_impl_no_errors(src: &str) -> (NoirTrait, NoirTraitImpl) { + let (noir_trait, noir_trait_impl) = parse_trait_opt_impl_no_errors(src); + (noir_trait, noir_trait_impl.expect("expected a NoirTraitImpl")) + } + + fn parse_trait_no_errors(src: &str) -> NoirTrait { + parse_trait_opt_impl_no_errors(src).0 } #[test] @@ -269,7 +346,7 @@ mod tests { #[test] fn parse_trait_alias_with_generics() { let src = "trait Foo = Bar + Baz;"; - let noir_trait_alias = parse_trait_no_errors(src); + let (noir_trait_alias, noir_trait_impl) = parse_trait_with_impl_no_errors(src); assert_eq!(noir_trait_alias.name.to_string(), "Foo"); assert_eq!(noir_trait_alias.generics.len(), 2); assert_eq!(noir_trait_alias.bounds.len(), 2); @@ -279,6 +356,15 @@ mod tests { assert!(noir_trait_alias.items.is_empty()); assert!(noir_trait_alias.is_alias); + assert_eq!(noir_trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(noir_trait_impl.impl_generics.len(), 3); + assert_eq!(noir_trait_impl.trait_generics.ordered_args.len(), 2); + assert_eq!(noir_trait_impl.where_clause.len(), 2); + assert_eq!(noir_trait_alias.bounds.len(), 2); + assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); + assert_eq!(noir_trait_alias.bounds[1].to_string(), "Baz"); + assert!(noir_trait_impl.items.is_empty()); + // Equivalent to let src = "trait Foo: Bar + Baz {}"; let noir_trait = parse_trait_no_errors(src); @@ -313,7 +399,7 @@ mod tests { #[test] fn parse_trait_alias_with_where_clause() { let src = "trait Foo = Bar + Baz where A: Z;"; - let noir_trait_alias = parse_trait_no_errors(src); + let (noir_trait_alias, noir_trait_impl) = parse_trait_with_impl_no_errors(src); assert_eq!(noir_trait_alias.name.to_string(), "Foo"); assert_eq!(noir_trait_alias.generics.len(), 2); assert_eq!(noir_trait_alias.bounds.len(), 2); @@ -323,6 +409,15 @@ mod tests { assert!(noir_trait_alias.items.is_empty()); assert!(noir_trait_alias.is_alias); + assert_eq!(noir_trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(noir_trait_impl.impl_generics.len(), 3); + assert_eq!(noir_trait_impl.trait_generics.ordered_args.len(), 2); + assert_eq!(noir_trait_impl.where_clause.len(), 3); + assert_eq!(noir_trait_impl.where_clause[0].to_string(), "A: Z"); + assert_eq!(noir_trait_impl.where_clause[1].to_string(), "#T: Bar"); + assert_eq!(noir_trait_impl.where_clause[2].to_string(), "#T: Baz"); + assert!(noir_trait_impl.items.is_empty()); + // Equivalent to let src = "trait Foo: Bar + Baz where A: Z {}"; let noir_trait = parse_trait_no_errors(src); @@ -418,7 +513,7 @@ mod tests { #[test] fn parse_trait_alias() { let src = "trait Foo = Bar + Baz;"; - let noir_trait_alias = parse_trait_no_errors(src); + let (noir_trait_alias, noir_trait_impl) = parse_trait_with_impl_no_errors(src); assert_eq!(noir_trait_alias.bounds.len(), 2); assert_eq!(noir_trait_alias.bounds[0].to_string(), "Bar"); @@ -427,6 +522,14 @@ mod tests { assert_eq!(noir_trait_alias.to_string(), "trait Foo = Bar + Baz;"); assert!(noir_trait_alias.is_alias); + assert_eq!(noir_trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(noir_trait_impl.impl_generics.len(), 1); + assert_eq!(noir_trait_impl.trait_generics.ordered_args.len(), 0); + assert_eq!(noir_trait_impl.where_clause.len(), 2); + assert_eq!(noir_trait_impl.where_clause[0].to_string(), "#T: Bar"); + assert_eq!(noir_trait_impl.where_clause[1].to_string(), "#T: Baz"); + assert!(noir_trait_impl.items.is_empty()); + // Equivalent to let src = "trait Foo: Bar + Baz {}"; let noir_trait = parse_trait_no_errors(src); diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index 42900d094b8..15a9bc16eaf 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -320,6 +320,103 @@ fn regression_6314_double_inheritance() { assert_no_errors(src); } +#[test] +fn trait_alias_single_member() { + let src = r#" + trait Foo { + fn foo(self) -> Self; + } + + trait Baz = Foo; + + impl Foo for Field { + fn foo(self) -> Self { self } + } + + fn baz(x: T) -> T where T: Baz { + x.foo() + } + + fn main() { + let x: Field = 0; + let _ = baz(x); + } + "#; + assert_no_errors(src); +} + +#[test] +fn trait_alias_two_members() { + let src = r#" + trait Foo { + fn foo(self) -> Self; + } + + trait Bar { + fn bar(self) -> Self; + } + + trait Baz = Foo + Bar; + + fn baz(x: T) -> T where T: Baz { + x.foo().bar() + } + + impl Foo for Field { + fn foo(self) -> Self { + self + 1 + } + } + + impl Bar for Field { + fn bar(self) -> Self { + self + 2 + } + } + + fn main() { + assert(0.foo().bar() == baz(0)); + }"#; + + assert_no_errors(src); +} + +#[test] +fn trait_alias_polymorphic_where_clause() { + let src = r#" + trait Foo { + fn foo(self) -> Self; + } + + trait Bar { + fn bar(self) -> T; + } + + trait Baz = Foo + Bar; + + fn baz(x: T) -> U where T: Baz { + x.foo().bar() + } + + impl Foo for Field { + fn foo(self) -> Self { + self + 1 + } + } + + impl Bar for Field { + fn bar(self) -> bool { + true + } + } + + fn main() { + assert(0.foo().bar() == baz(0)); + }"#; + + assert_no_errors(src); +} + #[test] fn removes_assumed_parent_traits_after_function_ends() { let src = r#" From 224a822680dfd1609e4e7a106b47b883a50009d0 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" Date: Wed, 6 Nov 2024 10:31:20 -0500 Subject: [PATCH 4/5] cargo fmt --- .../noirc_frontend/src/parser/parser/impls.rs | 2 +- .../noirc_frontend/src/parser/parser/item.rs | 9 +++----- .../src/parser/parser/parse_many.rs | 6 +----- .../src/parser/parser/traits.rs | 21 ++++++++++++++----- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/compiler/noirc_frontend/src/parser/parser/impls.rs b/compiler/noirc_frontend/src/parser/parser/impls.rs index ebe6e56d3ca..9215aec2742 100644 --- a/compiler/noirc_frontend/src/parser/parser/impls.rs +++ b/compiler/noirc_frontend/src/parser/parser/impls.rs @@ -58,7 +58,7 @@ impl<'a> Parser<'a> { ) -> Impl { let where_clause = self.parse_where_clause(); let methods = self.parse_type_impl_body(); - + Impl::Impl(TypeImpl { object_type, type_span, generics, where_clause, methods }) } diff --git a/compiler/noirc_frontend/src/parser/parser/item.rs b/compiler/noirc_frontend/src/parser/parser/item.rs index ff21fe60a61..34f97753855 100644 --- a/compiler/noirc_frontend/src/parser/parser/item.rs +++ b/compiler/noirc_frontend/src/parser/parser/item.rs @@ -50,7 +50,7 @@ impl<'a> Parser<'a> { continue; }; - return parsed_items + return parsed_items; } } @@ -156,11 +156,8 @@ impl<'a> Parser<'a> { if self.eat_keyword(Keyword::Trait) { self.comptime_mutable_and_unconstrained_not_applicable(modifiers); - let (noir_trait, noir_impl) = 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)); diff --git a/compiler/noirc_frontend/src/parser/parser/parse_many.rs b/compiler/noirc_frontend/src/parser/parser/parse_many.rs index 3ba965de6be..452be2d9a8e 100644 --- a/compiler/noirc_frontend/src/parser/parser/parse_many.rs +++ b/compiler/noirc_frontend/src/parser/parser/parse_many.rs @@ -48,11 +48,7 @@ impl<'a> Parser<'a> { vec![] } }; - self.parse_many_to_many_return_trailing_separator_if_any( - items, - separated_by, - f, - ) + 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. diff --git a/compiler/noirc_frontend/src/parser/parser/traits.rs b/compiler/noirc_frontend/src/parser/parser/traits.rs index 5363fd2d352..7d1e3cd6200 100644 --- a/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -2,7 +2,10 @@ use iter_extended::vecmap; use noirc_errors::Span; -use crate::ast::{Documented, GenericTypeArg, GenericTypeArgs, ItemVisibility, NoirTrait, Path, Pattern, TraitItem, UnresolvedGeneric, UnresolvedTraitConstraint, UnresolvedType}; +use crate::ast::{ + Documented, GenericTypeArg, GenericTypeArgs, ItemVisibility, NoirTrait, Path, Pattern, + TraitItem, UnresolvedGeneric, UnresolvedTraitConstraint, UnresolvedType, +}; use crate::{ ast::{Ident, UnresolvedTypeData}, parser::{labels::ParsingRuleLabel, NoirTraitImpl, ParserErrorReason}, @@ -77,12 +80,17 @@ impl<'a> Parser<'a> { let trait_generics: GenericTypeArgs = vecmap(generics.clone(), |generic| { let is_synthesized = true; let generic_type = UnresolvedType { - typ: UnresolvedTypeData::Named(Path::from_ident(generic.ident().clone()), vec![].into(), is_synthesized), + typ: UnresolvedTypeData::Named( + Path::from_ident(generic.ident().clone()), + vec![].into(), + is_synthesized, + ), span, }; GenericTypeArg::Ordered(generic_type) - }).into(); + }) + .into(); // bounds from trait let mut where_clause = where_clause.clone(); @@ -275,7 +283,7 @@ mod tests { use crate::{ ast::{NoirTrait, NoirTraitImpl, TraitItem}, parser::{ - parser::{parse_program, ParserErrorReason, tests::expect_no_errors}, + parser::{parse_program, tests::expect_no_errors, ParserErrorReason}, ItemKind, }, }; @@ -426,7 +434,10 @@ mod tests { assert_eq!(noir_trait.bounds.len(), noir_trait_alias.bounds.len()); assert_eq!(noir_trait.bounds[0].to_string(), noir_trait_alias.bounds[0].to_string()); assert_eq!(noir_trait.where_clause.len(), noir_trait_alias.where_clause.len()); - assert_eq!(noir_trait.where_clause[0].to_string(), noir_trait_alias.where_clause[0].to_string()); + assert_eq!( + noir_trait.where_clause[0].to_string(), + noir_trait_alias.where_clause[0].to_string() + ); assert_eq!(noir_trait.items.is_empty(), noir_trait_alias.items.is_empty()); assert!(!noir_trait.is_alias); } From e32f51ae020fa3e8ad156b4d02601a324b25e99b Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" Date: Wed, 6 Nov 2024 13:35:07 -0500 Subject: [PATCH 5/5] test 'pub' for trait aliases, add tests with where clause, reference issue with where clauses, add docs, cargo clippy/fmt --- .../src/hir/type_check/errors.rs | 2 +- compiler/noirc_frontend/src/tests/traits.rs | 157 +++++++++++++++++- docs/docs/noir/concepts/traits.md | 89 +++++++++- 3 files changed, 240 insertions(+), 8 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/type_check/errors.rs b/compiler/noirc_frontend/src/hir/type_check/errors.rs index a63601a4280..a6b6120986e 100644 --- a/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -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, } diff --git a/compiler/noirc_frontend/src/tests/traits.rs b/compiler/noirc_frontend/src/tests/traits.rs index d18541d6bba..811a32bab86 100644 --- a/compiler/noirc_frontend/src/tests/traits.rs +++ b/compiler/noirc_frontend/src/tests/traits.rs @@ -1,5 +1,6 @@ use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::resolution::errors::ResolverError; +use crate::hir::type_check::TypeCheckError; use crate::tests::{get_program_errors, get_program_with_maybe_parser_errors}; use super::assert_no_errors; @@ -348,15 +349,15 @@ fn trait_alias_single_member() { #[test] fn trait_alias_two_members() { let src = r#" - trait Foo { + pub trait Foo { fn foo(self) -> Self; } - trait Bar { + pub trait Bar { fn bar(self) -> Self; } - trait Baz = Foo + Bar; + pub trait Baz = Foo + Bar; fn baz(x: T) -> T where T: Baz { x.foo().bar() @@ -382,7 +383,7 @@ fn trait_alias_two_members() { } #[test] -fn trait_alias_polymorphic_where_clause() { +fn trait_alias_polymorphic_inheritance() { let src = r#" trait Foo { fn foo(self) -> Self; @@ -417,6 +418,154 @@ fn trait_alias_polymorphic_where_clause() { assert_no_errors(src); } +// TODO(https://github.com/noir-lang/noir/issues/6467): currently fails with the +// same errors as the desugared version +#[test] +fn trait_alias_polymorphic_where_clause() { + let src = r#" + trait Foo { + fn foo(self) -> Self; + } + + trait Bar { + fn bar(self) -> T; + } + + trait Baz { + fn baz(self) -> bool; + } + + trait Qux = Foo + Bar where T: Baz; + + fn qux(x: T) -> bool where T: Qux { + x.foo().bar().baz() + } + + impl Foo for Field { + fn foo(self) -> Self { + self + 1 + } + } + + impl Bar for Field { + fn bar(self) -> bool { + true + } + } + + impl Baz for bool { + fn baz(self) -> bool { + self + } + } + + fn main() { + assert(0.foo().bar().baz() == qux(0)); + } + "#; + + // TODO(https://github.com/noir-lang/noir/issues/6467) + // assert_no_errors(src); + let errors = get_program_errors(src); + assert_eq!(errors.len(), 2); + + match &errors[0].0 { + CompilationError::TypeError(TypeCheckError::UnresolvedMethodCall { + method_name, .. + }) => { + assert_eq!(method_name, "baz"); + } + other => { + panic!("expected UnresolvedMethodCall, but found {:?}", other); + } + } + + match &errors[1].0 { + CompilationError::TypeError(TypeCheckError::NoMatchingImplFound(err)) => { + assert_eq!(err.constraints.len(), 2); + assert_eq!(err.constraints[0].1, "Baz"); + assert_eq!(err.constraints[1].1, "Qux<_>"); + } + other => { + panic!("expected NoMatchingImplFound, but found {:?}", other); + } + } +} + +// TODO(https://github.com/noir-lang/noir/issues/6467): currently failing, so +// this just tests that the trait alias has an equivalent error to the expected +// desugared version +#[test] +fn trait_alias_with_where_clause_has_equivalent_errors() { + let src = r#" + trait Bar { + fn bar(self) -> Self; + } + + trait Baz { + fn baz(self) -> bool; + } + + trait Qux: Bar where T: Baz {} + + impl Qux for U where + U: Bar, + T: Baz, + {} + + pub fn qux(x: T, _: U) -> bool where U: Qux { + x.baz() + } + + fn main() {} + "#; + + let alias_src = r#" + trait Bar { + fn bar(self) -> Self; + } + + trait Baz { + fn baz(self) -> bool; + } + + trait Qux = Bar where T: Baz; + + pub fn qux(x: T, _: U) -> bool where U: Qux { + x.baz() + } + + fn main() {} + "#; + + let errors = get_program_errors(src); + let alias_errors = get_program_errors(alias_src); + + assert_eq!(errors.len(), 1); + assert_eq!(alias_errors.len(), 1); + + match (&errors[0].0, &alias_errors[0].0) { + ( + CompilationError::TypeError(TypeCheckError::UnresolvedMethodCall { + method_name, + object_type, + .. + }), + CompilationError::TypeError(TypeCheckError::UnresolvedMethodCall { + method_name: alias_method_name, + object_type: alias_object_type, + .. + }), + ) => { + assert_eq!(method_name, alias_method_name); + assert_eq!(object_type, alias_object_type); + } + other => { + panic!("expected UnresolvedMethodCall, but found {:?}", other); + } + } +} + #[test] fn removes_assumed_parent_traits_after_function_ends() { let src = r#" diff --git a/docs/docs/noir/concepts/traits.md b/docs/docs/noir/concepts/traits.md index 9da00a77587..b6c0a886eb0 100644 --- a/docs/docs/noir/concepts/traits.md +++ b/docs/docs/noir/concepts/traits.md @@ -490,12 +490,95 @@ trait CompSciStudent: Programmer + Student { } ``` +### Trait Aliases + +Similar to the proposed Rust feature for [trait aliases](https://github.com/rust-lang/rust/blob/4d215e2426d52ca8d1af166d5f6b5e172afbff67/src/doc/unstable-book/src/language-features/trait-alias.md), +Noir supports aliasing one or more traits and using those aliases wherever +traits would normally be used. + +```rust +trait Foo { + fn foo(self) -> Self; +} + +trait Bar { + fn bar(self) -> Self; +} + +// Equivalent to: +// trait Baz: Foo + Bar {} +// +// impl Baz for T where T: Foo + Bar {} +trait Baz = Foo + Bar; + +// We can use `Baz` to refer to `Foo + Bar` +fn baz(x: T) -> T where T: Baz { + x.foo().bar() +} +``` + +#### Generic Trait Aliases + +Trait aliases can also be generic by placing the generic arguments after the +trait name. These generics are in scope of every item within the trait alias. + +```rust +trait Foo { + fn foo(self) -> Self; +} + +trait Bar { + fn bar(self) -> T; +} + +// Equivalent to: +// trait Baz: Foo + Bar {} +// +// impl Baz for U where U: Foo + Bar {} +trait Baz = Foo + Bar; +``` + +#### Trait Alias Where Clauses + +Trait aliases support where clauses to add trait constraints to any of their +generic arguments, e.g. ensuring `T: Baz` for a trait alias `Qux`. + +```rust +trait Foo { + fn foo(self) -> Self; +} + +trait Bar { + fn bar(self) -> T; +} + +trait Baz { + fn baz(self) -> bool; +} + +// Equivalent to: +// trait Qux: Foo + Bar where T: Baz {} +// +// impl Qux for U where +// U: Foo + Bar, +// T: Baz, +// {} +trait Qux = Foo + Bar where T: Baz; +``` + +Note that while trait aliases support where clauses, +the equivalent traits can fail due to [#6467](https://github.com/noir-lang/noir/issues/6467) + ### Visibility -By default, like functions, traits are private to the module they exist in. You can use `pub` -to make the trait public or `pub(crate)` to make it public to just its crate: +By default, like functions, traits and trait aliases are private to the module +they exist in. You can use `pub` to make the trait public or `pub(crate)` to make +it public to just its crate: ```rust // This trait is now public pub trait Trait {} -``` \ No newline at end of file + +// This trait alias is now public +pub trait Baz = Foo + Bar; +```