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