move_syn/
lib.rs

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