Skip to main content

sway_core/transform/
attribute.rs

1//! Each item may have a list of attributes, each with a name and a list of zero or more args.
2//! Attributes may be specified more than once in which case we use the union of their args.
3//!
4//! E.g.,
5//!
6//! ```ignore
7//! #[foo(bar)]
8//! #[foo(baz, xyzzy)]
9//! ```
10//!
11//! is essentially equivalent to
12//!
13//! ```ignore
14//! #[foo(bar, baz, xyzzy)]
15//! ```
16//!
17//! and duplicates like
18//!
19//! ```ignore
20//! #[foo(bar)]
21//! #[foo(bar)]
22//! ```
23//!
24//! are equivalent to
25//!
26//! ```ignore
27//! #[foo(bar, bar)]
28//! ```
29//!
30//! Attribute args can have values:
31//!
32//! ```ignore
33//! #[foo(bar = "some value", baz = true)]
34//! ```
35//!
36//! All attributes have the following common properties:
37//! - targets: items that they can annotate. E.g., `#[inline]` can annotate only functions.
38//! - multiplicity: if they can be applied multiple time on an item. E.g., `#[inline]` can
39//!   be applied only once, but `#[cfg]` multiple times.
40//! - arguments multiplicity: how many arguments they can have.
41//! - arguments expectance: which arguments are expected and accepted as valid.
42//!
43//! All attribute arguments have the following common properties:
44//! - value expectance: if they must have values specified.
45//!
46//! Individual arguments might impose their own additional constraints.
47
48use indexmap::IndexMap;
49use itertools::Itertools;
50use serde::{Deserialize, Serialize};
51use std::{hash::Hash, sync::Arc};
52use sway_ast::{
53    attribute::*, AttributeDecl, ImplItemParent, ItemImplItem, ItemKind, ItemTraitItem, Literal,
54};
55use sway_error::{
56    convert_parse_tree_error::ConvertParseTreeError,
57    handler::{ErrorEmitted, Handler},
58};
59use sway_features::Feature;
60use sway_types::{Ident, Span, Spanned};
61
62use crate::language::{Inline, Purity, Trace};
63
64#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
65pub struct AttributeArg {
66    pub name: Ident,
67    pub value: Option<Literal>,
68    pub span: Span,
69}
70
71impl AttributeArg {
72    /// Returns a mandatory [String] value from `self`,
73    /// or an error if the value does not exist or is not of type [String].
74    ///
75    /// `attribute` is the parent [Attribute] of `self`.
76    pub fn get_string(
77        &self,
78        handler: &Handler,
79        attribute: &Attribute,
80    ) -> Result<&String, ErrorEmitted> {
81        match &self.value {
82            Some(literal) => match literal {
83                Literal::String(lit_string) => Ok(&lit_string.parsed),
84                _ => Err(handler.emit_err(
85                    ConvertParseTreeError::InvalidAttributeArgValueType {
86                        span: literal.span(),
87                        arg: self.name.clone(),
88                        expected_type: "str",
89                        received_type: literal.friendly_type_name(),
90                    }
91                    .into(),
92                )),
93            },
94            None => Err(handler.emit_err(
95                ConvertParseTreeError::InvalidAttributeArgExpectsValue {
96                    attribute: attribute.name.clone(),
97                    arg: (&self.name).into(),
98                    value_span: None,
99                }
100                .into(),
101            )),
102        }
103    }
104
105    /// Returns an optional [String] value from `self`,
106    /// or an error if the value exists but is not of type [String].
107    pub fn get_string_opt(&self, handler: &Handler) -> Result<Option<&String>, ErrorEmitted> {
108        match &self.value {
109            Some(literal) => match literal {
110                Literal::String(lit_string) => Ok(Some(&lit_string.parsed)),
111                _ => Err(handler.emit_err(
112                    ConvertParseTreeError::InvalidAttributeArgValueType {
113                        span: literal.span(),
114                        arg: self.name.clone(),
115                        expected_type: "str",
116                        received_type: literal.friendly_type_name(),
117                    }
118                    .into(),
119                )),
120            },
121            None => Ok(None),
122        }
123    }
124
125    /// Returns a mandatory `bool` value from `self`,
126    /// or an error if the value does not exist or is not of type `bool`.
127    ///
128    /// `attribute` is the parent [Attribute] of `self`.
129    pub fn get_bool(&self, handler: &Handler, attribute: &Attribute) -> Result<bool, ErrorEmitted> {
130        match &self.value {
131            Some(literal) => match literal {
132                Literal::Bool(lit_bool) => Ok(lit_bool.kind.into()),
133                _ => Err(handler.emit_err(
134                    ConvertParseTreeError::InvalidAttributeArgValueType {
135                        span: literal.span(),
136                        arg: self.name.clone(),
137                        expected_type: "bool",
138                        received_type: literal.friendly_type_name(),
139                    }
140                    .into(),
141                )),
142            },
143            None => Err(handler.emit_err(
144                ConvertParseTreeError::InvalidAttributeArgExpectsValue {
145                    attribute: attribute.name.clone(),
146                    arg: (&self.name).into(),
147                    value_span: None,
148                }
149                .into(),
150            )),
151        }
152    }
153
154    pub fn is_allow_dead_code(&self) -> bool {
155        self.name.as_str() == ALLOW_DEAD_CODE_ARG_NAME
156    }
157
158    pub fn is_allow_deprecated(&self) -> bool {
159        self.name.as_str() == ALLOW_DEPRECATED_ARG_NAME
160    }
161
162    pub fn is_cfg_target(&self) -> bool {
163        self.name.as_str() == CFG_TARGET_ARG_NAME
164    }
165
166    pub fn is_cfg_program_type(&self) -> bool {
167        self.name.as_str() == CFG_PROGRAM_TYPE_ARG_NAME
168    }
169
170    pub fn is_cfg_experimental(&self) -> bool {
171        Feature::CFG.contains(&self.name.as_str())
172    }
173
174    pub fn is_deprecated_note(&self) -> bool {
175        self.name.as_str() == DEPRECATED_NOTE_ARG_NAME
176    }
177
178    pub fn is_test_should_revert(&self) -> bool {
179        self.name.as_str() == TEST_SHOULD_REVERT_ARG_NAME
180    }
181
182    pub fn is_error_message(&self) -> bool {
183        self.name.as_str() == ERROR_M_ARG_NAME
184    }
185}
186
187impl Spanned for AttributeArg {
188    fn span(&self) -> Span {
189        self.span.clone()
190    }
191}
192
193// TODO: Currently we do not support arbitrary inner attributes.
194//       Only compiler-generated `doc-comment` attributes for `//!`
195//       can currently be inner attributes.
196//       All of the below properties assume we are inspecting
197//       outer attributes.
198//       Extend the infrastructure for attribute properties to
199//       support inner attributes, once we fully support them.
200//       See: https://github.com/FuelLabs/sway/issues/6924
201
202#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
203pub struct Attribute {
204    /// Attribute direction, taken from the enclosing [crate::AttributeDecl].
205    /// All attributes within the same [crate::AttributeDecl] will have the
206    /// same direction.
207    pub direction: AttributeDirection,
208    pub name: Ident,
209    pub args: Vec<AttributeArg>,
210    pub span: Span,
211    pub kind: AttributeKind,
212}
213
214#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
215pub enum AttributeDirection {
216    Inner,
217    Outer,
218}
219
220impl From<&AttributeHashKind> for AttributeDirection {
221    fn from(value: &AttributeHashKind) -> Self {
222        match value {
223            AttributeHashKind::Inner(_) => Self::Inner,
224            AttributeHashKind::Outer(_) => Self::Outer,
225        }
226    }
227}
228
229/// Defines the minimum and the maximum number of [AttributeArg]s
230/// that an [Attribute] can have.
231pub struct ArgsMultiplicity {
232    min: usize,
233    max: usize,
234}
235
236impl ArgsMultiplicity {
237    pub fn zero() -> Self {
238        Self { min: 0, max: 0 }
239    }
240    pub fn arbitrary() -> Self {
241        Self {
242            min: 0,
243            max: usize::MAX,
244        }
245    }
246    pub fn exactly(num: usize) -> Self {
247        Self { min: num, max: num }
248    }
249    pub fn at_least(num: usize) -> Self {
250        Self {
251            min: num,
252            max: usize::MAX,
253        }
254    }
255    pub fn at_most(num: usize) -> Self {
256        Self { min: 0, max: num }
257    }
258    pub fn between(min: usize, max: usize) -> Self {
259        assert!(
260            min <= max,
261            "min must be less than or equal to max; min was {min}, max was {max}"
262        );
263        Self { min, max }
264    }
265    pub fn contains(&self, value: usize) -> bool {
266        self.min <= value && value <= self.max
267    }
268}
269
270impl From<&ArgsMultiplicity> for (usize, usize) {
271    fn from(value: &ArgsMultiplicity) -> Self {
272        (value.min, value.max)
273    }
274}
275
276/// Defines which [AttributeArg]s an [Attribute] expects.
277pub enum ExpectedArgs {
278    /// The [Attribute] does not expect any [AttributeArg]s.
279    None,
280    /// The [Attribute] can accept any argument. The `doc-comment`
281    /// attribute is such an attribute - every documentation line
282    /// becomes an [AttributeArg] and is accepted as valid.
283    Any,
284    /// An [AttributeArg::name] **must be** one from the provided list.
285    /// If it is not, an error will be emitted.
286    MustBeIn(Vec<&'static str>),
287    /// An [AttributeArg::name] **should be** one from the provided list.
288    /// If it is not, a warning will be emitted.
289    ShouldBeIn(Vec<&'static str>),
290}
291
292impl ExpectedArgs {
293    /// Returns expected argument names, if any specific names are
294    /// expected, or an empty [Vec] if no names are expected or
295    /// if the [Attribute] can accept any argument name.
296    pub(crate) fn args_names(&self) -> Vec<&'static str> {
297        match self {
298            ExpectedArgs::None | ExpectedArgs::Any => vec![],
299            ExpectedArgs::MustBeIn(expected_args) | ExpectedArgs::ShouldBeIn(expected_args) => {
300                expected_args.clone()
301            }
302        }
303    }
304}
305
306/// Defines if [AttributeArg]s within the same [Attribute]
307/// can or must have a value specified.
308///
309/// E.g., `#[attribute(arg = <value>)`.
310///
311/// We consider the expected types of individual values not to be
312/// the part of the [AttributeArg]'s metadata. Final consumers of
313/// the attribute will check for the expected type and emit an error
314/// if a wrong type is provided.
315///
316/// E.g., `#[cfg(target = 42)]` will emit an error during the
317/// cfg-evaluation.
318pub enum ArgsExpectValues {
319    /// Each argument, if any, must have a value specified.
320    /// Specified values can be of different types.
321    ///
322    /// E.g.: `#[cfg(target = "fuel", experimental_new_encoding = false)]`.
323    Yes,
324    /// None of the arguments can have values specified, or the
325    /// [Attribute] does not expect any arguments.
326    ///
327    /// E.g.: `#[storage(read, write)]`, `#[fallback]`.
328    No,
329    /// Each argument, if any, can have a value specified, but must not
330    /// necessarily have one.
331    ///
332    /// E.g.: `#[some_attribute(arg_1 = 5, arg_2)]`.
333    Maybe,
334}
335
336/// Kinds of attributes supported by the compiler.
337#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
338pub enum AttributeKind {
339    /// Represents an [Attribute] unknown to the compiler.
340    /// We generate warnings for such attributes but in
341    /// general support them and pass them to the typed
342    /// tree. This allows third-party static analysis to
343    /// utilized proprietary attributes and inspect them
344    /// in the typed tree.
345    Unknown,
346    DocComment,
347    Storage,
348    Inline,
349    Test,
350    Payable,
351    Allow,
352    Cfg,
353    Deprecated,
354    Fallback,
355    ErrorType,
356    Error,
357    Trace,
358    AbiName,
359    Event,
360    Indexed,
361    Require,
362}
363
364/// Denotes if an [ItemTraitItem] belongs to an ABI or to a trait.
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366pub enum TraitItemParent {
367    Abi,
368    Trait,
369}
370
371/// Denotes if a [sway_ast::TypeField] belongs to a struct or to an enum.
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub enum StructOrEnumField {
374    StructField,
375    EnumField,
376}
377
378impl AttributeKind {
379    pub fn from_attribute_name(name: &str) -> Self {
380        match name {
381            DOC_COMMENT_ATTRIBUTE_NAME => AttributeKind::DocComment,
382            STORAGE_ATTRIBUTE_NAME => AttributeKind::Storage,
383            INLINE_ATTRIBUTE_NAME => AttributeKind::Inline,
384            TEST_ATTRIBUTE_NAME => AttributeKind::Test,
385            PAYABLE_ATTRIBUTE_NAME => AttributeKind::Payable,
386            ALLOW_ATTRIBUTE_NAME => AttributeKind::Allow,
387            CFG_ATTRIBUTE_NAME => AttributeKind::Cfg,
388            DEPRECATED_ATTRIBUTE_NAME => AttributeKind::Deprecated,
389            FALLBACK_ATTRIBUTE_NAME => AttributeKind::Fallback,
390            ERROR_TYPE_ATTRIBUTE_NAME => AttributeKind::ErrorType,
391            ERROR_ATTRIBUTE_NAME => AttributeKind::Error,
392            TRACE_ATTRIBUTE_NAME => AttributeKind::Trace,
393            ABI_NAME_ATTRIBUTE_NAME => AttributeKind::AbiName,
394            EVENT_ATTRIBUTE_NAME => AttributeKind::Event,
395            INDEXED_ATTRIBUTE_NAME => AttributeKind::Indexed,
396            REQUIRE_ATTRIBUTE_NAME => AttributeKind::Require,
397            _ => AttributeKind::Unknown,
398        }
399    }
400
401    /// True if multiple attributes of a this [AttributeKind] can
402    /// annotate the same item at the same time. E.g., the `inline`
403    /// attribute can be applied only once on an item and does not
404    /// allow multiple, while the `cfg` attribute can be applied
405    /// arbitrary many times.
406    ///
407    /// Currently we assume that the multiplicity does not depend
408    /// on the annotated item, but is an inherent property of
409    /// the [AttributeKind].
410    pub fn allows_multiple(&self) -> bool {
411        use AttributeKind::*;
412        match self {
413            Unknown => true,
414            DocComment => true,
415            Storage => false,
416            Inline => false,
417            Test => false,
418            Payable => false,
419            Allow => true,
420            Cfg => true,
421            Deprecated => false,
422            Fallback => false,
423            ErrorType => false,
424            Error => false,
425            Trace => false,
426            AbiName => false,
427            Event => false,
428            Indexed => false,
429            Require => false,
430        }
431    }
432}
433
434impl Attribute {
435    pub fn is_doc_comment(&self) -> bool {
436        self.kind == AttributeKind::DocComment
437    }
438
439    pub fn is_inner(&self) -> bool {
440        self.direction == AttributeDirection::Inner
441    }
442
443    pub fn is_outer(&self) -> bool {
444        self.direction == AttributeDirection::Outer
445    }
446
447    pub(crate) fn args_multiplicity(&self) -> ArgsMultiplicity {
448        use ArgsMultiplicity as Multiplicity;
449        use AttributeKind::*;
450        match self.kind {
451            Unknown => Multiplicity::arbitrary(),
452            // Each `doc-comment` attribute contains exactly one argument
453            // whose name is the actual documentation text and whose value is `None`.
454            // Thus, we expect exactly one argument.
455            DocComment => Multiplicity::exactly(1),
456            // `storage(read, write)`.
457            Storage => Multiplicity::between(1, 2),
458            // `inline(never)` or `inline(always)`.
459            Inline => Multiplicity::exactly(1),
460            // `test`, `test(should_revert)`.
461            Test => Multiplicity::at_most(1),
462            Payable => Multiplicity::zero(),
463            Allow => Multiplicity::at_least(1),
464            Cfg => Multiplicity::exactly(1),
465            // `deprecated`, `deprecated(note = "note")`.
466            Deprecated => Multiplicity::at_most(1),
467            Fallback => Multiplicity::zero(),
468            ErrorType => Multiplicity::zero(),
469            Error => Multiplicity::exactly(1),
470            // `trace(never)` or `trace(always)`.
471            Trace => Multiplicity::exactly(1),
472            AbiName => Multiplicity::exactly(1),
473            Event => Multiplicity::zero(),
474            Indexed => Multiplicity::zero(),
475            Require => Multiplicity::between(1, 2),
476        }
477    }
478
479    pub(crate) fn check_args_multiplicity(&self, handler: &Handler) -> Result<(), ErrorEmitted> {
480        if !self.args_multiplicity().contains(self.args.len()) {
481            Err(handler.emit_err(
482                ConvertParseTreeError::InvalidAttributeArgsMultiplicity {
483                    span: if self.args.is_empty() {
484                        self.name.span()
485                    } else {
486                        Span::join(
487                            self.args.first().unwrap().span(),
488                            &self.args.last().unwrap().span,
489                        )
490                    },
491                    attribute: self.name.clone(),
492                    args_multiplicity: (&self.args_multiplicity()).into(),
493                    num_of_args: self.args.len(),
494                }
495                .into(),
496            ))
497        } else {
498            Ok(())
499        }
500    }
501
502    pub(crate) fn can_have_arguments(&self) -> bool {
503        let args_multiplicity = self.args_multiplicity();
504        args_multiplicity.min != 0 || args_multiplicity.max != 0
505    }
506
507    pub(crate) fn expected_args(&self) -> ExpectedArgs {
508        use AttributeKind::*;
509        use ExpectedArgs::*;
510        match self.kind {
511            Unknown => Any,
512            DocComment => Any,
513            Storage => MustBeIn(vec![STORAGE_READ_ARG_NAME, STORAGE_WRITE_ARG_NAME]),
514            Inline => MustBeIn(vec![INLINE_ALWAYS_ARG_NAME, INLINE_NEVER_ARG_NAME]),
515            Test => MustBeIn(vec![TEST_SHOULD_REVERT_ARG_NAME]),
516            Payable => None,
517            Allow => ShouldBeIn(vec![ALLOW_DEAD_CODE_ARG_NAME, ALLOW_DEPRECATED_ARG_NAME]),
518            Cfg => {
519                let mut args = vec![
520                    // Arguments, ordered alphabetically.
521                    CFG_PROGRAM_TYPE_ARG_NAME,
522                    CFG_TARGET_ARG_NAME,
523                ];
524                args.extend(Feature::CFG.iter().sorted());
525                MustBeIn(args)
526            }
527            Deprecated => MustBeIn(vec![DEPRECATED_NOTE_ARG_NAME]),
528            Fallback => None,
529            ErrorType => None,
530            Error => MustBeIn(vec![ERROR_M_ARG_NAME]),
531            Trace => MustBeIn(vec![TRACE_ALWAYS_ARG_NAME, TRACE_NEVER_ARG_NAME]),
532            AbiName => MustBeIn(vec![ABI_NAME_NAME_ARG_NAME]),
533            Event => None,
534            Indexed => None,
535            Require => MustBeIn(vec![REQUIRE_ARG_NAME_TRIVIALLY_DECODABLE]),
536        }
537    }
538
539    pub(crate) fn args_expect_values(&self) -> ArgsExpectValues {
540        use ArgsExpectValues::*;
541        use AttributeKind::*;
542        match self.kind {
543            Unknown => Maybe,
544            // The actual documentation line is in the name of the attribute.
545            DocComment => No,
546            Storage => No,
547            Inline => No,
548            // `test(should_revert)`, `test(should_revert = "18446744073709486084")`.
549            Test => Maybe,
550            Payable => No,
551            Allow => No,
552            Cfg => Yes,
553            // `deprecated(note = "note")`.
554            Deprecated => Yes,
555            Fallback => No,
556            ErrorType => No,
557            // `error(msg = "msg")`.
558            Error => Yes,
559            Trace => No,
560            AbiName => Yes,
561            Event => No,
562            Indexed => No,
563            // require(trivially_decodable = "yes")
564            Require => Yes,
565        }
566    }
567
568    pub(crate) fn can_annotate_module_kind(&self) -> bool {
569        use AttributeKind::*;
570        match self.kind {
571            Unknown => false,
572            DocComment => self.direction == AttributeDirection::Inner,
573            Storage => false,
574            Inline => false,
575            Test => false,
576            Payable => false,
577            Allow => false,
578            Cfg => false,
579            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
580            //       Deprecating the module kind will mean deprecating all its items.
581            Deprecated => false,
582            Fallback => false,
583            ErrorType => false,
584            Error => false,
585            Trace => false,
586            AbiName => false,
587            Event => false,
588            Indexed => false,
589            Require => false,
590        }
591    }
592
593    pub(crate) fn can_annotate_abi(&self) -> bool {
594        use AttributeKind::*;
595        match self.kind {
596            Unknown => true,
597            DocComment => self.direction == AttributeDirection::Outer,
598            Storage => false,
599            Inline => false,
600            Test => false,
601            Payable => false,
602            Allow => true,
603            Cfg => true,
604            // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented.
605            Deprecated => false,
606            Fallback => false,
607            ErrorType => false,
608            Error => false,
609            Trace => false,
610            AbiName => false,
611            Event => false,
612            Indexed => false,
613            Require => false,
614        }
615    }
616
617    pub(crate) fn can_annotate_item_kind(&self, item_kind: &ItemKind) -> bool {
618        // TODO: Except for `DocComment`, we assume outer annotation here.
619        //       A separate check emits not-implemented error for all inner attributes.
620        //       Until we fully support inner attributes, this approach is sufficient.
621        //       See: https://github.com/FuelLabs/sway/issues/6924
622
623        // TODO: Currently we do not support any attributes on `mod`s, including doc comments.
624        //       See: https://github.com/FuelLabs/sway/issues/6879
625        //       See: https://github.com/FuelLabs/sway/issues/6925
626
627        // We accept all attribute kinds on the `ItemKind::Error`.
628        if matches!(item_kind, ItemKind::Error(..)) {
629            return true;
630        }
631
632        use AttributeKind::*;
633        match self.kind {
634            Unknown => !matches!(item_kind, ItemKind::Submodule(_)),
635            // We allow doc comments on all items including `storage` and `configurable`.
636            DocComment => {
637                self.direction == AttributeDirection::Outer
638                    && !matches!(item_kind, ItemKind::Submodule(_))
639            }
640            Storage => matches!(item_kind, ItemKind::Fn(_)),
641            Inline => matches!(item_kind, ItemKind::Fn(_)),
642            Test => matches!(item_kind, ItemKind::Fn(_)),
643            Payable => false,
644            Allow => !matches!(item_kind, ItemKind::Submodule(_)),
645            Cfg => !matches!(item_kind, ItemKind::Submodule(_)),
646            // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented.
647            Deprecated => match item_kind {
648                ItemKind::Submodule(_) => false,
649                ItemKind::Use(_) => false,
650                ItemKind::Struct(_) => true,
651                ItemKind::Enum(_) => true,
652                ItemKind::Fn(_) => true,
653                ItemKind::Trait(_) => false,
654                ItemKind::Impl(_) => false,
655                ItemKind::Abi(_) => false,
656                ItemKind::Const(_) => true,
657                ItemKind::Storage(_) => false,
658                // TODO: Currently, only single configurables can be deprecated.
659                //       Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
660                ItemKind::Configurable(_) => false,
661                ItemKind::TypeAlias(_) => false,
662                ItemKind::Error(_, _) => true,
663            },
664            Fallback => matches!(item_kind, ItemKind::Fn(_)),
665            ErrorType => matches!(item_kind, ItemKind::Enum(_)),
666            Error => false,
667            Trace => matches!(item_kind, ItemKind::Fn(_)),
668            AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
669            Event => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
670            Indexed => false,
671            Require => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
672        }
673    }
674
675    // TODO: Add `can_annotated_nested_item_kind`, once we properly support nested items.
676    //       E.g., the `#[test]` attribute can annotate module functions (`ItemKind::Fn`),
677    //       but will not be allowed on nested functions.
678
679    pub(crate) fn can_annotate_struct_or_enum_field(
680        &self,
681        struct_or_enum_field: StructOrEnumField,
682    ) -> bool {
683        use AttributeKind::*;
684        match self.kind {
685            Unknown => true,
686            DocComment => self.direction == AttributeDirection::Outer,
687            Storage => false,
688            Inline => false,
689            Test => false,
690            Payable => false,
691            Allow => true,
692            Cfg => true,
693            Deprecated => true,
694            Fallback => false,
695            ErrorType => false,
696            Error => struct_or_enum_field == StructOrEnumField::EnumField,
697            Trace => false,
698            AbiName => false,
699            Event => false,
700            Indexed => matches!(struct_or_enum_field, StructOrEnumField::StructField),
701            Require => false,
702        }
703    }
704
705    /// True if `self` can annotate an *interface* `item` of a trait or ABI.
706    /// E.g.:
707    /// ```ignore
708    /// trait Trait {
709    ///   #[some_attribute]
710    ///   fn interface_fn();
711    ///   #[some_attribute]
712    ///   const INTERFACE_CONST: u64;
713    /// }
714    /// ```
715    pub(crate) fn can_annotate_abi_or_trait_interface_item(
716        &self,
717        item: &ItemTraitItem,
718        parent: TraitItemParent,
719    ) -> bool {
720        use AttributeKind::*;
721        match self.kind {
722            Unknown => true,
723            DocComment => self.direction == AttributeDirection::Outer,
724            Storage => matches!(item, ItemTraitItem::Fn(..)),
725            // Functions in the trait or ABI interface surface cannot be marked as inlined
726            // because they don't have implementation.
727            Inline => false,
728            Test => false,
729            Payable => parent == TraitItemParent::Abi && matches!(item, ItemTraitItem::Fn(..)),
730            Allow => true,
731            Cfg => true,
732            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
733            Deprecated => false,
734            Fallback => false,
735            ErrorType => false,
736            Error => false,
737            // Functions in the trait or ABI interface surface cannot be marked as traced
738            // because they don't have implementation.
739            Trace => false,
740            AbiName => false,
741            Event => false,
742            Indexed => false,
743            Require => parent == TraitItemParent::Abi && matches!(item, ItemTraitItem::Fn(..)),
744        }
745    }
746
747    /// True if `self` can annotate an *interface* function of a trait or ABI.
748    /// E.g.:
749    /// ```ignore
750    /// trait Trait {
751    ///   #[some_attribute]
752    ///   fn interface_fn();
753    /// }
754    /// ```
755    pub(crate) fn can_annotate_abi_or_trait_interface_fn(&self, parent: TraitItemParent) -> bool {
756        use AttributeKind::*;
757        match self.kind {
758            Unknown => true,
759            DocComment => self.direction == AttributeDirection::Outer,
760            Storage => true,
761            // Functions in the trait or ABI interface surface cannot be marked as inlined
762            // because they don't have implementation.
763            Inline => false,
764            Test => false,
765            Payable => parent == TraitItemParent::Abi,
766            Allow => true,
767            Cfg => true,
768            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
769            Deprecated => false,
770            Fallback => false,
771            ErrorType => false,
772            Error => false,
773            // Functions in the trait or ABI interface surface cannot be marked as traced
774            // because they don't have implementation.
775            Trace => false,
776            AbiName => false,
777            Event => false,
778            Indexed => false,
779            Require => false,
780        }
781    }
782
783    /// True if `self` can annotate an *interface* const of a trait or ABI.
784    /// E.g.:
785    /// ```ignore
786    /// trait Trait {
787    ///   #[some_attribute]
788    ///   const INTERFACE_CONST: u64;
789    /// }
790    /// ```
791    pub(crate) fn can_annotate_abi_or_trait_interface_const(
792        &self,
793        _parent: TraitItemParent,
794    ) -> bool {
795        use AttributeKind::*;
796        match self.kind {
797            Unknown => true,
798            DocComment => self.direction == AttributeDirection::Outer,
799            Storage => false,
800            Inline => false,
801            Test => false,
802            Payable => false,
803            Allow => true,
804            Cfg => true,
805            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
806            Deprecated => false,
807            Fallback => false,
808            ErrorType => false,
809            Error => false,
810            Trace => false,
811            AbiName => false,
812            Event => false,
813            Indexed => false,
814            Require => false,
815        }
816    }
817
818    /// True if `self` can annotate an `item` in an `impl` block.
819    /// E.g.:
820    /// ```ignore
821    /// impl Trait for Type {
822    ///   #[some_attribute]
823    ///   fn impl_fn() {}
824    ///   #[some_attribute]
825    ///   const CONST: u64 = 42;
826    /// }
827    /// ```
828    pub(crate) fn can_annotate_impl_item(
829        &self,
830        item: &ItemImplItem,
831        parent: ImplItemParent,
832    ) -> bool {
833        use AttributeKind::*;
834        match self.kind {
835            Unknown => true,
836            DocComment => self.direction == AttributeDirection::Outer,
837            Storage => matches!(item, ItemImplItem::Fn(..)),
838            Inline => matches!(item, ItemImplItem::Fn(..)),
839            Test => false,
840            Payable => matches!(item, ItemImplItem::Fn(..)) && parent == ImplItemParent::Contract,
841            Allow => true,
842            Cfg => true,
843            Deprecated => !matches!(item, ItemImplItem::Type(_)),
844            Fallback => false,
845            ErrorType => false,
846            Error => false,
847            Trace => matches!(item, ItemImplItem::Fn(..)),
848            AbiName => false,
849            Event => false,
850            Indexed => false,
851            Require => false,
852        }
853    }
854
855    /// True if `self` can annotate a *provided* ABI or trait function.
856    /// E.g.:
857    /// ```ignore
858    /// trait Trait {
859    ///   fn interface_fn();
860    /// } {
861    ///   #[some_attribute]
862    ///   fn provided_fn() {}
863    /// }
864    /// ```
865    pub(crate) fn can_annotate_abi_or_trait_provided_fn(&self, parent: TraitItemParent) -> bool {
866        use AttributeKind::*;
867        match self.kind {
868            Unknown => true,
869            DocComment => self.direction == AttributeDirection::Outer,
870            Storage => true,
871            Inline => true,
872            Test => false,
873            Payable => parent == TraitItemParent::Abi,
874            Allow => true,
875            Cfg => true,
876            Deprecated => true,
877            Fallback => false,
878            ErrorType => false,
879            Error => false,
880            Trace => true,
881            AbiName => false,
882            Event => false,
883            Indexed => false,
884            Require => false,
885        }
886    }
887
888    pub(crate) fn can_annotate_storage_entry(&self) -> bool {
889        use AttributeKind::*;
890        match self.kind {
891            Unknown => true,
892            DocComment => self.direction == AttributeDirection::Outer,
893            Storage => false,
894            Inline => false,
895            Test => false,
896            Payable => false,
897            Allow => true,
898            Cfg => true,
899            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
900            Deprecated => false,
901            Fallback => false,
902            ErrorType => false,
903            Error => false,
904            Trace => false,
905            AbiName => false,
906            Event => false,
907            Indexed => false,
908            Require => false,
909        }
910    }
911
912    pub(crate) fn can_annotate_configurable_field(&self) -> bool {
913        use AttributeKind::*;
914        match self.kind {
915            Unknown => true,
916            DocComment => self.direction == AttributeDirection::Outer,
917            Storage => false,
918            Inline => false,
919            Test => false,
920            Payable => false,
921            Allow => true,
922            Cfg => true,
923            Deprecated => true,
924            Fallback => false,
925            ErrorType => false,
926            Error => false,
927            Trace => false,
928            AbiName => false,
929            Event => false,
930            Indexed => false,
931            Require => false,
932        }
933    }
934
935    pub(crate) fn can_only_annotate_help(&self, target_friendly_name: &str) -> Vec<&'static str> {
936        // Using strings to identify targets is not ideal, but there
937        // is no real need for a more complex and type-safe identification here.
938        use AttributeKind::*;
939        let help = match self.kind {
940            Unknown => vec![],
941            DocComment => match self.direction {
942                AttributeDirection::Inner => vec![
943                    "Inner doc comments (`//!`) can only document modules and must be",
944                    "at the beginning of the module file, before the module kind.",
945                ],
946                AttributeDirection::Outer => if target_friendly_name.starts_with("module kind") {
947                    vec![
948                        "To document modules, use inner doc comments (`//!`). E.g.:",
949                        "//! This doc comment documents a module.",
950                    ]
951                } else {
952                    vec![]
953                },
954            },
955            Storage => {
956                if target_friendly_name == "function signature" {
957                    vec![
958                        "\"storage\" attribute can only annotate functions that have an implementation.",
959                        "Function signatures in ABI and trait declarations do not have implementations.",
960                    ]
961                } else {
962                    vec![
963                        "\"storage\" attribute can only annotate functions.",
964                    ]
965                }
966            },
967            Inline => vec!["\"inline\" attribute can only annotate functions."],
968            Test => vec!["\"test\" attribute can only annotate module functions."],
969            Payable => vec![
970                "\"payable\" attribute can only annotate:",
971                "  - ABI function signatures and their implementations in contracts,",
972                "  - provided ABI functions.",
973            ],
974            Allow => vec![],
975            Cfg => vec![],
976            // TODO: Remove this help lines once https://github.com/FuelLabs/sway/issues/6942 is implemented.
977            Deprecated => vec![
978                "\"deprecated\" attribute is currently not implemented for all elements that could be deprecated.",
979            ],
980            Fallback => vec!["\"fallback\" attribute can only annotate module functions in a contract module."],
981            ErrorType => vec!["\"error_type\" attribute can only annotate enums."],
982            Error => vec!["\"error\" attribute can only annotate enum variants of enums annotated with the \"error_type\" attribute."],
983            Trace => vec!["\"trace\" attribute can only annotate functions."],
984            AbiName => vec![
985                "\"abi_name\" attribute can only annotate structs and enums.",
986            ],
987            Event => vec![
988                "\"event\" attribute can only annotate structs or enums.",
989            ],
990            Indexed => vec![
991                "\"indexed\" attribute can only annotate struct fields.",
992            ],
993            Require => vec![
994                "\"require\" attribute can only annotate structs and enums.",
995            ],
996        };
997
998        if help.is_empty() && target_friendly_name.starts_with("module kind") {
999            vec!["Annotating module kinds (contract, script, predicate, or library) is currently not implemented."]
1000        } else {
1001            help
1002        }
1003    }
1004}
1005
1006/// Stores the [Attribute]s that annotate an element.
1007///
1008/// Note that once stored in the [Attributes], the [Attribute]s lose
1009/// the information about their enclosing [AttributeDecl].
1010///
1011/// The map can contain erroneous attributes. A typical example s containing
1012/// several attributes of an [AttributeKind] that allows only a single attribute
1013/// to be applied, like, e.g., `#[deprecated]`, or `#[test]`.
1014///
1015/// When retrieving such attributes, we follow the last-wins approach
1016/// and return the last attribute in the order of declaration.
1017#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
1018pub struct Attributes {
1019    // Note that we don't need a map here, to store attributes because:
1020    //  - Attributes will mostly be empty.
1021    //  - Per `AttributeKind` there will usually be just one element.
1022    //  - The only exception are comments, that anyhow need to be traversed sequentially
1023    //    and will dominate in the list of attributes or mostly be the only attributes.
1024    //  - Most of the analysis requires traversing all attributes regardless of the `AttributeKind`.
1025    //  - Analysis that is interested in `AttributeKind` anyhow needs to apply a filter first.
1026    //  - Attributes are accessed only once, when checking the declaration of the annotated element.
1027    /// [Attribute]s, in the order of their declaration.
1028    attributes: Arc<Vec<Attribute>>,
1029    // `#[deprecated]` is the only attribute requested on call sites,
1030    // and we provide a O(1) access to it.
1031    /// The index of the last `#[deprecated]` attribute, if any.
1032    deprecated_attr_index: Option<usize>,
1033}
1034
1035impl Attributes {
1036    pub fn new(attribute_decls: &[AttributeDecl]) -> Attributes {
1037        let mut attributes: Vec<Attribute> = vec![];
1038        for attr_decl in attribute_decls {
1039            let attrs = attr_decl.attribute.get().into_iter();
1040            for attr in attrs {
1041                let name = attr.name.as_str();
1042                let args = attr
1043                    .args
1044                    .as_ref()
1045                    .map(|parens| {
1046                        parens
1047                            .get()
1048                            .into_iter()
1049                            .map(|arg| AttributeArg {
1050                                name: arg.name.clone(),
1051                                value: arg.value.clone(),
1052                                span: arg.span(),
1053                            })
1054                            .collect()
1055                    })
1056                    .unwrap_or_default();
1057
1058                let attribute = Attribute {
1059                    direction: (&attr_decl.hash_kind).into(),
1060                    name: attr.name.clone(),
1061                    args,
1062                    span: attr_decl.span(),
1063                    kind: AttributeKind::from_attribute_name(name),
1064                };
1065
1066                attributes.push(attribute);
1067            }
1068        }
1069
1070        Attributes {
1071            deprecated_attr_index: attributes
1072                .iter()
1073                .rposition(|attr| attr.kind == AttributeKind::Deprecated),
1074            attributes: Arc::new(attributes),
1075        }
1076    }
1077
1078    /// Creates new [Attributes] by copying `other`s attributes and retaining
1079    /// only those specified by the predicate `f`.
1080    pub fn retain_from(other: &Attributes, f: impl Fn(&Attribute) -> bool) -> Self {
1081        let attributes = other
1082            .attributes
1083            .iter()
1084            .filter(|attr| f(attr))
1085            .cloned()
1086            .collect_vec();
1087        Self {
1088            deprecated_attr_index: attributes
1089                .iter()
1090                .rposition(|attr| attr.kind == AttributeKind::Deprecated),
1091            attributes: Arc::new(attributes),
1092        }
1093    }
1094
1095    pub fn is_empty(&self) -> bool {
1096        self.attributes.is_empty()
1097    }
1098
1099    /// Returns the first attribute, ordered by span, or None if there are no attributes.
1100    pub fn first(&self) -> Option<&Attribute> {
1101        self.attributes.first()
1102    }
1103
1104    pub fn known_attribute_names(&self) -> &'static [&'static str] {
1105        KNOWN_ATTRIBUTE_NAMES
1106    }
1107
1108    pub fn all(&self) -> impl Iterator<Item = &Attribute> {
1109        self.attributes.iter()
1110    }
1111
1112    pub fn all_as_slice(&self) -> &[Attribute] {
1113        self.attributes.as_slice()
1114    }
1115
1116    pub fn all_by_kind<F>(&self, predicate: F) -> IndexMap<AttributeKind, Vec<&Attribute>>
1117    where
1118        F: Fn(&&Attribute) -> bool,
1119    {
1120        let mut result = IndexMap::<_, Vec<&Attribute>>::new();
1121        for attr in self.attributes.iter().filter(predicate) {
1122            result.entry(attr.kind).or_default().push(attr);
1123        }
1124        result
1125    }
1126
1127    pub fn of_kind(&self, kind: AttributeKind) -> impl Iterator<Item = &Attribute> {
1128        self.attributes.iter().filter(move |attr| attr.kind == kind)
1129    }
1130
1131    pub fn has_any_of_kind(&self, kind: AttributeKind) -> bool {
1132        self.of_kind(kind).any(|_| true)
1133    }
1134
1135    pub fn unknown(&self) -> impl Iterator<Item = &Attribute> {
1136        self.attributes
1137            .iter()
1138            .filter(|attr| attr.kind == AttributeKind::Unknown)
1139    }
1140
1141    pub fn has_allow_dead_code(&self) -> bool {
1142        self.has_allow(|arg| arg.is_allow_dead_code())
1143    }
1144
1145    pub fn has_allow_deprecated(&self) -> bool {
1146        self.has_allow(|arg| arg.is_allow_deprecated())
1147    }
1148
1149    fn has_allow(&self, arg_filter: impl Fn(&AttributeArg) -> bool) -> bool {
1150        self.of_kind(AttributeKind::Allow)
1151            .flat_map(|attribute| &attribute.args)
1152            .any(arg_filter)
1153    }
1154
1155    pub fn has_error_type(&self) -> bool {
1156        self.of_kind(AttributeKind::ErrorType).any(|_| true)
1157    }
1158
1159    pub fn has_error(&self) -> bool {
1160        self.of_kind(AttributeKind::Error).any(|_| true)
1161    }
1162
1163    /// Returns the value of the `#[inline]` [Attribute], or `None` if the
1164    /// [Attributes] does not contain any `#[inline]` attributes.
1165    pub fn inline(&self) -> Option<Inline> {
1166        // `inline` attribute can be applied only once (`AttributeMultiplicity::Single`),
1167        // and can have exactly one argument, otherwise an error is emitted.
1168        // Last-wins approach.
1169        match self
1170            .of_kind(AttributeKind::Inline)
1171            .last()?
1172            .args
1173            .last()?
1174            .name
1175            .as_str()
1176        {
1177            INLINE_NEVER_ARG_NAME => Some(Inline::Never),
1178            INLINE_ALWAYS_ARG_NAME => Some(Inline::Always),
1179            _ => None,
1180        }
1181    }
1182
1183    /// Returns the value of the `#[trace]` [Attribute], or `None` if the
1184    /// [Attributes] does not contain any `#[trace]` attributes.
1185    pub fn trace(&self) -> Option<Trace> {
1186        // `trace` attribute can be applied only once (`AttributeMultiplicity::Single`),
1187        // and can have exactly one argument, otherwise an error is emitted.
1188        // Last-wins approach.
1189        match self
1190            .of_kind(AttributeKind::Trace)
1191            .last()?
1192            .args
1193            .last()?
1194            .name
1195            .as_str()
1196        {
1197            TRACE_NEVER_ARG_NAME => Some(Trace::Never),
1198            TRACE_ALWAYS_ARG_NAME => Some(Trace::Always),
1199            _ => None,
1200        }
1201    }
1202
1203    /// Returns the value of the `#[storage]` [Attribute], or [Purity::Pure] if the
1204    /// [Attributes] does not contain any `#[storage]` attributes.
1205    pub fn purity(&self) -> Purity {
1206        // `storage` attribute can be applied only once (`AttributeMultiplicity::Single`).
1207        // Last-wins approach.
1208        let Some(storage_attr) = self.of_kind(AttributeKind::Storage).last() else {
1209            return Purity::Pure;
1210        };
1211
1212        let mut purity = Purity::Pure;
1213
1214        let mut add_impurity = |new_impurity, counter_impurity| {
1215            if purity == Purity::Pure {
1216                purity = new_impurity;
1217            } else if purity == counter_impurity {
1218                purity = Purity::ReadsWrites;
1219            }
1220        };
1221
1222        for arg in storage_attr.args.iter() {
1223            match arg.name.as_str() {
1224                STORAGE_READ_ARG_NAME => add_impurity(Purity::Reads, Purity::Writes),
1225                STORAGE_WRITE_ARG_NAME => add_impurity(Purity::Writes, Purity::Reads),
1226                _ => {}
1227            }
1228        }
1229
1230        purity
1231    }
1232
1233    /// Returns the `#[deprecated]` [Attribute], or `None` if the
1234    /// [Attributes] does not contain any `#[deprecated]` attributes.
1235    pub fn deprecated(&self) -> Option<&Attribute> {
1236        self.deprecated_attr_index
1237            .map(|index| &self.attributes[index])
1238    }
1239
1240    /// Returns the `#[abi_name]` [Attribute], or `None` if the
1241    /// [Attributes] does not contain any `#[abi_name]` attributes.
1242    pub fn abi_name(&self) -> Option<&Attribute> {
1243        self.of_kind(AttributeKind::AbiName).last()
1244    }
1245
1246    /// Returns the `#[test]` [Attribute], or `None` if the
1247    /// [Attributes] does not contain any `#[test]` attributes.
1248    pub fn test(&self) -> Option<&Attribute> {
1249        // Last-wins approach.
1250        self.of_kind(AttributeKind::Test).last()
1251    }
1252
1253    /// Returns the `#[error]` [Attribute], or `None` if the
1254    /// [Attributes] does not contain any `#[error]` attributes.
1255    pub fn error(&self) -> Option<&Attribute> {
1256        // Last-wins approach.
1257        self.of_kind(AttributeKind::Error).last()
1258    }
1259
1260    /// Returns the error message of the `#[error]` [Attribute],
1261    /// or `None` if the [Attributes] does not contain any
1262    /// `#[error]` attributes, or if the attribute does not
1263    /// contain a message argument (`m`), or if the message
1264    /// argument is not a string.
1265    pub fn error_message(&self) -> Option<&String> {
1266        // Last-wins approach.
1267        self.error().and_then(|error_attr| {
1268            error_attr
1269                .args
1270                .iter()
1271                .rfind(|arg| arg.is_error_message())
1272                .and_then(|arg| arg.get_string_opt(&Handler::default()).ok().flatten())
1273        })
1274    }
1275
1276    /// Returns the `#[event]` [Attribute], or `None` if the
1277    /// [Attributes] does not contain any `#[event]` attributes.
1278    pub fn event(&self) -> Option<&Attribute> {
1279        self.of_kind(AttributeKind::Event).last()
1280    }
1281
1282    /// Returns the `#[indexed]` [Attribute], or `None` if the
1283    /// [Attributes] does not contain any `#[indexed]` attributes.
1284    pub fn indexed(&self) -> Option<&Attribute> {
1285        self.of_kind(AttributeKind::Indexed).last()
1286    }
1287}
1288
1289pub struct AllowDeprecatedEnterToken {
1290    diff: i32,
1291}
1292
1293#[derive(Default)]
1294pub struct AllowDeprecatedState {
1295    allowed: u32,
1296}
1297impl AllowDeprecatedState {
1298    pub(crate) fn enter(&mut self, attributes: Attributes) -> AllowDeprecatedEnterToken {
1299        if attributes.has_allow_deprecated() {
1300            self.allowed += 1;
1301
1302            AllowDeprecatedEnterToken { diff: -1 }
1303        } else {
1304            AllowDeprecatedEnterToken { diff: 0 }
1305        }
1306    }
1307
1308    pub(crate) fn exit(&mut self, token: AllowDeprecatedEnterToken) {
1309        self.allowed = self.allowed.saturating_add_signed(token.diff);
1310    }
1311
1312    pub(crate) fn is_allowed(&self) -> bool {
1313        self.allowed > 0
1314    }
1315}