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