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