diff --git a/cli/src/build.ts b/cli/src/build.ts index 1d512310..51f66f97 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -383,6 +383,9 @@ export class ExternalObject { dts += indentLines(`}`, nest) + '\n' break case 'enum': + if (!nested) { + idents.push(def.name) + } dts += indentLines(`${def.js_doc}export enum ${def.name} {`, nest) + '\n' dts += indentLines(def.def, nest + 2) + '\n' diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index dc2efb75..655d6ab5 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -239,17 +239,37 @@ impl NapiStruct { for field in self.fields.iter() { let field_js_name = &field.js_name; let ty = &field.ty; - + let is_optional_field = if let syn::Type::Path(syn::TypePath { + path: syn::Path { segments, .. }, + .. + }) = &ty + { + if let Some(last_path) = segments.last() { + last_path.ident == "Option" + } else { + false + } + } else { + false + }; match &field.name { syn::Member::Named(ident) => { field_destructions.push(quote! { #ident }); obj_field_setters.push(quote! { obj.set(#field_js_name, #ident)?; }); - obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + if is_optional_field { + obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?; }); + } else { + obj_field_getters.push(quote! { let #ident: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + } } syn::Member::Unnamed(i) => { field_destructions.push(quote! { arg#i }); obj_field_setters.push(quote! { obj.set(#field_js_name, arg#1)?; }); - obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + if is_optional_field { + obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?; }); + } else { + obj_field_getters.push(quote! { let arg#i: #ty = obj.get(#field_js_name)?.expect(&format!("Field {} should exist", #field_js_name)); }); + } } } } diff --git a/crates/backend/src/typegen.rs b/crates/backend/src/typegen.rs index 2b86c926..8076e4b9 100644 --- a/crates/backend/src/typegen.rs +++ b/crates/backend/src/typegen.rs @@ -3,7 +3,7 @@ mod r#enum; mod r#fn; pub(crate) mod r#struct; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap}; use once_cell::sync::Lazy; use syn::Type; @@ -17,6 +17,16 @@ pub struct TypeDef { pub js_doc: String, } +thread_local! { + static ALIAS: RefCell> = Default::default(); +} + +fn add_alias(name: String, alias: String) { + ALIAS.with(|aliases| { + aliases.borrow_mut().insert(name, alias); + }); +} + pub fn js_doc_from_comments(comments: &[String]) -> String { if comments.is_empty() { return "".to_owned(); @@ -248,7 +258,13 @@ pub fn ty_to_ts_type(ty: &Type, is_return_ty: bool) -> (String, bool) { )); } else { // there should be runtime registered type in else - ts_ty = Some((rust_ty, false)); + let type_alias = ALIAS.with(|aliases| { + aliases + .borrow() + .get(rust_ty.as_str()) + .map(|a| (a.to_owned(), false)) + }); + ts_ty = type_alias.or(Some((rust_ty, false))); } } diff --git a/crates/backend/src/typegen/const.rs b/crates/backend/src/typegen/const.rs index 5ac266be..29343d3a 100644 --- a/crates/backend/src/typegen/const.rs +++ b/crates/backend/src/typegen/const.rs @@ -1,9 +1,10 @@ use super::{ToTypeDef, TypeDef}; -use crate::{js_doc_from_comments, ty_to_ts_type, NapiConst}; +use crate::{js_doc_from_comments, ty_to_ts_type, typegen::add_alias, NapiConst}; impl ToTypeDef for NapiConst { fn to_type_def(&self) -> TypeDef { + add_alias(self.name.to_string(), self.js_name.to_string()); TypeDef { kind: "const".to_owned(), name: self.js_name.to_owned(), diff --git a/crates/backend/src/typegen/enum.rs b/crates/backend/src/typegen/enum.rs index 3046c648..eeabbe53 100644 --- a/crates/backend/src/typegen/enum.rs +++ b/crates/backend/src/typegen/enum.rs @@ -1,8 +1,9 @@ -use super::{ToTypeDef, TypeDef}; +use super::{add_alias, ToTypeDef, TypeDef}; use crate::{js_doc_from_comments, NapiEnum}; impl ToTypeDef for NapiEnum { fn to_type_def(&self) -> TypeDef { + add_alias(self.name.to_string(), self.js_name.to_string()); TypeDef { kind: "enum".to_owned(), name: self.js_name.to_owned(), diff --git a/crates/backend/src/typegen/struct.rs b/crates/backend/src/typegen/struct.rs index d44a8b24..c592bf8b 100644 --- a/crates/backend/src/typegen/struct.rs +++ b/crates/backend/src/typegen/struct.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; use std::collections::HashMap; -use super::{ToTypeDef, TypeDef}; +use super::{add_alias, ToTypeDef, TypeDef}; use crate::{js_doc_from_comments, ty_to_ts_type, NapiImpl, NapiStruct, NapiStructKind}; thread_local! { @@ -15,6 +15,7 @@ impl ToTypeDef for NapiStruct { c.borrow_mut() .insert(self.name.to_string(), self.js_name.clone()); }); + add_alias(self.name.to_string(), self.js_name.to_string()); TypeDef { kind: String::from(if self.kind == NapiStructKind::Object { "interface" diff --git a/crates/macro/src/parser/attrs.rs b/crates/macro/src/parser/attrs.rs index a04da0ef..111135d5 100644 --- a/crates/macro/src/parser/attrs.rs +++ b/crates/macro/src/parser/attrs.rs @@ -1,9 +1,8 @@ +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; + use napi_derive_backend::{bail_span, BindgenResult, Diagnostic}; use proc_macro2::{Delimiter, Ident, Span, TokenTree}; -use std::{ - cell::{Cell, RefCell}, - collections::HashMap, -}; use syn::spanned::Spanned; thread_local! { diff --git a/crates/napi/src/bindgen_runtime/js_values/object.rs b/crates/napi/src/bindgen_runtime/js_values/object.rs index ef0b4d94..04870e59 100644 --- a/crates/napi/src/bindgen_runtime/js_values/object.rs +++ b/crates/napi/src/bindgen_runtime/js_values/object.rs @@ -35,7 +35,7 @@ impl Object { let ty = type_of!(self.0.env, ret)?; - Ok(if ty == ValueType::Undefined { + Ok(if ty == ValueType::Undefined || ty == ValueType::Null { None } else { Some(V::from_napi_value(self.0.env, ret)?) diff --git a/examples/napi/__test__/typegen.spec.ts.md b/examples/napi/__test__/typegen.spec.ts.md index a098885d..4b3b4e40 100644 --- a/examples/napi/__test__/typegen.spec.ts.md +++ b/examples/napi/__test__/typegen.spec.ts.md @@ -73,6 +73,20 @@ Generated by [AVA](https://avajs.dev). export function getGlobal(): typeof global␊ export function getUndefined(): void␊ export function getNull(): JsNull␊ + export interface AllOptionalObject {␊ + name?: string | undefined | null␊ + age?: number | undefined | null␊ + }␊ + export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void␊ + export enum ALIAS {␊ + A = 0,␊ + B = 1␊ + }␊ + export interface AliasedStruct {␊ + a: ALIAS␊ + b: number␊ + }␊ + export function fnReceivedAliased(s: AliasedStruct, e: ALIAS): void␊ export function asyncPlus100(p: Promise): Promise␊ /** This is an interface for package.json */␊ export interface PackageJson {␊ diff --git a/examples/napi/__test__/typegen.spec.ts.snap b/examples/napi/__test__/typegen.spec.ts.snap index f674e93d..b73f0fe8 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 570fbd3c..a66153cb 100644 --- a/examples/napi/__test__/values.spec.ts +++ b/examples/napi/__test__/values.spec.ts @@ -59,6 +59,10 @@ import { convertU32Array, createExternalTypedArray, mutateTypedArray, + receiveAllOptionalObject, + fnReceivedAliased, + ALIAS, + AliasedStruct, } from '../' test('export const', (t) => { @@ -202,6 +206,20 @@ test('function ts type override', (t) => { t.deepEqual(tsRename({ foo: 1, bar: 2, baz: 2 }), ['foo', 'bar', 'baz']) }) +test('option object', (t) => { + t.notThrows(() => receiveAllOptionalObject()) + t.notThrows(() => receiveAllOptionalObject({})) +}) + +test('aliased rust struct and enum', (t) => { + const a: ALIAS = ALIAS.A + const b: AliasedStruct = { + a, + b: 1, + } + t.notThrows(() => fnReceivedAliased(b, ALIAS.B)) +}) + test('serde-json', (t) => { const packageJson = readPackageJson() t.is(packageJson.name, 'napi-rs') diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index f514c30d..3e9dbb64 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -63,6 +63,20 @@ export function createObj(): object export function getGlobal(): typeof global export function getUndefined(): void export function getNull(): JsNull +export interface AllOptionalObject { + name?: string | undefined | null + age?: number | undefined | null +} +export function receiveAllOptionalObject(obj?: AllOptionalObject | undefined | null): void +export enum ALIAS { + A = 0, + B = 1 +} +export interface AliasedStruct { + a: ALIAS + b: number +} +export function fnReceivedAliased(s: AliasedStruct, e: ALIAS): void export function asyncPlus100(p: Promise): Promise /** This is an interface for package.json */ export interface PackageJson { diff --git a/examples/napi/src/object.rs b/examples/napi/src/object.rs index 71c868a1..741c74cf 100644 --- a/examples/napi/src/object.rs +++ b/examples/napi/src/object.rs @@ -27,3 +27,35 @@ fn get_undefined(env: Env) -> Result { fn get_null(env: Env) -> Result { env.get_null() } + +#[napi(object)] +struct AllOptionalObject { + pub name: Option, + pub age: Option, +} + +#[napi] +fn receive_all_optional_object(obj: Option) -> Result<()> { + if obj.is_some() { + assert!(obj.as_ref().unwrap().name.is_none()); + assert!(obj.as_ref().unwrap().age.is_none()); + } + Ok(()) +} + +#[napi(js_name = "ALIAS")] +pub enum AliasedEnum { + A, + B, +} + +#[napi(object, js_name = "AliasedStruct")] +pub struct StructContainsAliasedEnum { + pub a: AliasedEnum, + pub b: u32, +} + +#[napi] +fn fn_received_aliased(mut s: StructContainsAliasedEnum, e: AliasedEnum) { + s.a = e; +}