Skip to main content

move_syn/
lib.rs

1#![cfg_attr(nightly, feature(doc_cfg))]
2#![expect(clippy::result_large_err, reason = "Error from the unsynn crate")]
3
4//! Move syntax parsing using [`unsynn`](::unsynn).
5
6use std::borrow::Cow;
7use std::collections::HashMap;
8
9pub use unsynn;
10use unsynn::*;
11
12mod functions;
13#[cfg(test)]
14mod tests;
15mod vis;
16
17#[cfg(feature = "fun-sig")]
18pub use self::functions::FunctionArg;
19pub use self::functions::{Function, NativeFun};
20pub use self::vis::Visibility;
21
22/// Process raw Move code so that it can be used as input to Rust's tokenizer.
23///
24/// Move's and Rust's tokens are very similar, with the exception of raw identifiers for which Move
25/// uses the syntax "`ident`".
26///
27/// This function the backticks around identifiers, if found. Thus, we can re-use Rust's tokenizer
28/// afterwards, implemented by the [`proc_macro2`] crate. This is relevant because
29/// [`unsynn!`]-generated types requires Rust's [`TokenStream`] as input for parsing.
30pub fn sanitize_for_tokenizer(content: &str) -> String {
31    let regex = raw_ident_regex();
32    let mut lines = content.lines().map(|line| {
33        // Ignore commented or doc lines
34        if !line.trim_start().starts_with("//") {
35            regex.replace(line, "$1")
36        } else {
37            Cow::Borrowed(line)
38        }
39    });
40    lines.next().map_or_else(String::new, |line| {
41        let mut sanitized = String::with_capacity(content.len());
42        sanitized.push_str(&line);
43        for line in lines {
44            sanitized.push('\n');
45            sanitized.push_str(&line);
46        }
47        sanitized
48    })
49}
50
51fn raw_ident_regex() -> regex::Regex {
52    regex::Regex::new("`([[:alnum:]_]+)`").expect("Valid regex")
53}
54
55pub mod kw {
56    //! Move keywords.
57    use unsynn::*;
58
59    unsynn! {
60        pub keyword Struct = "struct";
61        pub keyword Phantom = "phantom";
62        pub keyword Public = "public";
63        pub keyword Has = "has";
64        pub keyword Copy = "copy";
65        pub keyword Drop = "drop";
66        pub keyword Key = "key";
67        pub keyword Store = "store";
68        pub keyword Module = "module";
69        pub keyword Package = "package";
70        pub keyword Friend = "friend";
71        pub keyword Use = "use";
72        pub keyword Fun = "fun";
73        pub keyword As = "as";
74        pub keyword Const = "const";
75        pub keyword Mut = "mut";
76        pub keyword Entry = "entry";
77        pub keyword Native = "native";
78        pub keyword Macro = "macro";
79        pub keyword Vector = "vector";
80        pub keyword Enum = "enum";
81    }
82}
83
84unsynn! {
85    pub enum File {
86        /// A Move file in the 2024 recommended format.
87        ModuleLabel(LabeledModule),
88        /// A Move file in the legacy style.
89        Legacy(Vec<Module>),
90    }
91
92    /// A single module defined with a top-level [label].
93    ///
94    /// [label]: https://move-book.com/guides/code-quality-checklist#using-module-label
95    pub struct LabeledModule {
96        attrs: Vec<Attributes>,
97        keyword: kw::Module,
98        named_address: Ident,
99        path_sep: PathSep,
100        ident: Ident,
101        semicolon: Semicolon,
102        contents: Vec<Item>,
103    }
104
105    /// A Move module declaration.
106    pub struct Module {
107        pub attrs: Vec<Attributes>,
108        keyword: kw::Module,
109        pub named_address: Ident,
110        path_sep: PathSep,
111        pub ident: Ident,
112        contents: BraceGroupContaining<Vec<Item>>,
113    }
114
115    /// A Move language item.
116    pub struct Item {
117        pub attrs: Vec<Attributes>,
118        vis: Option<Vis>,
119        pub kind: ItemKind,
120    }
121
122    // === Attributes ===
123
124    /// A Move [attributes] anottation.
125    ///
126    /// Examples: `#[test_only]`, `#[allow(...)]`, doc comment (`/// ...`).
127    ///
128    /// [attributes]: https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L1202-L1204
129    #[derive(Clone)]
130    pub struct Attributes {
131        pound: Pound,
132        contents: BracketGroupContaining<DelimitedVec<Attribute, Comma, TrailingDelimiter::Optional>>,
133    }
134
135    /// A single [attribute].
136    ///
137    /// Attribute =
138    ///     "for"
139    ///     | <Identifier>
140    ///     | <Identifier> "=" <AttributeValue>
141    ///     | <Identifier> "(" Comma<Attribute> ")"
142    ///
143    /// [attribute]: https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L1154-L1158
144    #[derive(Clone)]
145    enum Attribute {
146        // NOTE: special case for doc strings
147        Doc(Cons<DocKw, Assign, LiteralString>),
148        For(ForKw),
149        Other {
150            ident: Ident,
151            sub: Option<SubAttribute>,
152        }
153    }
154
155    keyword DocKw = "doc";
156    keyword ForKw = "for";
157
158    #[derive(Clone)]
159    enum SubAttribute {
160        Eq(Cons<Assign, AttributeValue>),
161        List(ParenthesisGroupContaining<DelimitedVec<Box<Attribute>, Comma, TrailingDelimiter::Optional>>),
162    }
163
164    /// AttributeValue =
165    ///     <Value>
166    ///     | <NameAccessChain>
167    ///
168    /// Based on
169    /// <https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L1135-L1138>
170    #[derive(Clone)]
171    enum AttributeValue {
172        Lit(Literal),
173        //      NameAccessChain =
174        //          <LeadingNameAccess> <OptionalTypeArgs>
175        //              ( "::" <Identifier> <OptionalTypeArgs> )^n
176        NameAccessChain {
177            // TODO: support NumericalAddress
178            // LeadingNameAccess = <NumericalAddress> | <Identifier> | <SyntaxIdentifier>
179            leading_name_access: Either<SyntaxIdent, Ident>,
180            // NOTE: ignoring <OptionalTypeArgs> for now
181            // https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L3168
182            path: DelimitedVec<PathSep, Ident, TrailingDelimiter::Forbidden>,
183        },
184    }
185
186    // === Visibility modifiers ===
187
188    /// Move item visibility.
189    ///
190    /// `public`, `public(package)`, `public(friend)`
191    #[derive(Clone)]
192    struct Vis {
193        public: kw::Public,
194        modifier: Option<ParenthesisGroupContaining<VisibilityModifier>>,
195    }
196
197    /// Move item visibility modifier.
198    ///
199    /// Examples:
200    /// - `public(package)`
201    /// - `public(friend)`
202    #[derive(Clone)]
203    enum VisibilityModifier {
204        Package(kw::Package),
205        Friend(kw::Friend)
206    }
207
208    // === ===
209
210    /// All Move item types.
211    #[non_exhaustive]
212    pub enum ItemKind {
213        Struct(Struct),
214        Enum(Enum),
215        Import(Import),
216        UseFun(UseFun),
217        Const(Const),
218        Function(Function),
219        MacroFun(MacroFun),
220        NativeFun(NativeFun)
221    }
222
223    /// Alias for a receiver method, like `use fun foo as Bar.bar;`
224    pub struct UseFun {
225        keyword: kw::Use,
226        fun_kw: kw::Fun,
227        fun_path: ItemPath,
228        as_kw: kw::As,
229        ty: Ident,
230        dot: Dot,
231        method: Ident,
232        semicolon: Semicolon,
233    }
234
235    // === Constants ===
236
237    pub struct Const {
238        /// `const`
239        const_kw: kw::Const,
240        /// `NAME`
241        ident: Ident,
242        /// `:`
243        colon: Colon,
244        /// Type
245        ty: Type,
246        /// `=`
247        assign: Assign,
248        /// Hack to parse anything until (but excluding) a `;`
249        expr: Vec<Cons<Except<Semicolon>, TokenTree>>,
250        /// `;`
251        semicolon: Semicolon,
252    }
253
254    // === Imports ===
255
256    pub struct Import {
257        keyword: kw::Use,
258        named_address: Ident,
259        path_sep: PathSep,
260        module: ImportModule,
261        semicolon: Semicolon,
262    }
263
264    /// `module`, `module as alias`, `module::...`, `{module, ...}`
265    enum ImportModule {
266        One(ModuleOrItems),
267        Many(BraceGroupContaining<CommaDelimitedVec<ModuleOrItems>>),
268    }
269
270    #[derive(Clone)]
271    struct ModuleOrItems {
272        ident: Ident,
273        next: Option<AliasOrItems>,
274    }
275
276    #[derive(Clone)]
277    enum AliasOrItems {
278        Alias {
279            as_kw: kw::As,
280            alias: Ident,
281        },
282        Items {
283            sep: PathSep,
284            item: ImportItem,
285        }
286    }
287
288    #[derive(Clone)]
289    enum ImportItem {
290        One(MaybeAliased),
291        Many(BraceGroupContaining<CommaDelimitedVec<MaybeAliased>>)
292    }
293
294    #[derive(Clone)]
295    struct MaybeAliased {
296        ident: Ident,
297        alias: Option<Cons<kw::As, Ident>>,
298    }
299
300    // === Structs ===
301
302    /// A Move struct.
303    #[derive(Clone)]
304    pub struct Struct {
305        keyword: kw::Struct,
306        pub ident: Ident,
307        pub generics: Option<Generics>,
308        pub kind: StructKind,
309    }
310
311    /// The kinds of structs; either a braced or tuple one.
312    #[derive(Clone)]
313    pub enum StructKind {
314        Braced(BracedStruct),
315        Tuple(TupleStruct),
316    }
317
318    /// Braced structs have their abilities declared before their fields.
319    #[derive(Clone)]
320    pub struct BracedStruct {
321        abilities: Option<Abilities>,
322        pub fields: NamedFields,
323    }
324
325    /// Tuple structs have their abilities declared after their fields, with a trailing semicolon
326    /// if so.
327    #[derive(Clone)]
328    pub struct TupleStruct {
329        pub fields: PositionalFields,
330        abilities: Option<Cons<Abilities, Semicolon>>
331    }
332
333    // === Enums ===
334
335    #[derive(Clone)]
336    pub struct Enum {
337        keyword: kw::Enum,
338        pub ident: Ident,
339        pub generics: Option<Generics>,
340        abilities: Option<Abilities>,
341        content: BraceGroupContaining<CommaDelimitedVec<EnumVariant>>,
342    }
343
344    #[derive(Clone)]
345    pub struct EnumVariant {
346        pub attrs: Vec<Attributes>,
347        pub ident: Ident,
348        /// The fields of the enum variants. If none, it's a "unit" or "empty" variant.
349        pub fields: Option<FieldsKind>
350    }
351
352    /// Kinds of fields for a Move enum.
353    #[derive(Clone)]
354    pub enum FieldsKind {
355        Positional(PositionalFields),
356        Named(NamedFields),
357    }
358
359    // === Datatype fields ===
360
361    /// Parenthesis group containing comma-delimited unnamed fields.
362    #[derive(Clone)]
363    pub struct PositionalFields(ParenthesisGroupContaining<DelimitedVec<UnnamedField, Comma>>);
364
365    /// Brace group containing comma-delimited named fields.
366    #[derive(Clone)]
367    pub struct NamedFields(BraceGroupContaining<DelimitedVec<NamedField, Comma>>);
368
369    /// Named datatype field.
370    #[derive(Clone)]
371    pub struct NamedField {
372        pub attrs: Vec<Attributes>,
373        pub ident: Ident,
374        colon: Colon,
375        pub ty: Type,
376    }
377
378    /// Unnamed datatype field.
379    #[derive(Clone)]
380    pub struct UnnamedField {
381        pub attrs: Vec<Attributes>,
382        pub ty: Type,
383    }
384
385    // === Generics ===
386
387    /// The generics of a datatype or function.
388    ///
389    /// # Example
390    /// `<T, U: drop, V: key + store>`
391    #[derive(Clone)]
392    pub struct Generics {
393        lt_token: Lt,
394        type_args: DelimitedVec<Generic, Comma>,
395        gt_token: Gt,
396    }
397
398    /// A generic type declaration.
399    ///
400    /// # Examples
401    /// * `T`
402    /// * `T: drop`
403    /// * `T: key + store`
404    /// * `phantom T`
405    #[derive(Clone)]
406    pub struct Generic {
407        pub phantom: Option<kw::Phantom>,
408        pub ident: Ident,
409        bounds: Option<GenericBounds>
410    }
411
412    /// Captures the fact that:
413    /// * `:` must be followed by an ability
414    /// * additional abilities are preceeded by `+`
415    #[derive(Clone)]
416    struct GenericBounds {
417        colon: Colon,
418        abilities: Many<Ability, Plus, TrailingDelimiter::Forbidden>,
419    }
420
421    // === Abilities ===
422
423    /// Abilities declaration for a datatype.
424    ///
425    /// Example: `has key, store`
426    #[derive(Clone)]
427    struct Abilities {
428        has: kw::Has,
429        keywords: Many<Ability, Comma, TrailingDelimiter::Forbidden>,
430    }
431
432    /// Ability keywords.
433    #[derive(Clone)]
434    pub enum Ability {
435        Copy(kw::Copy),
436        Drop(kw::Drop),
437        Key(kw::Key),
438        Store(kw::Store),
439    }
440
441    // === Macros ===
442
443    pub struct MacroFun {
444        macro_kw: kw::Macro,
445        fun_kw: kw::Fun,
446        ident: Ident,
447        generics: Option<MacroGenerics>,
448        args: ParenthesisGroup,
449        ret: Option<Cons<Colon, Either<MacroReturn, ParenthesisGroup>>>,
450        body: BraceGroup,
451    }
452
453    struct MacroGenerics {
454        lt_token: Lt,
455        type_args: DelimitedVec<MacroTypeArg, Comma>,
456        gt_token: Gt,
457    }
458
459    /// `$T: drop + store`
460    struct MacroTypeArg{
461        name: SyntaxIdent,
462        bounds: Option<GenericBounds>,
463    }
464
465    /// Either `_` or a 'concrete' type
466    enum MacroReturn {
467        Underscore(Underscore),
468        Concrete(Cons<Option<Ref>, MacroReturnType>),
469    }
470
471    /// Return type for macro funs.
472    ///
473    /// - `$T`
474    /// - `&mut $T`
475    /// - `&String`
476    /// - `Option<$T>`
477    enum MacroReturnType {
478        MacroTypeName(SyntaxIdent),
479        Hybrid(HybridMacroType)
480    }
481
482    struct HybridMacroType {
483        ident: Ident,
484        type_args: Option<Cons<Lt, Many<Either<Type, SyntaxIdent, Box<HybridMacroType>>, Comma>, Gt>>
485    }
486
487    /// `$T`
488    ///
489    /// Name based on
490    /// https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L675-L678
491    #[derive(Clone)]
492    struct SyntaxIdent {
493        dollar: Dollar,
494        ident: Ident,
495    }
496
497    // === Types ===
498
499    /// Type of function arguments or returns.
500    pub struct MaybeRefType {
501        r#ref: Option<Ref>,
502        r#type: Type,
503    }
504
505    /// The reference prefix
506    struct Ref {
507        and: And,
508        r#mut: Option<kw::Mut>,
509    }
510
511    /// Non-reference type, used in datatype fields.
512    #[derive(Clone)]
513    pub struct Type {
514        pub path: ItemPath,
515        pub type_args: Option<TypeArgs>
516    }
517
518    /// Path to an item.
519    #[derive(Clone)]
520    pub enum ItemPath {
521        /// Fully qualified,
522        Full {
523            named_address: Ident,
524            sep0: PathSep,
525            module: Ident,
526            sep1: PathSep,
527            item: Ident,
528        },
529        /// Module prefix only, if it was imported already.
530        Module {
531            module: Ident,
532            sep: PathSep,
533            item: Ident,
534        },
535        /// Only the item identifier.
536        Ident(Ident),
537    }
538
539    /// Angle bracket group (`<...>`) containing comma-delimited types.
540    #[derive(Clone)]
541    pub struct TypeArgs {
542        lt: Lt,
543        args: Many<Box<Type>, Comma>,
544        gt: Gt,
545    }
546}
547
548impl File {
549    pub fn into_modules(self) -> impl Iterator<Item = Module> {
550        match self {
551            Self::ModuleLabel(labeled) => std::iter::once(labeled.into_module()).boxed(),
552            Self::Legacy(modules) => modules.into_iter().boxed(),
553        }
554    }
555}
556
557impl LabeledModule {
558    pub fn into_module(self) -> Module {
559        Module {
560            attrs: self.attrs,
561            keyword: self.keyword,
562            named_address: self.named_address,
563            path_sep: self.path_sep,
564            ident: self.ident,
565            contents: BraceGroupContaining {
566                content: self.contents,
567            },
568        }
569    }
570}
571
572impl Module {
573    /// Add `sui` implicit imports as explicit `use` statements to the module.
574    ///
575    /// [Reference](https://move-book.com/programmability/sui-framework#implicit-imports)
576    pub fn with_implicit_sui_imports(&mut self) -> &mut Self {
577        // Build the map of implicit imports keyed by the identifiers they export.
578        let implicit_imports: HashMap<_, _> = [
579            "use sui::object;",
580            "use sui::object::ID;",
581            "use sui::object::UID;",
582            "use sui::tx_context;",
583            "use sui::tx_context::TxContext;",
584            "use sui::transfer;",
585        ]
586        .into_iter()
587        .map(|text| {
588            text.to_token_iter()
589                .parse_all::<Import>()
590                .expect("Valid imports")
591        })
592        .map(|import| {
593            let ident = import
594                .imported_idents()
595                .next()
596                .expect("Each import exposes exactly one ident");
597            (ident.clone(), import)
598        })
599        .collect();
600
601        self.add_implicit_imports(implicit_imports)
602    }
603
604    /// Add `iota` implicit imports as explicit `use` statements to the module.
605    ///
606    /// Adapted from the `sui` equivalents.
607    pub fn with_implicit_iota_imports(&mut self) -> &mut Self {
608        // Build the map of implicit imports keyed by the identifiers they export.
609        let implicit_imports: HashMap<_, _> = [
610            "use iota::object;",
611            "use iota::object::ID;",
612            "use iota::object::UID;",
613            "use iota::tx_context;",
614            "use iota::tx_context::TxContext;",
615            "use iota::transfer;",
616        ]
617        .into_iter()
618        .map(|text| {
619            text.to_token_iter()
620                .parse_all::<Import>()
621                .expect("Valid imports")
622        })
623        .map(|import| {
624            let ident = import
625                .imported_idents()
626                .next()
627                .expect("Each import exposes exactly one ident");
628            (ident.clone(), import)
629        })
630        .collect();
631
632        self.add_implicit_imports(implicit_imports)
633    }
634
635    /// Resolve all datatype field types to their fully-qualified paths.
636    pub fn fully_qualify_datatype_field_types(&mut self) -> &mut Self {
637        // Collect all imported types and their paths
638        let imports: HashMap<_, _> = self
639            .items()
640            .filter_map(|item| match &item.kind {
641                ItemKind::Import(import) => Some(import),
642                _ => None,
643            })
644            .flat_map(|import| import.flatten())
645            .collect();
646
647        // Resolve datatype fields' types
648        for item in &mut self.contents.content {
649            match &mut item.kind {
650                ItemKind::Enum(e) => {
651                    let generics = &e.type_param_idents();
652                    e.map_types(|ty| ty.resolve(&imports, generics));
653                }
654                ItemKind::Struct(s) => {
655                    let generics = &s.type_param_idents();
656                    s.map_types(|ty| ty.resolve(&imports, generics));
657                }
658                _ => (),
659            }
660        }
661
662        self
663    }
664
665    pub fn items(&self) -> impl Iterator<Item = &Item> {
666        self.contents.content.iter()
667    }
668
669    #[cfg(test)]
670    pub fn into_items(self) -> impl Iterator<Item = Item> {
671        self.contents.content.into_iter()
672    }
673
674    fn add_implicit_imports(&mut self, mut implicit_imports: HashMap<Ident, Import>) -> &mut Self {
675        // Filter out any that were shadowed by existing imports
676        for item in self.items() {
677            let ItemKind::Import(import) = &item.kind else {
678                continue;
679            };
680            for ident in import.imported_idents() {
681                implicit_imports.remove(ident);
682            }
683        }
684
685        // Add the remaining implicit imports to the list of module items
686        for (_, import) in implicit_imports {
687            self.contents.content.push(Item {
688                attrs: vec![],
689                vis: None,
690                kind: ItemKind::Import(import),
691            })
692        }
693        self
694    }
695}
696
697impl Import {
698    /// List of idents (or aliases) brought into scope by this import and their paths
699    /// (`named_address::module(::item)?`).
700    pub fn flatten(&self) -> impl Iterator<Item = (Ident, FlatImport)> + '_ {
701        let named_address = self.named_address.clone();
702        match &self.module {
703            // use named_address::module...
704            ImportModule::One(module_or_items) => module_or_items.flatten(named_address),
705            // use named_address::{...}
706            ImportModule::Many(BraceGroupContaining { content: ms }) => ms
707                .iter()
708                .flat_map(move |Delimited { value, .. }| value.flatten(named_address.clone()))
709                .boxed(),
710        }
711    }
712
713    /// The list of item idents brought into scope by this import.
714    fn imported_idents(&self) -> impl Iterator<Item = &Ident> {
715        match &self.module {
716            ImportModule::One(module_or_items) => module_or_items.available_idents(),
717            ImportModule::Many(BraceGroupContaining { content: ms }) => ms
718                .iter()
719                .flat_map(|delimited| delimited.value.available_idents())
720                .boxed(),
721        }
722    }
723}
724
725impl ModuleOrItems {
726    /// Flat canonical imports (`named_address::module(::item)?`).
727    fn flatten(&self, named_address: Ident) -> Box<dyn Iterator<Item = (Ident, FlatImport)> + '_> {
728        let module = self.ident.clone();
729
730        let Some(next) = &self.next else {
731            // module;
732            return std::iter::once((
733                module.clone(),
734                FlatImport::Module {
735                    named_address,
736                    module,
737                },
738            ))
739            .boxed();
740        };
741
742        match next {
743            // module as alias;
744            AliasOrItems::Alias { alias, .. } => std::iter::once((
745                alias.clone(),
746                FlatImport::Module {
747                    named_address,
748                    module,
749                },
750            ))
751            .boxed(),
752
753            // module::item( as alias)?;
754            AliasOrItems::Items {
755                item: ImportItem::One(maybe_aliased),
756                ..
757            } => std::iter::once(maybe_aliased.flat_import(named_address, module)).boxed(),
758
759            // module::{(item( as alias)?),+};
760            AliasOrItems::Items {
761                item: ImportItem::Many(BraceGroupContaining { content: items }),
762                ..
763            } => items
764                .iter()
765                .map(move |Delimited { value, .. }| {
766                    value.flat_import(named_address.clone(), module.clone())
767                })
768                .boxed(),
769        }
770    }
771
772    /// Identifiers this import makes available in scope.
773    fn available_idents(&self) -> Box<dyn Iterator<Item = &Ident> + '_> {
774        let Some(next) = &self.next else {
775            return std::iter::once(&self.ident).boxed();
776        };
777
778        match next {
779            AliasOrItems::Alias { alias, .. } => std::iter::once(alias).boxed(),
780
781            AliasOrItems::Items {
782                item: ImportItem::One(item),
783                ..
784            } => std::iter::once(item.available_ident(&self.ident)).boxed(),
785
786            AliasOrItems::Items {
787                item: ImportItem::Many(BraceGroupContaining { content: items }),
788                ..
789            } => items
790                .iter()
791                .map(|delimited| delimited.value.available_ident(&self.ident))
792                .boxed(),
793        }
794    }
795}
796
797impl MaybeAliased {
798    /// Special handling for `Self` imports.
799    fn flat_import(&self, named_address: Ident, module: Ident) -> (Ident, FlatImport) {
800        if self.ident == "Self" {
801            (
802                self.alias().unwrap_or(&module).clone(),
803                FlatImport::Module {
804                    named_address,
805                    module,
806                },
807            )
808        } else {
809            (
810                self.alias().unwrap_or(&self.ident).clone(),
811                FlatImport::Item {
812                    named_address,
813                    module,
814                    r#type: self.ident.clone(),
815                },
816            )
817        }
818    }
819
820    fn available_ident<'a>(&'a self, module: &'a Ident) -> &'a Ident {
821        if self.ident == "Self" {
822            self.alias().unwrap_or(module)
823        } else {
824            self.alias().unwrap_or(&self.ident)
825        }
826    }
827
828    /// The identifier alias that's available in scope, if any.
829    fn alias(&self) -> Option<&Ident> {
830        self.alias.as_ref().map(|cons| &cons.second)
831    }
832}
833
834impl Attributes {
835    /// Whether this is a `#[doc = "..."]`.
836    pub fn is_doc(&self) -> bool {
837        matches!(
838            &self.contents.content[..],
839            [Delimited {
840                value: Attribute::Doc(_),
841                ..
842            }]
843        )
844    }
845
846    /// Everything inside the bracket group, `#[...]`.
847    pub const fn contents(&self) -> &impl ToTokens {
848        &self.contents.content
849    }
850
851    /// Contents of each [attribute].
852    ///
853    /// [attribute]: https://github.com/MystenLabs/sui/blob/129788902da4afc54a10af4ae45971a57ef080be/external-crates/move/crates/move-compiler/src/parser/syntax.rs#L1154-L1158
854    pub fn erased_attributes(&self) -> impl Iterator<Item = &dyn ToTokens> + '_ {
855        self.contents
856            .content
857            .iter()
858            .map(|delimited| &delimited.value as _)
859    }
860
861    /// Contents of parameterized attributes as `#[ext(<external_attribute>)]`
862    pub fn external_attributes(&self) -> impl Iterator<Item = &dyn ToTokens> + '_ {
863        self.contents.content.iter().filter_map(|d| match &d.value {
864            Attribute::Other {
865                ident,
866                sub: Some(SubAttribute::List(inner)),
867            } if ident == "ext" => Some(&inner.content as _),
868            _ => None,
869        })
870    }
871}
872
873impl ItemKind {
874    /// Whether this item is a datatype (enum/struct) declaration.
875    pub const fn is_datatype(&self) -> bool {
876        matches!(self, Self::Enum(_) | Self::Struct(_))
877    }
878}
879
880impl Struct {
881    pub fn abilities(&self) -> impl Iterator<Item = &Ability> {
882        use StructKind as K;
883        match &self.kind {
884            K::Braced(braced) => braced
885                .abilities
886                .iter()
887                .flat_map(|a| a.keywords.iter())
888                .map(|d| &d.value)
889                .boxed(),
890            K::Tuple(tuple) => tuple
891                .abilities
892                .iter()
893                .flat_map(|a| a.first.keywords.iter())
894                .map(|d| &d.value)
895                .boxed(),
896        }
897    }
898}
899
900impl BracedStruct {
901    pub fn fields(&self) -> impl Iterator<Item = &NamedField> + Clone + '_ {
902        self.fields.fields()
903    }
904
905    /// Whether this struct has no fields.
906    pub fn is_empty(&self) -> bool {
907        self.fields.is_empty()
908    }
909}
910
911impl TupleStruct {
912    pub fn fields(&self) -> impl Iterator<Item = &UnnamedField> + Clone + '_ {
913        self.fields.fields()
914    }
915
916    /// Whether this struct has no fields.
917    pub fn is_empty(&self) -> bool {
918        self.fields.is_empty()
919    }
920}
921
922impl Enum {
923    pub fn abilities(&self) -> impl Iterator<Item = &Ability> {
924        self.abilities
925            .iter()
926            .flat_map(|a| a.keywords.iter())
927            .map(|d| &d.value)
928    }
929
930    pub fn variants(&self) -> impl Iterator<Item = &EnumVariant> {
931        self.content
932            .content
933            .iter()
934            .map(|Delimited { value, .. }| value)
935    }
936}
937
938impl NamedFields {
939    pub fn fields(&self) -> impl Iterator<Item = &NamedField> + Clone + '_ {
940        self.0.content.iter().map(|d| &d.value)
941    }
942
943    pub fn is_empty(&self) -> bool {
944        self.0.content.is_empty()
945    }
946}
947
948impl PositionalFields {
949    pub fn new() -> Self {
950        Self(ParenthesisGroupContaining {
951            content: std::iter::empty::<UnnamedField>()
952                .collect::<DelimitedVec<_, _, TrailingDelimiter::Mandatory>>()
953                .into(),
954        })
955    }
956
957    pub fn fields(&self) -> impl Iterator<Item = &UnnamedField> + Clone + '_ {
958        self.0.content.iter().map(|d| &d.value)
959    }
960
961    pub fn is_empty(&self) -> bool {
962        self.0.content.is_empty()
963    }
964}
965
966impl Default for PositionalFields {
967    fn default() -> Self {
968        Self::new()
969    }
970}
971
972impl Type {
973    /// Resolve the types' path to a fully-qualified declaration, recursively.
974    fn resolve(&mut self, imports: &HashMap<Ident, FlatImport>, generics: &[Ident]) {
975        use ItemPath as P;
976        // First, resolve the type arguments
977        self.map_types(|ty| ty.resolve(imports, generics));
978
979        // Then resolve its own path
980        // HACK: We trust the Move code is valid, so the expected import should always be found,
981        // hence we don't error/panic if it isn't
982        let resolved = match &self.path {
983            P::Module {
984                module,
985                item: r#type,
986                ..
987            } => {
988                let Some(FlatImport::Module {
989                    named_address,
990                    module,
991                }) = imports.get(module)
992                else {
993                    return;
994                };
995                P::Full {
996                    named_address: named_address.clone(),
997                    sep0: PathSep::default(),
998                    module: module.clone(),
999                    sep1: PathSep::default(),
1000                    item: r#type.clone(),
1001                }
1002            }
1003            P::Ident(ident) if !generics.contains(ident) => {
1004                let Some(FlatImport::Item {
1005                    named_address,
1006                    module,
1007                    r#type,
1008                }) = imports.get(ident)
1009                else {
1010                    return;
1011                };
1012                P::Full {
1013                    named_address: named_address.clone(),
1014                    sep0: PathSep::default(),
1015                    module: module.clone(),
1016                    sep1: PathSep::default(),
1017                    item: r#type.clone(),
1018                }
1019            }
1020            // Already fully-qualified types or idents shadowed by generics should be left alone
1021            _ => return,
1022        };
1023        self.path = resolved;
1024    }
1025}
1026
1027impl TypeArgs {
1028    /// Guaranteed to be non-empty.
1029    pub fn types(&self) -> impl Iterator<Item = &Type> {
1030        self.args.iter().map(|args| &*args.value)
1031    }
1032}
1033
1034impl Generics {
1035    pub fn generics(&self) -> impl Iterator<Item = &Generic> + '_ {
1036        self.type_args.iter().map(|d| &d.value)
1037    }
1038}
1039
1040impl MaybeRefType {
1041    /// Whether this is an immutable reference to a type.
1042    pub fn is_ref(&self) -> bool {
1043        self.r#ref.as_ref().is_some_and(|r| r.r#mut.is_none())
1044    }
1045
1046    /// Reference to the Move type
1047    pub const fn type_(&self) -> &Type {
1048        &self.r#type
1049    }
1050}
1051
1052// === Non-lang items ===
1053
1054#[cfg_attr(test, derive(derive_more::Display))]
1055pub enum FlatImport {
1056    #[cfg_attr(test, display("{named_address}::{module}"))]
1057    Module { named_address: Ident, module: Ident },
1058    #[cfg_attr(test, display("{named_address}::{module}::{type}"))]
1059    Item {
1060        named_address: Ident,
1061        module: Ident,
1062        r#type: Ident,
1063    },
1064}
1065
1066// === Misc helpers ===
1067
1068/// Box an iterator, necessary when returning different types that implement [`Iterator`].
1069trait IteratorBoxed<'a>: Iterator + 'a {
1070    fn boxed(self) -> Box<dyn Iterator<Item = Self::Item> + 'a>
1071    where
1072        Self: Sized,
1073    {
1074        Box::new(self)
1075    }
1076}
1077
1078impl<'a, T> IteratorBoxed<'a> for T where T: Iterator + 'a {}
1079
1080/// Something that can be generic over type parameters.
1081trait HasGenerics {
1082    fn generics(&self) -> Option<&Generics>;
1083
1084    /// Identifiers of the generic type parameters.
1085    fn type_param_idents(&self) -> Vec<Ident> {
1086        self.generics()
1087            .iter()
1088            .flat_map(|generics| generics.generics())
1089            .map(|generic| generic.ident.clone())
1090            .collect()
1091    }
1092}
1093
1094impl HasGenerics for Enum {
1095    fn generics(&self) -> Option<&Generics> {
1096        self.generics.as_ref()
1097    }
1098}
1099
1100impl HasGenerics for Struct {
1101    fn generics(&self) -> Option<&Generics> {
1102        self.generics.as_ref()
1103    }
1104}
1105
1106/// Something that has inner types, e.g., datatype fields, function arguments and returns.
1107trait Typed {
1108    /// Field types. Used to resolve into fully-qualified paths.
1109    fn map_types(&mut self, f: impl FnMut(&mut Type));
1110}
1111
1112impl Typed for Enum {
1113    fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
1114        mutate_delimited_vec(&mut self.content.content, |variant| {
1115            variant.map_types(&mut f)
1116        });
1117    }
1118}
1119
1120impl Typed for EnumVariant {
1121    fn map_types(&mut self, f: impl FnMut(&mut Type)) {
1122        let Some(fields) = &mut self.fields else {
1123            return;
1124        };
1125        fields.map_types(f);
1126    }
1127}
1128
1129impl Typed for Struct {
1130    fn map_types(&mut self, f: impl FnMut(&mut Type)) {
1131        match &mut self.kind {
1132            StructKind::Braced(braced_struct) => braced_struct.fields.map_types(f),
1133            StructKind::Tuple(tuple_struct) => tuple_struct.fields.map_types(f),
1134        }
1135    }
1136}
1137
1138impl Typed for FieldsKind {
1139    fn map_types(&mut self, f: impl FnMut(&mut Type)) {
1140        match self {
1141            Self::Named(named) => named.map_types(f),
1142            Self::Positional(positional) => positional.map_types(f),
1143        }
1144    }
1145}
1146
1147impl Typed for NamedFields {
1148    fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
1149        mutate_delimited_vec(&mut self.0.content, |field| f(&mut field.ty));
1150    }
1151}
1152
1153impl Typed for PositionalFields {
1154    fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
1155        mutate_delimited_vec(&mut self.0.content, |field| f(&mut field.ty));
1156    }
1157}
1158
1159impl Typed for Type {
1160    fn map_types(&mut self, mut f: impl FnMut(&mut Self)) {
1161        if let Some(args) = &mut self.type_args {
1162            mutate_delimited_vec(&mut args.args, |t| f(&mut *t))
1163        }
1164    }
1165}
1166
1167// HACK: circumvent the fact that `DelimitedVec` doesn't have a `DerefMut` implementation.
1168// WARN: this changes `P` to be `Forbidden`
1169fn mutate_delimited_vec<T, D: Default, const MIN: usize, const MAX: usize>(
1170    dvec: &mut DelimitedVec<T, D, TrailingDelimiter::Optional, MIN, MAX>,
1171    mut f: impl FnMut(&mut T),
1172) {
1173    type ForbiddenDelimited<T, D, const MIN: usize, const MAX: usize> =
1174        DelimitedVec<T, D, TrailingDelimiter::Forbidden, MIN, MAX>;
1175
1176    let temp: ForbiddenDelimited<T, D, MIN, MAX> = std::iter::empty::<T>().collect();
1177    let mut swapped = std::mem::replace(dvec, temp.into());
1178    swapped = swapped
1179        .into_iter()
1180        .map(|mut d| {
1181            f(&mut d.value);
1182            d.value
1183        })
1184        .collect::<ForbiddenDelimited<T, D, MIN, MAX>>()
1185        .into();
1186    *dvec = swapped;
1187}