use std::collections::BTreeSet;
use crate::util::*;
use crate::{ClassDefs, FfiType, TsModule};
use dodo_bindgen_rust_ir::{Abi, Type, TypeNumeric};
use proc_macro2::TokenStream;
use syn::Ident;
#[derive(Clone, Debug, PartialEq)]
pub enum RustTypeNumeric {
U8,
U16,
U32,
I8,
I16,
I32,
F32,
F64,
U64,
I64,
Usize,
Isize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum RustType {
Void,
Numeric(RustTypeNumeric),
Boolean,
FnPtr(String), Ptr(Box<RustType>),
PtrMut(Box<RustType>),
Ref(Box<RustType>),
RefMut(Box<RustType>),
Box(Box<RustType>),
Str,
Slice(Box<RustType>),
Vec(Box<RustType>),
Tuple(Vec<RustType>),
UserDefined(Ident),
Unsupported, Custom {
ts_type: String,
to_rust: String,
from_rust: String,
},
}
fn numeric(type_numeric: TypeNumeric) -> RustTypeNumeric {
match type_numeric {
TypeNumeric::U8 => RustTypeNumeric::U8,
TypeNumeric::U16 => RustTypeNumeric::U16,
TypeNumeric::U32 => RustTypeNumeric::U32,
TypeNumeric::I8 => RustTypeNumeric::I8,
TypeNumeric::I16 => RustTypeNumeric::I16,
TypeNumeric::I32 => RustTypeNumeric::I32,
TypeNumeric::F32 => RustTypeNumeric::F32,
TypeNumeric::F64 => RustTypeNumeric::F64,
TypeNumeric::U64 => RustTypeNumeric::U64,
TypeNumeric::I64 => RustTypeNumeric::I64,
TypeNumeric::Usize => RustTypeNumeric::Usize,
TypeNumeric::Isize => RustTypeNumeric::Isize,
}
}
pub trait TypeExt {
fn unwrap(self, module: &mut TsModule) -> RustType;
}
impl TypeExt for Type {
fn unwrap(self, module: &mut TsModule) -> RustType {
fn match_str_or_slice(
elem: Box<Type>,
module: &mut TsModule,
fallback: &dyn Fn(Box<Type>, &mut TsModule) -> RustType,
) -> RustType {
match *elem {
Type::Str => {
module.type_defs.insert(RustTypeList::Str);
RustType::Str
},
Type::Slice(elem) => {
module.type_defs.insert(RustTypeList::Slice);
let elem = Box::new(elem.unwrap(module));
RustType::Slice(elem)
},
_ => fallback(elem, module),
}
}
if let Some(class) = module.class_types.get(&self) {
return RustType::UserDefined(class.clone());
}
let type_defs = &mut module.type_defs;
match self {
Type::Void => RustType::Void,
Type::Numeric(type_numeric) => RustType::Numeric(numeric(type_numeric)),
Type::Bool => RustType::Boolean,
fn_ptr @ Type::FnPtr { .. } => {
type_defs.insert(RustTypeList::FnPtr);
RustType::FnPtr(fn_ptr.to_string())
},
Type::Ptr(elem) => match_str_or_slice(elem, module, &|elem, module| {
module.type_defs.insert(RustTypeList::Ptr);
RustType::Ptr(Box::new(elem.unwrap(module)))
}),
Type::PtrMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
module.type_defs.insert(RustTypeList::PtrMut);
RustType::PtrMut(Box::new(elem.unwrap(module)))
}),
Type::Ref(elem) => match_str_or_slice(elem, module, &|elem, module| {
module.type_defs.insert(RustTypeList::Ref);
RustType::Ref(Box::new(elem.unwrap(module)))
}),
Type::RefMut(elem) => match_str_or_slice(elem, module, &|elem, module| {
module.type_defs.insert(RustTypeList::RefMut);
RustType::RefMut(Box::new(elem.unwrap(module)))
}),
Type::Box(elem) => match_str_or_slice(elem, module, &|elem, module| {
module.type_defs.insert(RustTypeList::Box);
RustType::Box(Box::new(elem.unwrap(module)))
}),
Type::Str => {
type_defs.insert(RustTypeList::Str);
RustType::Str
},
Type::Slice(elem) => {
type_defs.insert(RustTypeList::Slice);
RustType::Slice(Box::new(elem.unwrap(module)))
},
Type::Vec(elem) => {
type_defs.insert(RustTypeList::Vec);
RustType::Vec(Box::new(elem.unwrap(module)))
},
Type::Tuple(elems) => {
type_defs.insert(RustTypeList::Tuple);
RustType::Tuple(elems.into_iter().map(|elem| elem.unwrap(module)).collect())
},
Type::Object { name, .. } => {
let ident = format_ident!("{}", name);
module.user_defs.insert(ident.clone());
RustType::UserDefined(ident)
},
Type::Custom {
ts_type,
to_rust,
from_rust,
helper,
} => {
if !helper.is_empty() {
module.used_helpers.insert(helper);
}
RustType::Custom {
ts_type,
to_rust,
from_rust,
}
},
Type::String | Type::Array { .. } | Type::Opaque => {
type_defs.insert(RustTypeList::Unsupported);
RustType::Unsupported
},
}
}
}
impl From<Abi> for FfiType {
fn from(abi: Abi) -> Self {
match abi {
Abi::Void => FfiType::Void,
Abi::U8 => FfiType::U8,
Abi::U16 => FfiType::U16,
Abi::U32 => FfiType::U32,
Abi::U64 => FfiType::U64,
Abi::Usize => FfiType::Usize,
Abi::I8 => FfiType::I8,
Abi::I16 => FfiType::I16,
Abi::I32 => FfiType::I32,
Abi::I64 => FfiType::I64,
Abi::Isize => FfiType::Isize,
Abi::F32 => FfiType::F32,
Abi::F64 => FfiType::F64,
Abi::Bool => FfiType::Bool,
Abi::Pointer => FfiType::Pointer,
Abi::Buffer => FfiType::Buffer,
Abi::Function => FfiType::FnPointer,
}
}
}
#[cfg(test)]
mod transform_tests {
use super::*;
#[test]
fn test_ts_type() {
let mut module = TsModule::default();
assert_eq!(Type::Void.unwrap(&mut module), RustType::Void);
assert_eq!(
Type::Ref(Box::new(Type::Str)).unwrap(&mut module),
RustType::Str
);
assert_eq!(
Type::FnPtr {
inputs: vec![Type::Numeric(TypeNumeric::U8)],
output: Box::new(Type::Numeric(TypeNumeric::U8)),
}
.unwrap(&mut module),
RustType::FnPtr("extern \"C\" fn(u8) -> u8".to_string())
);
}
#[test]
fn test_custom_records_helper() {
let mut module = TsModule::default();
let ty = Type::Custom {
ts_type: "string".to_string(),
to_rust: "encode({})".to_string(),
from_rust: "decode({})".to_string(),
helper: "codec".to_string(),
};
assert!(matches!(ty.unwrap(&mut module), RustType::Custom { .. }));
assert!(module.used_helpers.contains("codec"));
}
}
impl ToTokens for RustTypeNumeric {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
RustTypeNumeric::U64
| RustTypeNumeric::I64
| RustTypeNumeric::Usize
| RustTypeNumeric::Isize => quote! { bigint },
_ => quote! { number },
});
}
}
#[rustfmt::skip]
impl ToTokens for RustType {
fn to_tokens(&self, tokens: &mut TokenStream) {
fn get_str_or_slice(elem: &Box<RustType>, fallback: TokenStream) -> TokenStream {
match &**elem {
RustType::Str => quote! { RustStr },
RustType::Slice(elem) => quote! { RustSlice<#elem> },
_ => fallback,
}
}
tokens.extend(match self {
RustType::Void => quote! { void },
RustType::Numeric(ts_type_numeric) => ts_type_numeric.to_token_stream(),
RustType::Boolean => quote! { boolean },
RustType::FnPtr(sig) => quote! { RustFnPtr<#sig> },
RustType::Ptr(elem) =>
get_str_or_slice(elem, quote! { RustPtr<#elem> }),
RustType::PtrMut(elem) =>
get_str_or_slice(elem, quote! { RustPtrMut<#elem> }),
RustType::Ref(elem) =>
get_str_or_slice(elem, quote! { RustRef<#elem> }),
RustType::RefMut(elem) =>
get_str_or_slice(elem, quote! { RustRefMut<#elem> }),
RustType::Box(elem) =>
get_str_or_slice(elem, quote! { RustBox<#elem> }),
RustType::Str => quote! { RustStr },
RustType::Slice(elem) => quote! { RustSlice<#elem> },
RustType::Vec(elem) => quote! { RustVec<#elem> },
RustType::Tuple(elem) => quote! { RustTuple<[#(#elem),*]> },
RustType::UserDefined(ident) => quote! { #ident },
RustType::Unsupported => quote! { RustUnsupportedType },
RustType::Custom { ts_type, .. } => raw(ts_type.as_str()).to_token_stream(),
});
}
}
#[derive(Clone, Debug, Default)]
pub struct UserDefinedDefs {
store: BTreeSet<Ident>,
}
impl UserDefinedDefs {
fn insert(&mut self, ident: Ident) {
self.store.insert(ident);
}
pub fn dedup(&mut self, class_defs: &ClassDefs) {
class_defs.store.keys().for_each(|class_name| {
self.store
.extract_if(.., |user_defined| *user_defined == *class_name)
.for_each(drop);
});
}
}
impl ToTokens for UserDefinedDefs {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(
self.store
.iter()
.map(|ident| {
quote! {
export class #ident extends RustPrototype<#ident> {}
}
})
.collect::<TokenStream>(),
);
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum RustTypeList {
FnPtr,
Ptr,
PtrMut,
Ref,
RefMut,
Box,
Str,
Slice,
Vec,
Tuple,
Unsupported,
}
impl ToTokens for RustTypeList {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
RustTypeList::FnPtr => quote! { RustFnPtr },
RustTypeList::Ptr => quote! { RustPtr },
RustTypeList::PtrMut => quote! { RustPtrMut },
RustTypeList::Ref => quote! { RustRef },
RustTypeList::RefMut => quote! { RustRefMut },
RustTypeList::Box => quote! { RustBox },
RustTypeList::Str => quote! { RustStr },
RustTypeList::Slice => quote! { RustSlice },
RustTypeList::Vec => quote! { RustVec },
RustTypeList::Tuple => quote! { RustTuple },
RustTypeList::Unsupported => quote! { RustUnsupportedType },
});
}
}
#[derive(Clone, Debug, Default)]
pub struct RustTypeDefs {
store: BTreeSet<RustTypeList>,
}
impl RustTypeDefs {
fn insert(&mut self, ty: RustTypeList) {
self.store.insert(ty);
}
}
impl RustTypeDefs {
pub fn print_separate(
&self,
mut type_defs: String, type_defs_name: &str,
) -> (String, String) {
let mut type_imports = Vec::new();
let mut class_imports = vec![
quote! { RustPrototype },
quote! { __dodo_borrow },
quote! { __dodo_borrow_mut },
quote! { __dodo_borrow_async },
quote! { __dodo_borrow_mut_async },
quote! { __dodo_take_async },
quote! { __dodo_dispose },
];
for ty in &self.store {
match ty {
RustTypeList::Box
| RustTypeList::Str
| RustTypeList::Slice
| RustTypeList::Vec
| RustTypeList::Tuple => class_imports.push(ty.to_token_stream()),
rest => type_imports.push(rest.to_token_stream()),
};
}
type_defs.push('\n');
type_defs.push_str(
"e! {
export type { #(#type_imports),* };
export { #(#class_imports),* };
}
.to_string(),
);
let type_defs_name = format!("./{type_defs_name}");
let type_imports = if type_imports.is_empty() {
TokenStream::new()
} else {
quote! { import type { #(#type_imports),* } from #type_defs_name; }
};
let imports = quote! {
#type_imports
import { #(#class_imports),* } from #type_defs_name;
}
.to_string();
(type_defs, imports)
}
}