diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 4f7c1604..35e2df94 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -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! { ::from_raw_unchecked(env, cb.this) }, diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index 332ab69a..9ea18085 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -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"), +]; diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index d0a2357c..ead99fac 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -120,35 +120,8 @@ pub trait ToTypeDef { static KNOWN_TYPES: Lazy> = 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> = 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"), diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index a5541c14..e9122006 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -13,14 +13,18 @@ struct FnArg { } struct FnArgList { + this: Option, args: Vec, last_required: Option, } 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 for FnArgList { fn from_iter>(iter: T) -> Self { - let args = iter.into_iter().collect::>(); + 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; + } } } diff --git a/crates/napi/src/bindgen_runtime/js_values/class.rs b/crates/napi/src/bindgen_runtime/js_values/class.rs index 2b700fed..9005ce33 100644 --- a/crates/napi/src/bindgen_runtime/js_values/class.rs +++ b/crates/napi/src/bindgen_runtime/js_values/class.rs @@ -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; pub struct ClassInstance { pub value: sys::napi_value, diff --git a/crates/napi/src/bindgen_runtime/js_values/number.rs b/crates/napi/src/bindgen_runtime/js_values/number.rs index 97296809..80421706 100644 --- a/crates/napi/src/bindgen_runtime/js_values/number.rs +++ b/crates/napi/src/bindgen_runtime/js_values/number.rs @@ -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, )?; diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index 00c6ddf5..839b73db 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -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␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index 17f9c8a7..4b49767a 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 999a77ac..2b884642 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -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) => { diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 0ea1ca8e..970ce77a 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -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 diff --git a/examples/napi/src/class.rs b/examples/napi/src/class.rs index b0c3a545..72c3a5ff 100644 --- a/examples/napi/src/class.rs +++ b/examples/napi/src/class.rs @@ -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 +}