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" {
|
} else if p.ident == "This" {
|
||||||
if !is_in_class {
|
if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||||
bail_span!(p, "`This` is only allowed in class methods");
|
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(
|
args.push(
|
||||||
quote! { <napi::bindgen_prelude::This as napi::NapiValue>::from_raw_unchecked(env, cb.this) },
|
quote! { <napi::bindgen_prelude::This as napi::NapiValue>::from_raw_unchecked(env, cb.this) },
|
||||||
|
|
|
@ -55,3 +55,40 @@ napi_ast_impl! {
|
||||||
(Enum, NapiEnum),
|
(Enum, NapiEnum),
|
||||||
(Const, NapiConst),
|
(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(|| {
|
static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
|
map.extend(crate::PRIMITIVE_TYPES.iter().cloned());
|
||||||
map.extend([
|
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"),
|
("JsObject", "object"),
|
||||||
("Object", "object"),
|
("Object", "object"),
|
||||||
("Array", "unknown[]"),
|
("Array", "unknown[]"),
|
||||||
|
@ -201,14 +174,8 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||||
("Either24", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
("Either24", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||||
("Either25", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
("Either25", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||||
("Either26", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
("Either26", "{} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {} | {}"),
|
||||||
("Null", "null"),
|
|
||||||
("JsNull", "null"),
|
|
||||||
("null", "null"),
|
|
||||||
("Symbol", "symbol"),
|
|
||||||
("JsSymbol", "symbol"),
|
|
||||||
("external", "object"),
|
("external", "object"),
|
||||||
("AbortSignal", "AbortSignal"),
|
("AbortSignal", "AbortSignal"),
|
||||||
("JsFunction", "(...args: any[]) => any"),
|
|
||||||
("JsGlobal", "typeof global"),
|
("JsGlobal", "typeof global"),
|
||||||
("External", "ExternalObject<{}>"),
|
("External", "ExternalObject<{}>"),
|
||||||
("unknown", "unknown"),
|
("unknown", "unknown"),
|
||||||
|
|
|
@ -13,14 +13,18 @@ struct FnArg {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FnArgList {
|
struct FnArgList {
|
||||||
|
this: Option<FnArg>,
|
||||||
args: Vec<FnArg>,
|
args: Vec<FnArg>,
|
||||||
last_required: Option<usize>,
|
last_required: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FnArgList {
|
impl Display for FnArgList {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
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() {
|
for (i, arg) in self.args.iter().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 || self.this.is_some() {
|
||||||
write!(f, ", ")?;
|
write!(f, ", ")?;
|
||||||
}
|
}
|
||||||
let is_optional = arg.is_optional
|
let is_optional = arg.is_optional
|
||||||
|
@ -39,13 +43,22 @@ impl Display for FnArgList {
|
||||||
|
|
||||||
impl FromIterator<FnArg> for FnArgList {
|
impl FromIterator<FnArg> for FnArgList {
|
||||||
fn from_iter<T: IntoIterator<Item = FnArg>>(iter: T) -> Self {
|
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
|
let last_required = args
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.rfind(|(_, arg)| !arg.is_optional)
|
.rfind(|(_, arg)| !arg.is_optional)
|
||||||
.map(|(i, _)| i);
|
.map(|(i, _)| i);
|
||||||
FnArgList {
|
FnArgList {
|
||||||
|
this,
|
||||||
args,
|
args,
|
||||||
last_required,
|
last_required,
|
||||||
}
|
}
|
||||||
|
@ -128,12 +141,27 @@ impl NapiFn {
|
||||||
if let syn::Type::Path(path) = path.ty.as_ref() {
|
if let syn::Type::Path(path) = path.ty.as_ref() {
|
||||||
if let Some(PathSegment {
|
if let Some(PathSegment {
|
||||||
ident,
|
ident,
|
||||||
arguments: PathArguments::AngleBracketed(_),
|
arguments:
|
||||||
|
PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
|
||||||
|
args: angle_bracketed_args,
|
||||||
|
..
|
||||||
|
}),
|
||||||
}) = path.path.segments.last()
|
}) = path.path.segments.last()
|
||||||
{
|
{
|
||||||
if ident == "Reference" || ident == "WeakReference" {
|
if ident == "Reference" || ident == "WeakReference" {
|
||||||
return None;
|
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::ops::{Deref, DerefMut};
|
||||||
use std::ptr;
|
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 struct ClassInstance<T: 'static> {
|
||||||
pub value: sys::napi_value,
|
pub value: sys::napi_value,
|
||||||
|
|
|
@ -41,7 +41,7 @@ macro_rules! impl_number_conversions {
|
||||||
check_status!(
|
check_status!(
|
||||||
unsafe { sys::$get(env, napi_val, &mut ret) },
|
unsafe { sys::$get(env, napi_val, &mut ret) },
|
||||||
"Failed to convert napi value {:?} into rust type `{}`",
|
"Failed to convert napi value {:?} into rust type `{}`",
|
||||||
type_of!(env, napi_val),
|
type_of!(env, napi_val)?,
|
||||||
$name,
|
$name,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ Generated by [AVA](https://avajs.dev).
|
||||||
}␊
|
}␊
|
||||||
export function createObjectWithClassField(): ObjectFieldClassInstance␊
|
export function createObjectWithClassField(): ObjectFieldClassInstance␊
|
||||||
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊
|
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird␊
|
||||||
|
export function plusOne(this: Width): number␊
|
||||||
export function dateToNumber(input: Date): number␊
|
export function dateToNumber(input: Date): number␊
|
||||||
export function chronoDateToMillis(input: Date): number␊
|
export function chronoDateToMillis(input: Date): number␊
|
||||||
export function chronoDateAdd1Minute(input: Date): Date␊
|
export function chronoDateAdd1Minute(input: Date): Date␊
|
||||||
|
@ -298,6 +299,10 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export class CustomFinalize {␊
|
export class CustomFinalize {␊
|
||||||
constructor(width: number, height: number)␊
|
constructor(width: number, height: number)␊
|
||||||
}␊
|
}␊
|
||||||
|
export class Width {␊
|
||||||
|
value: number␊
|
||||||
|
constructor(value: number)␊
|
||||||
|
}␊
|
||||||
export class ClassWithFactory {␊
|
export class ClassWithFactory {␊
|
||||||
name: string␊
|
name: string␊
|
||||||
static withName(name: string): ClassWithFactory␊
|
static withName(name: string): ClassWithFactory␊
|
||||||
|
|
Binary file not shown.
|
@ -108,6 +108,8 @@ import {
|
||||||
getNumArr,
|
getNumArr,
|
||||||
getNestedNumArr,
|
getNestedNumArr,
|
||||||
CustomFinalize,
|
CustomFinalize,
|
||||||
|
plusOne,
|
||||||
|
Width,
|
||||||
} from '../'
|
} from '../'
|
||||||
|
|
||||||
test('export const', (t) => {
|
test('export const', (t) => {
|
||||||
|
@ -161,7 +163,7 @@ test('enum', (t) => {
|
||||||
t.is(enumToI32(CustomNumEnum.Eight), 8)
|
t.is(enumToI32(CustomNumEnum.Eight), 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('class', (t) => {
|
test.only('class', (t) => {
|
||||||
const dog = new Animal(Kind.Dog, '旺财')
|
const dog = new Animal(Kind.Dog, '旺财')
|
||||||
|
|
||||||
t.is(dog.name, '旺财')
|
t.is(dog.name, '旺财')
|
||||||
|
@ -192,6 +194,13 @@ test('class', (t) => {
|
||||||
const turtle = NinjaTurtle.newRaph()
|
const turtle = NinjaTurtle.newRaph()
|
||||||
t.is(turtle.returnThis(), turtle)
|
t.is(turtle.returnThis(), turtle)
|
||||||
t.is(NinjaTurtle.isInstanceOf(turtle), true)
|
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) => {
|
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 createObjectWithClassField(): ObjectFieldClassInstance
|
||||||
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird
|
export function receiveObjectWithClassField(object: ObjectFieldClassInstance): Bird
|
||||||
|
export function plusOne(this: Width): number
|
||||||
export function dateToNumber(input: Date): number
|
export function dateToNumber(input: Date): number
|
||||||
export function chronoDateToMillis(input: Date): number
|
export function chronoDateToMillis(input: Date): number
|
||||||
export function chronoDateAdd1Minute(input: Date): Date
|
export function chronoDateAdd1Minute(input: Date): Date
|
||||||
|
@ -288,6 +289,10 @@ export class NotWritableClass {
|
||||||
export class CustomFinalize {
|
export class CustomFinalize {
|
||||||
constructor(width: number, height: number)
|
constructor(width: number, height: number)
|
||||||
}
|
}
|
||||||
|
export class Width {
|
||||||
|
value: number
|
||||||
|
constructor(value: number)
|
||||||
|
}
|
||||||
export class ClassWithFactory {
|
export class ClassWithFactory {
|
||||||
name: string
|
name: string
|
||||||
static withName(name: string): ClassWithFactory
|
static withName(name: string): ClassWithFactory
|
||||||
|
|
|
@ -395,3 +395,13 @@ impl ObjectFinalize for CustomFinalize {
|
||||||
Ok(())
|
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