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