feat(napi-derive): support inject This<Value> into raw function
This commit is contained in:
parent
5030cfb8fb
commit
0ef482c6ca
11 changed files with 160 additions and 43 deletions
|
@ -156,8 +156,63 @@ impl NapiFn {
|
|||
}
|
||||
}
|
||||
} else if p.ident == "This" {
|
||||
if !is_in_class {
|
||||
bail_span!(p, "`This` is only allowed in class methods");
|
||||
if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||
args: angle_bracketed_args,
|
||||
..
|
||||
}) = &p.arguments
|
||||
{
|
||||
if let Some(syn::GenericArgument::Type(generic_type)) =
|
||||
angle_bracketed_args.first()
|
||||
{
|
||||
if let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { segments, .. },
|
||||
..
|
||||
}) = generic_type
|
||||
{
|
||||
if let Some(syn::PathSegment { ident, .. }) = segments.first() {
|
||||
if let Some((primitive_type, _)) =
|
||||
crate::PRIMITIVE_TYPES.iter().find(|(p, _)| ident == *p)
|
||||
{
|
||||
bail_span!(
|
||||
ident,
|
||||
"This type must not be {} \nthis in JavaScript function must be `Object` type or `undefined`",
|
||||
primitive_type
|
||||
);
|
||||
}
|
||||
args.push(
|
||||
quote! {
|
||||
{
|
||||
<#ident as napi::bindgen_prelude::FromNapiValue>::from_napi_value(env, cb.this)?
|
||||
}
|
||||
},
|
||||
);
|
||||
skipped_arg_count += 1;
|
||||
continue;
|
||||
}
|
||||
} else if let syn::Type::Reference(syn::TypeReference {
|
||||
elem,
|
||||
mutability,
|
||||
..
|
||||
}) = generic_type
|
||||
{
|
||||
if let syn::Type::Path(syn::TypePath {
|
||||
path: syn::Path { segments, .. },
|
||||
..
|
||||
}) = elem.as_ref()
|
||||
{
|
||||
if let Some(syn::PathSegment { ident, .. }) = segments.first() {
|
||||
let token = if mutability.is_some() {
|
||||
quote! { <#ident as napi::bindgen_prelude::FromNapiMutRef>::from_napi_mut_ref(env, cb.this)? }
|
||||
} else {
|
||||
quote! { <#ident as napi::bindgen_prelude::FromNapiRef>::from_napi_ref(env, cb.this)? }
|
||||
};
|
||||
args.push(token);
|
||||
skipped_arg_count += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
args.push(
|
||||
quote! { <napi::bindgen_prelude::This as napi::NapiValue>::from_raw_unchecked(env, cb.this) },
|
||||
|
|
|
@ -55,3 +55,40 @@ napi_ast_impl! {
|
|||
(Enum, NapiEnum),
|
||||
(Const, NapiConst),
|
||||
}
|
||||
|
||||
pub(crate) static PRIMITIVE_TYPES: &[(&str, &str)] = &[
|
||||
("JsUndefined", "undefined"),
|
||||
("()", "undefined"),
|
||||
("Undefined", "undefined"),
|
||||
("JsNumber", "number"),
|
||||
("i8", "number"),
|
||||
("i16", "number"),
|
||||
("i32", "number"),
|
||||
("i64", "number"),
|
||||
("f64", "number"),
|
||||
("u8", "number"),
|
||||
("u16", "number"),
|
||||
("u32", "number"),
|
||||
("u64", "bigint"),
|
||||
("i64n", "bigint"),
|
||||
("u128", "bigint"),
|
||||
("i128", "bigint"),
|
||||
("usize", "bigint"),
|
||||
("isize", "bigint"),
|
||||
("JsBigInt", "bigint"),
|
||||
("BigInt", "bigint"),
|
||||
("JsBoolean", "boolean"),
|
||||
("bool", "boolean"),
|
||||
("JsString", "string"),
|
||||
("String", "string"),
|
||||
("str", "string"),
|
||||
("Latin1String", "string"),
|
||||
("Utf16String", "string"),
|
||||
("char", "string"),
|
||||
("Null", "null"),
|
||||
("JsNull", "null"),
|
||||
("null", "null"),
|
||||
("Symbol", "symbol"),
|
||||
("JsSymbol", "symbol"),
|
||||
("JsFunction", "(...args: any[]) => any"),
|
||||
];
|
||||
|
|
|
@ -120,35 +120,8 @@ pub trait ToTypeDef {
|
|||
|
||||
static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
let mut map = HashMap::default();
|
||||
map.extend(crate::PRIMITIVE_TYPES.iter().cloned());
|
||||
map.extend([
|
||||
("JsUndefined", "undefined"),
|
||||
("()", "undefined"),
|
||||
("Undefined", "undefined"),
|
||||
("JsNumber", "number"),
|
||||
("i8", "number"),
|
||||
("i16", "number"),
|
||||
("i32", "number"),
|
||||
("i64", "number"),
|
||||
("f64", "number"),
|
||||
("u8", "number"),
|
||||
("u16", "number"),
|
||||
("u32", "number"),
|
||||
("u64", "bigint"),
|
||||
("i64n", "bigint"),
|
||||
("u128", "bigint"),
|
||||
("i128", "bigint"),
|
||||
("usize", "bigint"),
|
||||
("isize", "bigint"),
|
||||
("JsBigInt", "bigint"),
|
||||
("BigInt", "bigint"),
|
||||
("JsBoolean", "boolean"),
|
||||
("bool", "boolean"),
|
||||
("JsString", "string"),
|
||||
("String", "string"),
|
||||
("str", "string"),
|
||||
("Latin1String", "string"),
|
||||
("Utf16String", "string"),
|
||||
("char", "string"),
|
||||
("JsObject", "object"),
|
||||
("Object", "object"),
|
||||
("Array", "unknown[]"),
|
||||
|
@ -201,14 +174,8 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
|||
("Either24", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||
("Either25", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||
("Either26", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||
("Null", "null"),
|
||||
("JsNull", "null"),
|
||||
("null", "null"),
|
||||
("Symbol", "symbol"),
|
||||
("JsSymbol", "symbol"),
|
||||
("external", "object"),
|
||||
("AbortSignal", "AbortSignal"),
|
||||
("JsFunction", "(...args: any[]) => any"),
|
||||
("JsGlobal", "typeof global"),
|
||||
("External", "ExternalObject<{}>"),
|
||||
("unknown", "unknown"),
|
||||
|
|
|
@ -13,14 +13,18 @@ struct FnArg {
|
|||
}
|
||||
|
||||
struct FnArgList {
|
||||
this: Option<FnArg>,
|
||||
args: Vec<FnArg>,
|
||||
last_required: Option<usize>,
|
||||
}
|
||||
|
||||
impl Display for FnArgList {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(this) = &self.this {
|
||||
write!(f, "this: {}", this.ts_type)?;
|
||||
}
|
||||
for (i, arg) in self.args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
if i != 0 || self.this.is_some() {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
let is_optional = arg.is_optional
|
||||
|
@ -39,13 +43,22 @@ impl Display for FnArgList {
|
|||
|
||||
impl FromIterator<FnArg> for FnArgList {
|
||||
fn from_iter<T: IntoIterator<Item = FnArg>>(iter: T) -> Self {
|
||||
let args = iter.into_iter().collect::<Vec<_>>();
|
||||
let mut args = Vec::new();
|
||||
let mut this = None;
|
||||
for arg in iter.into_iter() {
|
||||
if arg.arg != "this" {
|
||||
args.push(arg);
|
||||
} else {
|
||||
this = Some(arg);
|
||||
}
|
||||
}
|
||||
let last_required = args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.rfind(|(_, arg)| !arg.is_optional)
|
||||
.map(|(i, _)| i);
|
||||
FnArgList {
|
||||
this,
|
||||
args,
|
||||
last_required,
|
||||
}
|
||||
|
@ -128,12 +141,27 @@ impl NapiFn {
|
|||
if let syn::Type::Path(path) = path.ty.as_ref() {
|
||||
if let Some(PathSegment {
|
||||
ident,
|
||||
arguments: PathArguments::AngleBracketed(_),
|
||||
arguments:
|
||||
PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||
args: angle_bracketed_args,
|
||||
..
|
||||
}),
|
||||
}) = path.path.segments.last()
|
||||
{
|
||||
if ident == "Reference" || ident == "WeakReference" {
|
||||
return None;
|
||||
}
|
||||
if ident == "This" {
|
||||
if let Some(syn::GenericArgument::Type(ty)) = angle_bracketed_args.first() {
|
||||
let (ts_type, _) = ty_to_ts_type(&ty, false, false);
|
||||
return Some(FnArg {
|
||||
arg: "this".to_owned(),
|
||||
ts_type,
|
||||
is_optional: false,
|
||||
});
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@ use std::any::type_name;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
|
||||
use crate::{bindgen_runtime::FromNapiValue, check_status, sys, JsObject, NapiRaw};
|
||||
use super::Object;
|
||||
use crate::{bindgen_runtime::FromNapiValue, check_status, sys, NapiRaw};
|
||||
|
||||
pub type This = JsObject;
|
||||
pub type This<T = Object> = T;
|
||||
|
||||
pub struct ClassInstance<T: 'static> {
|
||||
pub value: sys::napi_value,
|
||||
|
|
|
@ -41,7 +41,7 @@ macro_rules! impl_number_conversions {
|
|||
check_status!(
|
||||
unsafe { sys::$get(env, napi_val, &mut ret) },
|
||||
"Failed to convert napi value {:?} into rust type `{}`",
|
||||
type_of!(env, napi_val),
|
||||
type_of!(env, napi_val)?,
|
||||
$name,
|
||||
)?;
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ Generated by [AVA](https://avajs.dev).
|
|||
}␊
|
||||
export function createObjectWithClassField(): ObjectFieldClassInstance␊
|
||||
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊
|
||||
export function plusOne(this: Width): number␊
|
||||
export function dateToNumber(input: Date): number␊
|
||||
export function chronoDateToMillis(input: Date): number␊
|
||||
export function chronoDateAdd1Minute(input: Date): Date␊
|
||||
|
@ -298,6 +299,10 @@ Generated by [AVA](https://avajs.dev).
|
|||
export class CustomFinalize {␊
|
||||
constructor(width: number, height: number)␊
|
||||
}␊
|
||||
export class Width {␊
|
||||
value: number␊
|
||||
constructor(value: number)␊
|
||||
}␊
|
||||
export class ClassWithFactory {␊
|
||||
name: string␊
|
||||
static withName(name: string): ClassWithFactory␊
|
||||
|
|
Binary file not shown.
|
@ -108,6 +108,8 @@ import {
|
|||
getNumArr,
|
||||
getNestedNumArr,
|
||||
CustomFinalize,
|
||||
plusOne,
|
||||
Width,
|
||||
} from '../'
|
||||
|
||||
test('export const', (t) => {
|
||||
|
@ -161,7 +163,7 @@ test('enum', (t) => {
|
|||
t.is(enumToI32(CustomNumEnum.Eight), 8)
|
||||
})
|
||||
|
||||
test('class', (t) => {
|
||||
test.only('class', (t) => {
|
||||
const dog = new Animal(Kind.Dog, '旺财')
|
||||
|
||||
t.is(dog.name, '旺财')
|
||||
|
@ -192,6 +194,13 @@ test('class', (t) => {
|
|||
const turtle = NinjaTurtle.newRaph()
|
||||
t.is(turtle.returnThis(), turtle)
|
||||
t.is(NinjaTurtle.isInstanceOf(turtle), true)
|
||||
// Inject this to function
|
||||
const width = new Width(1)
|
||||
t.is(plusOne.call(width), 2)
|
||||
t.throws(() => {
|
||||
// @ts-expect-error
|
||||
plusOne.call('')
|
||||
})
|
||||
})
|
||||
|
||||
test('class factory', (t) => {
|
||||
|
|
5
examples/napi/index.d.ts
vendored
5
examples/napi/index.d.ts
vendored
|
@ -38,6 +38,7 @@ export interface ObjectFieldClassInstance {
|
|||
}
|
||||
export function createObjectWithClassField(): ObjectFieldClassInstance
|
||||
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird
|
||||
export function plusOne(this: Width): number
|
||||
export function dateToNumber(input: Date): number
|
||||
export function chronoDateToMillis(input: Date): number
|
||||
export function chronoDateAdd1Minute(input: Date): Date
|
||||
|
@ -288,6 +289,10 @@ export class NotWritableClass {
|
|||
export class CustomFinalize {
|
||||
constructor(width: number, height: number)
|
||||
}
|
||||
export class Width {
|
||||
value: number
|
||||
constructor(value: number)
|
||||
}
|
||||
export class ClassWithFactory {
|
||||
name: string
|
||||
static withName(name: string): ClassWithFactory
|
||||
|
|
|
@ -395,3 +395,13 @@ impl ObjectFinalize for CustomFinalize {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[napi(constructor)]
|
||||
pub struct Width {
|
||||
pub value: i32,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn plus_one(this: This<&Width>) -> i32 {
|
||||
this.value + 1
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue