diff --git a/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/Nargo.toml b/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/Nargo.toml new file mode 100644 index 0000000000..7e699d4bbe --- /dev/null +++ b/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "multiple_primary_attributes_fail" +type = "bin" +authors = [""] +compiler_version = "0.9.0" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/src/main.nr b/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/src/main.nr new file mode 100644 index 0000000000..c8d8b0a196 --- /dev/null +++ b/crates/nargo_cli/tests/compile_failure/multiple_primary_attributes_fail/src/main.nr @@ -0,0 +1,6 @@ + +#[oracle(oracleName)] +#[builtin(builtinName)] +fn main(x: Field) -> pub Field { + x + 1 +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/Nargo.toml b/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/Nargo.toml new file mode 100644 index 0000000000..96b221d6c9 --- /dev/null +++ b/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "attributes_multiple" +type = "bin" +authors = [""] +compiler_version = "0.10.5" + +[dependencies] diff --git a/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/src/main.nr b/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/src/main.nr new file mode 100644 index 0000000000..46b761065f --- /dev/null +++ b/crates/nargo_cli/tests/compile_success_empty/attributes_multiple/src/main.nr @@ -0,0 +1,8 @@ + +fn main() { + another_func() +} + +#[aztec(private)] +#[internal] +fn another_func() {} \ No newline at end of file diff --git a/crates/noirc_frontend/src/ast/expression.rs b/crates/noirc_frontend/src/ast/expression.rs index 1a829bd0ce..9b695eb3e5 100644 --- a/crates/noirc_frontend/src/ast/expression.rs +++ b/crates/noirc_frontend/src/ast/expression.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use crate::token::{Attribute, Token}; +use crate::token::{Attributes, Token}; use crate::{ Distinctness, Ident, Path, Pattern, Recoverable, Statement, TraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, @@ -351,8 +351,9 @@ pub struct Lambda { pub struct FunctionDefinition { pub name: Ident, - // XXX: Currently we only have one attribute defined. If more attributes are needed per function, we can make this a vector and make attribute definition more expressive - pub attribute: Option, + // The `Attributes` container holds both `primary` (ones that change the function kind) + // and `secondary` attributes (ones that do not change the function kind) + pub attributes: Attributes, /// True if this function was defined with the 'open' keyword pub is_open: bool, @@ -643,7 +644,7 @@ impl FunctionDefinition { .collect(); FunctionDefinition { name: name.clone(), - attribute: None, + attributes: Attributes::empty(), is_open: false, is_internal: false, is_unconstrained: false, @@ -661,9 +662,7 @@ impl FunctionDefinition { impl Display for FunctionDefinition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(attribute) = &self.attribute { - writeln!(f, "{attribute}")?; - } + writeln!(f, "{:?}", self.attributes)?; let parameters = vecmap(&self.parameters, |(name, r#type, visibility)| { format!("{name}: {visibility} {type}") diff --git a/crates/noirc_frontend/src/ast/function.rs b/crates/noirc_frontend/src/ast/function.rs index 9f200f87bd..72ed1db6e3 100644 --- a/crates/noirc_frontend/src/ast/function.rs +++ b/crates/noirc_frontend/src/ast/function.rs @@ -2,7 +2,10 @@ use std::fmt::Display; use noirc_errors::Span; -use crate::{token::Attribute, FunctionReturnType, Ident, Pattern, Visibility}; +use crate::{ + token::{Attributes, PrimaryAttribute, SecondaryAttribute}, + FunctionReturnType, Ident, Pattern, Visibility, +}; use super::{FunctionDefinition, UnresolvedType, UnresolvedTypeData}; @@ -59,8 +62,14 @@ impl NoirFunction { pub fn parameters(&self) -> &Vec<(Pattern, UnresolvedType, Visibility)> { &self.def.parameters } - pub fn attribute(&self) -> Option<&Attribute> { - self.def.attribute.as_ref() + pub fn attributes(&self) -> &Attributes { + &self.def.attributes + } + pub fn primary_attribute(&self) -> Option<&PrimaryAttribute> { + self.def.attributes.primary.as_ref() + } + pub fn secondary_attributes(&self) -> &Vec { + self.def.attributes.secondary.as_ref() } pub fn def(&self) -> &FunctionDefinition { &self.def @@ -80,20 +89,20 @@ impl NoirFunction { FunctionKind::LowLevel => {} _ => return None, } - assert!(self.attribute().unwrap().is_foreign()); + assert!(self.primary_attribute().unwrap().is_foreign()); Some(&self.def) } } impl From for NoirFunction { fn from(fd: FunctionDefinition) -> Self { - let kind = match fd.attribute { - Some(Attribute::Builtin(_)) => FunctionKind::Builtin, - Some(Attribute::Foreign(_)) => FunctionKind::LowLevel, - Some(Attribute::Test { .. }) => FunctionKind::Normal, - Some(Attribute::Oracle(_)) => FunctionKind::Oracle, - Some(Attribute::Deprecated(_)) | None => FunctionKind::Normal, - Some(Attribute::Custom(_)) => FunctionKind::Normal, + // 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, + None => FunctionKind::Normal, }; NoirFunction { def: fd, kind } diff --git a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs index 0784fb467f..2679059ceb 100644 --- a/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/crates/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -6,8 +6,8 @@ use crate::{ hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, node_interner::TraitId, parser::SubModule, - FunctionDefinition, Ident, LetStatement, NoirFunction, NoirStruct, - NoirTrait, NoirTypeAlias, ParsedModule, TraitImpl, TraitImplItem, TraitItem, TypeImpl, + FunctionDefinition, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, + ParsedModule, TraitImpl, TraitImplItem, TraitItem, TypeImpl, }; use super::{ @@ -213,6 +213,7 @@ impl<'a> ModCollector<'a> { } for item in &trait_def.items { + // TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629 if let TraitItem::Function { name, generics, diff --git a/crates/noirc_frontend/src/hir/def_map/mod.rs b/crates/noirc_frontend/src/hir/def_map/mod.rs index c4eaecb6c4..4c7f241efa 100644 --- a/crates/noirc_frontend/src/hir/def_map/mod.rs +++ b/crates/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::{Attribute, TestScope}; +use crate::token::{PrimaryAttribute, TestScope}; use arena::{Arena, Index}; use fm::{FileId, FileManager}; use noirc_errors::{FileDiagnostic, Location}; @@ -139,8 +139,10 @@ impl CrateDefMap { self.modules.iter().flat_map(|(_, module)| { module.value_definitions().filter_map(|id| { if let Some(func_id) = id.as_function() { - match interner.function_meta(&func_id).attributes { - Some(Attribute::Test(scope)) => Some(TestFunction::new(func_id, scope)), + match interner.function_meta(&func_id).attributes.primary { + Some(PrimaryAttribute::Test(scope)) => { + Some(TestFunction::new(func_id, scope)) + } _ => None, } } else { diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 1ab4118099..411e91f2cf 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -17,7 +17,7 @@ use crate::hir_def::expr::{ HirIfExpression, HirIndexExpression, HirInfixExpression, HirLambda, HirLiteral, HirMemberAccess, HirMethodCallExpression, HirPrefixExpression, }; -use crate::token::Attribute; +use crate::token::PrimaryAttribute; use regex::Regex; use std::collections::{BTreeMap, HashSet}; use std::rc::Rc; @@ -678,7 +678,7 @@ impl<'a> Resolver<'a> { let id = self.interner.function_definition_id(func_id); let name_ident = HirIdent { id, location }; - let attributes = func.attribute().cloned(); + let attributes = func.attributes().clone(); let mut generics = vecmap(self.generics.clone(), |(name, typevar, _)| match &*typevar.borrow() { @@ -731,7 +731,9 @@ impl<'a> Resolver<'a> { self.push_err(ResolverError::DistinctNotAllowed { ident: func.name_ident().clone() }); } - if matches!(attributes, Some(Attribute::Test { .. })) && !parameters.is_empty() { + if matches!(attributes.primary, Some(PrimaryAttribute::Test { .. })) + && !parameters.is_empty() + { self.push_err(ResolverError::TestFunctionHasParameters { span: func.name_ident().span(), }); diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index e4e632835a..3b5d3758c4 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -11,7 +11,6 @@ use crate::{ types::Type, }, node_interner::{DefinitionKind, ExprId, FuncId}, - token::Attribute::Deprecated, Shared, Signedness, TypeBinding, TypeVariableKind, UnaryOp, }; @@ -26,7 +25,7 @@ impl<'interner> TypeChecker<'interner> { self.interner.try_definition(id).map(|def| &def.kind) { let meta = self.interner.function_meta(func_id); - if let Some(Deprecated(note)) = meta.attributes { + if let Some(note) = meta.attributes.get_deprecated_note() { self.errors.push(TypeCheckError::CallDeprecated { name: self.interner.definition_name(id).to_string(), note, diff --git a/crates/noirc_frontend/src/hir/type_check/mod.rs b/crates/noirc_frontend/src/hir/type_check/mod.rs index d17fbdc17d..ea1793b7e7 100644 --- a/crates/noirc_frontend/src/hir/type_check/mod.rs +++ b/crates/noirc_frontend/src/hir/type_check/mod.rs @@ -187,6 +187,7 @@ mod test { stmt::HirStatement, }; use crate::node_interner::{DefinitionKind, FuncId, NodeInterner}; + use crate::token::Attributes; use crate::{ hir::{ def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, @@ -257,7 +258,7 @@ mod test { name, kind: FunctionKind::Normal, module_id: ModuleId::dummy_id(), - attributes: None, + attributes: Attributes::empty(), location, contract_function_type: None, is_internal: None, diff --git a/crates/noirc_frontend/src/hir_def/function.rs b/crates/noirc_frontend/src/hir_def/function.rs index 5ef2e89d81..c552100c91 100644 --- a/crates/noirc_frontend/src/hir_def/function.rs +++ b/crates/noirc_frontend/src/hir_def/function.rs @@ -5,7 +5,7 @@ use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use crate::hir::def_map::ModuleId; use crate::node_interner::{ExprId, NodeInterner}; -use crate::{token::Attribute, FunctionKind}; +use crate::{token::Attributes, FunctionKind}; use crate::{ContractFunctionType, Distinctness, FunctionReturnType, Type, Visibility}; /// A Hir function is a block expression @@ -99,9 +99,9 @@ pub struct FuncMeta { pub module_id: ModuleId, /// A function's attributes are the `#[...]` items above the function - /// definition, if any. Currently, this is limited to a maximum of only one - /// Attribute per function. - pub attributes: Option, + /// definition. + /// Primary Attributes will alter the function kind, secondary attributes do not + pub attributes: Attributes, /// This function's type in its contract. /// If this function is not in a contract, this is always 'Secret'. diff --git a/crates/noirc_frontend/src/lexer/lexer.rs b/crates/noirc_frontend/src/lexer/lexer.rs index 27d073de35..f3fe0b6aef 100644 --- a/crates/noirc_frontend/src/lexer/lexer.rs +++ b/crates/noirc_frontend/src/lexer/lexer.rs @@ -411,7 +411,7 @@ impl<'a> Iterator for Lexer<'a> { #[cfg(test)] mod tests { use super::*; - use crate::token::TestScope; + use crate::token::{Attribute, PrimaryAttribute, SecondaryAttribute, TestScope}; #[test] fn test_single_double_char() { let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; @@ -474,7 +474,10 @@ mod tests { let mut lexer = Lexer::new(input); let token = lexer.next().unwrap().unwrap(); - assert_eq!(token.token(), &Token::Attribute(Attribute::Deprecated(None))); + assert_eq!( + token.token(), + &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Deprecated(None))) + ); } #[test] @@ -485,7 +488,9 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Deprecated("hello".to_string().into())) + &Token::Attribute(Attribute::Secondary(crate::token::SecondaryAttribute::Deprecated( + "hello".to_string().into() + ))) ); } @@ -494,9 +499,9 @@ mod tests { let input = "#[foreign(sha256)]#[foreign(blake2s)]#[builtin(sum)]"; let expected = vec![ - Token::Attribute(Attribute::Foreign("sha256".to_string())), - Token::Attribute(Attribute::Foreign("blake2s".to_string())), - Token::Attribute(Attribute::Builtin("sum".to_string())), + 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()))), ]; let mut lexer = Lexer::new(input); @@ -514,7 +519,9 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Custom("custom(hello)".to_string())) + &Token::Attribute(Attribute::Secondary(SecondaryAttribute::Custom( + "custom(hello)".to_string() + ))) ); } @@ -524,7 +531,10 @@ mod tests { let mut lexer = Lexer::new(input); let token = lexer.next().unwrap().unwrap(); - assert_eq!(token.token(), &Token::Attribute(Attribute::Test(TestScope::None))); + assert_eq!( + token.token(), + &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test(TestScope::None))) + ); } #[test] fn test_attribute_with_valid_scope() { @@ -534,7 +544,9 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Test(TestScope::ShouldFailWith { reason: None })) + &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test( + TestScope::ShouldFailWith { reason: None } + ))) ); } @@ -546,9 +558,9 @@ mod tests { let token = lexer.next().unwrap().unwrap(); assert_eq!( token.token(), - &Token::Attribute(Attribute::Test(TestScope::ShouldFailWith { - reason: Some("hello".to_owned()) - })) + &Token::Attribute(Attribute::Primary(PrimaryAttribute::Test( + TestScope::ShouldFailWith { reason: Some("hello".to_owned()) } + ))) ); } diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index d710f69a64..89b4429209 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -362,25 +362,41 @@ impl fmt::Display for TestScope { // Attributes are special language markers in the target language // An example of one is `#[SHA256]` . Currently only Foreign attributes are supported // 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, + // Each function can have many Secondary Attributes + pub secondary: Vec, +} + +impl Attributes { + pub fn empty() -> Self { + Self { primary: None, secondary: Vec::new() } + } + + /// Returns note if a deprecated secondary attribute is found + pub fn get_deprecated_note(&self) -> Option> { + self.secondary.iter().find_map(|attr| match attr { + SecondaryAttribute::Deprecated(note) => Some(note.clone()), + _ => None, + }) + } +} + +/// An Attribute can be either a Primary Attribute or a Secondary Attribute +/// A Primary Attribute can alter the function type, thus there can only be one +/// 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 { - Foreign(String), - Builtin(String), - Oracle(String), - Deprecated(Option), - Test(TestScope), - Custom(String), + Primary(PrimaryAttribute), + Secondary(SecondaryAttribute), } impl fmt::Display for Attribute { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Attribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), - Attribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), - Attribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), - Attribute::Test(scope) => write!(f, "#[test{}]", scope), - Attribute::Deprecated(None) => write!(f, "#[deprecated]"), - Attribute::Deprecated(Some(ref note)) => write!(f, r#"#[deprecated("{note}")]"#), - Attribute::Custom(ref k) => write!(f, "#[{k}]"), + Attribute::Primary(attribute) => write!(f, "{}", attribute), + Attribute::Secondary(attribute) => write!(f, "{}", attribute), } } } @@ -413,19 +429,31 @@ impl Attribute { }; let attribute = match &word_segments[..] { + // Primary Attributes ["foreign", name] => { validate(name)?; - Attribute::Foreign(name.to_string()) + Attribute::Primary(PrimaryAttribute::Foreign(name.to_string())) } ["builtin", name] => { validate(name)?; - Attribute::Builtin(name.to_string()) + Attribute::Primary(PrimaryAttribute::Builtin(name.to_string())) } ["oracle", name] => { validate(name)?; - Attribute::Oracle(name.to_string()) + Attribute::Primary(PrimaryAttribute::Oracle(name.to_string())) + } + ["test"] => Attribute::Primary(PrimaryAttribute::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)), + None => return Err(malformed_scope), + } } - ["deprecated"] => Attribute::Deprecated(None), + // Secondary attributes + ["deprecated"] => Attribute::Secondary(SecondaryAttribute::Deprecated(None)), ["deprecated", name] => { if !name.starts_with('"') && !name.ends_with('"') { return Err(LexerErrorKind::MalformedFuncAttribute { @@ -434,64 +462,107 @@ impl Attribute { }); } - Attribute::Deprecated(name.trim_matches('"').to_string().into()) - } - ["test"] => Attribute::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::Test(scope), - None => return Err(malformed_scope), - } + Attribute::Secondary(SecondaryAttribute::Deprecated( + name.trim_matches('"').to_string().into(), + )) } tokens => { tokens.iter().try_for_each(|token| validate(token))?; - Attribute::Custom(word.to_owned()) + Attribute::Secondary(SecondaryAttribute::Custom(word.to_owned())) } }; Ok(Token::Attribute(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 { + Foreign(String), + Builtin(String), + Oracle(String), + Test(TestScope), +} + +impl PrimaryAttribute { pub fn builtin(self) -> Option { match self { - Attribute::Builtin(name) => Some(name), + PrimaryAttribute::Builtin(name) => Some(name), _ => None, } } pub fn foreign(self) -> Option { match self { - Attribute::Foreign(name) => Some(name), + PrimaryAttribute::Foreign(name) => Some(name), _ => None, } } pub fn is_foreign(&self) -> bool { - matches!(self, Attribute::Foreign(_)) + matches!(self, PrimaryAttribute::Foreign(_)) } pub fn is_low_level(&self) -> bool { - matches!(self, Attribute::Foreign(_) | Attribute::Builtin(_)) + matches!(self, PrimaryAttribute::Foreign(_) | PrimaryAttribute::Builtin(_)) + } +} + +impl fmt::Display for PrimaryAttribute { + 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})]"), + } + } +} + +/// Secondary attributes are those which a function can have many of. +/// They are not able to change the `FunctionKind` and thus do not have direct impact on the IR output +/// They are often consumed by libraries or used as notices for the developer +#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] +pub enum SecondaryAttribute { + Deprecated(Option), + Custom(String), +} + +impl fmt::Display for SecondaryAttribute { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SecondaryAttribute::Deprecated(None) => write!(f, "#[deprecated]"), + SecondaryAttribute::Deprecated(Some(ref note)) => { + write!(f, r#"#[deprecated("{note}")]"#) + } + SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"), + } + } +} + +impl AsRef for PrimaryAttribute { + fn as_ref(&self) -> &str { + match self { + PrimaryAttribute::Foreign(string) => string, + PrimaryAttribute::Builtin(string) => string, + PrimaryAttribute::Oracle(string) => string, + PrimaryAttribute::Test { .. } => "", + } } } -impl AsRef for Attribute { +impl AsRef for SecondaryAttribute { fn as_ref(&self) -> &str { match self { - Attribute::Foreign(string) => string, - Attribute::Builtin(string) => string, - Attribute::Oracle(string) => string, - Attribute::Deprecated(Some(string)) => string, - Attribute::Test { .. } | Attribute::Deprecated(None) => "", - Attribute::Custom(string) => string, + SecondaryAttribute::Deprecated(Some(string)) => string, + SecondaryAttribute::Deprecated(None) => "", + SecondaryAttribute::Custom(string) => string, } } } -/// All keywords in Noir. /// Note that `self` is not present - it is a contextual keyword rather than a true one as it is /// only special within `impl`s. Otherwise `self` functions as a normal identifier. #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, PartialOrd, Ord)] diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 58dc619952..2a7687731b 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -21,7 +21,7 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId}, - token::Attribute, + token::PrimaryAttribute, 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.expect("all low level functions must contain an attribute which contains the opcode which it links to"); + let attribute = meta.attributes.primary.expect("all low level functions must contain a primary 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.expect("all low level functions must contain an attribute which contains the opcode which it links to"); + let attribute = meta.attributes.primary.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", ); @@ -163,10 +163,13 @@ impl<'interner> Monomorphizer<'interner> { Definition::Function(id) } FunctionKind::Oracle => { - let attr = - meta.attributes.expect("Oracle function must have an oracle attribute"); + let attr = meta + .attributes + .primary + .expect("Oracle function must have an oracle attribute"); + match attr { - Attribute::Oracle(name) => Definition::Oracle(name), + PrimaryAttribute::Oracle(name) => Definition::Oracle(name), _ => unreachable!("Oracle function must have an oracle attribute"), } } diff --git a/crates/noirc_frontend/src/parser/errors.rs b/crates/noirc_frontend/src/parser/errors.rs index 80832abb3d..45832ce39d 100644 --- a/crates/noirc_frontend/src/parser/errors.rs +++ b/crates/noirc_frontend/src/parser/errors.rs @@ -31,6 +31,10 @@ pub enum ParserErrorReason { ExperimentalFeature(&'static str), #[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." + )] + MultiplePrimaryAttributesFound, #[error("Assert statements can only accept string literals")] AssertMessageNotString, } diff --git a/crates/noirc_frontend/src/parser/parser.rs b/crates/noirc_frontend/src/parser/parser.rs index 84f1ab0da8..34aa0ccb07 100644 --- a/crates/noirc_frontend/src/parser/parser.rs +++ b/crates/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, Keyword, Token, TokenKind}; +use crate::token::{Attribute, Attributes, Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, ConstrainStatement, Distinctness, FunctionDefinition, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Lambda, Literal, @@ -161,7 +161,7 @@ fn contract(module_parser: impl NoirParser) -> impl NoirParser impl NoirParser { - attribute() + attributes() .or_not() .then(function_modifiers()) .then_ignore(keyword(Keyword::Fn)) @@ -172,12 +172,15 @@ fn function_definition(allow_self: bool) -> impl NoirParser { .then(where_clause()) .then(spanned(block(expression()))) .validate(|(((args, ret), where_clause), (body, body_span)), span, emit| { - let ((((attribute, modifiers), name), generics), parameters) = args; + let ((((attributes, modifiers), name), generics), parameters) = args; + + // Validate collected attributes, filtering them into primary and secondary variants + let attrs = validate_attributes(attributes, span, emit); validate_where_clause(&generics, &where_clause, span, emit); FunctionDefinition { span: body_span, name, - attribute, // XXX: Currently we only have one attribute defined. If more attributes are needed per function, we can make this a vector and make attribute definition more expressive + attributes: attrs, is_unconstrained: modifiers.0, is_open: modifiers.1, is_internal: modifiers.2, @@ -283,6 +286,10 @@ fn attribute() -> impl NoirParser { }) } +fn attributes() -> impl NoirParser> { + attribute().repeated() +} + fn struct_fields() -> impl NoirParser> { ident() .then_ignore(just(Token::Colon)) @@ -418,6 +425,38 @@ fn trait_function_declaration() -> impl NoirParser { ) } +fn validate_attributes( + attributes: Option>, + span: Span, + emit: &mut dyn FnMut(ParserError), +) -> Attributes { + if attributes.is_none() { + return Attributes::empty(); + } + + let attrs = attributes.unwrap(); + + let mut primary = None; + let mut secondary = Vec::new(); + + for attribute in attrs { + match attribute { + Attribute::Primary(attr) => { + if primary.is_some() { + emit(ParserError::with_reason( + ParserErrorReason::MultiplePrimaryAttributesFound, + span, + )); + } + primary = Some(attr); + } + Attribute::Secondary(attr) => secondary.push(attr), + } + } + + Attributes { primary, secondary } +} + fn validate_where_clause( generics: &Vec, where_clause: &Vec,