diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 3830143a..a5197acb 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -69,7 +69,6 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Buffer", "Buffer"), // TODO: Vec should be Buffer, now is Array ("Vec", "Array<{}>"), - ("Option", "{} | null"), ("Result", "Error | {}"), ("Either", "{} | {}"), ("Either3", "{} | {} | {}"), @@ -106,21 +105,24 @@ fn fill_ty(template: &str, args: Vec) -> String { ret } -pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String { +pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { match ty { Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty), Type::Tuple(tuple) => { if tuple.elems.is_empty() { - "undefined".to_owned() + ("undefined".to_owned(), false) } else { - format!( - "[{}]", - tuple - .elems - .iter() - .map(|elem| ty_to_ts_type(elem, false)) - .collect::>() - .join(", ") + ( + format!( + "[{}]", + tuple + .elems + .iter() + .map(|elem| ty_to_ts_type(elem, false).0) + .collect::>() + .join(", ") + ), + false, ) } } @@ -144,36 +146,46 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> String { if rust_ty == "Result" && is_return_ty { ts_ty = Some(args.first().unwrap().to_owned()); + } else if rust_ty == "Option" { + ts_ty = args + .first() + .map(|(arg, _)| (format!("{} | undefined | null", arg), true)); } else if rust_ty == "AsyncTask" { ts_ty = r#struct::TASK_STRUCTS.with(|t| { - let output_type = args.first().unwrap().to_owned(); + let (output_type, _) = args.first().unwrap().to_owned(); if let Some(o) = t.borrow().get(&output_type) { - Some(format!("Promise<{}>", o)) + Some((format!("Promise<{}>", o), false)) } else { - Some("Promise".to_owned()) + Some(("Promise".to_owned(), false)) } }); } else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) { if known_ty.contains("{}") { - ts_ty = Some(fill_ty(known_ty, args)); + ts_ty = Some(( + fill_ty(known_ty, args.into_iter().map(|(arg, _)| arg).collect()), + false, + )); } else { - ts_ty = Some(known_ty.to_owned()); + ts_ty = Some((known_ty.to_owned(), false)); } } else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS .with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned()) { - ts_ty = Some(t); + ts_ty = Some((t, false)); } else if rust_ty == "Promise" { - ts_ty = Some(format!("Promise<{}>", args.first().unwrap())); + ts_ty = Some(( + format!("Promise<{}>", args.first().map(|(arg, _)| arg).unwrap()), + false, + )); } else { // there should be runtime registered type in else - ts_ty = Some(rust_ty); + ts_ty = Some((rust_ty, false)); } } - ts_ty.unwrap_or_else(|| "any".to_owned()) + ts_ty.unwrap_or_else(|| ("any".to_owned(), false)) } Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty), - _ => "any".to_owned(), + _ => ("any".to_owned(), false), } } diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index 48a779e5..733e303c 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -30,11 +30,18 @@ fn gen_callback_type(callback: &CallbackArg) -> String { .args .iter() .enumerate() - .map(|(i, arg)| { format!("arg{}: {}", i, ty_to_ts_type(arg, false)) }) + .map(|(i, arg)| { + let (arg, is_optional) = ty_to_ts_type(arg, false); + if is_optional { + format!("arg{}?: {}", i, arg) + } else { + format!("arg{}: {}", i, arg) + } + }) .collect::>() .join(", "), ret = match &callback.ret { - Some(ty) => ty_to_ts_type(ty, true), + Some(ty) => ty_to_ts_type(ty, true).0, None => "void".to_owned(), } ) @@ -56,8 +63,9 @@ impl NapiFn { i.mutability = None; } let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); - arg.push_str(": "); - arg.push_str(&ty_to_ts_type(&path.ty, false)); + let (ts_arg, is_optional) = ty_to_ts_type(&path.ty, false); + arg.push_str(if is_optional { "?: " } else { ": " }); + arg.push_str(&ts_arg); Some(arg) } @@ -100,7 +108,7 @@ impl NapiFn { .unwrap_or_else(|| "".to_owned()), _ => { let ret = if let Some(ret) = &self.ret { - let ts_type = ty_to_ts_type(ret, true); + let (ts_type, _) = ty_to_ts_type(ret, true); if ts_type == "undefined" { "void".to_owned() } else { diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index 3790bf75..e871ce08 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -32,7 +32,7 @@ impl ToTypeDef for NapiImpl { if let Some(output_type) = &self.task_output_type { TASK_STRUCTS.with(|t| { t.borrow_mut() - .insert(self.js_name.clone(), ty_to_ts_type(output_type, false)); + .insert(self.js_name.clone(), ty_to_ts_type(output_type, false).0); }); } TypeDef { @@ -61,7 +61,9 @@ impl NapiStruct { if !f.setter { field_str.push_str("readonly ") } - let arg = format!("{}: {}", &f.js_name, ty_to_ts_type(&f.ty, false)); + let (arg, is_optional) = ty_to_ts_type(&f.ty, false); + let sep = if is_optional { "?" } else { "" }; + let arg = format!("{}{}: {}", &f.js_name, sep, arg); if self.kind == NapiStructKind::Constructor { ctor_args.push(arg.clone()); } diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 229af9ce..4a78e624 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -7,7 +7,7 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt export function createBigInt(): BigInt export function createBigIntI64(): BigInt export function getCwd(callback: (arg0: string) => void): void -export function readFile(callback: (arg0: Error | undefined, arg1: string | null) => void): void +export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function eitherStringOrNumber(input: string | number): number export function returnEither(input: number): string | number export function either3(input: string | number | boolean): number @@ -19,7 +19,7 @@ export enum Kind { Dog = 0, Cat = 1, Duck = 2 } export enum CustomNumEnum { One = 1, Two = 2, Three = 3, Four = 4, Six = 6, Eight = 8, Nine = 9, Ten = 10 } export function enumToI32(e: CustomNumEnum): number export function throwError(): void -export function mapOption(val: number | null): number | null +export function mapOption(val?: number | undefined | null): number | undefined | null export function add(a: number, b: number): number export function fibonacci(n: number): number export function listObjKeys(obj: object): Array @@ -31,8 +31,8 @@ export function asyncPlus100(p: Promise): Promise interface PackageJson { name: string version: string - dependencies: Record | null - devDependencies: Record | null + dependencies?: Record | undefined | null + devDependencies?: Record | undefined | null } export function readPackageJson(): PackageJson export function getPackageJsonName(packageJson: PackageJson): string @@ -64,7 +64,7 @@ export class Blake2BKey { } export class Context { - + maybeNeed?: boolean | undefined | null constructor() static withData(data: string): Context method(): string diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index 73b03119..3cd699c2 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -79,6 +79,7 @@ impl Blake2bKey { #[napi] pub struct Context { data: String, + pub maybe_need: Option, } // Test for return `napi::Result` and `Result` @@ -88,12 +89,16 @@ impl Context { pub fn new() -> napi::Result { Ok(Self { data: "not empty".into(), + maybe_need: None, }) } #[napi(factory)] pub fn with_data(data: String) -> Result { - Ok(Self { data }) + Ok(Self { + data, + maybe_need: Some(true), + }) } #[napi]