Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support cfg(feature = "name") attributes #7097

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/elaborator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::{
DefinitionKind, DependencyId, ExprId, FuncId, FunctionModifiers, GlobalId, NodeInterner,
ReferenceId, TraitId, TraitImplId, TypeAliasId, TypeId,
},
token::SecondaryAttribute,
token::{CfgAttribute, SecondaryAttribute},
Shared, Type, TypeVariable,
};
use crate::{
Expand Down Expand Up @@ -2003,4 +2003,9 @@ impl<'context> Elaborator<'context> {
_ => true,
})
}

pub(crate) fn is_cfg_attribute_enabled(&self, _opt_cfg_attribute: Option<CfgAttribute>) -> bool {
// TODO
true
}
}
20 changes: 13 additions & 7 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,14 +797,20 @@ fn quoted_as_expr(
);

let value =
result.ok().map(
|statement_or_expression_or_lvalue| match statement_or_expression_or_lvalue {
StatementOrExpressionOrLValue::Expression(expr) => Value::expression(expr.kind),
StatementOrExpressionOrLValue::Statement(statement) => {
Value::statement(statement.kind)
result.ok().and_then(
|(statement_or_expression_or_lvalue, opt_cfg_attribute)| {
if elaborator.is_cfg_attribute_enabled(opt_cfg_attribute) {
Some(match statement_or_expression_or_lvalue {
StatementOrExpressionOrLValue::Expression(expr) => Value::expression(expr.kind),
StatementOrExpressionOrLValue::Statement(statement) => {
Value::statement(statement.kind)
}
StatementOrExpressionOrLValue::LValue(lvalue) => Value::lvalue(lvalue),
})
} else {
None
}
StatementOrExpressionOrLValue::LValue(lvalue) => Value::lvalue(lvalue),
},
}
);

option(return_type, value, location.span)
Expand Down
68 changes: 68 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,15 @@ impl fmt::Display for Attribute {
}
}

impl Attribute {
pub(crate) fn is_disabled_cfg(&self) -> bool {
match self {
Attribute::Function(_) => false,
Attribute::Secondary(secondary) => secondary.is_disabled_cfg(),
}
}
}

/// 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)]
Expand Down Expand Up @@ -904,6 +913,9 @@ pub enum SecondaryAttribute {

/// Allow chosen warnings to happen so they are silenced.
Allow(String),

// A #[cfg(..)] attribute
Cfg(CfgAttribute),
}

impl SecondaryAttribute {
Expand All @@ -921,6 +933,7 @@ impl SecondaryAttribute {
SecondaryAttribute::Varargs => Some("varargs".to_string()),
SecondaryAttribute::UseCallersScope => Some("use_callers_scope".to_string()),
SecondaryAttribute::Allow(_) => Some("allow".to_string()),
SecondaryAttribute::Cfg(_) => Some("cfg".to_string()),
}
}

Expand All @@ -935,6 +948,22 @@ impl SecondaryAttribute {
matches!(self, SecondaryAttribute::Abi(_))
}

pub(crate) fn is_disabled_cfg(&self) -> bool {
match self {
SecondaryAttribute::Deprecated(..) => false,
SecondaryAttribute::Tag(..) => false,
SecondaryAttribute::Meta(..) => false,
SecondaryAttribute::ContractLibraryMethod => false,
SecondaryAttribute::Export => false,
SecondaryAttribute::Field(..) => false,
SecondaryAttribute::Abi(..) => false,
SecondaryAttribute::Varargs => false,
SecondaryAttribute::UseCallersScope => false,
SecondaryAttribute::Allow(..) => false,
SecondaryAttribute::Cfg(ref cfg_attribute) => cfg_attribute.is_disabled(),
}
}

pub(crate) fn contents(&self) -> String {
match self {
SecondaryAttribute::Deprecated(None) => "deprecated".to_string(),
Expand All @@ -950,6 +979,7 @@ impl SecondaryAttribute {
SecondaryAttribute::Varargs => "varargs".to_string(),
SecondaryAttribute::UseCallersScope => "use_callers_scope".to_string(),
SecondaryAttribute::Allow(ref k) => format!("allow({k})"),
SecondaryAttribute::Cfg(ref cfg_attribute) => format!("cfg({cfg_attribute})"),
}
}
}
Expand Down Expand Up @@ -979,6 +1009,38 @@ impl Display for MetaAttribute {
}
}

#[derive(PartialEq, Eq, Debug, Clone)]
pub enum CfgAttribute {
// feature = "{name}"
Feature {
name: String,
span: Span,
},
}

impl Display for CfgAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CfgAttribute::Feature { name, span: _ } => {
write!(f, "feature = {:?}", name)
}
}
}
}

// TODO: enable more features once working
impl CfgAttribute {
pub(crate) fn is_disabled(&self) -> bool {
match self {
CfgAttribute::Feature { name, .. } => {
name != &"default"
}
}
}

}


