Skip to content

Commit

Permalink
Improve attribute parsing (FuelLabs#4134)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlicanC authored Mar 13, 2023
1 parent 2099b9a commit 7f04334
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 38 deletions.
2 changes: 1 addition & 1 deletion forc-pkg/src/pkg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1896,7 +1896,7 @@ impl PkgTestEntry {
.get(&AttributeKind::Test)
.expect("test declaration is missing test attribute")
.iter()
.flat_map(|attr| attr.args.iter().map(|arg| arg.to_string()))
.flat_map(|attr| attr.args.iter().map(|arg| arg.name.to_string()))
.collect();

let pass_condition = if test_args.is_empty() {
Expand Down
7 changes: 4 additions & 3 deletions forc-plugins/forc-doc/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,9 +1559,10 @@ impl DocStrings for AttributesMap {
let mut docs = String::new();

if let Some(vec_attrs) = attributes {
for ident in vec_attrs.iter().flat_map(|attribute| &attribute.args) {
writeln!(docs, "{}", ident.as_str())
.expect("problem appending `ident.as_str()` to `docs` with `writeln` macro.");
for arg in vec_attrs.iter().flat_map(|attribute| &attribute.args) {
writeln!(docs, "{}", arg.name.as_str()).expect(
"problem appending `arg.name.as_str()` to `docs` with `writeln` macro.",
);
}
}
docs
Expand Down
18 changes: 17 additions & 1 deletion sway-ast/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,26 @@ pub enum AttributeHashKind {
Outer(HashToken),
}

#[derive(Clone, Debug, Serialize)]
pub struct AttributeArg {
pub name: Ident,
pub value: Option<Literal>,
}

impl Spanned for AttributeArg {
fn span(&self) -> Span {
if let Some(value) = &self.value {
Span::join(self.name.span(), value.span())
} else {
self.name.span()
}
}
}

#[derive(Clone, Debug, Serialize)]
pub struct Attribute {
pub name: Ident,
pub args: Option<Parens<Punctuated<Ident, CommaToken>>>,
pub args: Option<Parens<Punctuated<AttributeArg, CommaToken>>>,
}

impl Spanned for Attribute {
Expand Down
2 changes: 1 addition & 1 deletion sway-core/src/abi_generation/fuel_json_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,7 @@ fn generate_json_abi_attributes_map(
.flat_map(|(_attr_kind, attrs)| {
attrs.iter().map(|attr| program_abi::Attribute {
name: attr.name.to_string(),
arguments: attr.args.iter().map(|arg| arg.to_string()).collect(),
arguments: attr.args.iter().map(|arg| arg.name.to_string()).collect(),
})
})
.collect(),
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/control_flow_analysis/dead_code_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,7 @@ fn allow_dead_code(attributes: AttributesMap) -> bool {
.last()?
.args
.first()?
.name
.as_str()
== ALLOW_DEAD_CODE_NAME,
)
Expand Down
1 change: 1 addition & 0 deletions sway-core/src/language/ty/declaration/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ impl TyFunctionDeclaration {
.last()?
.args
.first()?
.name
.as_str()
{
INLINE_NEVER_NAME => Some(Inline::Never),
Expand Down
15 changes: 13 additions & 2 deletions sway-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use sway_ir::{
MODULEPRINTER_NAME,
};
use sway_types::constants::DOC_COMMENT_ATTRIBUTE_NAME;
use transform::{Attribute, AttributeKind, AttributesMap};
use transform::{Attribute, AttributeArg, AttributeKind, AttributesMap};

pub use semantic_analysis::namespace::{self, Namespace};
pub mod types;
Expand Down Expand Up @@ -121,7 +121,18 @@ fn module_attrs_to_map(
let args = attr
.args
.as_ref()
.map(|parens| parens.get().into_iter().cloned().collect())
.map(|parens| {
parens
.get()
.into_iter()
.cloned()
.map(|arg| AttributeArg {
name: arg.name.clone(),
value: arg.value.clone(),
span: arg.span(),
})
.collect()
})
.unwrap_or_else(Vec::new);

let attribute = Attribute {
Expand Down
18 changes: 16 additions & 2 deletions sway-core/src/transform/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,31 @@
//!
//! #[foo(bar, bar)]
use sway_types::{constants::ALLOW_DEAD_CODE_NAME, Ident, Span};
use sway_ast::Literal;
use sway_types::{constants::ALLOW_DEAD_CODE_NAME, Ident, Span, Spanned};

use std::{collections::HashMap, hash::Hash, sync::Arc};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AttributeArg {
pub name: Ident,
pub value: Option<Literal>,
pub span: Span,
}

impl Spanned for AttributeArg {
fn span(&self) -> Span {
self.span.clone()
}
}

/// An attribute has a name (i.e "doc", "storage"),
/// a vector of possible arguments and
/// a span from its declaration.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Attribute {
pub name: Ident,
pub args: Vec<Ident>,
pub args: Vec<AttributeArg>,
pub span: Span,
}

Expand Down
19 changes: 15 additions & 4 deletions sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ fn get_attributed_purity(
match attributes.get(&AttributeKind::Storage) {
Some(attrs) if !attrs.is_empty() => {
for arg in attrs.iter().flat_map(|attr| &attr.args) {
match arg.as_str() {
match arg.name.as_str() {
STORAGE_PURITY_READ_NAME => add_impurity(Purity::Reads, Purity::Writes),
STORAGE_PURITY_WRITE_NAME => add_impurity(Purity::Writes, Purity::Reads),
_otherwise => {
Expand Down Expand Up @@ -3704,7 +3704,18 @@ fn item_attrs_to_map(
let args = attr
.args
.as_ref()
.map(|parens| parens.get().into_iter().cloned().collect())
.map(|parens| {
parens
.get()
.into_iter()
.cloned()
.map(|arg| AttributeArg {
name: arg.name.clone(),
value: arg.value.clone(),
span: arg.span(),
})
.collect()
})
.unwrap_or_else(Vec::new);

let attribute = Attribute {
Expand Down Expand Up @@ -3759,12 +3770,12 @@ fn item_attrs_to_map(
for (index, arg) in attribute.args.iter().enumerate() {
let possible_values = attribute_kind.clone().expected_args_values(index);
if let Some(possible_values) = possible_values {
if !possible_values.iter().any(|v| v == arg.as_str()) {
if !possible_values.iter().any(|v| v == arg.name.as_str()) {
handler.emit_warn(CompileWarning {
span: attribute.name.span().clone(),
warning_content: Warning::UnexpectedAttributeArgumentValue {
attrib_name: attribute.name.clone(),
received_value: arg.as_str().to_string(),
received_value: arg.name.as_str().to_string(),
expected_values: possible_values,
},
})
Expand Down
2 changes: 1 addition & 1 deletion sway-lsp/src/capabilities/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn format_doc_attributes(token: &Token) -> String {
doc_comment = attributes
.iter()
.map(|attribute| {
let comment = attribute.args.first().unwrap().as_str();
let comment = attribute.args.first().unwrap().name.as_str();
format!("{comment}\n")
})
.collect()
Expand Down
91 changes: 81 additions & 10 deletions sway-parse/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::priv_prelude::{Peek, Peeker};
use crate::{Parse, ParseBracket, ParseResult, ParseToEnd, Parser, ParserConsumed};

use sway_ast::attribute::{Annotated, Attribute, AttributeDecl, AttributeHashKind};
use sway_ast::attribute::{Annotated, Attribute, AttributeArg, AttributeDecl, AttributeHashKind};
use sway_ast::brackets::{Parens, SquareBrackets};
use sway_ast::keywords::{HashBangToken, HashToken, StorageToken, Token};
use sway_ast::keywords::{EqToken, HashBangToken, HashToken, StorageToken, Token};
use sway_ast::punctuated::Punctuated;
use sway_ast::token::{DocComment, DocStyle};
use sway_error::parser_error::ParseErrorKind;
Expand Down Expand Up @@ -37,7 +37,7 @@ impl<T: Parse> Parse for Annotated<T> {
let doc_comment = parser.parse::<DocComment>()?;
// TODO: Use a Literal instead of an Ident when Attribute args
// start supporting them and remove `Ident::new_no_trim`.
let value = Ident::new_no_trim(doc_comment.content_span.clone());
let name = Ident::new_no_trim(doc_comment.content_span.clone());
attribute_list.push(AttributeDecl {
hash_kind: AttributeHashKind::Outer(HashToken::new(doc_comment.span.clone())),
attribute: SquareBrackets::new(
Expand All @@ -47,7 +47,7 @@ impl<T: Parse> Parse for Annotated<T> {
doc_comment.span.clone(),
),
args: Some(Parens::new(
Punctuated::single(value),
Punctuated::single(AttributeArg { name, value: None }),
doc_comment.content_span,
)),
}),
Expand Down Expand Up @@ -90,6 +90,22 @@ impl Parse for AttributeHashKind {
}
}

impl Parse for AttributeArg {
fn parse(parser: &mut Parser) -> ParseResult<Self> {
let name = parser.parse()?;
match parser.take::<EqToken>() {
Some(_) => {
let value = parser.parse()?;
Ok(AttributeArg {
name,
value: Some(value),
})
}
None => Ok(AttributeArg { name, value: None }),
}
}
}

impl Parse for Attribute {
fn parse(parser: &mut Parser) -> ParseResult<Self> {
let name = if let Some(storage) = parser.take::<StorageToken>() {
Expand Down Expand Up @@ -147,9 +163,12 @@ mod tests {
args: Some(Parens(
inner: Punctuated(
value_separator_pairs: [],
final_value_opt: Some(Ident(
to_string: " This is a doc comment.",
span: (85, 108),
final_value_opt: Some(AttributeArg(
name: Ident(
to_string: " This is a doc comment.",
span: (85, 108),
),
value: None,
)),
),
span: (85, 108),
Expand All @@ -174,9 +193,12 @@ mod tests {
args: Some(Parens(
inner: Punctuated(
value_separator_pairs: [],
final_value_opt: Some(Ident(
to_string: "read",
span: (131, 135),
final_value_opt: Some(AttributeArg(
name: Ident(
to_string: "read",
span: (131, 135),
),
value: None,
)),
),
span: (130, 136),
Expand Down Expand Up @@ -222,4 +244,53 @@ mod tests {
)
"###);
}

#[test]
fn parse_attribute() {
assert_ron_snapshot!(parse::<Attribute>(r#"
name(arg1, arg2 = "value", arg3)
"#,), @r###"
Attribute(
name: Ident(
to_string: "name",
span: (13, 17),
),
args: Some(Parens(
inner: Punctuated(
value_separator_pairs: [
(AttributeArg(
name: Ident(
to_string: "arg1",
span: (18, 22),
),
value: None,
), CommaToken(
span: (22, 23),
)),
(AttributeArg(
name: Ident(
to_string: "arg2",
span: (24, 28),
),
value: Some(String(LitString(
span: (31, 38),
parsed: "value",
))),
), CommaToken(
span: (38, 39),
)),
],
final_value_opt: Some(AttributeArg(
name: Ident(
to_string: "arg3",
span: (40, 44),
),
value: None,
)),
),
span: (17, 45),
)),
)
"###);
}
}
42 changes: 39 additions & 3 deletions sway-parse/src/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ mod tests {
.map(|att| {
(
att.name.as_str(),
att.args
.as_ref()
.map(|arg| arg.get().into_iter().map(|a| a.as_str()).collect()),
att.args.as_ref().map(|arg| {
arg.get().into_iter().map(|a| a.name.as_str()).collect()
}),
)
})
.collect()
Expand Down Expand Up @@ -283,6 +283,42 @@ mod tests {
assert_eq!(attributes(&item.attribute_list), vec![[("foo", None)]]);
}

#[test]
fn parse_attributes_fn_one_arg_value() {
let item = parse::<Item>(
r#"
#[cfg(target = "evm")]
fn f() -> bool {
false
}
"#,
);

assert!(matches!(item.value, ItemKind::Fn(_)));
assert_eq!(
attributes(&item.attribute_list),
vec![[("cfg", Some(vec!["target"]))]]
);
}

#[test]
fn parse_attributes_fn_two_arg_values() {
let item = parse::<Item>(
r#"
#[cfg(target = "evm", feature = "test")]
fn f() -> bool {
false
}
"#,
);

assert!(matches!(item.value, ItemKind::Fn(_)));
assert_eq!(
attributes(&item.attribute_list),
vec![[("cfg", Some(vec!["target", "feature"]))]]
);
}

#[test]
fn parse_attributes_fn_two_basic() {
let item = parse::<Item>(
Expand Down
Loading

0 comments on commit 7f04334

Please sign in to comment.