move_syn/
lib.rs

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