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