feat(napi-derive): support inject This<Value> into raw function

This commit is contained in:
LongYinan 2022-08-17 17:49:22 +08:00
parent 5030cfb8fb
commit 0ef482c6ca
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
11 changed files with 160 additions and 43 deletions

View file

@ -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) },

View file

@ -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"),
];

View file

@ -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"),

View file

@ -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;
}
} }
} }

View file

@ -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,

View file

@ -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,
)?; )?;

View file

@ -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␊

View file

@ -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) => {

View file

@ -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

View file

@ -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
}