#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
pub struct CustomAttribute {
pub contents: String,
Expand Down Expand Up @@ -1010,6 +1072,7 @@ pub enum Keyword {
Bool,
Break,
CallData,
Cfg,
Char,
Comptime,
Constrain,
Expand All @@ -1022,6 +1085,7 @@ pub enum Keyword {
Enum,
EnumDefinition,
Expr,
Feature,
Field,
Fn,
For,
Expand Down Expand Up @@ -1070,6 +1134,7 @@ impl fmt::Display for Keyword {
Keyword::AssertEq => write!(f, "assert_eq"),
Keyword::Bool => write!(f, "bool"),
Keyword::Break => write!(f, "break"),
Keyword::Cfg => write!(f, "cfg"),
Keyword::Char => write!(f, "char"),
Keyword::CallData => write!(f, "call_data"),
Keyword::Comptime => write!(f, "comptime"),
Expand All @@ -1083,6 +1148,7 @@ impl fmt::Display for Keyword {
Keyword::Enum => write!(f, "enum"),
Keyword::EnumDefinition => write!(f, "EnumDefinition"),
Keyword::Expr => write!(f, "Expr"),
Keyword::Feature => write!(f, "feature"),
Keyword::Field => write!(f, "Field"),
Keyword::Fn => write!(f, "fn"),
Keyword::For => write!(f, "for"),
Expand Down Expand Up @@ -1135,6 +1201,7 @@ impl Keyword {
"bool" => Keyword::Bool,
"break" => Keyword::Break,
"call_data" => Keyword::CallData,
"cfg" => Keyword::Cfg,
"char" => Keyword::Char,
"comptime" => Keyword::Comptime,
"constrain" => Keyword::Constrain,
Expand All @@ -1147,6 +1214,7 @@ impl Keyword {
"enum" => Keyword::Enum,
"EnumDefinition" => Keyword::EnumDefinition,
"Expr" => Keyword::Expr,
"feature" => Keyword::Feature,
"Field" => Keyword::Field,
"fn" => Keyword::Fn,
"for" => Keyword::For,
Expand Down
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/parser/errors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::ast::{Expression, IntegerBitSize, ItemVisibility};
use crate::lexer::errors::LexerErrorKind;
use crate::lexer::token::Token;
use crate::lexer::token::{CfgAttribute, Token};
use crate::token::TokenKind;
use small_ord_set::SmallOrdSet;
use thiserror::Error;
Expand Down Expand Up @@ -79,6 +79,11 @@ pub enum ParserErrorReason {
ComptimeDeprecated,
#[error("{0} are experimental and aren't fully supported yet")]
ExperimentalFeature(&'static str),
#[error("Only one 'cfg' attribute is allowed, but found {cfg_attribute} and {second_cfg_attribute}")]
MultipleCfgAttributesFound {
cfg_attribute: CfgAttribute,
second_cfg_attribute: CfgAttribute,
},
#[error(
"Multiple primary attributes found. Only one function attribute is allowed per function"
)]
Expand Down
7 changes: 6 additions & 1 deletion compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use noirc_errors::Span;
use crate::{
ast::{Ident, ItemVisibility},
lexer::{Lexer, SpannedTokenResult},
token::{FmtStrFragment, IntType, Keyword, SpannedToken, Token, TokenKind, Tokens},
token::{CfgAttribute, FmtStrFragment, IntType, Keyword, SpannedToken, Token, TokenKind, Tokens},
};

use super::{labels::ParsingRuleLabel, ParsedModule, ParserError, ParserErrorReason};
Expand Down Expand Up @@ -571,6 +571,11 @@ impl<'a> Parser<'a> {
fn push_error(&mut self, reason: ParserErrorReason, span: Span) {
self.errors.push(ParserError::with_reason(reason, span));
}

pub(crate) fn is_enabled_cfg(&self, _opt_cfg_attribute: Option<CfgAttribute>) -> bool {
// TODO
true
}
}

fn eof_spanned_token() -> SpannedToken {
Expand Down
79 changes: 73 additions & 6 deletions compiler/noirc_frontend/src/parser/parser/attributes.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use noirc_errors::Span;

use crate::ast::{Expression, ExpressionKind, Ident, Literal, Path};
use crate::lexer::token::Keyword;
use crate::lexer::errors::LexerErrorKind;
use crate::parser::labels::ParsingRuleLabel;
use crate::parser::ParserErrorReason;
use crate::token::{Attribute, FunctionAttribute, MetaAttribute, TestScope, Token};
use crate::token::{Attribute, CfgAttribute, FunctionAttribute, MetaAttribute, TestScope, Token};
use crate::token::{CustomAttribute, SecondaryAttribute};

use super::parse_many::without_separator;
Expand Down Expand Up @@ -60,13 +61,17 @@ impl<'a> Parser<'a> {
/// | 'allow' '(' AttributeValue ')'
/// | 'deprecated'
/// | 'deprecated' '(' string ')'
/// | 'cfg' '(' CfgAttribute ')'
/// | 'contract_library_method'
/// | 'export'
/// | 'field' '(' AttributeValue ')'
/// | 'use_callers_scope'
/// | 'varargs'
/// | MetaAttribute
///
/// CfgAttribute
/// = Arguments
///
/// MetaAttribute
/// = Path Arguments?
///
Expand Down Expand Up @@ -134,10 +139,16 @@ impl<'a> Parser<'a> {
if matches!(&self.token.token(), Token::Keyword(..))
&& (self.next_is(Token::LeftParen) || self.next_is(Token::RightBracket))
{
// This is a Meta attribute with the syntax `keyword(arg1, arg2, .., argN)`
let path = Path::from_single(self.token.to_string(), self.current_token_span);
self.bump();
self.parse_meta_attribute(path, start_span)
if self.token.token() == &Token::Keyword(Keyword::Cfg) {
// This is a Cfg attribute with the syntax `cfg(arg1)`
self.bump();
self.parse_cfg_attribute(start_span)
} else {
// This is a Meta attribute with the syntax `keyword(arg1, arg2, .., argN)`
let path = Path::from_single(self.token.to_string(), self.current_token_span);
self.bump();
self.parse_meta_attribute(path, start_span)
}
} else if let Some(path) = self.parse_path_no_turbofish() {
if let Some(ident) = path.as_ident() {
if ident.0.contents == "test" {
Expand All @@ -148,6 +159,9 @@ impl<'a> Parser<'a> {
// Every other attribute has the form `name(arg1, arg2, .., argN)`
self.parse_ident_attribute_other_than_test(ident, start_span)
}
} else if path.to_string() == "cfg" {
// This is a Cfg attribute with the syntax `cfg(arg1)`
self.parse_cfg_attribute(start_span)
} else {
// This is a Meta attribute with the syntax `path(arg1, arg2, .., argN)`
self.parse_meta_attribute(path, start_span)
Expand All @@ -158,6 +172,34 @@ impl<'a> Parser<'a> {
}
}

fn parse_cfg_attribute(&mut self, start_span: Span) -> Attribute {
// `[cfg` <- already parsed
// `(feature = "{str}"`
self.eat_or_error(Token::LeftParen);
self.eat_or_error(Token::Keyword(Keyword::Feature));
self.eat_or_error(Token::Assign);
let name = self.eat_str().unwrap_or_else(|| {
self.push_error(
ParserErrorReason::WrongNumberOfAttributeArguments {
name: "cfg".to_string(),
min: 1,
max: 1,
found: 0,
},
self.span_since(start_span),
);
String::new()
});

// `)]`
self.eat_or_error(Token::RightParen);
self.eat_or_error(Token::RightBracket);
Attribute::Secondary(SecondaryAttribute::Cfg(CfgAttribute::Feature {
name,
span: self.span_since(start_span),
}))
}

fn parse_meta_attribute(&mut self, name: Path, start_span: Span) -> Attribute {
let arguments = self.parse_arguments().unwrap_or_default();
self.skip_until_right_bracket();
Expand Down Expand Up @@ -389,7 +431,7 @@ mod tests {

use crate::{
parser::{parser::tests::expect_no_errors, Parser},
token::{Attribute, FunctionAttribute, SecondaryAttribute, TestScope},
token::{Attribute, CfgAttribute, FunctionAttribute, SecondaryAttribute, TestScope},
};

fn parse_inner_secondary_attribute_no_errors(src: &str, expected: SecondaryAttribute) {
Expand Down Expand Up @@ -621,6 +663,31 @@ mod tests {
assert_eq!(meta.arguments[0].to_string(), "1");
}

#[test]
fn parses_cfg_feature() {
let src = "#[cfg(feature = \"foo\")]";
let mut parser = Parser::for_str(src);
let (attribute, _span) = parser.parse_attribute().unwrap();
expect_no_errors(&parser.errors);
let Attribute::Secondary(SecondaryAttribute::Cfg(cfg_attribute)) = attribute else {
panic!("Expected cfg attribute");
};
let CfgAttribute::Feature { name: feature_name, span: _ } = cfg_attribute;
assert_eq!(feature_name, "foo");
}

#[test]
fn parsing_cfg_feature_requires_nonempty_string() {
let src = "#[cfg(feature = \"\")]";
let mut parser = Parser::for_str(src);
let (attribute, _span) = parser.parse_attribute().unwrap();
match attribute {
Attribute::Secondary(SecondaryAttribute::Cfg(CfgAttribute::Feature { name, .. })) => assert_eq!(name, String::new()),
other => panic!("expected an CfgAttribute::Feature, but found {other:?}"),
}
expect_no_errors(&parser.errors);
}

#[test]
fn parses_attributes() {
let src = "#[test] #[deprecated]";
Expand Down
7 changes: 5 additions & 2 deletions compiler/noirc_frontend/src/parser/parser/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,8 +770,11 @@ impl<'a> Parser<'a> {
}

fn parse_statement_in_block(&mut self) -> Option<(Statement, (Option<Token>, Span))> {
if let Some(statement) = self.parse_statement() {
Some(statement)
if let Some((statement, semicolon, opt_cfg_attribute)) = self.parse_statement() {
if !self.is_enabled_cfg(opt_cfg_attribute) {
return None;
}
Some((statement, semicolon))
} else {
self.expected_label(ParsingRuleLabel::Statement);
None
Expand Down
Loading