diff --git a/compiler/noirc_frontend/src/ast/enumeration.rs b/compiler/noirc_frontend/src/ast/enumeration.rs new file mode 100644 index 00000000000..eeeb823b9fc --- /dev/null +++ b/compiler/noirc_frontend/src/ast/enumeration.rs @@ -0,0 +1,50 @@ +use std::fmt::Display; + +use crate::ast::{Ident, UnresolvedGenerics, UnresolvedType}; +use crate::token::SecondaryAttribute; + +use iter_extended::vecmap; +use noirc_errors::Span; + +use super::{Documented, ItemVisibility}; + +/// Ast node for an enum +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NoirEnumeration { + pub name: Ident, + pub attributes: Vec, + pub visibility: ItemVisibility, + pub generics: UnresolvedGenerics, + pub variants: Vec>, + pub span: Span, +} + +impl NoirEnumeration { + pub fn is_abi(&self) -> bool { + self.attributes.iter().any(|attr| attr.is_abi()) + } +} + +/// We only support variants of the form `Name(A, B, ...)` currently. +/// Enum variants like `Name { a: A, b: B, .. }` will be implemented later +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EnumVariant { + pub name: Ident, + pub parameters: Vec, +} + +impl Display for NoirEnumeration { + 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(", ") }; + + writeln!(f, "enum {}{} {{", self.name, generics)?; + + for variant in self.variants.iter() { + let parameters = vecmap(&variant.item.parameters, ToString::to_string).join(", "); + writeln!(f, " {}({}),", variant.item.name, parameters)?; + } + + write!(f, "}}") + } +} diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index f8a82574bee..33f504437c0 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -5,6 +5,7 @@ //! Noir's Ast is produced by the parser and taken as input to name resolution, //! where it is converted into the Hir (defined in the hir_def module). mod docs; +mod enumeration; mod expression; mod function; mod statement; @@ -24,6 +25,7 @@ use proptest_derive::Arbitrary; use acvm::FieldElement; pub use docs::*; +pub use enumeration::*; use noirc_errors::Span; use serde::{Deserialize, Serialize}; pub use statement::*; diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index ec50a982a70..5c4781df7a5 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -21,15 +21,17 @@ use crate::{ }; use super::{ - ForBounds, FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, Pattern, - Signedness, TraitBound, TraitImplItemKind, TypePath, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, + ForBounds, FunctionReturnType, GenericTypeArgs, IntegerBitSize, ItemVisibility, + NoirEnumeration, Pattern, Signedness, TraitBound, TraitImplItemKind, TypePath, + UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, + UnresolvedTypeExpression, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AttributeTarget { Module, Struct, + Enum, Trait, Function, Let, @@ -142,6 +144,10 @@ pub trait Visitor { true } + fn visit_noir_enum(&mut self, _: &NoirEnumeration, _: Span) -> bool { + true + } + fn visit_noir_type_alias(&mut self, _: &NoirTypeAlias, _: Span) -> bool { true } @@ -527,6 +533,7 @@ impl Item { } ItemKind::TypeAlias(noir_type_alias) => noir_type_alias.accept(self.span, visitor), ItemKind::Struct(noir_struct) => noir_struct.accept(self.span, visitor), + ItemKind::Enum(noir_enum) => noir_enum.accept(self.span, visitor), ItemKind::ModuleDecl(module_declaration) => { module_declaration.accept(self.span, visitor); } @@ -775,6 +782,26 @@ impl NoirStruct { } } +impl NoirEnumeration { + pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { + if visitor.visit_noir_enum(self, span) { + self.accept_children(visitor); + } + } + + pub fn accept_children(&self, visitor: &mut impl Visitor) { + for attribute in &self.attributes { + attribute.accept(AttributeTarget::Enum, visitor); + } + + for variant in &self.variants { + for parameter in &variant.item.parameters { + parameter.accept(visitor); + } + } + } +} + impl NoirTypeAlias { pub fn accept(&self, span: Span, visitor: &mut impl Visitor) { if visitor.visit_noir_type_alias(self, span) { diff --git a/compiler/noirc_frontend/src/elaborator/comptime.rs b/compiler/noirc_frontend/src/elaborator/comptime.rs index d88bb62e871..9f5eef6e785 100644 --- a/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -442,7 +442,21 @@ impl<'context> Elaborator<'context> { self.crate_id, &mut self.errors, ) { - generated_items.types.insert(type_id, the_struct); + generated_items.structs.insert(type_id, the_struct); + } + } + ItemKind::Enum(enum_def) => { + if let Some((type_id, the_enum)) = dc_mod::collect_enum( + self.interner, + self.def_maps.get_mut(&self.crate_id).unwrap(), + self.usage_tracker, + Documented::new(enum_def, item.doc_comments), + self.file, + self.local_module, + self.crate_id, + &mut self.errors, + ) { + generated_items.enums.insert(type_id, the_enum); } } ItemKind::Impl(r#impl) => { diff --git a/compiler/noirc_frontend/src/elaborator/mod.rs b/compiler/noirc_frontend/src/elaborator/mod.rs index 5299d9f5653..79f6be444ce 100644 --- a/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/compiler/noirc_frontend/src/elaborator/mod.rs @@ -319,7 +319,7 @@ impl<'context> Elaborator<'context> { } // Must resolve structs before we resolve globals. - self.collect_struct_definitions(&items.types); + self.collect_struct_definitions(&items.structs); self.define_function_metas(&mut items.functions, &mut items.impls, &mut items.trait_impls); @@ -349,7 +349,7 @@ impl<'context> Elaborator<'context> { // since the generated items are checked beforehand as well. self.run_attributes( &items.traits, - &items.types, + &items.structs, &items.functions, &items.module_attributes, ); 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 7f6509b9f16..10866f4b309 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -14,7 +14,7 @@ use crate::{Generics, Type}; use crate::hir::resolution::import::{resolve_import, ImportDirective}; use crate::hir::Context; -use crate::ast::Expression; +use crate::ast::{Expression, NoirEnumeration}; use crate::node_interner::{ FuncId, GlobalId, ModuleAttributes, NodeInterner, ReferenceId, StructId, TraitId, TraitImplId, TypeAliasId, @@ -64,6 +64,12 @@ pub struct UnresolvedStruct { pub struct_def: NoirStruct, } +pub struct UnresolvedEnum { + pub file_id: FileId, + pub module_id: LocalModuleId, + pub enum_def: NoirEnumeration, +} + #[derive(Clone)] pub struct UnresolvedTrait { pub file_id: FileId, @@ -141,7 +147,8 @@ pub struct DefCollector { #[derive(Default)] pub struct CollectedItems { pub functions: Vec, - pub(crate) types: BTreeMap, + pub(crate) structs: BTreeMap, + pub(crate) enums: BTreeMap, pub(crate) type_aliases: BTreeMap, pub(crate) traits: BTreeMap, pub globals: Vec, @@ -153,7 +160,7 @@ pub struct CollectedItems { impl CollectedItems { pub fn is_empty(&self) -> bool { self.functions.is_empty() - && self.types.is_empty() + && self.structs.is_empty() && self.type_aliases.is_empty() && self.traits.is_empty() && self.globals.is_empty() @@ -254,7 +261,8 @@ impl DefCollector { imports: vec![], items: CollectedItems { functions: vec![], - types: BTreeMap::new(), + structs: BTreeMap::new(), + enums: BTreeMap::new(), type_aliases: BTreeMap::new(), traits: BTreeMap::new(), impls: HashMap::default(), diff --git a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index ead6a801ba7..41234980942 100644 --- a/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -12,8 +12,9 @@ use rustc_hash::FxHashMap as HashMap; use crate::ast::{ Documented, Expression, FunctionDefinition, Ident, ItemVisibility, LetStatement, - ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, Pattern, - TraitImplItemKind, TraitItem, TypeImpl, UnresolvedType, UnresolvedTypeData, + ModuleDeclaration, NoirEnumeration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, + NoirTypeAlias, Pattern, TraitImplItemKind, TraitItem, TypeImpl, UnresolvedType, + UnresolvedTypeData, }; use crate::hir::resolution::errors::ResolverError; use crate::node_interner::{ModuleAttributes, NodeInterner, ReferenceId, StructId}; @@ -27,8 +28,8 @@ use crate::{ }; use crate::{Generics, Kind, ResolvedGeneric, Type, TypeVariable}; -use super::dc_crate::CollectedItems; use super::dc_crate::ModuleAttribute; +use super::dc_crate::{CollectedItems, UnresolvedEnum}; use super::{ dc_crate::{ CompilationError, DefCollector, UnresolvedFunctions, UnresolvedGlobal, UnresolvedTraitImpl, @@ -91,7 +92,7 @@ pub fn collect_defs( errors.extend(collector.collect_traits(context, ast.traits, crate_id)); - errors.extend(collector.collect_structs(context, ast.types, crate_id)); + errors.extend(collector.collect_structs(context, ast.structs, crate_id)); errors.extend(collector.collect_type_aliases(context, ast.type_aliases, crate_id)); @@ -317,7 +318,7 @@ impl<'a> ModCollector<'a> { krate, &mut definition_errors, ) { - self.def_collector.items.types.insert(id, the_struct); + self.def_collector.items.structs.insert(id, the_struct); } } definition_errors @@ -1078,6 +1079,20 @@ pub fn collect_struct( Some((id, unresolved)) } +#[allow(clippy::too_many_arguments)] +pub fn collect_enum( + _interner: &mut NodeInterner, + _def_map: &mut CrateDefMap, + _usage_tracker: &mut UsageTracker, + _enum_definition: Documented, + _file_id: FileId, + _module_id: LocalModuleId, + _krate: CrateId, + _definition_errors: &mut [(CompilationError, FileId)], +) -> Option<(StructId, UnresolvedEnum)> { + todo!("Implement collect_enum") +} + pub fn collect_impl( interner: &mut NodeInterner, items: &mut CollectedItems, diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 513240a7495..4eeec314917 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -315,6 +315,7 @@ pub enum QuotedType { Type, TypedExpr, StructDefinition, + EnumDefinition, TraitConstraint, TraitDefinition, TraitImpl, @@ -958,6 +959,7 @@ impl std::fmt::Display for QuotedType { QuotedType::Type => write!(f, "Type"), QuotedType::TypedExpr => write!(f, "TypedExpr"), QuotedType::StructDefinition => write!(f, "StructDefinition"), + QuotedType::EnumDefinition => write!(f, "EnumDefinition"), QuotedType::TraitDefinition => write!(f, "TraitDefinition"), QuotedType::TraitConstraint => write!(f, "TraitConstraint"), QuotedType::TraitImpl => write!(f, "TraitImpl"), diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 8c136f5e45d..7d11b97ca16 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -1020,6 +1020,7 @@ pub enum Keyword { Dep, Else, Enum, + EnumDefinition, Expr, Field, Fn, @@ -1080,6 +1081,7 @@ impl fmt::Display for Keyword { Keyword::Dep => write!(f, "dep"), Keyword::Else => write!(f, "else"), Keyword::Enum => write!(f, "enum"), + Keyword::EnumDefinition => write!(f, "EnumDefinition"), Keyword::Expr => write!(f, "Expr"), Keyword::Field => write!(f, "Field"), Keyword::Fn => write!(f, "fn"), @@ -1143,6 +1145,7 @@ impl Keyword { "dep" => Keyword::Dep, "else" => Keyword::Else, "enum" => Keyword::Enum, + "EnumDefinition" => Keyword::EnumDefinition, "Expr" => Keyword::Expr, "Field" => Keyword::Field, "fn" => Keyword::Fn, diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index f44f109e1ce..508ed33857e 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -83,8 +83,8 @@ pub enum ParserErrorReason { "Multiple primary attributes found. Only one function attribute is allowed per function" )] MultipleFunctionAttributesFound, - #[error("A function attribute cannot be placed on a struct")] - NoFunctionAttributesAllowedOnStruct, + #[error("A function attribute cannot be placed on a struct or enum")] + NoFunctionAttributesAllowedOnType, #[error("Assert statements can only accept string literals")] AssertMessageNotString, #[error("Integer bit size {0} isn't supported")] diff --git a/compiler/noirc_frontend/src/parser/mod.rs b/compiler/noirc_frontend/src/parser/mod.rs index 17c156476a7..c433adbfdfb 100644 --- a/compiler/noirc_frontend/src/parser/mod.rs +++ b/compiler/noirc_frontend/src/parser/mod.rs @@ -13,7 +13,8 @@ mod parser; use crate::ast::{ Documented, Ident, ImportStatement, ItemVisibility, LetStatement, ModuleDeclaration, - NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TypeImpl, UseTree, + NoirEnumeration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TypeImpl, + UseTree, }; use crate::token::SecondaryAttribute; @@ -26,7 +27,8 @@ pub use parser::{parse_program, Parser, StatementOrExpressionOrLValue}; pub struct SortedModule { pub imports: Vec, pub functions: Vec>, - pub types: Vec>, + pub structs: Vec>, + pub enums: Vec>, pub traits: Vec>, pub trait_impls: Vec, pub impls: Vec, @@ -57,7 +59,7 @@ impl std::fmt::Display for SortedModule { write!(f, "{global_const}")?; } - for type_ in &self.types { + for type_ in &self.structs { write!(f, "{type_}")?; } @@ -96,7 +98,8 @@ impl ParsedModule { match item.kind { ItemKind::Import(import, visibility) => module.push_import(import, visibility), ItemKind::Function(func) => module.push_function(func, item.doc_comments), - ItemKind::Struct(typ) => module.push_type(typ, item.doc_comments), + ItemKind::Struct(typ) => module.push_struct(typ, item.doc_comments), + ItemKind::Enum(typ) => module.push_enum(typ, item.doc_comments), ItemKind::Trait(noir_trait) => module.push_trait(noir_trait, item.doc_comments), ItemKind::TraitImpl(trait_impl) => module.push_trait_impl(trait_impl), ItemKind::Impl(r#impl) => module.push_impl(r#impl), @@ -134,6 +137,7 @@ pub enum ItemKind { Import(UseTree, ItemVisibility), Function(NoirFunction), Struct(NoirStruct), + Enum(NoirEnumeration), Trait(NoirTrait), TraitImpl(NoirTraitImpl), Impl(TypeImpl), @@ -147,6 +151,7 @@ pub enum ItemKind { impl std::fmt::Display for ItemKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + ItemKind::Enum(e) => e.fmt(f), ItemKind::Function(fun) => fun.fmt(f), ItemKind::ModuleDecl(m) => m.fmt(f), ItemKind::Import(tree, visibility) => { @@ -222,8 +227,12 @@ impl SortedModule { self.functions.push(Documented::new(func, doc_comments)); } - fn push_type(&mut self, typ: NoirStruct, doc_comments: Vec) { - self.types.push(Documented::new(typ, doc_comments)); + fn push_struct(&mut self, typ: NoirStruct, doc_comments: Vec) { + self.structs.push(Documented::new(typ, doc_comments)); + } + + fn push_enum(&mut self, typ: NoirEnumeration, doc_comments: Vec) { + self.enums.push(Documented::new(typ, doc_comments)); } fn push_trait(&mut self, noir_trait: NoirTrait, doc_comments: Vec) { diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 05f8ae3c2bb..e554248fb03 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -13,6 +13,7 @@ use super::{labels::ParsingRuleLabel, ParsedModule, ParserError, ParserErrorReas mod arguments; mod attributes; mod doc_comments; +mod enums; mod expression; mod function; mod generics; @@ -191,14 +192,10 @@ impl<'a> Parser<'a> { fn read_token_internal(&mut self) -> SpannedToken { loop { - let token = self.tokens.next(); - if let Some(token) = token { - match token { - Ok(token) => return token, - Err(lexer_error) => self.errors.push(lexer_error.into()), - } - } else { - return eof_spanned_token(); + match self.tokens.next() { + Some(Ok(token)) => return token, + Some(Err(lexer_error)) => self.errors.push(lexer_error.into()), + None => return eof_spanned_token(), } } } diff --git a/compiler/noirc_frontend/src/parser/parser/attributes.rs b/compiler/noirc_frontend/src/parser/parser/attributes.rs index 12cb37edb4b..e32e7d3cb23 100644 --- a/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -92,7 +92,7 @@ impl<'a> Parser<'a> { .into_iter() .filter_map(|(attribute, span)| match attribute { Attribute::Function(..) => { - self.push_error(ParserErrorReason::NoFunctionAttributesAllowedOnStruct, span); + self.push_error(ParserErrorReason::NoFunctionAttributesAllowedOnType, span); None } Attribute::Secondary(attr) => Some(attr), diff --git a/compiler/noirc_frontend/src/parser/parser/enums.rs b/compiler/noirc_frontend/src/parser/parser/enums.rs new file mode 100644 index 00000000000..f95c0f8f72b --- /dev/null +++ b/compiler/noirc_frontend/src/parser/parser/enums.rs @@ -0,0 +1,265 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Documented, EnumVariant, Ident, ItemVisibility, NoirEnumeration, UnresolvedGenerics}, + parser::ParserErrorReason, + token::{Attribute, SecondaryAttribute, Token}, +}; + +use super::{ + parse_many::{separated_by_comma_until_right_brace, separated_by_comma_until_right_paren}, + Parser, +}; + +impl<'a> Parser<'a> { + /// Enum = 'enum' identifier Generics '{' EnumVariant* '}' + /// + /// EnumField = OuterDocComments identifier ':' Type + pub(crate) fn parse_enum( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirEnumeration { + let attributes = self.validate_secondary_attributes(attributes); + + self.push_error(ParserErrorReason::ExperimentalFeature("Enums"), start_span); + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return self.empty_enum( + Ident::default(), + attributes, + visibility, + Vec::new(), + start_span, + ); + }; + + let generics = self.parse_generics(); + + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return self.empty_enum(name, attributes, visibility, generics, start_span); + } + + let comma_separated = separated_by_comma_until_right_brace(); + let variants = self.parse_many("enum variants", comma_separated, Self::parse_enum_variant); + + NoirEnumeration { + name, + attributes, + visibility, + generics, + variants, + span: self.span_since(start_span), + } + } + + fn parse_enum_variant(&mut self) -> Option> { + let mut doc_comments; + let name; + + // Loop until we find an identifier, skipping anything that's not one + loop { + let doc_comments_start_span = self.current_token_span; + doc_comments = self.parse_outer_doc_comments(); + + if let Some(ident) = self.eat_ident() { + name = ident; + break; + } + + if !doc_comments.is_empty() { + self.push_error( + ParserErrorReason::DocCommentDoesNotDocumentAnything, + self.span_since(doc_comments_start_span), + ); + } + + // Though we do have to stop at EOF + if self.at_eof() { + self.expected_token(Token::RightBrace); + return None; + } + + // Or if we find a right brace + if self.at(Token::RightBrace) { + return None; + } + + self.expected_identifier(); + self.bump(); + } + + let mut parameters = Vec::new(); + + if self.eat_left_paren() { + let comma_separated = separated_by_comma_until_right_paren(); + parameters = self.parse_many("variant parameters", comma_separated, Self::parse_type); + } + + Some(Documented::new(EnumVariant { name, parameters }, doc_comments)) + } + + fn empty_enum( + &self, + name: Ident, + attributes: Vec, + visibility: ItemVisibility, + generics: UnresolvedGenerics, + start_span: Span, + ) -> NoirEnumeration { + NoirEnumeration { + name, + attributes, + visibility, + generics, + variants: Vec::new(), + span: self.span_since(start_span), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{IntegerBitSize, NoirEnumeration, Signedness, UnresolvedGeneric, UnresolvedTypeData}, + parser::{ + parser::{ + parse_program, + tests::{expect_no_errors, get_source_with_error_span}, + }, + ItemKind, ParserErrorReason, + }, + }; + + fn parse_enum_no_errors(src: &str) -> NoirEnumeration { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Enum(noir_enum) = item.kind else { + panic!("Expected enum"); + }; + noir_enum + } + + #[test] + fn parse_empty_enum() { + let src = "enum Foo {}"; + let noir_enum = parse_enum_no_errors(src); + assert_eq!("Foo", noir_enum.name.to_string()); + assert!(noir_enum.variants.is_empty()); + assert!(noir_enum.generics.is_empty()); + } + + #[test] + fn parse_empty_enum_with_generics() { + let src = "enum Foo {}"; + let mut noir_enum = parse_enum_no_errors(src); + assert_eq!("Foo", noir_enum.name.to_string()); + assert!(noir_enum.variants.is_empty()); + assert_eq!(noir_enum.generics.len(), 2); + + let generic = noir_enum.generics.remove(0); + let UnresolvedGeneric::Variable(ident) = generic else { + panic!("Expected generic variable"); + }; + assert_eq!("A", ident.to_string()); + + let generic = noir_enum.generics.remove(0); + let UnresolvedGeneric::Numeric { ident, typ } = generic else { + panic!("Expected generic numeric"); + }; + assert_eq!("B", ident.to_string()); + assert_eq!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + ); + } + + #[test] + fn parse_enum_with_variants() { + let src = "enum Foo { X(i32), y(Field, u32), Z }"; + let mut noir_enum = parse_enum_no_errors(src); + assert_eq!("Foo", noir_enum.name.to_string()); + assert_eq!(noir_enum.variants.len(), 3); + + let variant = noir_enum.variants.remove(0).item; + assert_eq!("X", variant.name.to_string()); + assert!(matches!( + variant.parameters[0].typ, + UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + )); + + let variant = noir_enum.variants.remove(0).item; + assert_eq!("y", variant.name.to_string()); + assert!(matches!(variant.parameters[0].typ, UnresolvedTypeData::FieldElement)); + assert!(matches!(variant.parameters[1].typ, UnresolvedTypeData::Integer(..))); + + let variant = noir_enum.variants.remove(0).item; + assert_eq!("Z", variant.name.to_string()); + assert_eq!(variant.parameters.len(), 0); + } + + #[test] + fn parse_empty_enum_with_doc_comments() { + let src = "/// Hello\nenum Foo {}"; + let (module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + assert_eq!(item.doc_comments.len(), 1); + let ItemKind::Enum(noir_enum) = &item.kind else { + panic!("Expected enum"); + }; + assert_eq!("Foo", noir_enum.name.to_string()); + } + + #[test] + fn parse_unclosed_enum() { + let src = "enum Foo {"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 2); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Enum(noir_enum) = &item.kind else { + panic!("Expected enum"); + }; + assert_eq!("Foo", noir_enum.name.to_string()); + } + + #[test] + fn parse_error_no_function_attributes_allowed_on_enum() { + let src = " + #[test] enum Foo {} + ^^^^^^^ + "; + let (src, _) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = errors[0].reason().unwrap(); + assert!(matches!(reason, ParserErrorReason::NoFunctionAttributesAllowedOnType)); + } + + #[test] + fn recovers_on_non_field() { + let src = " + enum Foo { 42 X(i32) } + ^^ + "; + let (src, _) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Enum(noir_enum) = &item.kind else { + panic!("Expected enum"); + }; + assert_eq!("Foo", noir_enum.name.to_string()); + assert_eq!(noir_enum.variants.len(), 1); + + let error = &errors[1]; + assert_eq!(error.to_string(), "Expected an identifier but found '42'"); + } +} diff --git a/compiler/noirc_frontend/src/parser/parser/item.rs b/compiler/noirc_frontend/src/parser/parser/item.rs index ce712b559d8..d928d8e82d3 100644 --- a/compiler/noirc_frontend/src/parser/parser/item.rs +++ b/compiler/noirc_frontend/src/parser/parser/item.rs @@ -107,6 +107,7 @@ impl<'a> Parser<'a> { /// ( Use /// | ModOrContract /// | Struct + /// | Enum /// | Impl /// | Trait /// | Global @@ -148,6 +149,16 @@ impl<'a> Parser<'a> { ))]; } + if self.eat_keyword(Keyword::Enum) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return vec![ItemKind::Enum(self.parse_enum( + attributes, + modifiers.visibility, + start_span, + ))]; + } + if self.eat_keyword(Keyword::Impl) { self.comptime_mutable_and_unconstrained_not_applicable(modifiers); diff --git a/compiler/noirc_frontend/src/parser/parser/structs.rs b/compiler/noirc_frontend/src/parser/parser/structs.rs index da8ac64e021..b066565e680 100644 --- a/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -251,7 +251,7 @@ mod tests { let (src, span) = get_source_with_error_span(src); let (_, errors) = parse_program(&src); let reason = get_single_error_reason(&errors, span); - assert!(matches!(reason, ParserErrorReason::NoFunctionAttributesAllowedOnStruct)); + assert!(matches!(reason, ParserErrorReason::NoFunctionAttributesAllowedOnType)); } #[test] diff --git a/compiler/noirc_frontend/src/parser/parser/tests.rs b/compiler/noirc_frontend/src/parser/parser/tests.rs index ea8b1fc638d..7308458e948 100644 --- a/compiler/noirc_frontend/src/parser/parser/tests.rs +++ b/compiler/noirc_frontend/src/parser/parser/tests.rs @@ -44,7 +44,7 @@ pub(super) fn get_single_error_reason( } pub(super) fn expect_no_errors(errors: &[ParserError]) { - if errors.is_empty() { + if errors.is_empty() || errors.iter().all(|error| error.is_warning()) { return; } diff --git a/compiler/noirc_frontend/src/parser/parser/types.rs b/compiler/noirc_frontend/src/parser/parser/types.rs index 884db763698..f325c7e60ca 100644 --- a/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/compiler/noirc_frontend/src/parser/parser/types.rs @@ -208,6 +208,9 @@ impl<'a> Parser<'a> { if self.eat_keyword(Keyword::StructDefinition) { return Some(UnresolvedTypeData::Quoted(QuotedType::StructDefinition)); } + if self.eat_keyword(Keyword::EnumDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::EnumDefinition)); + } if self.eat_keyword(Keyword::TraitConstraint) { return Some(UnresolvedTypeData::Quoted(QuotedType::TraitConstraint)); } diff --git a/tooling/lsp/src/requests/completion/builtins.rs b/tooling/lsp/src/requests/completion/builtins.rs index 90b8c6301b7..10267d4719b 100644 --- a/tooling/lsp/src/requests/completion/builtins.rs +++ b/tooling/lsp/src/requests/completion/builtins.rs @@ -91,6 +91,9 @@ impl<'a> NodeFinder<'a> { AttributeTarget::Struct => { self.suggest_one_argument_attributes(prefix, &["abi"]); } + AttributeTarget::Enum => { + self.suggest_one_argument_attributes(prefix, &["abi"]); + } AttributeTarget::Function => { let no_arguments_attributes = &[ "contract_library_method", @@ -156,6 +159,7 @@ pub(super) fn keyword_builtin_type(keyword: &Keyword) -> Option<&'static str> { match keyword { Keyword::Bool => Some("bool"), Keyword::CtString => Some("CtString"), + Keyword::EnumDefinition => Some("EnumDefinition"), Keyword::Expr => Some("Expr"), Keyword::Field => Some("Field"), Keyword::FunctionDefinition => Some("FunctionDefinition"), @@ -247,6 +251,7 @@ pub(super) fn keyword_builtin_function(keyword: &Keyword) -> Option NodeFinder<'a> { match target { AttributeTarget::Module => Some(Type::Quoted(QuotedType::Module)), AttributeTarget::Struct => Some(Type::Quoted(QuotedType::StructDefinition)), + AttributeTarget::Enum => Some(Type::Quoted(QuotedType::EnumDefinition)), AttributeTarget::Trait => Some(Type::Quoted(QuotedType::TraitDefinition)), AttributeTarget::Function => Some(Type::Quoted(QuotedType::FunctionDefinition)), AttributeTarget::Let => { diff --git a/tooling/nargo_fmt/src/formatter.rs b/tooling/nargo_fmt/src/formatter.rs index 4184ff288d7..2a8adb3fb28 100644 --- a/tooling/nargo_fmt/src/formatter.rs +++ b/tooling/nargo_fmt/src/formatter.rs @@ -14,6 +14,7 @@ mod attribute; mod buffer; mod comments_and_whitespace; mod doc_comments; +mod enums; mod expression; mod function; mod generics; diff --git a/tooling/nargo_fmt/src/formatter/enums.rs b/tooling/nargo_fmt/src/formatter/enums.rs new file mode 100644 index 00000000000..b596ec95c94 --- /dev/null +++ b/tooling/nargo_fmt/src/formatter/enums.rs @@ -0,0 +1,202 @@ +use noirc_frontend::{ + ast::NoirEnumeration, + token::{Keyword, Token}, +}; + +use super::Formatter; +use crate::chunks::ChunkGroup; + +impl<'a> Formatter<'a> { + pub(super) fn format_enum(&mut self, noir_enum: NoirEnumeration) { + self.format_secondary_attributes(noir_enum.attributes); + self.write_indentation(); + self.format_item_visibility(noir_enum.visibility); + self.write_keyword(Keyword::Enum); + self.write_space(); + self.write_identifier(noir_enum.name); + self.format_generics(noir_enum.generics); + self.skip_comments_and_whitespace(); + + // A case like `enum Foo;` + if self.is_at(Token::Semicolon) { + self.write_semicolon(); + return; + } + + // A case like `enum Foo { ... }` + self.write_space(); + self.write_left_brace(); + + if noir_enum.variants.is_empty() { + self.format_empty_block_contents(); + } else { + self.increase_indentation(); + self.write_line(); + + for (index, documented_variant) in noir_enum.variants.into_iter().enumerate() { + if index > 0 { + self.write_comma(); + self.write_line(); + } + + let doc_comments = documented_variant.doc_comments; + if !doc_comments.is_empty() { + self.format_outer_doc_comments(); + } + + let variant = documented_variant.item; + self.write_indentation(); + self.write_identifier(variant.name); + + if !variant.parameters.is_empty() { + self.write_token(Token::LeftParen); + for (i, parameter) in variant.parameters.into_iter().enumerate() { + if i != 0 { + self.write_comma(); + self.write_space(); + } + self.format_type(parameter); + } + self.write_token(Token::RightParen); + } else { + // Remove `()` from an empty `Variant()` + self.skip_comments_and_whitespace(); + if self.is_at(Token::LeftParen) { + self.bump(); + } + self.skip_comments_and_whitespace(); + if self.is_at(Token::RightParen) { + self.bump(); + } + } + } + + // Take the comment chunk so we can put it after a trailing comma we add, in case there's no comma + let mut group = ChunkGroup::new(); + let mut comments_and_whitespace_chunk = + self.chunk_formatter().skip_comments_and_whitespace_chunk(); + comments_and_whitespace_chunk.string = + comments_and_whitespace_chunk.string.trim_end().to_string(); + group.text(comments_and_whitespace_chunk); + + if self.is_at(Token::Comma) { + self.bump(); + } + self.write(","); + + self.format_chunk_group(group); + self.skip_comments_and_whitespace(); + + self.decrease_indentation(); + self.write_line(); + self.write_indentation(); + } + + self.write_right_brace(); + } +} + +#[cfg(test)] +mod tests { + use crate::assert_format; + + #[test] + fn format_empty_enum_with_generics() { + let src = " mod moo { enum Foo < A, B, let N : u32 > {} }"; + let expected = "mod moo { + enum Foo {} +} +"; + assert_format(src, expected); + } + + #[test] + fn format_enum_with_variants() { + let src = " mod moo { enum Foo { +// hello +/// comment + Variant ( Field , i32 ) , + // comment + Another ( ), + } }"; + let expected = "mod moo { + enum Foo { + // hello + /// comment + Variant(Field, i32), + // comment + Another, + } +} +"; + assert_format(src, expected); + } + + #[test] + fn format_enum_with_multiple_newlines() { + let src = " mod moo { + + + enum Foo { + + +X( Field) , + + +Y ( Field ) + + +} + + +}"; + let expected = "mod moo { + + enum Foo { + + X(Field), + + Y(Field), + } + +} +"; + assert_format(src, expected); + } + + #[test] + fn format_two_enums() { + let src = " enum Foo { } enum Bar {} + "; + let expected = "enum Foo {} +enum Bar {} +"; + assert_format(src, expected); + } + + #[test] + fn format_enum_field_without_trailing_comma_but_comment() { + let src = "enum Foo { + field(Field) // comment + }"; + let expected = "enum Foo { + field(Field), // comment +} +"; + assert_format(src, expected); + } + + #[test] + fn format_comment_after_last_enum_field() { + let src = "enum Foo { + field(Field) + /* comment */ + }"; + let expected = "enum Foo { + field(Field), + /* comment */ +} +"; + assert_format(src, expected); + } +} diff --git a/tooling/nargo_fmt/src/formatter/item.rs b/tooling/nargo_fmt/src/formatter/item.rs index 3365e52ec29..499acb8415c 100644 --- a/tooling/nargo_fmt/src/formatter/item.rs +++ b/tooling/nargo_fmt/src/formatter/item.rs @@ -63,6 +63,7 @@ impl<'a> Formatter<'a> { false, // skip visibility ), ItemKind::Struct(noir_struct) => self.format_struct(noir_struct), + ItemKind::Enum(noir_enum) => self.format_enum(noir_enum), ItemKind::Trait(noir_trait) => self.format_trait(noir_trait), ItemKind::TraitImpl(noir_trait_impl) => self.format_trait_impl(noir_trait_impl), ItemKind::Impl(type_impl) => self.format_impl(type_impl),