fix(napi-derive-backend): Option value should produce optional types

This commit is contained in:
LongYinan 2021-11-16 13:02:40 +08:00
parent 0e0bfb1c0a
commit b4a8cadb21
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
5 changed files with 61 additions and 34 deletions

View file

@ -69,7 +69,6 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("Buffer", "Buffer"), ("Buffer", "Buffer"),
// TODO: Vec<u8> should be Buffer, now is Array<number> // TODO: Vec<u8> should be Buffer, now is Array<number>
("Vec", "Array<{}>"), ("Vec", "Array<{}>"),
("Option", "{} | null"),
("Result", "Error | {}"), ("Result", "Error | {}"),
("Either", "{} | {}"), ("Either", "{} | {}"),
("Either3", "{} | {} | {}"), ("Either3", "{} | {} | {}"),
@ -106,21 +105,24 @@ fn fill_ty(template: &str, args: Vec<String>) -> String {
ret 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 { match ty {
Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty), Type::Reference(r) => ty_to_ts_type(&r.elem, is_return_ty),
Type::Tuple(tuple) => { Type::Tuple(tuple) => {
if tuple.elems.is_empty() { if tuple.elems.is_empty() {
"undefined".to_owned() ("undefined".to_owned(), false)
} else { } else {
format!( (
"[{}]", format!(
tuple "[{}]",
.elems tuple
.iter() .elems
.map(|elem| ty_to_ts_type(elem, false)) .iter()
.collect::<Vec<_>>() .map(|elem| ty_to_ts_type(elem, false).0)
.join(", ") .collect::<Vec<_>>()
.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 { if rust_ty == "Result" && is_return_ty {
ts_ty = Some(args.first().unwrap().to_owned()); 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" { } else if rust_ty == "AsyncTask" {
ts_ty = r#struct::TASK_STRUCTS.with(|t| { 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) { if let Some(o) = t.borrow().get(&output_type) {
Some(format!("Promise<{}>", o)) Some((format!("Promise<{}>", o), false))
} else { } else {
Some("Promise<unknown>".to_owned()) Some(("Promise<unknown>".to_owned(), false))
} }
}); });
} else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) { } else if let Some(&known_ty) = KNOWN_TYPES.get(rust_ty.as_str()) {
if known_ty.contains("{}") { 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 { } 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 } else if let Some(t) = crate::typegen::r#struct::CLASS_STRUCTS
.with(|c| c.borrow_mut().get(rust_ty.as_str()).cloned()) .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" { } 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 { } else {
// there should be runtime registered type in 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), Type::Group(g) => ty_to_ts_type(&g.elem, is_return_ty),
_ => "any".to_owned(), _ => ("any".to_owned(), false),
} }
} }

View file

@ -30,11 +30,18 @@ fn gen_callback_type(callback: &CallbackArg) -> String {
.args .args
.iter() .iter()
.enumerate() .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::<Vec<_>>() .collect::<Vec<_>>()
.join(", "), .join(", "),
ret = match &callback.ret { 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(), None => "void".to_owned(),
} }
) )
@ -56,8 +63,9 @@ impl NapiFn {
i.mutability = None; i.mutability = None;
} }
let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel); let mut arg = path.pat.to_token_stream().to_string().to_case(Case::Camel);
arg.push_str(": "); let (ts_arg, is_optional) = ty_to_ts_type(&path.ty, false);
arg.push_str(&ty_to_ts_type(&path.ty, false)); arg.push_str(if is_optional { "?: " } else { ": " });
arg.push_str(&ts_arg);
Some(arg) Some(arg)
} }
@ -100,7 +108,7 @@ impl NapiFn {
.unwrap_or_else(|| "".to_owned()), .unwrap_or_else(|| "".to_owned()),
_ => { _ => {
let ret = if let Some(ret) = &self.ret { 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" { if ts_type == "undefined" {
"void".to_owned() "void".to_owned()
} else { } else {

View file

@ -32,7 +32,7 @@ impl ToTypeDef for NapiImpl {
if let Some(output_type) = &self.task_output_type { if let Some(output_type) = &self.task_output_type {
TASK_STRUCTS.with(|t| { TASK_STRUCTS.with(|t| {
t.borrow_mut() 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 { TypeDef {
@ -61,7 +61,9 @@ impl NapiStruct {
if !f.setter { if !f.setter {
field_str.push_str("readonly ") 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 { if self.kind == NapiStructKind::Constructor {
ctor_args.push(arg.clone()); ctor_args.push(arg.clone());
} }

View file

@ -7,7 +7,7 @@ export function bigintAdd(a: BigInt, b: BigInt): BigInt
export function createBigInt(): BigInt export function createBigInt(): BigInt
export function createBigIntI64(): BigInt export function createBigIntI64(): BigInt
export function getCwd(callback: (arg0: string) => void): void 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 eitherStringOrNumber(input: string | number): number
export function returnEither(input: number): string | number export function returnEither(input: number): string | number
export function either3(input: string | number | boolean): 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 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 enumToI32(e: CustomNumEnum): number
export function throwError(): void 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 add(a: number, b: number): number
export function fibonacci(n: number): number export function fibonacci(n: number): number
export function listObjKeys(obj: object): Array<string> export function listObjKeys(obj: object): Array<string>
@ -31,8 +31,8 @@ export function asyncPlus100(p: Promise<number>): Promise<number>
interface PackageJson { interface PackageJson {
name: string name: string
version: string version: string
dependencies: Record<string, any> | null dependencies?: Record<string, any> | undefined | null
devDependencies: Record<string, any> | null devDependencies?: Record<string, any> | undefined | null
} }
export function readPackageJson(): PackageJson export function readPackageJson(): PackageJson
export function getPackageJsonName(packageJson: PackageJson): string export function getPackageJsonName(packageJson: PackageJson): string
@ -64,7 +64,7 @@ export class Blake2BKey {
} }
export class Context { export class Context {
maybeNeed?: boolean | undefined | null
constructor() constructor()
static withData(data: string): Context static withData(data: string): Context
method(): string method(): string

View file

@ -79,6 +79,7 @@ impl Blake2bKey {
#[napi] #[napi]
pub struct Context { pub struct Context {
data: String, data: String,
pub maybe_need: Option<bool>,
} }
// Test for return `napi::Result` and `Result` // Test for return `napi::Result` and `Result`
@ -88,12 +89,16 @@ impl Context {
pub fn new() -> napi::Result<Self> { pub fn new() -> napi::Result<Self> {
Ok(Self { Ok(Self {
data: "not empty".into(), data: "not empty".into(),
maybe_need: None,
}) })
} }
#[napi(factory)] #[napi(factory)]
pub fn with_data(data: String) -> Result<Self> { pub fn with_data(data: String) -> Result<Self> {
Ok(Self { data }) Ok(Self {
data,
maybe_need: Some(true),
})
} }
#[napi] #[napi]