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" {
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) },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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