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