From 53038c3b76916e928cadbef278f8c9365ba6aecb Mon Sep 17 00:00:00 2001 From: Ellen Arteca Date: Sat, 7 Dec 2024 19:39:27 -0800 Subject: [PATCH] adding OpaqueType instead of OpaqueStruct, and adding tests --- core/src/ast/lifetimes.rs | 5 +- core/src/ast/mod.rs | 5 +- core/src/ast/modules.rs | 14 ++-- core/src/ast/opaque.rs | 64 +++++++++++++++++++ core/src/ast/structs.rs | 57 +---------------- core/src/ast/types.rs | 6 +- core/src/hir/lowering.rs | 9 +-- ..._hir__type_context__tests__opaque_ffi.snap | 4 +- core/src/hir/type_context.rs | 21 +++++- feature_tests/c/include/MyOpaqueEnum.d.h | 19 ++++++ feature_tests/c/include/MyOpaqueEnum.h | 29 +++++++++ feature_tests/cpp/include/MyOpaqueEnum.d.hpp | 41 ++++++++++++ feature_tests/cpp/include/MyOpaqueEnum.hpp | 64 +++++++++++++++++++ .../dart/lib/src/MyOpaqueEnum.g.dart | 49 ++++++++++++++ feature_tests/dart/lib/src/lib.g.dart | 1 + feature_tests/demo_gen/demo/MyOpaqueEnum.d.ts | 2 + feature_tests/demo_gen/demo/MyOpaqueEnum.mjs | 13 ++++ feature_tests/demo_gen/demo/index.mjs | 11 ++++ feature_tests/js/api/MyOpaqueEnum.d.ts | 12 ++++ feature_tests/js/api/MyOpaqueEnum.mjs | 58 +++++++++++++++++ feature_tests/js/api/index.d.ts | 2 + feature_tests/js/api/index.mjs | 2 + .../dev/diplomattest/somelib/MyOpaqueEnum.kt | 51 +++++++++++++++ feature_tests/src/structs.rs | 28 ++++++++ macro/src/lib.rs | 19 +++--- 25 files changed, 500 insertions(+), 86 deletions(-) create mode 100644 core/src/ast/opaque.rs create mode 100644 feature_tests/c/include/MyOpaqueEnum.d.h create mode 100644 feature_tests/c/include/MyOpaqueEnum.h create mode 100644 feature_tests/cpp/include/MyOpaqueEnum.d.hpp create mode 100644 feature_tests/cpp/include/MyOpaqueEnum.hpp create mode 100644 feature_tests/dart/lib/src/MyOpaqueEnum.g.dart create mode 100644 feature_tests/demo_gen/demo/MyOpaqueEnum.d.ts create mode 100644 feature_tests/demo_gen/demo/MyOpaqueEnum.mjs create mode 100644 feature_tests/js/api/MyOpaqueEnum.d.ts create mode 100644 feature_tests/js/api/MyOpaqueEnum.mjs create mode 100644 feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/MyOpaqueEnum.kt diff --git a/core/src/ast/lifetimes.rs b/core/src/ast/lifetimes.rs index 07bd352df..7ed2e7fef 100644 --- a/core/src/ast/lifetimes.rs +++ b/core/src/ast/lifetimes.rs @@ -155,7 +155,10 @@ impl LifetimeEnv { LifetimeEnv::new() } - pub fn from_enum_item(enm: &syn::ItemEnum, variant_fields: &[(Option, TypeName, Docs, Attrs)]) -> Self { + pub fn from_enum_item( + enm: &syn::ItemEnum, + variant_fields: &[(Option, TypeName, Docs, Attrs)], + ) -> Self { let mut this = LifetimeEnv::new(); this.extend_generics(&enm.generics); for (_, typ, _, _) in variant_fields { diff --git a/core/src/ast/mod.rs b/core/src/ast/mod.rs index bde46affa..d31d16c93 100644 --- a/core/src/ast/mod.rs +++ b/core/src/ast/mod.rs @@ -13,7 +13,10 @@ mod modules; pub use modules::{File, Module}; mod structs; -pub use structs::{OpaqueStruct, Struct}; +pub use structs::Struct; + +mod opaque; +pub use opaque::OpaqueType; mod traits; pub use traits::{Trait, TraitMethod}; diff --git a/core/src/ast/modules.rs b/core/src/ast/modules.rs index 91cf402bc..a2790cba3 100644 --- a/core/src/ast/modules.rs +++ b/core/src/ast/modules.rs @@ -6,8 +6,8 @@ use serde::Serialize; use syn::{ImplItem, Item, ItemMod, UseTree, Visibility}; use super::{ - AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability, - OpaqueStruct, Path, PathType, RustLink, Struct, Trait, + AttrInheritContext, Attrs, CustomType, Enum, Ident, Method, ModSymbol, Mutability, OpaqueType, + Path, PathType, RustLink, Struct, Trait, }; use crate::environment::*; @@ -18,7 +18,7 @@ enum DiplomatStructAttribute { /// contain an owned opaque in the form of a `Box`. Out, /// An attribute that can correspond to a type (struct or enum). - TypeAttr(DiplomatTypeAttribute) + TypeAttr(DiplomatTypeAttribute), } /// Custom Diplomat attribute that can be placed on an enum or struct definition. @@ -192,10 +192,10 @@ impl Module { CustomType::Struct(Struct::new(strct, true, &type_parent_attrs)) } Ok(Some(DiplomatStructAttribute::TypeAttr(DiplomatTypeAttribute::Opaque))) => { - CustomType::Opaque(OpaqueStruct::new_struct(strct, Mutability::Immutable, &type_parent_attrs)) + CustomType::Opaque(OpaqueType::new_struct(strct, Mutability::Immutable, &type_parent_attrs)) } Ok(Some(DiplomatStructAttribute::TypeAttr(DiplomatTypeAttribute::OpaqueMut))) => { - CustomType::Opaque(OpaqueStruct::new_struct(strct, Mutability::Mutable, &type_parent_attrs)) + CustomType::Opaque(OpaqueType::new_struct(strct, Mutability::Mutable, &type_parent_attrs)) } Err(errors) => { panic!("Multiple conflicting Diplomat struct attributes, there can be at most one: {errors:?}"); @@ -212,10 +212,10 @@ impl Module { let custom_enum = match DiplomatTypeAttribute::parse(&enm.attrs[..]) { Ok(None) => CustomType::Enum(Enum::new(enm, &type_parent_attrs)), Ok(Some(DiplomatTypeAttribute::Opaque)) => { - CustomType::Opaque(OpaqueStruct::new_enum(enm, Mutability::Immutable, &type_parent_attrs)) + CustomType::Opaque(OpaqueType::new_enum(enm, Mutability::Immutable, &type_parent_attrs)) } Ok(Some(DiplomatTypeAttribute::OpaqueMut)) => { - CustomType::Opaque(OpaqueStruct::new_enum(enm, Mutability::Mutable, &type_parent_attrs)) + CustomType::Opaque(OpaqueType::new_enum(enm, Mutability::Mutable, &type_parent_attrs)) } Err(errors) => { panic!("Multiple conflicting Diplomat enum attributes, there can be at most one: {errors:?}"); diff --git a/core/src/ast/opaque.rs b/core/src/ast/opaque.rs new file mode 100644 index 000000000..94102f623 --- /dev/null +++ b/core/src/ast/opaque.rs @@ -0,0 +1,64 @@ +use serde::Serialize; + +use super::docs::Docs; +use super::{Attrs, Ident, LifetimeEnv, Method, Mutability}; + +/// A type annotated with [`diplomat::opaque`] whose fields/variants are not visible. +/// Opaque types cannot be passed by-value across the FFI boundary, so they +/// must be boxed or passed as references. +#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)] +#[non_exhaustive] +pub struct OpaqueType { + pub name: Ident, + pub docs: Docs, + pub lifetimes: LifetimeEnv, + pub methods: Vec, + pub mutability: Mutability, + pub attrs: Attrs, + /// The ABI name of the generated destructor + pub dtor_abi_name: Ident, +} + +impl OpaqueType { + /// Extract a [`OpaqueType`] metadata value from an AST node representing a struct. + pub fn new_struct( + strct: &syn::ItemStruct, + mutability: Mutability, + parent_attrs: &Attrs, + ) -> Self { + let mut attrs = parent_attrs.clone(); + attrs.add_attrs(&strct.attrs); + let name = Ident::from(&strct.ident); + OpaqueType { + dtor_abi_name: Self::dtor_abi_name(&name, &attrs), + name, + docs: Docs::from_attrs(&strct.attrs), + lifetimes: LifetimeEnv::from_struct_item(strct, &[]), + methods: vec![], + mutability, + attrs, + } + } + + /// Extract a [`OpaqueType`] metadata value from an AST node representing an enum. + pub fn new_enum(enm: &syn::ItemEnum, mutability: Mutability, parent_attrs: &Attrs) -> Self { + let mut attrs = parent_attrs.clone(); + attrs.add_attrs(&enm.attrs); + let name = Ident::from(&enm.ident); + OpaqueType { + dtor_abi_name: Self::dtor_abi_name(&name, &attrs), + name, + docs: Docs::from_attrs(&enm.attrs), + lifetimes: LifetimeEnv::from_enum_item(enm, &[]), + methods: vec![], + mutability, + attrs, + } + } + + fn dtor_abi_name(name: &Ident, attrs: &Attrs) -> Ident { + let dtor_abi_name = format!("{}_destroy", name); + let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into())); + Ident::from(dtor_abi_name) + } +} diff --git a/core/src/ast/structs.rs b/core/src/ast/structs.rs index 6abc363ca..ab1ac2152 100644 --- a/core/src/ast/structs.rs +++ b/core/src/ast/structs.rs @@ -1,7 +1,7 @@ use serde::Serialize; use super::docs::Docs; -use super::{Attrs, Ident, LifetimeEnv, Method, Mutability, PathType, TypeName}; +use super::{Attrs, Ident, LifetimeEnv, Method, PathType, TypeName}; /// A struct declaration in an FFI module that is not opaque. #[derive(Clone, PartialEq, Eq, Hash, Serialize, Debug)] @@ -52,61 +52,6 @@ impl Struct { } } -/// A type annotated with [`diplomat::opaque`] whose fields/variants are not visible. -/// Opaque types cannot be passed by-value across the FFI boundary, so they -/// must be boxed or passed as references. -#[derive(Clone, Serialize, Debug, Hash, PartialEq, Eq)] -#[non_exhaustive] -pub struct OpaqueStruct { - pub name: Ident, - pub docs: Docs, - pub lifetimes: LifetimeEnv, - pub methods: Vec, - pub mutability: Mutability, - pub attrs: Attrs, - /// The ABI name of the generated destructor - pub dtor_abi_name: Ident, -} - -impl OpaqueStruct { - /// Extract a [`OpaqueStruct`] metadata value from an AST node representing a struct. - pub fn new_struct(strct: &syn::ItemStruct, mutability: Mutability, parent_attrs: &Attrs) -> Self { - let mut attrs = parent_attrs.clone(); - attrs.add_attrs(&strct.attrs); - let name = Ident::from(&strct.ident); - let dtor_abi_name = format!("{}_destroy", name); - let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into())); - let dtor_abi_name = Ident::from(dtor_abi_name); - OpaqueStruct { - name, - docs: Docs::from_attrs(&strct.attrs), - lifetimes: LifetimeEnv::from_struct_item(strct, &[]), - methods: vec![], - mutability, - attrs, - dtor_abi_name, - } - } - - pub fn new_enum(enm: &syn::ItemEnum, mutability: Mutability, parent_attrs: &Attrs) -> Self { - let mut attrs = parent_attrs.clone(); - attrs.add_attrs(&enm.attrs); - let name = Ident::from(&enm.ident); - let dtor_abi_name = format!("{}_destroy", name); - let dtor_abi_name = String::from(attrs.abi_rename.apply(dtor_abi_name.into())); - let dtor_abi_name = Ident::from(dtor_abi_name); - OpaqueStruct { - name, - docs: Docs::from_attrs(&enm.attrs), - lifetimes: LifetimeEnv::from_enum_item(enm, &[]), - methods: vec![], - mutability, - attrs, - dtor_abi_name, - } - } -} - #[cfg(test)] mod tests { use insta::{self, Settings}; diff --git a/core/src/ast/types.rs b/core/src/ast/types.rs index 72594ac1c..195831712 100644 --- a/core/src/ast/types.rs +++ b/core/src/ast/types.rs @@ -9,7 +9,7 @@ use std::str::FromStr; use super::{ Attrs, Docs, Enum, Ident, Lifetime, LifetimeEnv, LifetimeTransitivity, Method, NamedLifetime, - OpaqueStruct, Path, RustLink, Struct, Trait, + OpaqueType, Path, RustLink, Struct, Trait, }; use crate::Env; @@ -19,8 +19,8 @@ use crate::Env; pub enum CustomType { /// A non-opaque struct whose fields will be visible across the FFI boundary. Struct(Struct), - /// A struct annotated with [`diplomat::opaque`] whose fields are not visible. - Opaque(OpaqueStruct), + /// A type annotated with [`diplomat::opaque`] whose fields are not visible. + Opaque(OpaqueType), /// A fieldless enum. Enum(Enum), } diff --git a/core/src/hir/lowering.rs b/core/src/hir/lowering.rs index efe250de0..0ce2e54ea 100644 --- a/core/src/hir/lowering.rs +++ b/core/src/hir/lowering.rs @@ -179,7 +179,7 @@ impl<'ast> LoweringContext<'ast> { } pub(super) fn lower_all_opaques( &mut self, - ast_defs: impl ExactSizeIterator>, + ast_defs: impl ExactSizeIterator>, ) -> Result, ()> { self.lower_all(ast_defs, Self::lower_opaque) } @@ -257,10 +257,7 @@ impl<'ast> LoweringContext<'ast> { Ok(def) } - fn lower_opaque( - &mut self, - item: ItemAndInfo<'ast, ast::OpaqueStruct>, - ) -> Result { + fn lower_opaque(&mut self, item: ItemAndInfo<'ast, ast::OpaqueType>) -> Result { let ast_opaque = item.item; self.errors.set_item(ast_opaque.name.as_str()); let name = self.lower_ident(&ast_opaque.name, "opaque name"); @@ -416,7 +413,7 @@ impl<'ast> LoweringContext<'ast> { }; let lifetimes = self.lower_type_lifetime_env(&ast_trait.lifetimes); let def = TraitDef::new(ast_trait.docs.clone(), trait_name, fcts, attrs, lifetimes?); - + self.attr_validator .validate(&def.attrs, AttributeContext::Trait(&def), &mut self.errors); Ok(def) diff --git a/core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap b/core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap index 1d470de59..f6c4fee44 100644 --- a/core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap +++ b/core/src/hir/snapshots/diplomat_core__hir__type_context__tests__opaque_ffi.snap @@ -1,7 +1,9 @@ --- source: core/src/hir/type_context.rs +assertion_line: 691 expression: output --- +Lowering error in MyOpaqueEnum::new_broken: Opaque passed by value in input: MyOpaqueEnum +Lowering error in MyOpaqueEnum::do_thing_broken: Method `MyOpaqueEnum_do_thing_broken` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs Lowering error in MyOpaqueStruct::new_broken: Opaque passed by value in input: MyOpaqueStruct Lowering error in MyOpaqueStruct::do_thing_broken: Method `MyOpaqueStruct_do_thing_broken` takes an opaque by value as the self parameter, but opaques as inputs must be behind refs - diff --git a/core/src/hir/type_context.rs b/core/src/hir/type_context.rs index 747261783..d9f8fff71 100644 --- a/core/src/hir/type_context.rs +++ b/core/src/hir/type_context.rs @@ -459,7 +459,7 @@ impl TypeContext { pub(super) struct LookupId<'ast> { out_struct_map: HashMap<&'ast ast::Struct, OutStructId>, struct_map: HashMap<&'ast ast::Struct, StructId>, - opaque_map: HashMap<&'ast ast::OpaqueStruct, OpaqueId>, + opaque_map: HashMap<&'ast ast::OpaqueType, OpaqueId>, enum_map: HashMap<&'ast ast::Enum, EnumId>, trait_map: HashMap<&'ast ast::Trait, TraitId>, } @@ -469,7 +469,7 @@ impl<'ast> LookupId<'ast> { fn new( out_structs: &[ItemAndInfo<'ast, ast::Struct>], structs: &[ItemAndInfo<'ast, ast::Struct>], - opaques: &[ItemAndInfo<'ast, ast::OpaqueStruct>], + opaques: &[ItemAndInfo<'ast, ast::OpaqueType>], enums: &[ItemAndInfo<'ast, ast::Enum>], traits: &[ItemAndInfo<'ast, ast::Trait>], ) -> Self { @@ -510,7 +510,7 @@ impl<'ast> LookupId<'ast> { self.struct_map.get(strct).copied() } - pub(super) fn resolve_opaque(&self, opaque: &ast::OpaqueStruct) -> Option { + pub(super) fn resolve_opaque(&self, opaque: &ast::OpaqueType) -> Option { self.opaque_map.get(opaque).copied() } @@ -701,6 +701,21 @@ mod tests { pub fn do_thing_broken(self) {} pub fn broken_differently(&self, x: &MyOpaqueStruct) {} } + + #[diplomat::opaque] + enum MyOpaqueEnum { + A(UnknownType), + B, + C(i32, i32, UnknownType2), + } + + impl MyOpaqueEnum { + pub fn new() -> Box {} + pub fn new_broken() -> MyOpaqueEnum {} + pub fn do_thing(&self) {} + pub fn do_thing_broken(self) {} + pub fn broken_differently(&self, x: &MyOpaqueEnum) {} + } } } } diff --git a/feature_tests/c/include/MyOpaqueEnum.d.h b/feature_tests/c/include/MyOpaqueEnum.d.h new file mode 100644 index 000000000..d5237a1a5 --- /dev/null +++ b/feature_tests/c/include/MyOpaqueEnum.d.h @@ -0,0 +1,19 @@ +#ifndef MyOpaqueEnum_D_H +#define MyOpaqueEnum_D_H + +#include +#include +#include +#include +#include "diplomat_runtime.h" + + + + + +typedef struct MyOpaqueEnum MyOpaqueEnum; + + + + +#endif // MyOpaqueEnum_D_H diff --git a/feature_tests/c/include/MyOpaqueEnum.h b/feature_tests/c/include/MyOpaqueEnum.h new file mode 100644 index 000000000..df8fcb1a3 --- /dev/null +++ b/feature_tests/c/include/MyOpaqueEnum.h @@ -0,0 +1,29 @@ +#ifndef MyOpaqueEnum_H +#define MyOpaqueEnum_H + +#include +#include +#include +#include +#include "diplomat_runtime.h" + + +#include "MyOpaqueEnum.d.h" + + + + + + +MyOpaqueEnum* MyOpaqueEnum_new(void); + +void MyOpaqueEnum_to_string(const MyOpaqueEnum* self, DiplomatWrite* write); + + +void MyOpaqueEnum_destroy(MyOpaqueEnum* self); + + + + + +#endif // MyOpaqueEnum_H diff --git a/feature_tests/cpp/include/MyOpaqueEnum.d.hpp b/feature_tests/cpp/include/MyOpaqueEnum.d.hpp new file mode 100644 index 000000000..453c93aa9 --- /dev/null +++ b/feature_tests/cpp/include/MyOpaqueEnum.d.hpp @@ -0,0 +1,41 @@ +#ifndef MyOpaqueEnum_D_HPP +#define MyOpaqueEnum_D_HPP + +#include +#include +#include +#include +#include +#include +#include "diplomat_runtime.hpp" + + +namespace diplomat { +namespace capi { + struct MyOpaqueEnum; +} // namespace capi +} // namespace + +class MyOpaqueEnum { +public: + + inline static std::unique_ptr new_(); + + inline std::string to_string() const; + + inline const diplomat::capi::MyOpaqueEnum* AsFFI() const; + inline diplomat::capi::MyOpaqueEnum* AsFFI(); + inline static const MyOpaqueEnum* FromFFI(const diplomat::capi::MyOpaqueEnum* ptr); + inline static MyOpaqueEnum* FromFFI(diplomat::capi::MyOpaqueEnum* ptr); + inline static void operator delete(void* ptr); +private: + MyOpaqueEnum() = delete; + MyOpaqueEnum(const MyOpaqueEnum&) = delete; + MyOpaqueEnum(MyOpaqueEnum&&) noexcept = delete; + MyOpaqueEnum operator=(const MyOpaqueEnum&) = delete; + MyOpaqueEnum operator=(MyOpaqueEnum&&) noexcept = delete; + static void operator delete[](void*, size_t) = delete; +}; + + +#endif // MyOpaqueEnum_D_HPP diff --git a/feature_tests/cpp/include/MyOpaqueEnum.hpp b/feature_tests/cpp/include/MyOpaqueEnum.hpp new file mode 100644 index 000000000..ef7429b7c --- /dev/null +++ b/feature_tests/cpp/include/MyOpaqueEnum.hpp @@ -0,0 +1,64 @@ +#ifndef MyOpaqueEnum_HPP +#define MyOpaqueEnum_HPP + +#include "MyOpaqueEnum.d.hpp" + +#include +#include +#include +#include +#include +#include +#include "diplomat_runtime.hpp" + + +namespace diplomat { +namespace capi { + extern "C" { + + diplomat::capi::MyOpaqueEnum* MyOpaqueEnum_new(void); + + void MyOpaqueEnum_to_string(const diplomat::capi::MyOpaqueEnum* self, diplomat::capi::DiplomatWrite* write); + + + void MyOpaqueEnum_destroy(MyOpaqueEnum* self); + + } // extern "C" +} // namespace capi +} // namespace + +inline std::unique_ptr MyOpaqueEnum::new_() { + auto result = diplomat::capi::MyOpaqueEnum_new(); + return std::unique_ptr(MyOpaqueEnum::FromFFI(result)); +} + +inline std::string MyOpaqueEnum::to_string() const { + std::string output; + diplomat::capi::DiplomatWrite write = diplomat::WriteFromString(output); + diplomat::capi::MyOpaqueEnum_to_string(this->AsFFI(), + &write); + return output; +} + +inline const diplomat::capi::MyOpaqueEnum* MyOpaqueEnum::AsFFI() const { + return reinterpret_cast(this); +} + +inline diplomat::capi::MyOpaqueEnum* MyOpaqueEnum::AsFFI() { + return reinterpret_cast(this); +} + +inline const MyOpaqueEnum* MyOpaqueEnum::FromFFI(const diplomat::capi::MyOpaqueEnum* ptr) { + return reinterpret_cast(ptr); +} + +inline MyOpaqueEnum* MyOpaqueEnum::FromFFI(diplomat::capi::MyOpaqueEnum* ptr) { + return reinterpret_cast(ptr); +} + +inline void MyOpaqueEnum::operator delete(void* ptr) { + diplomat::capi::MyOpaqueEnum_destroy(reinterpret_cast(ptr)); +} + + +#endif // MyOpaqueEnum_HPP diff --git a/feature_tests/dart/lib/src/MyOpaqueEnum.g.dart b/feature_tests/dart/lib/src/MyOpaqueEnum.g.dart new file mode 100644 index 000000000..6dfcddae8 --- /dev/null +++ b/feature_tests/dart/lib/src/MyOpaqueEnum.g.dart @@ -0,0 +1,49 @@ +// generated by diplomat-tool + +part of 'lib.g.dart'; + +final class MyOpaqueEnum implements ffi.Finalizable { + final ffi.Pointer _ffi; + + // These are "used" in the sense that they keep dependencies alive + // ignore: unused_field + final core.List _selfEdge; + + // This takes in a list of lifetime edges (including for &self borrows) + // corresponding to data this may borrow from. These should be flat arrays containing + // references to objects, and this object will hold on to them to keep them alive and + // maintain borrow validity. + MyOpaqueEnum._fromFfi(this._ffi, this._selfEdge) { + if (_selfEdge.isEmpty) { + _finalizer.attach(this, _ffi.cast()); + } + } + + static final _finalizer = ffi.NativeFinalizer(ffi.Native.addressOf(_MyOpaqueEnum_destroy)); + + static MyOpaqueEnum new_() { + final result = _MyOpaqueEnum_new(); + return MyOpaqueEnum._fromFfi(result, []); + } + + String toString() { + final write = _Write(); + _MyOpaqueEnum_to_string(_ffi, write._ffi); + return write.finalize(); + } +} + +@meta.RecordUse() +@ffi.Native)>(isLeaf: true, symbol: 'MyOpaqueEnum_destroy') +// ignore: non_constant_identifier_names +external void _MyOpaqueEnum_destroy(ffi.Pointer self); + +@meta.RecordUse() +@ffi.Native Function()>(isLeaf: true, symbol: 'MyOpaqueEnum_new') +// ignore: non_constant_identifier_names +external ffi.Pointer _MyOpaqueEnum_new(); + +@meta.RecordUse() +@ffi.Native, ffi.Pointer)>(isLeaf: true, symbol: 'MyOpaqueEnum_to_string') +// ignore: non_constant_identifier_names +external void _MyOpaqueEnum_to_string(ffi.Pointer self, ffi.Pointer write); diff --git a/feature_tests/dart/lib/src/lib.g.dart b/feature_tests/dart/lib/src/lib.g.dart index c00c757cb..f54e694aa 100644 --- a/feature_tests/dart/lib/src/lib.g.dart +++ b/feature_tests/dart/lib/src/lib.g.dart @@ -25,6 +25,7 @@ part 'Float64Vec.g.dart'; part 'Foo.g.dart'; part 'ImportedStruct.g.dart'; part 'MyEnum.g.dart'; +part 'MyOpaqueEnum.g.dart'; part 'MyString.g.dart'; part 'MyStruct.g.dart'; part 'MyZst.g.dart'; diff --git a/feature_tests/demo_gen/demo/MyOpaqueEnum.d.ts b/feature_tests/demo_gen/demo/MyOpaqueEnum.d.ts new file mode 100644 index 000000000..18bece253 --- /dev/null +++ b/feature_tests/demo_gen/demo/MyOpaqueEnum.d.ts @@ -0,0 +1,2 @@ +import { MyOpaqueEnum } from "../../js/api/index.mjs" +export function toString(); diff --git a/feature_tests/demo_gen/demo/MyOpaqueEnum.mjs b/feature_tests/demo_gen/demo/MyOpaqueEnum.mjs new file mode 100644 index 000000000..77ff634f1 --- /dev/null +++ b/feature_tests/demo_gen/demo/MyOpaqueEnum.mjs @@ -0,0 +1,13 @@ +import { MyOpaqueEnum } from "../../js/api/index.mjs" +export function toString() { + return (function (...args) { return args[0].toString(...args.slice(1)) }).apply( + null, + [ + MyOpaqueEnum.new_.apply( + null, + [ + ] + ) + ] + ); +} diff --git a/feature_tests/demo_gen/demo/index.mjs b/feature_tests/demo_gen/demo/index.mjs index c957e2564..1fadc0dd6 100644 --- a/feature_tests/demo_gen/demo/index.mjs +++ b/feature_tests/demo_gen/demo/index.mjs @@ -9,6 +9,8 @@ import * as Float64VecDemo from "./Float64Vec.mjs"; export * as Float64VecDemo from "./Float64Vec.mjs"; import * as MyStringDemo from "./MyString.mjs"; export * as MyStringDemo from "./MyString.mjs"; +import * as MyOpaqueEnumDemo from "./MyOpaqueEnum.mjs"; +export * as MyOpaqueEnumDemo from "./MyOpaqueEnum.mjs"; import * as OpaqueDemo from "./Opaque.mjs"; export * as OpaqueDemo from "./Opaque.mjs"; import * as Utf16WrapDemo from "./Utf16Wrap.mjs"; @@ -107,6 +109,15 @@ let termini = Object.assign({ ] }, + "MyOpaqueEnum.toString": { + func: MyOpaqueEnumDemo.toString, + // For avoiding webpacking minifying issues: + funcName: "MyOpaqueEnum.toString", + parameters: [ + + ] + }, + "Opaque.getDebugStr": { func: OpaqueDemo.getDebugStr, // For avoiding webpacking minifying issues: diff --git a/feature_tests/js/api/MyOpaqueEnum.d.ts b/feature_tests/js/api/MyOpaqueEnum.d.ts new file mode 100644 index 000000000..43d7dd7e9 --- /dev/null +++ b/feature_tests/js/api/MyOpaqueEnum.d.ts @@ -0,0 +1,12 @@ +// generated by diplomat-tool +import type { pointer, codepoint } from "./diplomat-runtime.d.ts"; + +export class MyOpaqueEnum { + + + get ffiValue(): pointer; + + static new_(): MyOpaqueEnum; + + toString(): string; +} \ No newline at end of file diff --git a/feature_tests/js/api/MyOpaqueEnum.mjs b/feature_tests/js/api/MyOpaqueEnum.mjs new file mode 100644 index 000000000..ca9ebbc9a --- /dev/null +++ b/feature_tests/js/api/MyOpaqueEnum.mjs @@ -0,0 +1,58 @@ +// generated by diplomat-tool +import wasm from "./diplomat-wasm.mjs"; +import * as diplomatRuntime from "./diplomat-runtime.mjs"; + +const MyOpaqueEnum_box_destroy_registry = new FinalizationRegistry((ptr) => { + wasm.MyOpaqueEnum_destroy(ptr); +}); + +export class MyOpaqueEnum { + // Internal ptr reference: + #ptr = null; + + // Lifetimes are only to keep dependencies alive. + // Since JS won't garbage collect until there are no incoming edges. + #selfEdge = []; + + constructor(symbol, ptr, selfEdge) { + if (symbol !== diplomatRuntime.internalConstructor) { + console.error("MyOpaqueEnum is an Opaque type. You cannot call its constructor."); + return; + } + + this.#ptr = ptr; + this.#selfEdge = selfEdge; + + // Are we being borrowed? If not, we can register. + if (this.#selfEdge.length === 0) { + MyOpaqueEnum_box_destroy_registry.register(this, this.#ptr); + } + } + + get ffiValue() { + return this.#ptr; + } + + static new_() { + const result = wasm.MyOpaqueEnum_new(); + + try { + return new MyOpaqueEnum(diplomatRuntime.internalConstructor, result, []); + } + + finally {} + } + + toString() { + const write = new diplomatRuntime.DiplomatWriteBuf(wasm); + wasm.MyOpaqueEnum_to_string(this.ffiValue, write.buffer); + + try { + return write.readString8(); + } + + finally { + write.free(); + } + } +} \ No newline at end of file diff --git a/feature_tests/js/api/index.d.ts b/feature_tests/js/api/index.d.ts index 2199eee70..8d58c8aea 100644 --- a/feature_tests/js/api/index.d.ts +++ b/feature_tests/js/api/index.d.ts @@ -70,6 +70,8 @@ export { Float64Vec } from "./Float64Vec" export { MyString } from "./MyString" +export { MyOpaqueEnum } from "./MyOpaqueEnum" + export { Opaque } from "./Opaque" export { OpaqueMutexedString } from "./OpaqueMutexedString" diff --git a/feature_tests/js/api/index.mjs b/feature_tests/js/api/index.mjs index 4abbad7fe..1b9d4286d 100644 --- a/feature_tests/js/api/index.mjs +++ b/feature_tests/js/api/index.mjs @@ -68,6 +68,8 @@ export { Float64Vec } from "./Float64Vec.mjs" export { MyString } from "./MyString.mjs" +export { MyOpaqueEnum } from "./MyOpaqueEnum.mjs" + export { Opaque } from "./Opaque.mjs" export { OpaqueMutexedString } from "./OpaqueMutexedString.mjs" diff --git a/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/MyOpaqueEnum.kt b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/MyOpaqueEnum.kt new file mode 100644 index 000000000..aa3c90e51 --- /dev/null +++ b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/MyOpaqueEnum.kt @@ -0,0 +1,51 @@ +package dev.diplomattest.somelib; +import com.sun.jna.Callback +import com.sun.jna.Library +import com.sun.jna.Native +import com.sun.jna.Pointer +import com.sun.jna.Structure + + +internal interface MyOpaqueEnumLib: Library { + fun MyOpaqueEnum_destroy(handle: Pointer) + fun MyOpaqueEnum_new(): Pointer + fun MyOpaqueEnum_to_string(handle: Pointer, write: Pointer): Unit +} + +class MyOpaqueEnum internal constructor ( + internal val handle: Pointer, + // These ensure that anything that is borrowed is kept alive and not cleaned + // up by the garbage collector. + internal val selfEdges: List, +) { + + internal class MyOpaqueEnumCleaner(val handle: Pointer, val lib: MyOpaqueEnumLib) : Runnable { + override fun run() { + lib.MyOpaqueEnum_destroy(handle) + } + } + + companion object { + internal val libClass: Class = MyOpaqueEnumLib::class.java + internal val lib: MyOpaqueEnumLib = Native.load("somelib", libClass) + + fun new_(): MyOpaqueEnum { + + val returnVal = lib.MyOpaqueEnum_new(); + val selfEdges: List = listOf() + val handle = returnVal + val returnOpaque = MyOpaqueEnum(handle, selfEdges) + CLEANER.register(returnOpaque, MyOpaqueEnum.MyOpaqueEnumCleaner(handle, MyOpaqueEnum.lib)); + return returnOpaque + } + } + + fun toString_(): String { + val write = DW.lib.diplomat_buffer_write_create(0) + val returnVal = lib.MyOpaqueEnum_to_string(handle, write); + + val returnString = DW.writeToString(write) + return returnString + } + +} diff --git a/feature_tests/src/structs.rs b/feature_tests/src/structs.rs index 8cb1515a8..a6a19da9e 100644 --- a/feature_tests/src/structs.rs +++ b/feature_tests/src/structs.rs @@ -35,6 +35,14 @@ pub mod ffi { F = 3, } + #[diplomat::opaque] + pub enum MyOpaqueEnum { + A(String), + B(Utf16Wrap), + C, + D(i32, ImportedStruct), + } + pub struct MyStruct { a: u8, b: bool, @@ -160,6 +168,26 @@ pub mod ffi { } } + impl MyOpaqueEnum { + #[diplomat::demo(default_constructor)] + pub fn new() -> Box { + Box::new(MyOpaqueEnum::A("a".into())) + } + + pub fn to_string(&self, write: &mut DiplomatWrite) { + let _infallible = write!( + write, + "MyOpaqueEnum::{}", + match self { + MyOpaqueEnum::A(..) => "A", + MyOpaqueEnum::B(..) => "B", + MyOpaqueEnum::C => "C", + MyOpaqueEnum::D(..) => "D", + } + ); + } + } + impl MyStruct { #[diplomat::attr(auto, constructor)] pub fn new() -> MyStruct { diff --git a/macro/src/lib.rs b/macro/src/lib.rs index b04043ab3..00f4eeed1 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -456,20 +456,23 @@ fn gen_bridge(mut input: ItemMod) -> ItemMod { Item::Enum(e) => { let info = AttributeInfo::extract(&mut e.attrs); - if info.opaque { - panic!("#[diplomat::opaque] not allowed on enums") - } + for v in &mut e.variants { let info = AttributeInfo::extract(&mut v.attrs); if info.opaque { panic!("#[diplomat::opaque] not allowed on enum variants"); } } - *e = syn::parse_quote! { - #[repr(C)] - #[derive(Clone, Copy)] - #e - }; + + // Normal opaque types don't need repr(transparent) because the inner type is + // never referenced. + if !info.opaque { + *e = syn::parse_quote! { + #[repr(C)] + #[derive(Clone, Copy)] + #e + }; + } } Item::Impl(i) => {