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_item_kind(&self, item_kind: &ItemKind) -> bool {
586        // TODO: Except for `DocComment`, we assume outer annotation here.
587        //       A separate check emits not-implemented error for all inner attributes.
588        //       Until we fully support inner attributes, this approach is sufficient.
589        //       See: https://github.com/FuelLabs/sway/issues/6924
590
591        // TODO: Currently we do not support any attributes on `mod`s, including doc comments.
592        //       See: https://github.com/FuelLabs/sway/issues/6879
593        //       See: https://github.com/FuelLabs/sway/issues/6925
594
595        // We accept all attribute kinds on the `ItemKind::Error`.
596        if matches!(item_kind, ItemKind::Error(..)) {
597            return true;
598        }
599
600        use AttributeKind::*;
601        match self.kind {
602            Unknown => !matches!(item_kind, ItemKind::Submodule(_)),
603            // We allow doc comments on all items including `storage` and `configurable`.
604            DocComment => {
605                self.direction == AttributeDirection::Outer
606                    && !matches!(item_kind, ItemKind::Submodule(_))
607            }
608            Storage => matches!(item_kind, ItemKind::Fn(_)),
609            Inline => matches!(item_kind, ItemKind::Fn(_)),
610            Test => matches!(item_kind, ItemKind::Fn(_)),
611            Payable => false,
612            Allow => !matches!(item_kind, ItemKind::Submodule(_)),
613            Cfg => !matches!(item_kind, ItemKind::Submodule(_)),
614            // TODO: Adapt once https://github.com/FuelLabs/sway/issues/6942 is implemented.
615            Deprecated => match item_kind {
616                ItemKind::Submodule(_) => false,
617                ItemKind::Use(_) => false,
618                ItemKind::Struct(_) => true,
619                ItemKind::Enum(_) => true,
620                ItemKind::Fn(_) => true,
621                ItemKind::Trait(_) => false,
622                ItemKind::Impl(_) => false,
623                ItemKind::Abi(_) => false,
624                ItemKind::Const(_) => true,
625                ItemKind::Storage(_) => false,
626                // TODO: Currently, only single configurables can be deprecated.
627                //       Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
628                ItemKind::Configurable(_) => false,
629                ItemKind::TypeAlias(_) => false,
630                ItemKind::Error(_, _) => true,
631            },
632            Fallback => matches!(item_kind, ItemKind::Fn(_)),
633            ErrorType => matches!(item_kind, ItemKind::Enum(_)),
634            Error => false,
635            Trace => matches!(item_kind, ItemKind::Fn(_)),
636            AbiName => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
637            Event => matches!(item_kind, ItemKind::Struct(_) | ItemKind::Enum(_)),
638            Indexed => false,
639        }
640    }
641
642    // TODO: Add `can_annotated_nested_item_kind`, once we properly support nested items.
643    //       E.g., the `#[test]` attribute can annotate module functions (`ItemKind::Fn`),
644    //       but will not be allowed on nested functions.
645
646    pub(crate) fn can_annotate_struct_or_enum_field(
647        &self,
648        struct_or_enum_field: StructOrEnumField,
649    ) -> bool {
650        use AttributeKind::*;
651        match self.kind {
652            Unknown => true,
653            DocComment => self.direction == AttributeDirection::Outer,
654            Storage => false,
655            Inline => false,
656            Test => false,
657            Payable => false,
658            Allow => true,
659            Cfg => true,
660            Deprecated => true,
661            Fallback => false,
662            ErrorType => false,
663            Error => struct_or_enum_field == StructOrEnumField::EnumField,
664            Trace => false,
665            AbiName => false,
666            Event => false,
667            Indexed => matches!(struct_or_enum_field, StructOrEnumField::StructField),
668        }
669    }
670
671    pub(crate) fn can_annotate_abi_or_trait_item(
672        &self,
673        item: &ItemTraitItem,
674        parent: TraitItemParent,
675    ) -> bool {
676        use AttributeKind::*;
677        match self.kind {
678            Unknown => true,
679            DocComment => self.direction == AttributeDirection::Outer,
680            Storage => matches!(item, ItemTraitItem::Fn(..)),
681            // Functions in the trait or ABI interface surface cannot be marked as inlined
682            // because they don't have implementation.
683            Inline => false,
684            Test => false,
685            Payable => parent == TraitItemParent::Abi && matches!(item, ItemTraitItem::Fn(..)),
686            Allow => true,
687            Cfg => true,
688            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
689            Deprecated => false,
690            Fallback => false,
691            ErrorType => false,
692            Error => false,
693            // Functions in the trait or ABI interface surface cannot be marked as traced
694            // because they don't have implementation.
695            Trace => false,
696            AbiName => false,
697            Event => false,
698            Indexed => false,
699        }
700    }
701
702    pub(crate) fn can_annotate_impl_item(
703        &self,
704        item: &ItemImplItem,
705        parent: ImplItemParent,
706    ) -> bool {
707        use AttributeKind::*;
708        match self.kind {
709            Unknown => true,
710            DocComment => self.direction == AttributeDirection::Outer,
711            Storage => matches!(item, ItemImplItem::Fn(..)),
712            Inline => matches!(item, ItemImplItem::Fn(..)),
713            Test => false,
714            Payable => parent == ImplItemParent::Contract,
715            Allow => true,
716            Cfg => true,
717            Deprecated => !matches!(item, ItemImplItem::Type(_)),
718            Fallback => false,
719            ErrorType => false,
720            Error => false,
721            Trace => matches!(item, ItemImplItem::Fn(..)),
722            AbiName => false,
723            Event => false,
724            Indexed => false,
725        }
726    }
727
728    pub(crate) fn can_annotate_abi_or_trait_item_fn(
729        &self,
730        abi_or_trait_item: TraitItemParent,
731    ) -> bool {
732        use AttributeKind::*;
733        match self.kind {
734            Unknown => true,
735            DocComment => self.direction == AttributeDirection::Outer,
736            Storage => true,
737            Inline => true,
738            Test => false,
739            Payable => abi_or_trait_item == TraitItemParent::Abi,
740            Allow => true,
741            Cfg => true,
742            Deprecated => true,
743            Fallback => false,
744            ErrorType => false,
745            Error => false,
746            Trace => true,
747            AbiName => false,
748            Event => false,
749            Indexed => false,
750        }
751    }
752
753    pub(crate) fn can_annotate_storage_entry(&self) -> bool {
754        use AttributeKind::*;
755        match self.kind {
756            Unknown => true,
757            DocComment => self.direction == AttributeDirection::Outer,
758            Storage => false,
759            Inline => false,
760            Test => false,
761            Payable => false,
762            Allow => true,
763            Cfg => true,
764            // TODO: Change to true once https://github.com/FuelLabs/sway/issues/6942 is implemented.
765            Deprecated => false,
766            Fallback => false,
767            ErrorType => false,
768            Error => false,
769            Trace => false,
770            AbiName => false,
771            Event => false,
772            Indexed => false,
773        }
774    }
775
776    pub(crate) fn can_annotate_configurable_field(&self) -> bool {
777        use AttributeKind::*;
778        match self.kind {
779            Unknown => true,
780            DocComment => self.direction == AttributeDirection::Outer,
781            Storage => false,
782            Inline => false,
783            Test => false,
784            Payable => false,
785            Allow => true,
786            Cfg => true,
787            Deprecated => true,
788            Fallback => false,
789            ErrorType => false,
790            Error => false,
791            Trace => false,
792            AbiName => false,
793            Event => false,
794            Indexed => false,
795        }
796    }
797
798    pub(crate) fn can_only_annotate_help(&self, target_friendly_name: &str) -> Vec<&'static str> {
799        // Using strings to identify targets is not ideal, but there
800        // is no real need for a more complex and type-safe identification here.
801        use AttributeKind::*;
802        let help = match self.kind {
803            Unknown => vec![],
804            DocComment => match self.direction {
805                AttributeDirection::Inner => vec![
806                    "Inner doc comments (`//!`) can only document modules and must be",
807                    "at the beginning of the module file, before the module kind.",
808                ],
809                AttributeDirection::Outer => if target_friendly_name.starts_with("module kind") {
810                    vec![
811                        "To document modules, use inner doc comments (`//!`). E.g.:",
812                        "//! This doc comment documents a module.",
813                    ]
814                } else {
815                    vec![]
816                },
817            },
818            Storage => {
819                if target_friendly_name == "function signature" {
820                    vec![
821                        "\"storage\" attribute can only annotate functions that have an implementation.",
822                        "Function signatures in ABI and trait declarations do not have implementations.",
823                    ]
824                } else {
825                    vec![
826                        "\"storage\" attribute can only annotate functions.",
827                    ]
828                }
829            },
830            Inline => vec!["\"inline\" attribute can only annotate functions."],
831            Test => vec!["\"test\" attribute can only annotate module functions."],
832            Payable => vec![
833                "\"payable\" attribute can only annotate:",
834                "  - ABI function signatures and their implementations in contracts,",
835                "  - provided ABI functions.",
836            ],
837            Allow => vec![],
838            Cfg => vec![],
839            // TODO: Remove this help lines once https://github.com/FuelLabs/sway/issues/6942 is implemented.
840            Deprecated => vec![
841                "\"deprecated\" attribute is currently not implemented for all elements that could be deprecated.",
842            ],
843            Fallback => vec!["\"fallback\" attribute can only annotate module functions in a contract module."],
844            ErrorType => vec!["\"error_type\" attribute can only annotate enums."],
845            Error => vec!["\"error\" attribute can only annotate enum variants of enums annotated with the \"error_type\" attribute."],
846            Trace => vec!["\"trace\" attribute can only annotate functions."],
847            AbiName => vec![
848                "\"abi_name\" attribute can only annotate structs and enums.",
849            ],
850            Event => vec![
851                "\"event\" attribute can only annotate structs or enums.",
852            ],
853            Indexed => vec![
854                "\"indexed\" attribute can only annotate struct fields.",
855            ],
856        };
857
858        if help.is_empty() && target_friendly_name.starts_with("module kind") {
859            vec!["Annotating module kinds (contract, script, predicate, or library) is currently not implemented."]
860        } else {
861            help
862        }
863    }
864}
865
866/// Stores the [Attribute]s that annotate an element.
867///
868/// Note that once stored in the [Attributes], the [Attribute]s lose
869/// the information about their enclosing [AttributeDecl].
870///
871/// The map can contain erroneous attributes. A typical example s containing
872/// several attributes of an [AttributeKind] that allows only a single attribute
873/// to be applied, like, e.g., `#[deprecated]`, or `#[test]`.
874///
875/// When retrieving such attributes, we follow the last-wins approach
876/// and return the last attribute in the order of declaration.
877#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
878pub struct Attributes {
879    // Note that we don't need a map here, to store attributes because:
880    //  - Attributes will mostly be empty.
881    //  - Per `AttributeKind` there will usually be just one element.
882    //  - The only exception are comments, that anyhow need to be traversed sequentially
883    //    and will dominate in the list of attributes or mostly be the only attributes.
884    //  - Most of the analysis requires traversing all attributes regardless of the `AttributeKind`.
885    //  - Analysis that is interested in `AttributeKind` anyhow needs to apply a filter first.
886    //  - Attributes are accessed only once, when checking the declaration of the annotated element.
887    /// [Attribute]s, in the order of their declaration.
888    attributes: Arc<Vec<Attribute>>,
889    // `#[deprecated]` is the only attribute requested on call sites,
890    // and we provide a O(1) access to it.
891    /// The index of the last `#[deprecated]` attribute, if any.
892    deprecated_attr_index: Option<usize>,
893}
894
895impl Attributes {
896    pub fn new(attribute_decls: &[AttributeDecl]) -> Attributes {
897        let mut attributes: Vec<Attribute> = vec![];
898        for attr_decl in attribute_decls {
899            let attrs = attr_decl.attribute.get().into_iter();
900            for attr in attrs {
901                let name = attr.name.as_str();
902                let args = attr
903                    .args
904                    .as_ref()
905                    .map(|parens| {
906                        parens
907                            .get()
908                            .into_iter()
909                            .cloned()
910                            .map(|arg| AttributeArg {
911                                name: arg.name.clone(),
912                                value: arg.value.clone(),
913                                span: arg.span(),
914                            })
915                            .collect()
916                    })
917                    .unwrap_or_default();
918
919                let attribute = Attribute {
920                    direction: (&attr_decl.hash_kind).into(),
921                    name: attr.name.clone(),
922                    args,
923                    span: attr_decl.span(),
924                    kind: AttributeKind::from_attribute_name(name),
925                };
926
927                attributes.push(attribute);
928            }
929        }
930
931        Attributes {
932            deprecated_attr_index: attributes
933                .iter()
934                .rposition(|attr| attr.kind == AttributeKind::Deprecated),
935            attributes: Arc::new(attributes),
936        }
937    }
938
939    pub fn is_empty(&self) -> bool {
940        self.attributes.is_empty()
941    }
942
943    /// Returns the first attribute, ordered by span, or None if there are no attributes.
944    pub fn first(&self) -> Option<&Attribute> {
945        self.attributes.first()
946    }
947
948    pub fn known_attribute_names(&self) -> &'static [&'static str] {
949        KNOWN_ATTRIBUTE_NAMES
950    }
951
952    pub fn all(&self) -> impl Iterator<Item = &Attribute> {
953        self.attributes.iter()
954    }
955
956    pub fn all_as_slice(&self) -> &[Attribute] {
957        self.attributes.as_slice()
958    }
959
960    pub fn all_by_kind<F>(&self, predicate: F) -> IndexMap<AttributeKind, Vec<&Attribute>>
961    where
962        F: Fn(&&Attribute) -> bool,
963    {
964        let mut result = IndexMap::<_, Vec<&Attribute>>::new();
965        for attr in self.attributes.iter().filter(predicate) {
966            result.entry(attr.kind).or_default().push(attr);
967        }
968        result
969    }
970
971    pub fn of_kind(&self, kind: AttributeKind) -> impl Iterator<Item = &Attribute> {
972        self.attributes.iter().filter(move |attr| attr.kind == kind)
973    }
974
975    pub fn has_any_of_kind(&self, kind: AttributeKind) -> bool {
976        self.of_kind(kind).any(|_| true)
977    }
978
979    pub fn unknown(&self) -> impl Iterator<Item = &Attribute> {
980        self.attributes
981            .iter()
982            .filter(|attr| attr.kind == AttributeKind::Unknown)
983    }
984
985    pub fn has_allow_dead_code(&self) -> bool {
986        self.has_allow(|arg| arg.is_allow_dead_code())
987    }
988
989    pub fn has_allow_deprecated(&self) -> bool {
990        self.has_allow(|arg| arg.is_allow_deprecated())
991    }
992
993    fn has_allow(&self, arg_filter: impl Fn(&AttributeArg) -> bool) -> bool {
994        self.of_kind(AttributeKind::Allow)
995            .flat_map(|attribute| &attribute.args)
996            .any(arg_filter)
997    }
998
999    pub fn has_error_type(&self) -> bool {
1000        self.of_kind(AttributeKind::ErrorType).any(|_| true)
1001    }
1002
1003    pub fn has_error(&self) -> bool {
1004        self.of_kind(AttributeKind::Error).any(|_| true)
1005    }
1006
1007    /// Returns the value of the `#[inline]` [Attribute], or `None` if the
1008    /// [Attributes] does not contain any `#[inline]` attributes.
1009    pub fn inline(&self) -> Option<Inline> {
1010        // `inline` attribute can be applied only once (`AttributeMultiplicity::Single`),
1011        // and can have exactly one argument, otherwise an error is emitted.
1012        // Last-wins approach.
1013        match self
1014            .of_kind(AttributeKind::Inline)
1015            .last()?
1016            .args
1017            .last()?
1018            .name
1019            .as_str()
1020        {
1021            INLINE_NEVER_ARG_NAME => Some(Inline::Never),
1022            INLINE_ALWAYS_ARG_NAME => Some(Inline::Always),
1023            _ => None,
1024        }
1025    }
1026
1027    /// Returns the value of the `#[trace]` [Attribute], or `None` if the
1028    /// [Attributes] does not contain any `#[trace]` attributes.
1029    pub fn trace(&self) -> Option<Trace> {
1030        // `trace` attribute can be applied only once (`AttributeMultiplicity::Single`),
1031        // and can have exactly one argument, otherwise an error is emitted.
1032        // Last-wins approach.
1033        match self
1034            .of_kind(AttributeKind::Trace)
1035            .last()?
1036            .args
1037            .last()?
1038            .name
1039            .as_str()
1040        {
1041            TRACE_NEVER_ARG_NAME => Some(Trace::Never),
1042            TRACE_ALWAYS_ARG_NAME => Some(Trace::Always),
1043            _ => None,
1044        }
1045    }
1046
1047    /// Returns the value of the `#[storage]` [Attribute], or [Purity::Pure] if the
1048    /// [Attributes] does not contain any `#[storage]` attributes.
1049    pub fn purity(&self) -> Purity {
1050        // `storage` attribute can be applied only once (`AttributeMultiplicity::Single`).
1051        // Last-wins approach.
1052        let Some(storage_attr) = self.of_kind(AttributeKind::Storage).last() else {
1053            return Purity::Pure;
1054        };
1055
1056        let mut purity = Purity::Pure;
1057
1058        let mut add_impurity = |new_impurity, counter_impurity| {
1059            if purity == Purity::Pure {
1060                purity = new_impurity;
1061            } else if purity == counter_impurity {
1062                purity = Purity::ReadsWrites;
1063            }
1064        };
1065
1066        for arg in storage_attr.args.iter() {
1067            match arg.name.as_str() {
1068                STORAGE_READ_ARG_NAME => add_impurity(Purity::Reads, Purity::Writes),
1069                STORAGE_WRITE_ARG_NAME => add_impurity(Purity::Writes, Purity::Reads),
1070                _ => {}
1071            }
1072        }
1073
1074        purity
1075    }
1076
1077    /// Returns the `#[deprecated]` [Attribute], or `None` if the
1078    /// [Attributes] does not contain any `#[deprecated]` attributes.
1079    pub fn deprecated(&self) -> Option<&Attribute> {
1080        self.deprecated_attr_index
1081            .map(|index| &self.attributes[index])
1082    }
1083
1084    /// Returns the `#[abi_name]` [Attribute], or `None` if the
1085    /// [Attributes] does not contain any `#[abi_name]` attributes.
1086    pub fn abi_name(&self) -> Option<&Attribute> {
1087        self.of_kind(AttributeKind::AbiName).last()
1088    }
1089
1090    /// Returns the `#[test]` [Attribute], or `None` if the
1091    /// [Attributes] does not contain any `#[test]` attributes.
1092    pub fn test(&self) -> Option<&Attribute> {
1093        // Last-wins approach.
1094        self.of_kind(AttributeKind::Test).last()
1095    }
1096
1097    /// Returns the `#[error]` [Attribute], or `None` if the
1098    /// [Attributes] does not contain any `#[error]` attributes.
1099    pub fn error(&self) -> Option<&Attribute> {
1100        // Last-wins approach.
1101        self.of_kind(AttributeKind::Error).last()
1102    }
1103
1104    /// Returns the error message of the `#[error]` [Attribute],
1105    /// or `None` if the [Attributes] does not contain any
1106    /// `#[error]` attributes, or if the attribute does not
1107    /// contain a message argument (`m`), or if the message
1108    /// argument is not a string.
1109    pub fn error_message(&self) -> Option<&String> {
1110        // Last-wins approach.
1111        self.error().and_then(|error_attr| {
1112            error_attr
1113                .args
1114                .iter()
1115                .filter(|arg| arg.is_error_message())
1116                .next_back()
1117                .and_then(|arg| arg.get_string_opt(&Handler::default()).ok().flatten())
1118        })
1119    }
1120
1121    /// Returns the `#[event]` [Attribute], or `None` if the
1122    /// [Attributes] does not contain any `#[event]` attributes.
1123    pub fn event(&self) -> Option<&Attribute> {
1124        self.of_kind(AttributeKind::Event).last()
1125    }
1126
1127    /// Returns the `#[indexed]` [Attribute], or `None` if the
1128    /// [Attributes] does not contain any `#[indexed]` attributes.
1129    pub fn indexed(&self) -> Option<&Attribute> {
1130        self.of_kind(AttributeKind::Indexed).last()
1131    }
1132}
1133
1134pub struct AllowDeprecatedEnterToken {
1135    diff: i32,
1136}
1137
1138#[derive(Default)]
1139pub struct AllowDeprecatedState {
1140    allowed: u32,
1141}
1142impl AllowDeprecatedState {
1143    pub(crate) fn enter(&mut self, attributes: Attributes) -> AllowDeprecatedEnterToken {
1144        if attributes.has_allow_deprecated() {
1145            self.allowed += 1;
1146
1147            AllowDeprecatedEnterToken { diff: -1 }
1148        } else {
1149            AllowDeprecatedEnterToken { diff: 0 }
1150        }
1151    }
1152
1153    pub(crate) fn exit(&mut self, token: AllowDeprecatedEnterToken) {
1154        self.allowed = self.allowed.saturating_add_signed(token.diff);
1155    }
1156
1157    pub(crate) fn is_allowed(&self) -> bool {
1158        self.allowed > 0
1159    }
1160}