move_syn/
lib.rs

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