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}
362
363/// Denotes if an [ItemTraitItem] belongs to an ABI or to a trait.
364#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum TraitItemParent {
366    Abi,
367    Trait,
368}
369
370/// Denotes if a [sway_ast::TypeField] belongs to a struct or to an enum.
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub enum StructOrEnumField {
373    StructField,
374    EnumField,
375}
376
377impl AttributeKind {
378    pub fn from_attribute_name(name: &str) -> Self {
379        match name {
380            DOC_COMMENT_ATTRIBUTE_NAME => AttributeKind::DocComment,
381            STORAGE_ATTRIBUTE_NAME => AttributeKind::Storage,
382            INLINE_ATTRIBUTE_NAME => AttributeKind::Inline,
383            TEST_ATTRIBUTE_NAME => AttributeKind::Test,
384            PAYABLE_ATTRIBUTE_NAME => AttributeKind::Payable,
385            ALLOW_ATTRIBUTE_NAME => AttributeKind::Allow,
386            CFG_ATTRIBUTE_NAME => AttributeKind::Cfg,
387            DEPRECATED_ATTRIBUTE_NAME => AttributeKind::Deprecated,
388            FALLBACK_ATTRIBUTE_NAME => AttributeKind::Fallback,
389            ERROR_TYPE_ATTRIBUTE_NAME => AttributeKind::ErrorType,
390            ERROR_ATTRIBUTE_NAME => AttributeKind::Error,
391            TRACE_ATTRIBUTE_NAME => AttributeKind::Trace,
392            ABI_NAME_ATTRIBUTE_NAME => AttributeKind::AbiName,
393            EVENT_ATTRIBUTE_NAME => AttributeKind::Event,
394            INDEXED_ATTRIBUTE_NAME => AttributeKind::Indexed,
395            _ => AttributeKind::Unknown,
396        }
397    }
398
399    /// True if multiple attributes of a this [AttributeKind] can
400    /// annotate the same item at the same time. E.g., the `inline`
401    /// attribute can be applied only once on an item and does not
402    /// allow multiple, while the `cfg` attribute can be applied
403    /// arbitrary many times.
404    ///
405    /// Currently we assume that the multiplicity does not depend
406    /// on the annotated item, but is an inherent property of
407    /// the [AttributeKind].
408    pub fn allows_multiple(&self) -> bool {
409        use AttributeKind::*;
410        match self {
411            Unknown => true,
412            DocComment => true,
413            Storage => false,
414            Inline => false,
415            Test => false,
416            Payable => false,
417            Allow => true,
418            Cfg => true,
419            Deprecated => false,
420            Fallback => false,
421            ErrorType => false,
422            Error => false,
423            Trace => false,
424            AbiName => false,
425            Event => false,
426            Indexed => false,
427        }
428    }
429}
430
431impl Attribute {
432    pub fn is_doc_comment(&self) -> bool {
433        self.kind == AttributeKind::DocComment
434    }
435
436    pub fn is_inner(&self) -> bool {
437        self.direction == AttributeDirection::Inner
438    }
439
440    pub fn is_outer(&self) -> bool {
441        self.direction == AttributeDirection::Outer
442    }
443
444    pub(crate) fn args_multiplicity(&self) -> ArgsMultiplicity {
445        use ArgsMultiplicity as Multiplicity;
446        use AttributeKind::*;
447        match self.kind {
448            Unknown => Multiplicity::arbitrary(),
449            // Each `doc-comment` attribute contains exactly one argument
450            // whose name is the actual documentation text and whose value is `None`.
451            // Thus, we expect exactly one argument.
452            DocComment => Multiplicity::exactly(1),
453            // `storage(read, write)`.
454            Storage => Multiplicity::between(1, 2),
455            // `inline(never)` or `inline(always)`.
456            Inline => Multiplicity::exactly(1),
457            // `test`, `test(should_revert)`.
458            Test => Multiplicity::at_most(1),
459            Payable => Multiplicity::zero(),
460            Allow => Multiplicity::at_least(1),
461            Cfg => Multiplicity::exactly(1),
462            // `deprecated`, `deprecated(note = "note")`.
463            Deprecated => Multiplicity::at_most(1),
464            Fallback => Multiplicity::zero(),
465            ErrorType => Multiplicity::zero(),
466            Error => Multiplicity::exactly(1),
467            // `trace(never)` or `trace(always)`.
468            Trace => Multiplicity::exactly(1),
469            AbiName => Multiplicity::exactly(1),
470            Event => Multiplicity::zero(),
471            Indexed => Multiplicity::zero(),
472        }
473    }
474
475    pub(crate) fn check_args_multiplicity(&self, handler: &Handler) -> Result<(), ErrorEmitted> {
476        if !self.args_multiplicity().contains(self.args.len()) {
477            Err(handler.emit_err(
478                ConvertParseTreeError::InvalidAttributeArgsMultiplicity {
479                    span: if self.args.is_empty() {
480                        self.name.span()
481                    } else {
482                        Span::join(
483                            self.args.first().unwrap().span(),
484                            &self.args.last().unwrap().span,
485                        )
486                    },
487                    attribute: self.name.clone(),
488                    args_multiplicity: (&self.args_multiplicity()).into(),
489                    num_of_args: self.args.len(),
490                }
491                .into(),
492            ))
493        } else {
494            Ok(())
495        }
496    }
497
498    pub(crate) fn can_have_arguments(&self) -> bool {
499        let args_multiplicity = self.args_multiplicity();
500        args_multiplicity.min != 0 || args_multiplicity.max != 0
501    }
502
503    pub(crate) fn expected_args(&self) -> ExpectedArgs {
504        use AttributeKind::*;
505        use ExpectedArgs::*;
506        match self.kind {
507            Unknown => Any,
508            DocComment => Any,
509            Storage => MustBeIn(vec![STORAGE_READ_ARG_NAME, STORAGE_WRITE_ARG_NAME]),
510            Inline => MustBeIn(vec![INLINE_ALWAYS_ARG_NAME, INLINE_NEVER_ARG_NAME]),
511            Test => MustBeIn(vec![TEST_SHOULD_REVERT_ARG_NAME]),
512            Payable => None,
513            Allow => ShouldBeIn(vec![ALLOW_DEAD_CODE_ARG_NAME, ALLOW_DEPRECATED_ARG_NAME]),
514            Cfg => {
515                let mut args = vec![
516                    // Arguments, ordered alphabetically.
517                    CFG_PROGRAM_TYPE_ARG_NAME,
518                    CFG_TARGET_ARG_NAME,
519                ];
520                args.extend(Feature::CFG.iter().sorted());
521                MustBeIn(args)
522            }
523            Deprecated => MustBeIn(vec![DEPRECATED_NOTE_ARG_NAME]),
524            Fallback => None,
525            ErrorType => None,
526            Error => MustBeIn(vec![ERROR_M_ARG_NAME]),
527            Trace => MustBeIn(vec![TRACE_ALWAYS_ARG_NAME, TRACE_NEVER_ARG_NAME]),
528            AbiName => MustBeIn(vec![ABI_NAME_NAME_ARG_NAME]),
529            Event => None,
530            Indexed => None,
531        }
532    }
533
534    pub(crate) fn args_expect_values(&self) -> ArgsExpectValues {
535        use ArgsExpectValues::*;
536        use AttributeKind::*;
537        match self.kind {
538            Unknown => Maybe,
539            // The actual documentation line is in the name of the attribute.
540            DocComment => No,
541            Storage => No,
542            Inline => No,
543            // `test(should_revert)`, `test(should_revert = "18446744073709486084")`.
544            Test => Maybe,
545            Payable => No,
546            Allow => No,
547            Cfg => Yes,
548            // `deprecated(note = "note")`.
549            Deprecated => Yes,
550            Fallback => No,
551            ErrorType => No,
552            // `error(msg = "msg")`.
553            Error => Yes,
554            Trace => No,
555            AbiName => Yes,
556            Event => No,
557            Indexed => No,
558        }
559    }
560
561    pub(crate) fn can_annotate_module_kind(&self) -> bool {
562        use AttributeKind::*;
563        match self.kind {
564            Unknown => false,
565            DocComment => self.direction == AttributeDirection::Inner,
566            Storage => false,
567            Inline => false,
568            Test => false,
569            Payable => false,
570            Allow => false,
571            Cfg => false,
572            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
573            //       Deprecating the module kind will mean deprecating all its items.
574            Deprecated => false,
575            Fallback => false,
576            ErrorType => false,
577            Error => false,
578            Trace => false,
579            AbiName => false,
580            Event => false,
581            Indexed => false,
582        }
583    }
584
585    pub(crate) fn can_annotate_abi(&self) -> bool {
586        use AttributeKind::*;
587        match self.kind {
588            Unknown => true,
589            DocComment => self.direction == AttributeDirection::Outer,
590            Storage => false,
591            Inline => false,
592            Test => false,
593            Payable => false,
594            Allow => true,
595            Cfg => true,
596            // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented.
597            Deprecated => false,
598            Fallback => false,
599            ErrorType => false,
600            Error => false,
601            Trace => false,
602            AbiName => false,
603            Event => false,
604            Indexed => false,
605        }
606    }
607
608    pub(crate) fn can_annotate_item_kind(&self, item_kind: &ItemKind) -> bool {
609        // TODO: Except for `DocComment`, we assume outer annotation here.
610        //       A separate check emits not-implemented error for all inner attributes.
611        //       Until we fully support inner attributes, this approach is sufficient.
612        //       See: https://github.com/FuelLabs/sway/issues/6924
613
614        // TODO: Currently we do not support any attributes on `mod`s, including doc comments.
615        //       See: https://github.com/FuelLabs/sway/issues/6879
616        //       See: https://github.com/FuelLabs/sway/issues/6925
617
618        // We accept all attribute kinds on the `ItemKind::Error`.
619        if matches!(item_kind, ItemKind::Error(..)) {
620            return true;
621        }
622
623        use AttributeKind::*;
624        match self.kind {
625            Unknown => !matches!(item_kind, ItemKind::Submodule(_)),
626            // We allow doc comments on all items including `storage` and `configurable`.
627            DocComment => {
628                self.direction == AttributeDirection::Outer
629                    && !matches!(item_kind, ItemKind::Submodule(_))
630            }
631            Storage => matches!(item_kind, ItemKind::Fn(_)),
632            Inline => matches!(item_kind, ItemKind::Fn(_)),
633            Test => matches!(item_kind, ItemKind::Fn(_)),
634            Payable => false,
635            Allow => !matches!(item_kind, ItemKind::Submodule(_)),
636            Cfg => !matches!(item_kind, ItemKind::Submodule(_)),
637            // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented.
638            Deprecated => match item_kind {
639                ItemKind::Submodule(_) => false,
640                ItemKind::Use(_) => false,
641                ItemKind::Struct(_) => true,
642                ItemKind::Enum(_) => true,
643                ItemKind::Fn(_) => true,
644                ItemKind::Trait(_) => false,
645                ItemKind::Impl(_) => false,
646                ItemKind::Abi(_) => false,
647                ItemKind::Const(_) => true,
648                ItemKind::Storage(_) => false,
649                // TODO: Currently, only single configurables can be deprecated.
650                //       Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
651                ItemKind::Configurable(_) => false,
652                ItemKind::TypeAlias(_) => false,
653                ItemKind::Error(_, _) => true,
654            },
655            Fallback => matches!(item_kind, ItemKind::Fn(_)),
656            ErrorType => matches!(item_kind, ItemKind::Enum(_)),
657            Error => false,
658            Trace => matches!(item_kind, ItemKind::Fn(_)),
659            AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
660            Event => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
661            Indexed => false,
662        }
663    }
664
665    // TODO: Add `can_annotated_nested_item_kind`, once we properly support nested items.
666    //       E.g., the `#[test]` attribute can annotate module functions (`ItemKind::Fn`),
667    //       but will not be allowed on nested functions.
668
669    pub(crate) fn can_annotate_struct_or_enum_field(
670        &self,
671        struct_or_enum_field: StructOrEnumField,
672    ) -> bool {
673        use AttributeKind::*;
674        match self.kind {
675            Unknown => true,
676            DocComment => self.direction == AttributeDirection::Outer,
677            Storage => false,
678            Inline => false,
679            Test => false,
680            Payable => false,
681            Allow => true,
682            Cfg => true,
683            Deprecated => true,
684            Fallback => false,
685            ErrorType => false,
686            Error => struct_or_enum_field == StructOrEnumField::EnumField,
687            Trace => false,
688            AbiName => false,
689            Event => false,
690            Indexed => matches!(struct_or_enum_field, StructOrEnumField::StructField),
691        }
692    }
693
694    /// True if `self` can annotate an *interface* `item` of a trait or ABI.
695    /// E.g.:
696    /// ```ignore
697    /// trait Trait {
698    ///   #[some_attribute]
699    ///   fn interface_fn();
700    ///   #[some_attribute]
701    ///   const INTERFACE_CONST: u64;
702    /// }
703    /// ```
704    pub(crate) fn can_annotate_abi_or_trait_interface_item(
705        &self,
706        item: &ItemTraitItem,
707        parent: TraitItemParent,
708    ) -> bool {
709        use AttributeKind::*;
710        match self.kind {
711            Unknown => true,
712            DocComment => self.direction == AttributeDirection::Outer,
713            Storage => matches!(item, ItemTraitItem::Fn(..)),
714            // Functions in the trait or ABI interface surface cannot be marked as inlined
715            // because they don't have implementation.
716            Inline => false,
717            Test => false,
718            Payable => parent == TraitItemParent::Abi && matches!(item, ItemTraitItem::Fn(..)),
719            Allow => true,
720            Cfg => true,
721            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
722            Deprecated => false,
723            Fallback => false,
724            ErrorType => false,
725            Error => false,
726            // Functions in the trait or ABI interface surface cannot be marked as traced
727            // because they don't have implementation.
728            Trace => false,
729            AbiName => false,
730            Event => false,
731            Indexed => false,
732        }
733    }
734
735    /// True if `self` can annotate an *interface* function of a trait or ABI.
736    /// E.g.:
737    /// ```ignore
738    /// trait Trait {
739    ///   #[some_attribute]
740    ///   fn interface_fn();
741    /// }
742    /// ```
743    pub(crate) fn can_annotate_abi_or_trait_interface_fn(&self, parent: TraitItemParent) -> bool {
744        use AttributeKind::*;
745        match self.kind {
746            Unknown => true,
747            DocComment => self.direction == AttributeDirection::Outer,
748            Storage => true,
749            // Functions in the trait or ABI interface surface cannot be marked as inlined
750            // because they don't have implementation.
751            Inline => false,
752            Test => false,
753            Payable => parent == TraitItemParent::Abi,
754            Allow => true,
755            Cfg => true,
756            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
757            Deprecated => false,
758            Fallback => false,
759            ErrorType => false,
760            Error => false,
761            // Functions in the trait or ABI interface surface cannot be marked as traced
762            // because they don't have implementation.
763            Trace => false,
764            AbiName => false,
765            Event => false,
766            Indexed => false,
767        }
768    }
769
770    /// True if `self` can annotate an *interface* const of a trait or ABI.
771    /// E.g.:
772    /// ```ignore
773    /// trait Trait {
774    ///   #[some_attribute]
775    ///   const INTERFACE_CONST: u64;
776    /// }
777    /// ```
778    pub(crate) fn can_annotate_abi_or_trait_interface_const(
779        &self,
780        _parent: TraitItemParent,
781    ) -> bool {
782        use AttributeKind::*;
783        match self.kind {
784            Unknown => true,
785            DocComment => self.direction == AttributeDirection::Outer,
786            Storage => false,
787            Inline => false,
788            Test => false,
789            Payable => false,
790            Allow => true,
791            Cfg => true,
792            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
793            Deprecated => false,
794            Fallback => false,
795            ErrorType => false,
796            Error => false,
797            Trace => false,
798            AbiName => false,
799            Event => false,
800            Indexed => false,
801        }
802    }
803
804    /// True if `self` can annotate an `item` in an `impl` block.
805    /// E.g.:
806    /// ```ignore
807    /// impl Trait for Type {
808    ///   #[some_attribute]
809    ///   fn impl_fn() {}
810    ///   #[some_attribute]
811    ///   const CONST: u64 = 42;
812    /// }
813    /// ```
814    pub(crate) fn can_annotate_impl_item(
815        &self,
816        item: &ItemImplItem,
817        parent: ImplItemParent,
818    ) -> bool {
819        use AttributeKind::*;
820        match self.kind {
821            Unknown => true,
822            DocComment => self.direction == AttributeDirection::Outer,
823            Storage => matches!(item, ItemImplItem::Fn(..)),
824            Inline => matches!(item, ItemImplItem::Fn(..)),
825            Test => false,
826            Payable => matches!(item, ItemImplItem::Fn(..)) && parent == ImplItemParent::Contract,
827            Allow => true,
828            Cfg => true,
829            Deprecated => !matches!(item, ItemImplItem::Type(_)),
830            Fallback => false,
831            ErrorType => false,
832            Error => false,
833            Trace => matches!(item, ItemImplItem::Fn(..)),
834            AbiName => false,
835            Event => false,
836            Indexed => false,
837        }
838    }
839
840    /// True if `self` can annotate a *provided* ABI or trait function.
841    /// E.g.:
842    /// ```ignore
843    /// trait Trait {
844    ///   fn interface_fn();
845    /// } {
846    ///   #[some_attribute]
847    ///   fn provided_fn() {}
848    /// }
849    /// ```
850    pub(crate) fn can_annotate_abi_or_trait_provided_fn(&self, parent: TraitItemParent) -> bool {
851        use AttributeKind::*;
852        match self.kind {
853            Unknown => true,
854            DocComment => self.direction == AttributeDirection::Outer,
855            Storage => true,
856            Inline => true,
857            Test => false,
858            Payable => parent == TraitItemParent::Abi,
859            Allow => true,
860            Cfg => true,
861            Deprecated => true,
862            Fallback => false,
863            ErrorType => false,
864            Error => false,
865            Trace => true,
866            AbiName => false,
867            Event => false,
868            Indexed => false,
869        }
870    }
871
872    pub(crate) fn can_annotate_storage_entry(&self) -> bool {
873        use AttributeKind::*;
874        match self.kind {
875            Unknown => true,
876            DocComment => self.direction == AttributeDirection::Outer,
877            Storage => false,
878            Inline => false,
879            Test => false,
880            Payable => false,
881            Allow => true,
882            Cfg => true,
883            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
884            Deprecated => false,
885            Fallback => false,
886            ErrorType => false,
887            Error => false,
888            Trace => false,
889            AbiName => false,
890            Event => false,
891            Indexed => false,
892        }
893    }
894
895    pub(crate) fn can_annotate_configurable_field(&self) -> bool {
896        use AttributeKind::*;
897        match self.kind {
898            Unknown => true,
899            DocComment => self.direction == AttributeDirection::Outer,
900            Storage => false,
901            Inline => false,
902            Test => false,
903            Payable => false,
904            Allow => true,
905            Cfg => true,
906            Deprecated => true,
907            Fallback => false,
908            ErrorType => false,
909            Error => false,
910            Trace => false,
911            AbiName => false,
912            Event => false,
913            Indexed => false,
914        }
915    }
916
917    pub(crate) fn can_only_annotate_help(&self, target_friendly_name: &str) -> Vec<&'static str> {
918        // Using strings to identify targets is not ideal, but there
919        // is no real need for a more complex and type-safe identification here.
920        use AttributeKind::*;
921        let help = match self.kind {
922            Unknown => vec![],
923            DocComment => match self.direction {
924                AttributeDirection::Inner => vec![
925                    "Inner doc comments (`//!`) can only document modules and must be",
926                    "at the beginning of the module file, before the module kind.",
927                ],
928                AttributeDirection::Outer => if target_friendly_name.starts_with("module kind") {
929                    vec![
930                        "To document modules, use inner doc comments (`//!`). E.g.:",
931                        "//! This doc comment documents a module.",
932                    ]
933                } else {
934                    vec![]
935                },
936            },
937            Storage => {
938                if target_friendly_name == "function signature" {
939                    vec![
940                        "\"storage\" attribute can only annotate functions that have an implementation.",
941                        "Function signatures in ABI and trait declarations do not have implementations.",
942                    ]
943                } else {
944                    vec![
945                        "\"storage\" attribute can only annotate functions.",
946                    ]
947                }
948            },
949            Inline => vec!["\"inline\" attribute can only annotate functions."],
950            Test => vec!["\"test\" attribute can only annotate module functions."],
951            Payable => vec![
952                "\"payable\" attribute can only annotate:",
953                "  - ABI function signatures and their implementations in contracts,",
954                "  - provided ABI functions.",
955            ],
956            Allow => vec![],
957            Cfg => vec![],
958            // TODO: Remove this help lines once https://github.com/FuelLabs/sway/issues/6942 is implemented.
959            Deprecated => vec![
960                "\"deprecated\" attribute is currently not implemented for all elements that could be deprecated.",
961            ],
962            Fallback => vec!["\"fallback\" attribute can only annotate module functions in a contract module."],
963            ErrorType => vec!["\"error_type\" attribute can only annotate enums."],
964            Error => vec!["\"error\" attribute can only annotate enum variants of enums annotated with the \"error_type\" attribute."],
965            Trace => vec!["\"trace\" attribute can only annotate functions."],
966            AbiName => vec![
967                "\"abi_name\" attribute can only annotate structs and enums.",
968            ],
969            Event => vec![
970                "\"event\" attribute can only annotate structs or enums.",
971            ],
972            Indexed => vec![
973                "\"indexed\" attribute can only annotate struct fields.",
974            ],
975        };
976
977        if help.is_empty() && target_friendly_name.starts_with("module kind") {
978            vec!["Annotating module kinds (contract, script, predicate, or library) is currently not implemented."]
979        } else {
980            help
981        }
982    }
983}
984
985/// Stores the [Attribute]s that annotate an element.
986///
987/// Note that once stored in the [Attributes], the [Attribute]s lose
988/// the information about their enclosing [AttributeDecl].
989///
990/// The map can contain erroneous attributes. A typical example s containing
991/// several attributes of an [AttributeKind] that allows only a single attribute
992/// to be applied, like, e.g., `#[deprecated]`, or `#[test]`.
993///
994/// When retrieving such attributes, we follow the last-wins approach
995/// and return the last attribute in the order of declaration.
996#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
997pub struct Attributes {
998    // Note that we don't need a map here, to store attributes because:
999    //  - Attributes will mostly be empty.
1000    //  - Per `AttributeKind` there will usually be just one element.
1001    //  - The only exception are comments, that anyhow need to be traversed sequentially
1002    //    and will dominate in the list of attributes or mostly be the only attributes.
1003    //  - Most of the analysis requires traversing all attributes regardless of the `AttributeKind`.
1004    //  - Analysis that is interested in `AttributeKind` anyhow needs to apply a filter first.
1005    //  - Attributes are accessed only once, when checking the declaration of the annotated element.
1006    /// [Attribute]s, in the order of their declaration.
1007    attributes: Arc<Vec<Attribute>>,
1008    // `#[deprecated]` is the only attribute requested on call sites,
1009    // and we provide a O(1) access to it.
1010    /// The index of the last `#[deprecated]` attribute, if any.
1011    deprecated_attr_index: Option<usize>,
1012}
1013
1014impl Attributes {
1015    pub fn new(attribute_decls: &[AttributeDecl]) -> Attributes {
1016        let mut attributes: Vec<Attribute> = vec![];
1017        for attr_decl in attribute_decls {
1018            let attrs = attr_decl.attribute.get().into_iter();
1019            for attr in attrs {
1020                let name = attr.name.as_str();
1021                let args = attr
1022                    .args
1023                    .as_ref()
1024                    .map(|parens| {
1025                        parens
1026                            .get()
1027                            .into_iter()
1028                            .map(|arg| AttributeArg {
1029                                name: arg.name.clone(),
1030                                value: arg.value.clone(),
1031                                span: arg.span(),
1032                            })
1033                            .collect()
1034                    })
1035                    .unwrap_or_default();
1036
1037                let attribute = Attribute {
1038                    direction: (&attr_decl.hash_kind).into(),
1039                    name: attr.name.clone(),
1040                    args,
1041                    span: attr_decl.span(),
1042                    kind: AttributeKind::from_attribute_name(name),
1043                };
1044
1045                attributes.push(attribute);
1046            }
1047        }
1048
1049        Attributes {
1050            deprecated_attr_index: attributes
1051                .iter()
1052                .rposition(|attr| attr.kind == AttributeKind::Deprecated),
1053            attributes: Arc::new(attributes),
1054        }
1055    }
1056
1057    /// Creates new [Attributes] by copying `other`s attributes and retaining
1058    /// only those specified by the predicate `f`.
1059    pub fn retain_from(other: &Attributes, f: impl Fn(&Attribute) -> bool) -> Self {
1060        let attributes = other
1061            .attributes
1062            .iter()
1063            .filter(|attr| f(attr))
1064            .cloned()
1065            .collect_vec();
1066        Self {
1067            deprecated_attr_index: attributes
1068                .iter()
1069                .rposition(|attr| attr.kind == AttributeKind::Deprecated),
1070            attributes: Arc::new(attributes),
1071        }
1072    }
1073
1074    pub fn is_empty(&self) -> bool {
1075        self.attributes.is_empty()
1076    }
1077
1078    /// Returns the first attribute, ordered by span, or None if there are no attributes.
1079    pub fn first(&self) -> Option<&Attribute> {
1080        self.attributes.first()
1081    }
1082
1083    pub fn known_attribute_names(&self) -> &'static [&'static str] {
1084        KNOWN_ATTRIBUTE_NAMES
1085    }
1086
1087    pub fn all(&self) -> impl Iterator<Item = &Attribute> {
1088        self.attributes.iter()
1089    }
1090
1091    pub fn all_as_slice(&self) -> &[Attribute] {
1092        self.attributes.as_slice()
1093    }
1094
1095    pub fn all_by_kind<F>(&self, predicate: F) -> IndexMap<AttributeKind, Vec<&Attribute>>
1096    where
1097        F: Fn(&&Attribute) -> bool,
1098    {
1099        let mut result = IndexMap::<_, Vec<&Attribute>>::new();
1100        for attr in self.attributes.iter().filter(predicate) {
1101            result.entry(attr.kind).or_default().push(attr);
1102        }
1103        result
1104    }
1105
1106    pub fn of_kind(&self, kind: AttributeKind) -> impl Iterator<Item = &Attribute> {
1107        self.attributes.iter().filter(move |attr| attr.kind == kind)
1108    }
1109
1110    pub fn has_any_of_kind(&self, kind: AttributeKind) -> bool {
1111        self.of_kind(kind).any(|_| true)
1112    }
1113
1114    pub fn unknown(&self) -> impl Iterator<Item = &Attribute> {
1115        self.attributes
1116            .iter()
1117            .filter(|attr| attr.kind == AttributeKind::Unknown)
1118    }
1119
1120    pub fn has_allow_dead_code(&self) -> bool {
1121        self.has_allow(|arg| arg.is_allow_dead_code())
1122    }
1123
1124    pub fn has_allow_deprecated(&self) -> bool {
1125        self.has_allow(|arg| arg.is_allow_deprecated())
1126    }
1127
1128    fn has_allow(&self, arg_filter: impl Fn(&AttributeArg) -> bool) -> bool {
1129        self.of_kind(AttributeKind::Allow)
1130            .flat_map(|attribute| &attribute.args)
1131            .any(arg_filter)
1132    }
1133
1134    pub fn has_error_type(&self) -> bool {
1135        self.of_kind(AttributeKind::ErrorType).any(|_| true)
1136    }
1137
1138    pub fn has_error(&self) -> bool {
1139        self.of_kind(AttributeKind::Error).any(|_| true)
1140    }
1141
1142    /// Returns the value of the `#[inline]` [Attribute], or `None` if the
1143    /// [Attributes] does not contain any `#[inline]` attributes.
1144    pub fn inline(&self) -> Option<Inline> {
1145        // `inline` attribute can be applied only once (`AttributeMultiplicity::Single`),
1146        // and can have exactly one argument, otherwise an error is emitted.
1147        // Last-wins approach.
1148        match self
1149            .of_kind(AttributeKind::Inline)
1150            .last()?
1151            .args
1152            .last()?
1153            .name
1154            .as_str()
1155        {
1156            INLINE_NEVER_ARG_NAME => Some(Inline::Never),
1157            INLINE_ALWAYS_ARG_NAME => Some(Inline::Always),
1158            _ => None,
1159        }
1160    }
1161
1162    /// Returns the value of the `#[trace]` [Attribute], or `None` if the
1163    /// [Attributes] does not contain any `#[trace]` attributes.
1164    pub fn trace(&self) -> Option<Trace> {
1165        // `trace` attribute can be applied only once (`AttributeMultiplicity::Single`),
1166        // and can have exactly one argument, otherwise an error is emitted.
1167        // Last-wins approach.
1168        match self
1169            .of_kind(AttributeKind::Trace)
1170            .last()?
1171            .args
1172            .last()?
1173            .name
1174            .as_str()
1175        {
1176            TRACE_NEVER_ARG_NAME => Some(Trace::Never),
1177            TRACE_ALWAYS_ARG_NAME => Some(Trace::Always),
1178            _ => None,
1179        }
1180    }
1181
1182    /// Returns the value of the `#[storage]` [Attribute], or [Purity::Pure] if the
1183    /// [Attributes] does not contain any `#[storage]` attributes.
1184    pub fn purity(&self) -> Purity {
1185        // `storage` attribute can be applied only once (`AttributeMultiplicity::Single`).
1186        // Last-wins approach.
1187        let Some(storage_attr) = self.of_kind(AttributeKind::Storage).last() else {
1188            return Purity::Pure;
1189        };
1190
1191        let mut purity = Purity::Pure;
1192
1193        let mut add_impurity = |new_impurity, counter_impurity| {
1194            if purity == Purity::Pure {
1195                purity = new_impurity;
1196            } else if purity == counter_impurity {
1197                purity = Purity::ReadsWrites;
1198            }
1199        };
1200
1201        for arg in storage_attr.args.iter() {
1202            match arg.name.as_str() {
1203                STORAGE_READ_ARG_NAME => add_impurity(Purity::Reads, Purity::Writes),
1204                STORAGE_WRITE_ARG_NAME => add_impurity(Purity::Writes, Purity::Reads),
1205                _ => {}
1206            }
1207        }
1208
1209        purity
1210    }
1211
1212    /// Returns the `#[deprecated]` [Attribute], or `None` if the
1213    /// [Attributes] does not contain any `#[deprecated]` attributes.
1214    pub fn deprecated(&self) -> Option<&Attribute> {
1215        self.deprecated_attr_index
1216            .map(|index| &self.attributes[index])
1217    }
1218
1219    /// Returns the `#[abi_name]` [Attribute], or `None` if the
1220    /// [Attributes] does not contain any `#[abi_name]` attributes.
1221    pub fn abi_name(&self) -> Option<&Attribute> {
1222        self.of_kind(AttributeKind::AbiName).last()
1223    }
1224
1225    /// Returns the `#[test]` [Attribute], or `None` if the
1226    /// [Attributes] does not contain any `#[test]` attributes.
1227    pub fn test(&self) -> Option<&Attribute> {
1228        // Last-wins approach.
1229        self.of_kind(AttributeKind::Test).last()
1230    }
1231
1232    /// Returns the `#[error]` [Attribute], or `None` if the
1233    /// [Attributes] does not contain any `#[error]` attributes.
1234    pub fn error(&self) -> Option<&Attribute> {
1235        // Last-wins approach.
1236        self.of_kind(AttributeKind::Error).last()
1237    }
1238
1239    /// Returns the error message of the `#[error]` [Attribute],
1240    /// or `None` if the [Attributes] does not contain any
1241    /// `#[error]` attributes, or if the attribute does not
1242    /// contain a message argument (`m`), or if the message
1243    /// argument is not a string.
1244    pub fn error_message(&self) -> Option<&String> {
1245        // Last-wins approach.
1246        self.error().and_then(|error_attr| {
1247            error_attr
1248                .args
1249                .iter()
1250                .rfind(|arg| arg.is_error_message())
1251                .and_then(|arg| arg.get_string_opt(&Handler::default()).ok().flatten())
1252        })
1253    }
1254
1255    /// Returns the `#[event]` [Attribute], or `None` if the
1256    /// [Attributes] does not contain any `#[event]` attributes.
1257    pub fn event(&self) -> Option<&Attribute> {
1258        self.of_kind(AttributeKind::Event).last()
1259    }
1260
1261    /// Returns the `#[indexed]` [Attribute], or `None` if the
1262    /// [Attributes] does not contain any `#[indexed]` attributes.
1263    pub fn indexed(&self) -> Option<&Attribute> {
1264        self.of_kind(AttributeKind::Indexed).last()
1265    }
1266}
1267
1268pub struct AllowDeprecatedEnterToken {
1269    diff: i32,
1270}
1271
1272#[derive(Default)]
1273pub struct AllowDeprecatedState {
1274    allowed: u32,
1275}
1276impl AllowDeprecatedState {
1277    pub(crate) fn enter(&mut self, attributes: Attributes) -> AllowDeprecatedEnterToken {
1278        if attributes.has_allow_deprecated() {
1279            self.allowed += 1;
1280
1281            AllowDeprecatedEnterToken { diff: -1 }
1282        } else {
1283            AllowDeprecatedEnterToken { diff: 0 }
1284        }
1285    }
1286
1287    pub(crate) fn exit(&mut self, token: AllowDeprecatedEnterToken) {
1288        self.allowed = self.allowed.saturating_add_signed(token.diff);
1289    }
1290
1291    pub(crate) fn is_allowed(&self) -> bool {
1292        self.allowed > 0
1293    }
1294}