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