sway_ast/
attribute.rs

1use crate::priv_prelude::*;
2
3#[derive(Clone, Debug, Serialize)]
4pub struct Annotated<T> {
5    pub attributes: Vec<AttributeDecl>,
6    pub value: T,
7}
8
9// Storage access and purity.
10pub const STORAGE_ATTRIBUTE_NAME: &str = "storage";
11pub const STORAGE_READ_ARG_NAME: &str = "read";
12pub const STORAGE_WRITE_ARG_NAME: &str = "write";
13
14// Function inlining.
15pub const INLINE_ATTRIBUTE_NAME: &str = "inline";
16pub const INLINE_NEVER_ARG_NAME: &str = "never";
17pub const INLINE_ALWAYS_ARG_NAME: &str = "always";
18
19// Payable functions.
20pub const PAYABLE_ATTRIBUTE_NAME: &str = "payable";
21
22// Fallback functions.
23pub const FALLBACK_ATTRIBUTE_NAME: &str = "fallback";
24
25// Documentation comments.
26// Note that because "doc-comment" is not a valid identifier,
27// doc-comment attributes cannot be declared in code.
28// They are exclusively created by the compiler to denote
29// doc comments, `///` and `//!`.
30pub const DOC_COMMENT_ATTRIBUTE_NAME: &str = "doc-comment";
31
32// In-language unit testing.
33pub const TEST_ATTRIBUTE_NAME: &str = "test";
34pub const TEST_SHOULD_REVERT_ARG_NAME: &str = "should_revert";
35
36// Allow warnings.
37pub const ALLOW_ATTRIBUTE_NAME: &str = "allow";
38pub const ALLOW_DEAD_CODE_ARG_NAME: &str = "dead_code";
39pub const ALLOW_DEPRECATED_ARG_NAME: &str = "deprecated";
40
41// Conditional compilation.
42pub const CFG_ATTRIBUTE_NAME: &str = "cfg";
43pub const CFG_TARGET_ARG_NAME: &str = "target";
44pub const CFG_PROGRAM_TYPE_ARG_NAME: &str = "program_type";
45
46// Deprecation.
47pub const DEPRECATED_ATTRIBUTE_NAME: &str = "deprecated";
48pub const DEPRECATED_NOTE_ARG_NAME: &str = "note";
49
50// Error types.
51pub const ERROR_TYPE_ATTRIBUTE_NAME: &str = "error_type";
52pub const ERROR_ATTRIBUTE_NAME: &str = "error";
53pub const ERROR_M_ARG_NAME: &str = "m";
54
55pub const KNOWN_ATTRIBUTE_NAMES: &[&str] = &[
56    STORAGE_ATTRIBUTE_NAME,
57    DOC_COMMENT_ATTRIBUTE_NAME,
58    TEST_ATTRIBUTE_NAME,
59    INLINE_ATTRIBUTE_NAME,
60    PAYABLE_ATTRIBUTE_NAME,
61    ALLOW_ATTRIBUTE_NAME,
62    CFG_ATTRIBUTE_NAME,
63    DEPRECATED_ATTRIBUTE_NAME,
64    FALLBACK_ATTRIBUTE_NAME,
65];
66
67/// An attribute declaration. Attribute declaration
68/// can potentially have an arbitrary number of [Attribute]s.
69/// [Attribute]s can potentially have any number of [AttributeArg]s.
70/// Each [AttributeArg] can have a value assigned.
71///
72/// E.g.:
73///
74/// ```ignore
75/// #[attribute]
76/// #[attribute_1, attribute_2]
77/// #[attribute()]
78/// #[attribute(arg)]
79/// #[attribute(arg_1, arg_2)]
80/// #[attribute(arg_1 = "value", arg_2 = true)]
81/// #[attribute_1, attribute_2(arg_1), attribute_3(arg_1, arg_2 = true)]
82/// ```
83///
84/// [AttributeDecl]s can be _inner_ or _outer_, as explained in [AttributeHashKind].
85// TODO: Currently, inner attributes are supported only on module doc comments,
86//       those starting with `//!`.
87//       See: https://github.com/FuelLabs/sway/issues/6924
88#[derive(Clone, Debug, Serialize)]
89pub struct AttributeDecl {
90    pub hash_kind: AttributeHashKind,
91    pub attribute: SquareBrackets<Punctuated<Attribute, CommaToken>>,
92}
93
94impl AttributeDecl {
95    /// Creates the `doc-comment` [AttributeDecl] for a single line of an outer comment. E.g.:
96    /// ```ignore
97    /// /// This is an outer comment.
98    /// ```
99    /// The `span` is the overall span: `/// This is an outer comment.`.
100    /// The `content_span` is the span of the content, without the leading `///`: ` This is an outer comment.`.
101    pub fn new_outer_doc_comment(span: Span, content_span: Span) -> Self {
102        Self::new_doc_comment(
103            span.clone(),
104            content_span,
105            AttributeHashKind::Outer(HashToken::new(span)),
106        )
107    }
108
109    /// Creates the `doc-comment` [AttributeDecl] for a single line of an inner comment. E.g.:
110    /// ```ignore
111    /// //! This is an inner comment.
112    /// ```
113    /// The `span` is the overall span: `//! This is an inner comment.`.
114    /// The `content_span` is the span of the content, without the leading `//!`: ` This is an inner comment.`.
115    pub fn new_inner_doc_comment(span: Span, content_span: Span) -> Self {
116        Self::new_doc_comment(
117            span.clone(),
118            content_span,
119            AttributeHashKind::Inner(HashBangToken::new(span)),
120        )
121    }
122
123    fn new_doc_comment(span: Span, content_span: Span, hash_kind: AttributeHashKind) -> Self {
124        // TODO: Store the comment line in an argument value as
125        //       discussed in https://github.com/FuelLabs/sway/issues/6938.
126        let name = Ident::new_no_trim(content_span.clone());
127        AttributeDecl {
128            hash_kind,
129            attribute: SquareBrackets::new(
130                Punctuated::single(Attribute {
131                    name: Ident::new_with_override(
132                        DOC_COMMENT_ATTRIBUTE_NAME.to_string(),
133                        span.clone(),
134                    ),
135                    args: Some(Parens::new(
136                        Punctuated::single(AttributeArg { name, value: None }),
137                        content_span,
138                    )),
139                }),
140                span,
141            ),
142        }
143    }
144
145    /// `self` is a doc comment, either an inner (`//!`) or outer (`///`).
146    pub fn is_doc_comment(&self) -> bool {
147        self.attribute.inner.value_separator_pairs.is_empty()
148            && self
149                .attribute
150                .inner
151                .final_value_opt
152                .as_ref()
153                .is_some_and(|attr| attr.is_doc_comment())
154    }
155
156    pub fn is_inner(&self) -> bool {
157        matches!(self.hash_kind, AttributeHashKind::Inner(_))
158    }
159}
160
161impl Spanned for AttributeDecl {
162    fn span(&self) -> Span {
163        let hash_span = match &self.hash_kind {
164            AttributeHashKind::Inner(hash_bang_token) => hash_bang_token.span(),
165            AttributeHashKind::Outer(hash_token) => hash_token.span(),
166        };
167        Span::join(hash_span, &self.attribute.span())
168    }
169}
170
171/// Denotes if an [AttributeDecl] is an _inner_ or _outer_ attribute declaration.
172///
173/// E.g.:
174/// ```ignore
175/// /// This is an outer doc comment.
176/// /// It annotates the struct `Foo`.
177/// struct Foo {}
178///
179/// // This is an outer attribute.
180/// // In annotates the function `fun`.
181/// #[inline(always)]
182/// fn fun() {}
183///
184/// //! This is an inner doc comment.
185/// //! It annotates the library module.
186/// library;
187///
188/// // This is an inner attribute.
189/// // In annotates whichever item it is declared in.
190/// #![allow(dead_code)]
191/// ```
192#[derive(Clone, Debug, Serialize)]
193pub enum AttributeHashKind {
194    /// Inner specifies that the attribute annotates
195    /// the item that the attribute is declared within.
196    Inner(HashBangToken),
197    /// Outer specifies that the attribute annotates
198    /// the item that immediately follows the attribute.
199    Outer(HashToken),
200}
201
202impl Spanned for AttributeHashKind {
203    fn span(&self) -> Span {
204        match self {
205            AttributeHashKind::Inner(token) => token.span(),
206            AttributeHashKind::Outer(token) => token.span(),
207        }
208    }
209}
210
211#[derive(Clone, Debug, Serialize)]
212pub struct AttributeArg {
213    pub name: Ident,
214    pub value: Option<Literal>,
215}
216
217impl Spanned for AttributeArg {
218    fn span(&self) -> Span {
219        if let Some(value) = &self.value {
220            Span::join(self.name.span(), &value.span())
221        } else {
222            self.name.span()
223        }
224    }
225}
226
227#[derive(Clone, Debug, Serialize)]
228pub struct Attribute {
229    pub name: Ident,
230    pub args: Option<Parens<Punctuated<AttributeArg, CommaToken>>>,
231}
232
233impl Attribute {
234    pub fn is_doc_comment(&self) -> bool {
235        self.name.as_str() == DOC_COMMENT_ATTRIBUTE_NAME
236    }
237    pub fn is_cfg(&self) -> bool {
238        self.name.as_str() == CFG_ATTRIBUTE_NAME
239    }
240}
241
242impl Spanned for Attribute {
243    fn span(&self) -> Span {
244        self.args
245            .as_ref()
246            .map(|args| Span::join(self.name.span(), &args.span()))
247            .unwrap_or_else(|| self.name.span())
248    }
249}