diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs index a8857afe..b016b577 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -38,6 +38,10 @@ impl NapiEnum { fn type_name() -> &'static str { #name_str } + + fn value_type() -> napi::ValueType { + napi::ValueType::Object + } } impl ValidateNapiValue for #name { diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index e29cd9ba..c85d9dc7 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -16,6 +16,10 @@ fn gen_napi_value_map_impl(name: &Ident, to_napi_val_impl: TokenStream) -> Token fn type_name() -> &'static str { #name_str } + + fn value_type() -> napi::ValueType { + napi::ValueType::Function + } } #to_napi_val_impl @@ -255,6 +259,10 @@ impl NapiStruct { fn type_name() -> &'static str { #name_str } + + fn value_type() -> napi::ValueType { + napi::ValueType::Object + } } impl ToNapiValue for #name { diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index f2404e8f..7839c9f8 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -59,6 +59,10 @@ static KNOWN_TYPES: Lazy> = Lazy::new(|| { ("Vec", "Array<{}>"), ("Option", "{} | null"), ("Result", "Error | {}"), + ("Either", "{} | {}"), + ("Either3", "{} | {} | {}"), + ("Either4", "{} | {} | {} | {}"), + ("Either5", "{} | {} | {} | {} | {}"), ]); map diff --git a/crates/napi/src/bindgen_runtime/js_values.rs b/crates/napi/src/bindgen_runtime/js_values.rs index 5e296f7c..7f35f72c 100644 --- a/crates/napi/src/bindgen_runtime/js_values.rs +++ b/crates/napi/src/bindgen_runtime/js_values.rs @@ -4,6 +4,7 @@ use std::ptr; mod array; mod boolean; mod buffer; +mod either; mod map; mod nil; mod number; @@ -14,6 +15,7 @@ mod string; pub use array::*; pub use buffer::*; +pub use either::*; pub use nil::*; pub use object::*; pub use string::*; @@ -23,6 +25,8 @@ pub use string::latin1_string::*; pub trait TypeName { fn type_name() -> &'static str; + + fn value_type() -> ValueType; } pub trait ToNapiValue { @@ -114,10 +118,14 @@ pub trait ValidateNapiValue: FromNapiValue + TypeName { } } -impl TypeName for Option { +impl TypeName for Option { fn type_name() -> &'static str { "Option" } + + fn value_type() -> ValueType { + T::value_type() + } } impl FromNapiValue for Option diff --git a/crates/napi/src/bindgen_runtime/js_values/array.rs b/crates/napi/src/bindgen_runtime/js_values/array.rs index c2500df5..3d9587f4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/array.rs +++ b/crates/napi/src/bindgen_runtime/js_values/array.rs @@ -74,6 +74,10 @@ impl TypeName for Array { fn type_name() -> &'static str { "Array" } + + fn value_type() -> ValueType { + ValueType::Object + } } impl ToNapiValue for Array { @@ -122,6 +126,10 @@ impl TypeName for Vec { fn type_name() -> &'static str { "Array" } + + fn value_type() -> ValueType { + ValueType::Object + } } impl ToNapiValue for Vec diff --git a/crates/napi/src/bindgen_runtime/js_values/boolean.rs b/crates/napi/src/bindgen_runtime/js_values/boolean.rs index ad061eca..b7398c8b 100644 --- a/crates/napi/src/bindgen_runtime/js_values/boolean.rs +++ b/crates/napi/src/bindgen_runtime/js_values/boolean.rs @@ -4,6 +4,10 @@ impl TypeName for bool { fn type_name() -> &'static str { "bool" } + + fn value_type() -> ValueType { + ValueType::Boolean + } } impl ValidateNapiValue for bool { diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index 64024d8a..66fd324a 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -52,6 +52,10 @@ impl TypeName for Buffer { fn type_name() -> &'static str { "Vec" } + + fn value_type() -> ValueType { + ValueType::Object + } } impl FromNapiValue for Buffer { diff --git a/crates/napi/src/bindgen_runtime/js_values/either.rs b/crates/napi/src/bindgen_runtime/js_values/either.rs new file mode 100644 index 00000000..192aa142 --- /dev/null +++ b/crates/napi/src/bindgen_runtime/js_values/either.rs @@ -0,0 +1,359 @@ +use super::{FromNapiValue, ToNapiValue, TypeName}; +use crate::{type_of, Status, ValueType}; + +const ERROR_MSG: &str = "The return value of typeof(T) should not be equal in Either"; + +#[derive(Debug, Clone, Copy)] +pub enum Either< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, +> { + A(A), + B(B), +} + +impl TypeName + for Either +{ + fn type_name() -> &'static str { + "Either" + } + + fn value_type() -> ValueType { + ValueType::Unknown + } +} + +impl + FromNapiValue for Either +{ + unsafe fn from_napi_value( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> crate::Result { + debug_assert!(A::value_type() != B::value_type(), "{}", ERROR_MSG); + let js_type = type_of!(env, napi_val)?; + if js_type == A::value_type() { + A::from_napi_value(env, napi_val).map(Self::A) + } else if js_type == B::value_type() { + B::from_napi_value(env, napi_val).map(Self::B) + } else { + Err(crate::Error::new( + Status::InvalidArg, + format!( + "Expect type {} or {}, but got {}", + A::value_type(), + B::value_type(), + js_type + ), + )) + } + } +} + +impl + ToNapiValue for Either +{ + unsafe fn to_napi_value( + env: napi_sys::napi_env, + value: Self, + ) -> crate::Result { + match value { + Self::A(a) => A::to_napi_value(env, a), + Self::B(b) => B::to_napi_value(env, b), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Either3< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, +> { + A(A), + B(B), + C(C), +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + > TypeName for Either3 +{ + fn type_name() -> &'static str { + "Either3" + } + + fn value_type() -> ValueType { + ValueType::Unknown + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + > FromNapiValue for Either3 +{ + unsafe fn from_napi_value( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> crate::Result { + debug_assert!( + { + let mut types = vec![A::value_type(), B::value_type(), C::value_type()]; + types.dedup(); + types.len() == 3 + }, + "{}", + ERROR_MSG + ); + let js_type = type_of!(env, napi_val)?; + if js_type == A::value_type() { + A::from_napi_value(env, napi_val).map(Self::A) + } else if js_type == B::value_type() { + B::from_napi_value(env, napi_val).map(Self::B) + } else if js_type == C::value_type() { + C::from_napi_value(env, napi_val).map(Self::C) + } else { + Err(crate::Error::new( + Status::InvalidArg, + format!( + "Expect type {} or {} or {}, but got {}", + A::value_type(), + B::value_type(), + C::value_type(), + js_type + ), + )) + } + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + > ToNapiValue for Either3 +{ + unsafe fn to_napi_value( + env: napi_sys::napi_env, + value: Self, + ) -> crate::Result { + match value { + Self::A(a) => A::to_napi_value(env, a), + Self::B(b) => B::to_napi_value(env, b), + Self::C(c) => C::to_napi_value(env, c), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Either4< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, +> { + A(A), + B(B), + C(C), + D(D), +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + > TypeName for Either4 +{ + fn type_name() -> &'static str { + "Either4" + } + + fn value_type() -> ValueType { + ValueType::Unknown + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + > FromNapiValue for Either4 +{ + unsafe fn from_napi_value( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> crate::Result { + debug_assert!( + { + let mut types = vec![ + A::value_type(), + B::value_type(), + C::value_type(), + D::value_type(), + ]; + types.dedup(); + types.len() == 4 + }, + "{}", + ERROR_MSG + ); + let js_type = type_of!(env, napi_val)?; + if js_type == A::value_type() { + A::from_napi_value(env, napi_val).map(Self::A) + } else if js_type == B::value_type() { + B::from_napi_value(env, napi_val).map(Self::B) + } else if js_type == C::value_type() { + C::from_napi_value(env, napi_val).map(Self::C) + } else if js_type == D::value_type() { + D::from_napi_value(env, napi_val).map(Self::D) + } else { + Err(crate::Error::new( + Status::InvalidArg, + format!( + "Expect type {} or {} or {} or {}, but got {}", + A::value_type(), + B::value_type(), + C::value_type(), + D::value_type(), + js_type + ), + )) + } + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + > ToNapiValue for Either4 +{ + unsafe fn to_napi_value( + env: napi_sys::napi_env, + value: Self, + ) -> crate::Result { + match value { + Self::A(a) => A::to_napi_value(env, a), + Self::B(b) => B::to_napi_value(env, b), + Self::C(c) => C::to_napi_value(env, c), + Self::D(d) => D::to_napi_value(env, d), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Either5< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + E: TypeName + FromNapiValue + ToNapiValue, +> { + A(A), + B(B), + C(C), + D(D), + E(E), +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + E: TypeName + FromNapiValue + ToNapiValue, + > TypeName for Either5 +{ + fn type_name() -> &'static str { + "Either5" + } + + fn value_type() -> ValueType { + ValueType::Unknown + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + E: TypeName + FromNapiValue + ToNapiValue, + > FromNapiValue for Either5 +{ + unsafe fn from_napi_value( + env: napi_sys::napi_env, + napi_val: napi_sys::napi_value, + ) -> crate::Result { + debug_assert!( + { + let mut types = vec![ + A::value_type(), + B::value_type(), + C::value_type(), + D::value_type(), + E::value_type(), + ]; + types.dedup(); + types.len() == 5 + }, + "{}", + ERROR_MSG + ); + let js_type = type_of!(env, napi_val)?; + if js_type == A::value_type() { + A::from_napi_value(env, napi_val).map(Self::A) + } else if js_type == B::value_type() { + B::from_napi_value(env, napi_val).map(Self::B) + } else if js_type == C::value_type() { + C::from_napi_value(env, napi_val).map(Self::C) + } else if js_type == D::value_type() { + D::from_napi_value(env, napi_val).map(Self::D) + } else if js_type == E::value_type() { + E::from_napi_value(env, napi_val).map(Self::E) + } else { + Err(crate::Error::new( + Status::InvalidArg, + format!( + "Expect type {} or {} or {} or {} or {}, but got {}", + A::value_type(), + B::value_type(), + C::value_type(), + D::value_type(), + E::value_type(), + js_type + ), + )) + } + } +} + +impl< + A: TypeName + FromNapiValue + ToNapiValue, + B: TypeName + FromNapiValue + ToNapiValue, + C: TypeName + FromNapiValue + ToNapiValue, + D: TypeName + FromNapiValue + ToNapiValue, + E: TypeName + FromNapiValue + ToNapiValue, + > ToNapiValue for Either5 +{ + unsafe fn to_napi_value( + env: napi_sys::napi_env, + value: Self, + ) -> crate::Result { + match value { + Self::A(a) => A::to_napi_value(env, a), + Self::B(b) => B::to_napi_value(env, b), + Self::C(c) => C::to_napi_value(env, c), + Self::D(d) => D::to_napi_value(env, d), + Self::E(e) => E::to_napi_value(env, e), + } + } +} diff --git a/crates/napi/src/bindgen_runtime/js_values/map.rs b/crates/napi/src/bindgen_runtime/js_values/map.rs index ddbd33e0..f91e7c51 100644 --- a/crates/napi/src/bindgen_runtime/js_values/map.rs +++ b/crates/napi/src/bindgen_runtime/js_values/map.rs @@ -7,6 +7,10 @@ impl TypeName for HashMap { fn type_name() -> &'static str { "HashMap" } + + fn value_type() -> ValueType { + ValueType::Object + } } impl ToNapiValue for HashMap diff --git a/crates/napi/src/bindgen_runtime/js_values/nil.rs b/crates/napi/src/bindgen_runtime/js_values/nil.rs index 50177192..11971b93 100644 --- a/crates/napi/src/bindgen_runtime/js_values/nil.rs +++ b/crates/napi/src/bindgen_runtime/js_values/nil.rs @@ -9,6 +9,10 @@ impl TypeName for Null { fn type_name() -> &'static str { "null" } + + fn value_type() -> ValueType { + ValueType::Null + } } impl ValidateNapiValue for Null { @@ -46,6 +50,10 @@ impl TypeName for Undefined { fn type_name() -> &'static str { "undefined" } + + fn value_type() -> ValueType { + ValueType::Undefined + } } impl ValidateNapiValue for Undefined { diff --git a/crates/napi/src/bindgen_runtime/js_values/number.rs b/crates/napi/src/bindgen_runtime/js_values/number.rs index 1c49a938..457b0ad4 100644 --- a/crates/napi/src/bindgen_runtime/js_values/number.rs +++ b/crates/napi/src/bindgen_runtime/js_values/number.rs @@ -8,6 +8,10 @@ macro_rules! impl_number_conversions { fn type_name() -> &'static str { $name } + + fn value_type() -> crate::ValueType { + crate::ValueType::Number + } } impl $crate::bindgen_prelude::ValidateNapiValue for $t { diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index 2d08c3af..4729b274 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -81,6 +81,10 @@ impl TypeName for Object { fn type_name() -> &'static str { "Object" } + + fn value_type() -> ValueType { + ValueType::Object + } } impl ToNapiValue for Object { diff --git a/crates/napi/src/bindgen_runtime/js_values/string.rs b/crates/napi/src/bindgen_runtime/js_values/string.rs index bad667bd..cc07a9db 100644 --- a/crates/napi/src/bindgen_runtime/js_values/string.rs +++ b/crates/napi/src/bindgen_runtime/js_values/string.rs @@ -11,6 +11,10 @@ impl TypeName for String { fn type_name() -> &'static str { "String" } + + fn value_type() -> ValueType { + ValueType::String + } } impl ToNapiValue for String { @@ -61,6 +65,10 @@ impl TypeName for &str { fn type_name() -> &'static str { "String" } + + fn value_type() -> ValueType { + ValueType::String + } } impl ToNapiValue for &str { @@ -96,6 +104,10 @@ impl TypeName for Utf16String { fn type_name() -> &'static str { "String(utf16)" } + + fn value_type() -> ValueType { + ValueType::String + } } impl FromNapiValue for Utf16String { @@ -181,6 +193,10 @@ pub mod latin1_string { fn type_name() -> &'static str { "String(latin1)" } + + fn value_type() -> ValueType { + ValueType::String + } } #[cfg(feature = "latin1")] diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 4f4a206f..75aac183 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -14,6 +14,13 @@ Generated by [AVA](https://avajs.dev). export function readFileAsync(path: string): Promise␊ export function getCwd(callback: (arg0: string) => void): void␊ export function readFile(callback: (arg0: Error | undefined, arg1: string | 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␊ + interface Obj {␊ + v: string | number␊ + }␊ + export function either4(input: string | number | boolean | Obj): number␊ 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␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index d423c065..bf6d133e 100644 Binary files a/examples/napi/__test__/typegen.spec.ts.snap and b/examples/napi/__test__/typegen.spec.ts.snap differ diff --git a/examples/napi/__test__/values.spec.ts b/examples/napi/__test__/values.spec.ts index fca2a6a8..c9c01474 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -26,6 +26,10 @@ import { getPackageJsonName, getBuffer, readFileAsync, + eitherStringOrNumber, + returnEither, + either3, + either4, } from '../' test('number', (t) => { @@ -130,3 +134,29 @@ test('async', async (t) => { await t.throwsAsync(() => readFileAsync('some_nonexist_path.file')) }) + +test('either', (t) => { + t.is(eitherStringOrNumber(2), 2) + t.is(eitherStringOrNumber('hello'), 'hello'.length) +}) + +test('return either', (t) => { + t.is(returnEither(2), 2) + t.is(returnEither(42), '42') +}) + +test('either3', (t) => { + t.is(either3(2), 2) + t.is(either3('hello'), 'hello'.length) + t.is(either3(true), 1) + t.is(either3(false), 0) +}) + +test('either4', (t) => { + t.is(either4(2), 2) + t.is(either4('hello'), 'hello'.length) + t.is(either4(true), 1) + t.is(either4(false), 0) + t.is(either4({ v: 1 }), 1) + t.is(either4({ v: 'world' }), 'world'.length) +}) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index d251a91d..54b6e118 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -4,6 +4,13 @@ export function sumNums(nums: Array): number export function readFileAsync(path: string): Promise export function getCwd(callback: (arg0: string) => void): void export function readFile(callback: (arg0: Error | undefined, arg1: string | 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 +interface Obj { + v: string | number +} +export function either4(input: string | number | boolean | Obj): number 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 diff --git a/examples/napi/src/either.rs b/examples/napi/src/either.rs new file mode 100644 index 00000000..12ddccdc --- /dev/null +++ b/examples/napi/src/either.rs @@ -0,0 +1,57 @@ +use napi::bindgen_prelude::*; + +#[napi] +fn either_string_or_number(input: Either) -> u32 { + match input { + Either::A(s) => s.len() as u32, + Either::B(n) => n, + } +} + +#[napi] +fn return_either(input: u32) -> Either { + if input > 10 { + Either::A(format!("{}", input)) + } else { + Either::B(input) + } +} + +#[napi] +fn either3(input: Either3) -> u32 { + match input { + Either3::A(s) => s.len() as u32, + Either3::B(n) => n, + Either3::C(b) => { + if b { + 1 + } else { + 0 + } + } + } +} + +#[napi(object)] +struct Obj { + pub v: Either, +} + +#[napi] +fn either4(input: Either4) -> u32 { + match input { + Either4::A(s) => s.len() as u32, + Either4::B(n) => n, + Either4::C(b) => { + if b { + 1 + } else { + 0 + } + } + Either4::D(f) => match f.v { + Either::A(s) => s.len() as u32, + Either::B(n) => n, + }, + } +} diff --git a/examples/napi/src/lib.rs b/examples/napi/src/lib.rs index 057d6756..b739a35f 100644 --- a/examples/napi/src/lib.rs +++ b/examples/napi/src/lib.rs @@ -7,6 +7,7 @@ mod array; mod r#async; mod callback; mod class; +mod either; mod r#enum; mod error; mod nullable;