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