From 7b3df8e8be11fe4288ed865951ef88566160f4af Mon Sep 17 00:00:00 2001 From: Maddiaa <47148561+Maddiaa0@users.noreply.github.com> Date: Thu, 21 Sep 2023 01:52:30 +0200 Subject: [PATCH] feat: add support for attributes on structs (#2733) Co-authored-by: kevaundray --- compiler/noirc_frontend/src/ast/function.rs | 20 +++---- compiler/noirc_frontend/src/ast/structure.rs | 6 +- .../noirc_frontend/src/hir/def_map/mod.rs | 6 +- .../src/hir/resolution/resolver.rs | 5 +- .../noirc_frontend/src/hir_def/function.rs | 2 +- compiler/noirc_frontend/src/lexer/lexer.rs | 20 ++++--- compiler/noirc_frontend/src/lexer/token.rs | 50 ++++++++--------- .../src/monomorphization/mod.rs | 10 ++-- compiler/noirc_frontend/src/parser/errors.rs | 6 +- compiler/noirc_frontend/src/parser/parser.rs | 56 +++++++++++++++---- .../primary_attribute_struct/Nargo.toml | 8 +++ .../primary_attribute_struct/src/main.nr | 8 +++ .../attributes_struct/Nargo.toml | 7 +++ .../attributes_struct/src/main.nr | 8 +++ 14 files changed, 143 insertions(+), 69 deletions(-) create mode 100644 tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/Nargo.toml create mode 100644 tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/src/main.nr create mode 100644 tooling/nargo_cli/tests/compile_success_empty/attributes_struct/Nargo.toml create mode 100644 tooling/nargo_cli/tests/compile_success_empty/attributes_struct/src/main.nr diff --git a/compiler/noirc_frontend/src/ast/function.rs b/compiler/noirc_frontend/src/ast/function.rs index 72ed1db6e3b..e16c0fcba15 100644 --- a/compiler/noirc_frontend/src/ast/function.rs +++ b/compiler/noirc_frontend/src/ast/function.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use noirc_errors::Span; use crate::{ - token::{Attributes, PrimaryAttribute, SecondaryAttribute}, + token::{Attributes, FunctionAttribute, SecondaryAttribute}, FunctionReturnType, Ident, Pattern, Visibility, }; @@ -65,8 +65,8 @@ impl NoirFunction { pub fn attributes(&self) -> &Attributes { &self.def.attributes } - pub fn primary_attribute(&self) -> Option<&PrimaryAttribute> { - self.def.attributes.primary.as_ref() + pub fn function_attribute(&self) -> Option<&FunctionAttribute> { + self.def.attributes.function.as_ref() } pub fn secondary_attributes(&self) -> &Vec { self.def.attributes.secondary.as_ref() @@ -89,19 +89,19 @@ impl NoirFunction { FunctionKind::LowLevel => {} _ => return None, } - assert!(self.primary_attribute().unwrap().is_foreign()); + assert!(self.function_attribute().unwrap().is_foreign()); Some(&self.def) } } impl From for NoirFunction { fn from(fd: FunctionDefinition) -> Self { - // The function type is determined by the existence of a primary attribute - let kind = match fd.attributes.primary { - Some(PrimaryAttribute::Builtin(_)) => FunctionKind::Builtin, - Some(PrimaryAttribute::Foreign(_)) => FunctionKind::LowLevel, - Some(PrimaryAttribute::Test { .. }) => FunctionKind::Normal, - Some(PrimaryAttribute::Oracle(_)) => FunctionKind::Oracle, + // The function type is determined by the existence of a function attribute + let kind = match fd.attributes.function { + Some(FunctionAttribute::Builtin(_)) => FunctionKind::Builtin, + Some(FunctionAttribute::Foreign(_)) => FunctionKind::LowLevel, + Some(FunctionAttribute::Test { .. }) => FunctionKind::Normal, + Some(FunctionAttribute::Oracle(_)) => FunctionKind::Oracle, None => FunctionKind::Normal, }; diff --git a/compiler/noirc_frontend/src/ast/structure.rs b/compiler/noirc_frontend/src/ast/structure.rs index 9b4c0153f52..6a32fa717f3 100644 --- a/compiler/noirc_frontend/src/ast/structure.rs +++ b/compiler/noirc_frontend/src/ast/structure.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::{Ident, UnresolvedGenerics, UnresolvedType}; +use crate::{token::SecondaryAttribute, Ident, UnresolvedGenerics, UnresolvedType}; use iter_extended::vecmap; use noirc_errors::Span; @@ -8,6 +8,7 @@ use noirc_errors::Span; #[derive(Clone, Debug, PartialEq, Eq)] pub struct NoirStruct { pub name: Ident, + pub attributes: Vec, pub generics: UnresolvedGenerics, pub fields: Vec<(Ident, UnresolvedType)>, pub span: Span, @@ -16,11 +17,12 @@ pub struct NoirStruct { impl NoirStruct { pub fn new( name: Ident, + attributes: Vec, generics: Vec, fields: Vec<(Ident, UnresolvedType)>, span: Span, ) -> NoirStruct { - NoirStruct { name, generics, fields, span } + NoirStruct { name, attributes, generics, fields, span } } } diff --git a/compiler/noirc_frontend/src/hir/def_map/mod.rs b/compiler/noirc_frontend/src/hir/def_map/mod.rs index 76db8ca753c..2c880e33242 100644 --- a/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -3,7 +3,7 @@ use crate::hir::def_collector::dc_crate::DefCollector; use crate::hir::Context; use crate::node_interner::{FuncId, NodeInterner}; use crate::parser::{parse_program, ParsedModule}; -use crate::token::{PrimaryAttribute, TestScope}; +use crate::token::{FunctionAttribute, TestScope}; use arena::{Arena, Index}; use fm::{FileId, FileManager}; use noirc_errors::{FileDiagnostic, Location}; @@ -140,8 +140,8 @@ impl CrateDefMap { module.value_definitions().filter_map(|id| { if let Some(func_id) = id.as_function() { let func_meta = interner.function_meta(&func_id); - match func_meta.attributes.primary { - Some(PrimaryAttribute::Test(scope)) => { + match func_meta.attributes.function { + Some(FunctionAttribute::Test(scope)) => { Some(TestFunction::new(func_id, scope, func_meta.name.location)) } _ => None, diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index d862c92c8e0..0ac60ce099b 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -17,8 +17,9 @@ use crate::hir_def::expr::{ HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }; + +use crate::token::FunctionAttribute; use crate::hir_def::traits::{Trait, TraitConstraint}; -use crate::token::PrimaryAttribute; use regex::Regex; use std::collections::{BTreeMap, HashSet}; use std::rc::Rc; @@ -748,7 +749,7 @@ impl<'a> Resolver<'a> { self.push_err(ResolverError::DistinctNotAllowed { ident: func.name_ident().clone() }); } - if matches!(attributes.primary, Some(PrimaryAttribute::Test { .. })) + if matches!(attributes.function, Some(FunctionAttribute::Test { .. })) && !parameters.is_empty() { self.push_err(ResolverError::TestFunctionHasParameters { diff --git a/compiler/noirc_frontend/src/hir_def/function.rs b/compiler/noirc_frontend/src/hir_def/function.rs index d5d3047c972..89f3c1745bc 100644 --- a/compiler/noirc_frontend/src/hir_def/function.rs +++ b/compiler/noirc_frontend/src/hir_def/function.rs @@ -101,7 +101,7 @@ pub struct FuncMeta { /// A function's attributes are the `#[...]` items above the function /// definition. - /// Primary Attributes will alter the function kind, secondary attributes do not + /// Function Attributes will alter the function kind, secondary attributes do not pub attributes: Attributes, /// This function's type in its contract. diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 5b6e010cc77..c32b956b716 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -1,6 +1,8 @@ +use crate::token::Attribute; + use super::{ errors::LexerErrorKind, - token::{Attribute, IntType, Keyword, SpannedToken, Token, Tokens}, + token::{IntType, Keyword, SpannedToken, Token, Tokens}, }; use acvm::FieldElement; use noirc_errors::{Position, Span}; @@ -411,7 +413,7 @@ impl<'a> Iterator for Lexer<'a> { #[cfg(test)] mod tests { use super::*; - use crate::token::{Attribute, PrimaryAttribute, SecondaryAttribute, TestScope}; + use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; #[test] fn test_single_double_char() { let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; @@ -499,9 +501,11 @@ mod tests { let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; let expected = vec![ - Token::Attribute(Attribute::Primary(PrimaryAttribute::Foreign("sha256".to_string()))), - Token::Attribute(Attribute::Primary(PrimaryAttribute::Foreign("blake2s".to_string()))), - Token::Attribute(Attribute::Primary(PrimaryAttribute::Builtin("sum".to_string()))), + Token::Attribute(Attribute::Function(FunctionAttribute::Foreign("sha256".to_string()))), + Token::Attribute(Attribute::Function(FunctionAttribute::Foreign( + "blake2s".to_string(), + ))), + Token::Attribute(Attribute::Function(FunctionAttribute::Builtin("sum".to_string()))), ]; let mut lexer = Lexer::new(input); @@ -533,7 +537,7 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test(TestScope::None))) + &Token::Attribute(Attribute::Function(FunctionAttribute::Test(TestScope::None))) ); } @@ -557,7 +561,7 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test( + &Token::Attribute(Attribute::Function(FunctionAttribute::Test( TestScope::ShouldFailWith { reason: None } ))) ); @@ -571,7 +575,7 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test( + &Token::Attribute(Attribute::Function(FunctionAttribute::Test( TestScope::ShouldFailWith { reason: Some("hello".to_owned()) } ))) ); diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index e99dcb72fb6..fb5ab220b2c 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -364,14 +364,14 @@ impl fmt::Display for TestScope { // Calls to functions which have the foreign attribute are executed in the host language pub struct Attributes { // Each function can have a single Primary Attribute - pub primary: Option, + pub function: Option, // Each function can have many Secondary Attributes pub secondary: Vec, } impl Attributes { pub fn empty() -> Self { - Self { primary: None, secondary: Vec::new() } + Self { function: None, secondary: Vec::new() } } /// Returns true if one of the secondary attributes is `contract_library_method` @@ -398,14 +398,14 @@ impl Attributes { /// A secondary attribute has no effect and is either consumed by a library or used as a notice for the developer #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] pub enum Attribute { - Primary(PrimaryAttribute), + Function(FunctionAttribute), Secondary(SecondaryAttribute), } impl fmt::Display for Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Attribute::Primary(attribute) => write!(f, "{}", attribute), + Attribute::Function(attribute) => write!(f, "{}", attribute), Attribute::Secondary(attribute) => write!(f, "{}", attribute), } } @@ -442,23 +442,23 @@ impl Attribute { // Primary Attributes ["foreign", name] => { validate(name)?; - Attribute::Primary(PrimaryAttribute::Foreign(name.to_string())) + Attribute::Function(FunctionAttribute::Foreign(name.to_string())) } ["builtin", name] => { validate(name)?; - Attribute::Primary(PrimaryAttribute::Builtin(name.to_string())) + Attribute::Function(FunctionAttribute::Builtin(name.to_string())) } ["oracle", name] => { validate(name)?; - Attribute::Primary(PrimaryAttribute::Oracle(name.to_string())) + Attribute::Function(FunctionAttribute::Oracle(name.to_string())) } - ["test"] => Attribute::Primary(PrimaryAttribute::Test(TestScope::None)), + ["test"] => Attribute::Function(FunctionAttribute::Test(TestScope::None)), ["test", name] => { validate(name)?; let malformed_scope = LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }; match TestScope::lookup_str(name) { - Some(scope) => Attribute::Primary(PrimaryAttribute::Test(scope)), + Some(scope) => Attribute::Function(FunctionAttribute::Test(scope)), None => return Err(malformed_scope), } } @@ -492,44 +492,44 @@ impl Attribute { /// Primary Attributes are those which a function can only have one of. /// They change the FunctionKind and thus have direct impact on the IR output #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] -pub enum PrimaryAttribute { +pub enum FunctionAttribute { Foreign(String), Builtin(String), Oracle(String), Test(TestScope), } -impl PrimaryAttribute { +impl FunctionAttribute { pub fn builtin(self) -> Option { match self { - PrimaryAttribute::Builtin(name) => Some(name), + FunctionAttribute::Builtin(name) => Some(name), _ => None, } } pub fn foreign(self) -> Option { match self { - PrimaryAttribute::Foreign(name) => Some(name), + FunctionAttribute::Foreign(name) => Some(name), _ => None, } } pub fn is_foreign(&self) -> bool { - matches!(self, PrimaryAttribute::Foreign(_)) + matches!(self, FunctionAttribute::Foreign(_)) } pub fn is_low_level(&self) -> bool { - matches!(self, PrimaryAttribute::Foreign(_) | PrimaryAttribute::Builtin(_)) + matches!(self, FunctionAttribute::Foreign(_) | FunctionAttribute::Builtin(_)) } } -impl fmt::Display for PrimaryAttribute { +impl fmt::Display for FunctionAttribute { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PrimaryAttribute::Test(scope) => write!(f, "#[test{}]", scope), - PrimaryAttribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), - PrimaryAttribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), - PrimaryAttribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), + FunctionAttribute::Test(scope) => write!(f, "#[test{}]", scope), + FunctionAttribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), + FunctionAttribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), + FunctionAttribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), } } } @@ -560,13 +560,13 @@ impl fmt::Display for SecondaryAttribute { } } -impl AsRef for PrimaryAttribute { +impl AsRef for FunctionAttribute { fn as_ref(&self) -> &str { match self { - PrimaryAttribute::Foreign(string) => string, - PrimaryAttribute::Builtin(string) => string, - PrimaryAttribute::Oracle(string) => string, - PrimaryAttribute::Test { .. } => "", + FunctionAttribute::Foreign(string) => string, + FunctionAttribute::Builtin(string) => string, + FunctionAttribute::Oracle(string) => string, + FunctionAttribute::Test { .. } => "", } } } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 2a7687731b9..d90d47645ea 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -21,7 +21,7 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId}, - token::PrimaryAttribute, + token::FunctionAttribute, ContractFunctionType, FunctionKind, Type, TypeBinding, TypeBindings, TypeVariableKind, Visibility, }; @@ -145,14 +145,14 @@ impl<'interner> Monomorphizer<'interner> { let meta = self.interner.function_meta(&id); match meta.kind { FunctionKind::LowLevel => { - let attribute = meta.attributes.primary.expect("all low level functions must contain a primary attribute which contains the opcode which it links to"); + let attribute = meta.attributes.function.expect("all low level functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.foreign().expect( "ice: function marked as foreign, but attribute kind does not match this", ); Definition::LowLevel(opcode) } FunctionKind::Builtin => { - let attribute = meta.attributes.primary.expect("all low level functions must contain a primary attribute which contains the opcode which it links to"); + let attribute = meta.attributes.function.expect("all low level functions must contain a primary attribute which contains the opcode which it links to"); let opcode = attribute.builtin().expect( "ice: function marked as builtin, but attribute kind does not match this", ); @@ -165,11 +165,11 @@ impl<'interner> Monomorphizer<'interner> { FunctionKind::Oracle => { let attr = meta .attributes - .primary + .function .expect("Oracle function must have an oracle attribute"); match attr { - PrimaryAttribute::Oracle(name) => Definition::Oracle(name), + FunctionAttribute::Oracle(name) => Definition::Oracle(name), _ => unreachable!("Oracle function must have an oracle attribute"), } } diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 4021503c0f2..3c5bc777186 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -34,9 +34,11 @@ pub enum ParserErrorReason { #[error("Where clauses are allowed only on functions with generic parameters")] WhereClauseOnNonGenericFunction, #[error( - "Multiple primary attributes found. Only one primary attribute is allowed per function." + "Multiple primary attributes found. Only one function attribute is allowed per function" )] - MultiplePrimaryAttributesFound, + MultipleFunctionAttributesFound, + #[error("A function attribute cannot be placed on a struct")] + NoFunctionAttributesAllowedOnStruct, #[error("Assert statements can only accept string literals")] AssertMessageNotString, } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 0ffb997291c..5f33606010b 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -35,7 +35,7 @@ use crate::ast::{ }; use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Attribute, Attributes, Keyword, Token, TokenKind}; +use crate::token::{Attribute, Attributes, Keyword, SecondaryAttribute, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, ConstrainStatement, Distinctness, FunctionDefinition, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, @@ -174,7 +174,7 @@ fn function_definition(allow_self: bool) -> impl NoirParser { .validate(|(((args, ret), where_clause), (body, body_span)), span, emit| { let ((((attributes, modifiers), name), generics), parameters) = args; - // Validate collected attributes, filtering them into primary and secondary variants + // Validate collected attributes, filtering them into function and secondary variants let attrs = validate_attributes(attributes, span, emit); validate_where_clause(&generics, &where_clause, span, emit); FunctionDefinition { @@ -237,11 +237,16 @@ fn struct_definition() -> impl NoirParser { ), ); - keyword(Struct).ignore_then(ident()).then(generics()).then(fields).map_with_span( - |((name, generics), fields), span| { - TopLevelStatement::Struct(NoirStruct { name, generics, fields, span }) - }, - ) + attributes() + .or_not() + .then_ignore(keyword(Struct)) + .then(ident()) + .then(generics()) + .then(fields) + .validate(|(((raw_attributes, name), generics), fields), span, emit| { + let attributes = validate_struct_attributes(raw_attributes, span, emit); + TopLevelStatement::Struct(NoirStruct { name, attributes, generics, fields, span }) + }) } fn type_alias_definition() -> impl NoirParser { @@ -441,10 +446,10 @@ fn validate_attributes( for attribute in attrs { match attribute { - Attribute::Primary(attr) => { + Attribute::Function(attr) => { if primary.is_some() { emit(ParserError::with_reason( - ParserErrorReason::MultiplePrimaryAttributesFound, + ParserErrorReason::MultipleFunctionAttributesFound, span, )); } @@ -454,7 +459,30 @@ fn validate_attributes( } } - Attributes { primary, secondary } + Attributes { function: primary, secondary } +} + +fn validate_struct_attributes( + attributes: Option>, + span: Span, + emit: &mut dyn FnMut(ParserError), +) -> Vec { + let attrs = attributes.unwrap_or_default(); + let mut struct_attributes = vec![]; + + for attribute in attrs { + match attribute { + Attribute::Function(..) => { + emit(ParserError::with_reason( + ParserErrorReason::NoFunctionAttributesAllowedOnStruct, + span, + )); + } + Attribute::Secondary(attr) => struct_attributes.push(attr), + } + } + + struct_attributes } fn validate_where_clause( @@ -2209,10 +2237,16 @@ mod test { "struct Foo { }", "struct Bar { ident: Field, }", "struct Baz { ident: Field, other: Field }", + "#[attribute] struct Baz { ident: Field, other: Field }", ]; parse_all(struct_definition(), cases); - let failing = vec!["struct { }", "struct Foo { bar: pub Field }"]; + let failing = vec![ + "struct { }", + "struct Foo { bar: pub Field }", + "struct Foo { bar: pub Field }", + "#[oracle(some)] struct Foo { bar: Field }", + ]; parse_all_failing(struct_definition(), failing); } diff --git a/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/Nargo.toml b/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/Nargo.toml new file mode 100644 index 00000000000..872c8f2030d --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/Nargo.toml @@ -0,0 +1,8 @@ + +[package] +name = "primary_attribute_struct" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] \ No newline at end of file diff --git a/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/src/main.nr b/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/src/main.nr new file mode 100644 index 00000000000..8922ef60091 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_failure/primary_attribute_struct/src/main.nr @@ -0,0 +1,8 @@ +// An primary attribute should not be able to be added to a struct defintion +#[oracle(some_oracle)] +struct SomeStruct{ + x: Field, + y: Field +} + +fn main() {} diff --git a/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/Nargo.toml b/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/Nargo.toml new file mode 100644 index 00000000000..0f00e7d9c73 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "attributes_struct" +type = "bin" +authors = [""] +compiler_version = "0.11.1" + +[dependencies] diff --git a/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/src/main.nr b/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/src/main.nr new file mode 100644 index 00000000000..0bad42aee57 --- /dev/null +++ b/tooling/nargo_cli/tests/compile_success_empty/attributes_struct/src/main.nr @@ -0,0 +1,8 @@ +#[some_attribute] +#[another_attribute] +struct SomeStruct { + a: Field, + b: Field +} + +fn main() {}