#![cfg_attr(nightly, feature(doc_cfg))]
#![expect(clippy::result_large_err, reason = "Error from the unsynn crate")]
use std::borrow::Cow;
use std::collections::HashMap;
pub use unsynn;
use unsynn::*;
mod functions;
#[cfg(test)]
mod tests;
mod vis;
#[cfg(feature = "fun-sig")]
pub use self::functions::FunctionArg;
pub use self::functions::{Function, NativeFun};
pub use self::vis::Visibility;
pub fn sanitize_for_tokenizer(content: &str) -> String {
let regex = raw_ident_regex();
let mut lines = content.lines().map(|line| {
if !line.trim_start().starts_with("//") {
regex.replace(line, "$1")
} else {
Cow::Borrowed(line)
}
});
lines.next().map_or_else(String::new, |line| {
let mut sanitized = String::with_capacity(content.len());
sanitized.push_str(&line);
for line in lines {
sanitized.push('\n');
sanitized.push_str(&line);
}
sanitized
})
}
fn raw_ident_regex() -> regex::Regex {
regex::Regex::new("`([[:alnum:]_]+)`").expect("Valid regex")
}
pub mod kw {
use unsynn::*;
unsynn! {
pub keyword Struct = "struct";
pub keyword Phantom = "phantom";
pub keyword Public = "public";
pub keyword Has = "has";
pub keyword Copy = "copy";
pub keyword Drop = "drop";
pub keyword Key = "key";
pub keyword Store = "store";
pub keyword Module = "module";
pub keyword Package = "package";
pub keyword Friend = "friend";
pub keyword Use = "use";
pub keyword Fun = "fun";
pub keyword As = "as";
pub keyword Const = "const";
pub keyword Mut = "mut";
pub keyword Entry = "entry";
pub keyword Native = "native";
pub keyword Macro = "macro";
pub keyword Vector = "vector";
pub keyword Enum = "enum";
}
}
unsynn! {
pub enum File {
ModuleLabel(LabeledModule),
Legacy(Vec<Module>),
}
pub struct LabeledModule {
attrs: Vec<Attributes>,
keyword: kw::Module,
named_address: Ident,
path_sep: PathSep,
ident: Ident,
semicolon: Semicolon,
contents: Vec<Item>,
}
pub struct Module {
pub attrs: Vec<Attributes>,
keyword: kw::Module,
pub named_address: Ident,
path_sep: PathSep,
pub ident: Ident,
contents: BraceGroupContaining<Vec<Item>>,
}
pub struct Item {
pub attrs: Vec<Attributes>,
vis: Option<Vis>,
pub kind: ItemKind,
}
#[derive(Clone)]
pub struct Attributes {
pound: Pound,
contents: BracketGroupContaining<DelimitedVec<Attribute, Comma, TrailingDelimiter::Optional>>,
}
#[derive(Clone)]
enum Attribute {
Doc(Cons<DocKw, Assign, LiteralString>),
For(ForKw),
Other {
ident: Ident,
sub: Option<SubAttribute>,
}
}
keyword DocKw = "doc";
keyword ForKw = "for";
#[derive(Clone)]
enum SubAttribute {
Eq(Cons<Assign, AttributeValue>),
List(ParenthesisGroupContaining<DelimitedVec<Box<Attribute>, Comma, TrailingDelimiter::Optional>>),
}
#[derive(Clone)]
enum AttributeValue {
Lit(Literal),
NameAccessChain {
leading_name_access: Either<SyntaxIdent, Ident>,
path: DelimitedVec<PathSep, Ident, TrailingDelimiter::Forbidden>,
},
}
#[derive(Clone)]
struct Vis {
public: kw::Public,
modifier: Option<ParenthesisGroupContaining<VisibilityModifier>>,
}
#[derive(Clone)]
enum VisibilityModifier {
Package(kw::Package),
Friend(kw::Friend)
}
#[non_exhaustive]
pub enum ItemKind {
Struct(Struct),
Enum(Enum),
Import(Import),
UseFun(UseFun),
Const(Const),
Function(Function),
MacroFun(MacroFun),
NativeFun(NativeFun)
}
pub struct UseFun {
keyword: kw::Use,
fun_kw: kw::Fun,
fun_path: ItemPath,
as_kw: kw::As,
ty: Ident,
dot: Dot,
method: Ident,
semicolon: Semicolon,
}
pub struct Const {
const_kw: kw::Const,
ident: Ident,
colon: Colon,
ty: Type,
assign: Assign,
expr: Vec<Cons<Except<Semicolon>, TokenTree>>,
semicolon: Semicolon,
}
pub struct Import {
keyword: kw::Use,
named_address: Ident,
path_sep: PathSep,
module: ImportModule,
semicolon: Semicolon,
}
enum ImportModule {
One(ModuleOrItems),
Many(BraceGroupContaining<CommaDelimitedVec<ModuleOrItems>>),
}
#[derive(Clone)]
struct ModuleOrItems {
ident: Ident,
next: Option<AliasOrItems>,
}
#[derive(Clone)]
enum AliasOrItems {
Alias {
as_kw: kw::As,
alias: Ident,
},
Items {
sep: PathSep,
item: ImportItem,
}
}
#[derive(Clone)]
enum ImportItem {
One(MaybeAliased),
Many(BraceGroupContaining<CommaDelimitedVec<MaybeAliased>>)
}
#[derive(Clone)]
struct MaybeAliased {
ident: Ident,
alias: Option<Cons<kw::As, Ident>>,
}
#[derive(Clone)]
pub struct Struct {
keyword: kw::Struct,
pub ident: Ident,
pub generics: Option<Generics>,
pub kind: StructKind,
}
#[derive(Clone)]
pub enum StructKind {
Braced(BracedStruct),
Tuple(TupleStruct),
}
#[derive(Clone)]
pub struct BracedStruct {
abilities: Option<Abilities>,
pub fields: NamedFields,
}
#[derive(Clone)]
pub struct TupleStruct {
pub fields: PositionalFields,
abilities: Option<Cons<Abilities, Semicolon>>
}
#[derive(Clone)]
pub struct Enum {
keyword: kw::Enum,
pub ident: Ident,
pub generics: Option<Generics>,
abilities: Option<Abilities>,
content: BraceGroupContaining<CommaDelimitedVec<EnumVariant>>,
}
#[derive(Clone)]
pub struct EnumVariant {
pub attrs: Vec<Attributes>,
pub ident: Ident,
pub fields: Option<FieldsKind>
}
#[derive(Clone)]
pub enum FieldsKind {
Positional(PositionalFields),
Named(NamedFields),
}
#[derive(Clone)]
pub struct PositionalFields(ParenthesisGroupContaining<DelimitedVec<UnnamedField, Comma>>);
#[derive(Clone)]
pub struct NamedFields(BraceGroupContaining<DelimitedVec<NamedField, Comma>>);
#[derive(Clone)]
pub struct NamedField {
pub attrs: Vec<Attributes>,
pub ident: Ident,
colon: Colon,
pub ty: Type,
}
#[derive(Clone)]
pub struct UnnamedField {
pub attrs: Vec<Attributes>,
pub ty: Type,
}
#[derive(Clone)]
pub struct Generics {
lt_token: Lt,
type_args: DelimitedVec<Generic, Comma>,
gt_token: Gt,
}
#[derive(Clone)]
pub struct Generic {
pub phantom: Option<kw::Phantom>,
pub ident: Ident,
bounds: Option<GenericBounds>
}
#[derive(Clone)]
struct GenericBounds {
colon: Colon,
abilities: Many<Ability, Plus, TrailingDelimiter::Forbidden>,
}
#[derive(Clone)]
struct Abilities {
has: kw::Has,
keywords: Many<Ability, Comma, TrailingDelimiter::Forbidden>,
}
#[derive(Clone)]
pub enum Ability {
Copy(kw::Copy),
Drop(kw::Drop),
Key(kw::Key),
Store(kw::Store),
}
pub struct MacroFun {
macro_kw: kw::Macro,
fun_kw: kw::Fun,
ident: Ident,
generics: Option<MacroGenerics>,
args: ParenthesisGroup,
ret: Option<Cons<Colon, Either<MacroReturn, ParenthesisGroup>>>,
body: BraceGroup,
}
struct MacroGenerics {
lt_token: Lt,
type_args: DelimitedVec<MacroTypeArg, Comma>,
gt_token: Gt,
}
struct MacroTypeArg{
name: SyntaxIdent,
bounds: Option<GenericBounds>,
}
enum MacroReturn {
Underscore(Underscore),
Concrete(Cons<Option<Ref>, MacroReturnType>),
}
enum MacroReturnType {
MacroTypeName(SyntaxIdent),
Hybrid(HybridMacroType)
}
struct HybridMacroType {
ident: Ident,
type_args: Option<Cons<Lt, Many<Either<Type, SyntaxIdent, Box<HybridMacroType>>, Comma>, Gt>>
}
#[derive(Clone)]
struct SyntaxIdent {
dollar: Dollar,
ident: Ident,
}
pub struct MaybeRefType {
r#ref: Option<Ref>,
r#type: Type,
}
struct Ref {
and: And,
r#mut: Option<kw::Mut>,
}
#[derive(Clone)]
pub struct Type {
pub path: ItemPath,
pub type_args: Option<TypeArgs>
}
#[derive(Clone)]
pub enum ItemPath {
Full {
named_address: Ident,
sep0: PathSep,
module: Ident,
sep1: PathSep,
item: Ident,
},
Module {
module: Ident,
sep: PathSep,
item: Ident,
},
Ident(Ident),
}
#[derive(Clone)]
pub struct TypeArgs {
lt: Lt,
args: Many<Box<Type>, Comma>,
gt: Gt,
}
}
impl File {
pub fn into_modules(self) -> impl Iterator<Item = Module> {
match self {
Self::ModuleLabel(labeled) => std::iter::once(labeled.into_module()).boxed(),
Self::Legacy(modules) => modules.into_iter().boxed(),
}
}
}
impl LabeledModule {
pub fn into_module(self) -> Module {
Module {
attrs: self.attrs,
keyword: self.keyword,
named_address: self.named_address,
path_sep: self.path_sep,
ident: self.ident,
contents: BraceGroupContaining {
content: self.contents,
},
}
}
}
impl Module {
pub fn with_implicit_sui_imports(&mut self) -> &mut Self {
let implicit_imports: HashMap<_, _> = [
"use sui::object;",
"use sui::object::ID;",
"use sui::object::UID;",
"use sui::tx_context;",
"use sui::tx_context::TxContext;",
"use sui::transfer;",
]
.into_iter()
.map(|text| {
text.to_token_iter()
.parse_all::<Import>()
.expect("Valid imports")
})
.map(|import| {
let ident = import
.imported_idents()
.next()
.expect("Each import exposes exactly one ident");
(ident.clone(), import)
})
.collect();
self.add_implicit_imports(implicit_imports)
}
pub fn with_implicit_iota_imports(&mut self) -> &mut Self {
let implicit_imports: HashMap<_, _> = [
"use iota::object;",
"use iota::object::ID;",
"use iota::object::UID;",
"use iota::tx_context;",
"use iota::tx_context::TxContext;",
"use iota::transfer;",
]
.into_iter()
.map(|text| {
text.to_token_iter()
.parse_all::<Import>()
.expect("Valid imports")
})
.map(|import| {
let ident = import
.imported_idents()
.next()
.expect("Each import exposes exactly one ident");
(ident.clone(), import)
})
.collect();
self.add_implicit_imports(implicit_imports)
}
pub fn fully_qualify_datatype_field_types(&mut self) -> &mut Self {
let imports: HashMap<_, _> = self
.items()
.filter_map(|item| match &item.kind {
ItemKind::Import(import) => Some(import),
_ => None,
})
.flat_map(|import| import.flatten())
.collect();
for item in &mut self.contents.content {
match &mut item.kind {
ItemKind::Enum(e) => {
let generics = &e.type_param_idents();
e.map_types(|ty| ty.resolve(&imports, generics));
}
ItemKind::Struct(s) => {
let generics = &s.type_param_idents();
s.map_types(|ty| ty.resolve(&imports, generics));
}
_ => (),
}
}
self
}
pub fn items(&self) -> impl Iterator<Item = &Item> {
self.contents.content.iter()
}
#[cfg(test)]
pub fn into_items(self) -> impl Iterator<Item = Item> {
self.contents.content.into_iter()
}
fn add_implicit_imports(&mut self, mut implicit_imports: HashMap<Ident, Import>) -> &mut Self {
for item in self.items() {
let ItemKind::Import(import) = &item.kind else {
continue;
};
for ident in import.imported_idents() {
implicit_imports.remove(ident);
}
}
for (_, import) in implicit_imports {
self.contents.content.push(Item {
attrs: vec![],
vis: None,
kind: ItemKind::Import(import),
})
}
self
}
}
impl Import {
pub fn flatten(&self) -> impl Iterator<Item = (Ident, FlatImport)> + '_ {
let named_address = self.named_address.clone();
match &self.module {
ImportModule::One(module_or_items) => module_or_items.flatten(named_address),
ImportModule::Many(BraceGroupContaining { content: ms }) => ms
.iter()
.flat_map(move |Delimited { value, .. }| value.flatten(named_address.clone()))
.boxed(),
}
}
fn imported_idents(&self) -> impl Iterator<Item = &Ident> {
match &self.module {
ImportModule::One(module_or_items) => module_or_items.available_idents(),
ImportModule::Many(BraceGroupContaining { content: ms }) => ms
.iter()
.flat_map(|delimited| delimited.value.available_idents())
.boxed(),
}
}
}
impl ModuleOrItems {
fn flatten(&self, named_address: Ident) -> Box<dyn Iterator<Item = (Ident, FlatImport)> + '_> {
let module = self.ident.clone();
let Some(next) = &self.next else {
return std::iter::once((
module.clone(),
FlatImport::Module {
named_address,
module,
},
))
.boxed();
};
match next {
AliasOrItems::Alias { alias, .. } => std::iter::once((
alias.clone(),
FlatImport::Module {
named_address,
module,
},
))
.boxed(),
AliasOrItems::Items {
item: ImportItem::One(maybe_aliased),
..
} => std::iter::once(maybe_aliased.flat_import(named_address, module)).boxed(),
AliasOrItems::Items {
item: ImportItem::Many(BraceGroupContaining { content: items }),
..
} => items
.iter()
.map(move |Delimited { value, .. }| {
value.flat_import(named_address.clone(), module.clone())
})
.boxed(),
}
}
fn available_idents(&self) -> Box<dyn Iterator<Item = &Ident> + '_> {
let Some(next) = &self.next else {
return std::iter::once(&self.ident).boxed();
};
match next {
AliasOrItems::Alias { alias, .. } => std::iter::once(alias).boxed(),
AliasOrItems::Items {
item: ImportItem::One(item),
..
} => std::iter::once(item.available_ident(&self.ident)).boxed(),
AliasOrItems::Items {
item: ImportItem::Many(BraceGroupContaining { content: items }),
..
} => items
.iter()
.map(|delimited| delimited.value.available_ident(&self.ident))
.boxed(),
}
}
}
impl MaybeAliased {
fn flat_import(&self, named_address: Ident, module: Ident) -> (Ident, FlatImport) {
if self.ident == "Self" {
(
self.alias().unwrap_or(&module).clone(),
FlatImport::Module {
named_address,
module,
},
)
} else {
(
self.alias().unwrap_or(&self.ident).clone(),
FlatImport::Item {
named_address,
module,
r#type: self.ident.clone(),
},
)
}
}
fn available_ident<'a>(&'a self, module: &'a Ident) -> &'a Ident {
if self.ident == "Self" {
self.alias().unwrap_or(module)
} else {
self.alias().unwrap_or(&self.ident)
}
}
fn alias(&self) -> Option<&Ident> {
self.alias.as_ref().map(|cons| &cons.second)
}
}
impl Attributes {
pub fn is_doc(&self) -> bool {
matches!(
&self.contents.content[..],
[Delimited {
value: Attribute::Doc(_),
..
}]
)
}
pub const fn contents(&self) -> &impl ToTokens {
&self.contents.content
}
pub fn erased_attributes(&self) -> impl Iterator<Item = &dyn ToTokens> + '_ {
self.contents
.content
.iter()
.map(|delimited| &delimited.value as _)
}
pub fn external_attributes(&self) -> impl Iterator<Item = &dyn ToTokens> + '_ {
self.contents.content.iter().filter_map(|d| match &d.value {
Attribute::Other {
ident,
sub: Some(SubAttribute::List(inner)),
} if ident == "ext" => Some(&inner.content as _),
_ => None,
})
}
}
impl ItemKind {
pub const fn is_datatype(&self) -> bool {
matches!(self, Self::Enum(_) | Self::Struct(_))
}
}
impl Struct {
pub fn abilities(&self) -> impl Iterator<Item = &Ability> {
use StructKind as K;
match &self.kind {
K::Braced(braced) => braced
.abilities
.iter()
.flat_map(|a| a.keywords.iter())
.map(|d| &d.value)
.boxed(),
K::Tuple(tuple) => tuple
.abilities
.iter()
.flat_map(|a| a.first.keywords.iter())
.map(|d| &d.value)
.boxed(),
}
}
}
impl BracedStruct {
pub fn fields(&self) -> impl Iterator<Item = &NamedField> + Clone + '_ {
self.fields.fields()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
impl TupleStruct {
pub fn fields(&self) -> impl Iterator<Item = &UnnamedField> + Clone + '_ {
self.fields.fields()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
impl Enum {
pub fn abilities(&self) -> impl Iterator<Item = &Ability> {
self.abilities
.iter()
.flat_map(|a| a.keywords.iter())
.map(|d| &d.value)
}
pub fn variants(&self) -> impl Iterator<Item = &EnumVariant> {
self.content
.content
.iter()
.map(|Delimited { value, .. }| value)
}
}
impl NamedFields {
pub fn fields(&self) -> impl Iterator<Item = &NamedField> + Clone + '_ {
self.0.content.iter().map(|d| &d.value)
}
pub fn is_empty(&self) -> bool {
self.0.content.is_empty()
}
}
impl PositionalFields {
pub fn new() -> Self {
Self(ParenthesisGroupContaining {
content: std::iter::empty::<UnnamedField>()
.collect::<DelimitedVec<_, _, TrailingDelimiter::Mandatory>>()
.into(),
})
}
pub fn fields(&self) -> impl Iterator<Item = &UnnamedField> + Clone + '_ {
self.0.content.iter().map(|d| &d.value)
}
pub fn is_empty(&self) -> bool {
self.0.content.is_empty()
}
}
impl Default for PositionalFields {
fn default() -> Self {
Self::new()
}
}
impl Type {
fn resolve(&mut self, imports: &HashMap<Ident, FlatImport>, generics: &[Ident]) {
use ItemPath as P;
self.map_types(|ty| ty.resolve(imports, generics));
let resolved = match &self.path {
P::Module {
module,
item: r#type,
..
} => {
let Some(FlatImport::Module {
named_address,
module,
}) = imports.get(module)
else {
return;
};
P::Full {
named_address: named_address.clone(),
sep0: PathSep::default(),
module: module.clone(),
sep1: PathSep::default(),
item: r#type.clone(),
}
}
P::Ident(ident) if !generics.contains(ident) => {
let Some(FlatImport::Item {
named_address,
module,
r#type,
}) = imports.get(ident)
else {
return;
};
P::Full {
named_address: named_address.clone(),
sep0: PathSep::default(),
module: module.clone(),
sep1: PathSep::default(),
item: r#type.clone(),
}
}
_ => return,
};
self.path = resolved;
}
}
impl TypeArgs {
pub fn types(&self) -> impl Iterator<Item = &Type> {
self.args.iter().map(|args| &*args.value)
}
}
impl Generics {
pub fn generics(&self) -> impl Iterator<Item = &Generic> + '_ {
self.type_args.iter().map(|d| &d.value)
}
}
impl MaybeRefType {
pub fn is_ref(&self) -> bool {
self.r#ref.as_ref().is_some_and(|r| r.r#mut.is_none())
}
pub const fn type_(&self) -> &Type {
&self.r#type
}
}
#[cfg_attr(test, derive(derive_more::Display))]
pub enum FlatImport {
#[cfg_attr(test, display("{named_address}::{module}"))]
Module { named_address: Ident, module: Ident },
#[cfg_attr(test, display("{named_address}::{module}::{type}"))]
Item {
named_address: Ident,
module: Ident,
r#type: Ident,
},
}
trait IteratorBoxed<'a>: Iterator + 'a {
fn boxed(self) -> Box<dyn Iterator<Item = Self::Item> + 'a>
where
Self: Sized,
{
Box::new(self)
}
}
impl<'a, T> IteratorBoxed<'a> for T where T: Iterator + 'a {}
trait HasGenerics {
fn generics(&self) -> Option<&Generics>;
fn type_param_idents(&self) -> Vec<Ident> {
self.generics()
.iter()
.flat_map(|generics| generics.generics())
.map(|generic| generic.ident.clone())
.collect()
}
}
impl HasGenerics for Enum {
fn generics(&self) -> Option<&Generics> {
self.generics.as_ref()
}
}
impl HasGenerics for Struct {
fn generics(&self) -> Option<&Generics> {
self.generics.as_ref()
}
}
trait Typed {
fn map_types(&mut self, f: impl FnMut(&mut Type));
}
impl Typed for Enum {
fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
mutate_delimited_vec(&mut self.content.content, |variant| {
variant.map_types(&mut f)
});
}
}
impl Typed for EnumVariant {
fn map_types(&mut self, f: impl FnMut(&mut Type)) {
let Some(fields) = &mut self.fields else {
return;
};
fields.map_types(f);
}
}
impl Typed for Struct {
fn map_types(&mut self, f: impl FnMut(&mut Type)) {
match &mut self.kind {
StructKind::Braced(braced_struct) => braced_struct.fields.map_types(f),
StructKind::Tuple(tuple_struct) => tuple_struct.fields.map_types(f),
}
}
}
impl Typed for FieldsKind {
fn map_types(&mut self, f: impl FnMut(&mut Type)) {
match self {
Self::Named(named) => named.map_types(f),
Self::Positional(positional) => positional.map_types(f),
}
}
}
impl Typed for NamedFields {
fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
mutate_delimited_vec(&mut self.0.content, |field| f(&mut field.ty));
}
}
impl Typed for PositionalFields {
fn map_types(&mut self, mut f: impl FnMut(&mut Type)) {
mutate_delimited_vec(&mut self.0.content, |field| f(&mut field.ty));
}
}
impl Typed for Type {
fn map_types(&mut self, mut f: impl FnMut(&mut Self)) {
if let Some(args) = &mut self.type_args {
mutate_delimited_vec(&mut args.args, |t| f(&mut *t))
}
}
}
fn mutate_delimited_vec<T, D: Default, const MIN: usize, const MAX: usize>(
dvec: &mut DelimitedVec<T, D, TrailingDelimiter::Optional, MIN, MAX>,
mut f: impl FnMut(&mut T),
) {
type ForbiddenDelimited<T, D, const MIN: usize, const MAX: usize> =
DelimitedVec<T, D, TrailingDelimiter::Forbidden, MIN, MAX>;
let temp: ForbiddenDelimited<T, D, MIN, MAX> = std::iter::empty::<T>().collect();
let mut swapped = std::mem::replace(dvec, temp.into());
swapped = swapped
.into_iter()
.map(|mut d| {
f(&mut d.value);
d.value
})
.collect::<ForbiddenDelimited<T, D, MIN, MAX>>()
.into();
*dvec = swapped;
}