From 240894a6eced0c5f54833e1ef7b60ea57fa4d28c Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 16:01:08 +0100 Subject: [PATCH 1/6] Cherry-pick various changes from out-of-tree. Change-Id: Ib7104063bad2def508878389e38b40316a6a6964 JJ-Change-Id: osyzvztwopmxmlkuzrrsrwrqlwrovzwy --- .github/workflows/CI.yml | 12 +- .gitignore | 12 +- ffi_tests/generated.metadata.json | 1409 +++++++++++++++++ ffi_tests/src/lib.rs | 1 + js_tests/src/lib.rs | 46 +- js_tests/tests/tests.mjs | 11 + scripts/build_docs.sh | 2 +- scripts/cargo_publish.sh | 2 +- scripts/change_version.sh | 2 +- .../formatting/setup_cargo_fmt_override.sh | 2 +- src/_lib.rs | 2 +- src/boxed.rs | 13 + src/c_char.rs | 4 + src/headers/_mod.rs | 24 +- src/headers/languages/c.rs | 53 +- src/headers/languages/metadata.rs | 470 ++++++ src/headers/languages/mod.rs | 13 + src/headers/templates/metadata/_prelude.txt | 13 + src/js/closures/node_js.rs | 44 +- src/js/ffi_helpers.rs | 22 +- src/layout/_mod.rs | 12 +- src/layout/impls.rs | 284 +++- src/layout/macros.rs | 60 +- src/layout/niche.rs | 100 +- src/proc_macro/_mod.rs | 8 + src/proc_macro/derives/c_type/struct_.rs | 84 +- src/proc_macro/derives/repr_c/enum_.rs | 4 + src/proc_macro/derives/repr_c/struct_.rs | 34 +- src/proc_macro/ffi_export/const_.rs | 1 + src/proc_macro/utils/_mod.rs | 1 + src/slice.rs | 5 + src/tuple.rs | 4 + src/utils/macros.rs | 1 + src/utils/markers.rs | 2 + src/vec.rs | 2 + tests/test_kotlin.rs | 122 ++ 36 files changed, 2753 insertions(+), 128 deletions(-) create mode 100644 ffi_tests/generated.metadata.json create mode 100644 src/headers/languages/metadata.rs create mode 100644 src/headers/templates/metadata/_prelude.txt create mode 100644 tests/test_kotlin.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 30a5d17de6..13eca5ccb1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -16,14 +16,14 @@ env: jobs: # Check no_std # check-nostd: - name: 'Check `#![no_std]`' + name: "Check `#![no_std]`" runs-on: ubuntu-latest steps: - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2025-01-03 # branch-from-master date of 1.85.0 + toolchain: nightly-2025-10-30 # branch-from-master date of 1.91.0 override: true components: rust-src @@ -55,7 +55,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2025-01-03 # branch-from-master date of 1.85.0 + toolchain: nightly-2025-10-30 # branch-from-master date of 1.91.0 override: true components: rustfmt @@ -75,7 +75,7 @@ jobs: - macos-latest # - windows-latest rust: - - 1.85.0 + - 1.91.0 - stable # - nightly steps: @@ -107,7 +107,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.85.0 + toolchain: 1.91.0 override: true - name: Clone repo @@ -239,7 +239,7 @@ jobs: fi echo "All jobs succeeded" - # Deploy to Github pages # + # Deploy to GitHub pages # deploy: if: github.ref == 'refs/heads/master' runs-on: ubuntu-20.04 diff --git a/.gitignore b/.gitignore index f9ee0de156..19bbb92cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,13 @@ **/*.rs.bk *.code-workspace .DS_Store -/target +.idea/ examples/point/Cargo.lock examples/point/main filename.h -generated.cffi -generated.cs -generated.h -generated.lua +/generated.cffi +/generated.cs +/generated.h +/generated.lua +/generated.metadata.json +target/ diff --git a/ffi_tests/generated.metadata.json b/ffi_tests/generated.metadata.json new file mode 100644 index 0000000000..9ceab7cd9b --- /dev/null +++ b/ffi_tests/generated.metadata.json @@ -0,0 +1,1409 @@ +[ + { + "kind": "Comment", + "lines": [ + "********************************************", + "* *", + "* File auto-generated by `::safer_ffi`. *", + "* *", + "* Do not manually edit this file. *", + "* *", + "********************************************" + ] + } + ,{ + "kind": "Enum", + "name": "Wow", + "cases": [ + { + "rustName": "Leroy", + "cName": "WOW_LEROY" + }, + { + "rustName": "Jenkins", + "cName": "WOW_JENKINS" + } + ], + "backingType": { + "kind": "u8" + } + } + ,{ + "kind": "Struct", + "name": "AnUnusedStruct", + "fields": [ + { + "name": "are_you_still_there", + "type": { + "kind": "Enum", + "name": "Wow" + } + } + ] + } + ,{ + "kind": "Struct", + "name": "ArraysStruct", + "fields": [ + { + "name": "floats", + "type": { + "kind": "StaticArray", + "backingTypeName": "float_3_array_t", + "size": 3, + "type": { + "kind": "f32" + } + } + }, + { + "name": "sizes", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint64_5_array_t", + "size": 5, + "type": { + "kind": "u64" + } + } + }, + { + "name": "dim_2", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_2_array_t", + "size": 2, + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_t", + "size": 1, + "type": { + "kind": "u8" + } + } + } + }, + { + "name": "dim_3", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_2_array_3_array_t", + "size": 3, + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_2_array_t", + "size": 2, + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_t", + "size": 1, + "type": { + "kind": "u8" + } + } + } + } + } + ] + } + ,{ + "kind": "Constant", + "name": "FOO", + "type": { + "kind": "i32" + } + } + ,{ + "kind": "Enum", + "name": "Bar", + "cases": [ + { + "value": "43", + "rustName": "A", + "cName": "BAR_A" + }, + { + "value": "42", + "rustName": "B", + "cName": "BAR_B" + } + ], + "backingType": { + "kind": "i8" + } + } + ,{ + "kind": "Struct", + "comment": { + "lines": [ + "Hello, `World`!" + ] + }, + "name": "next_generation", + "fields": [ + { + "comment": { + "lines": [ + "I test some `gen`-eration." + ] + }, + "name": "generation", + "type": { + "kind": "Enum", + "name": "Bar" + } + }, + { + "comment": { + "lines": [ + "with function pointers and everything!" + ] + }, + "name": "cb", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "bool" + } + ], + "returnType": { + "kind": "Optional", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + } + } + } + ] + } + ,{ + "kind": "Opaque", + "comment": { + "lines": [ + "The layout of `&str` is opaque/subject to changes." + ] + }, + "name": "Opaque__str" + } + ,{ + "kind": "Constant", + "name": "SOME_NAME", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + } + ,{ + "kind": "Struct", + "name": "ConstGenericStruct_uint8_1", + "fields": [ + { + "name": "data", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_1_array_t", + "size": 1, + "type": { + "kind": "u8" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "ConstGenericStruct_uint8_2", + "fields": [ + { + "name": "data", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint8_2_array_t", + "size": 2, + "type": { + "kind": "u8" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "ConstGenericStruct_uint16_3", + "fields": [ + { + "name": "data", + "type": { + "kind": "StaticArray", + "backingTypeName": "uint16_3_array_t", + "size": 3, + "type": { + "kind": "u16" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "SpecificConstGenericContainer", + "fields": [ + { + "name": "field1", + "type": { + "kind": "Struct", + "name": "ConstGenericStruct_uint8_1" + } + }, + { + "name": "field2", + "type": { + "kind": "Struct", + "name": "ConstGenericStruct_uint8_2" + } + }, + { + "name": "field3", + "type": { + "kind": "Struct", + "name": "ConstGenericStruct_uint16_3" + } + } + ] + } + ,{ + "kind": "Enum", + "comment": { + "lines": [ + "Hello, `World`!" + ] + }, + "name": "triforce", + "cases": [ + { + "value": "3", + "rustName": "Din", + "cName": "TRIFORCE_DIN" + }, + { + "value": "1", + "rustName": "Farore", + "cName": "TRIFORCE_FARORE" + }, + { + "rustName": "Naryu", + "cName": "TRIFORCE_NARYU" + } + ], + "backingType": { + "kind": "u8" + } + } + ,{ + "kind": "Function", + "comment": { + "lines": [ + "https://github.com/getditto/safer_ffi/issues/45" + ] + }, + "name": "_issue_45", + "valueParameters": [ + { + "name": "__arg_0", + "type": { + "kind": "i32" + } + } + ], + "returnType": { + "kind": "i32" + } + } + ,{ + "kind": "Opaque", + "name": "Enum" + } + ,{ + "kind": "Function", + "name": "_my_enum_is_opaque", + "valueParameters": [ + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Enum" + } + } + } + } + ,{ + "kind": "Opaque", + "comment": { + "lines": [ + "The layout of `alloc::string::String` is opaque/subject to changes." + ] + }, + "name": "Opaque_String" + } + ,{ + "kind": "Function", + "name": "_some_opaque_std_lib_type", + "valueParameters": [ + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Opaque_String" + } + } + } + } + ,{ + "kind": "Function", + "name": "async_get_ft", + "valueParameters": [ + ], + "returnType": { + "kind": "i32" + } + } + ,{ + "kind": "Struct", + "comment": { + "lines": [ + "`Arc Ret>`" + ] + }, + "name": "ArcDynFn0_void", + "fields": [ + { + "name": "env_ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + }, + { + "name": "call", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + }, + { + "name": "release", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + }, + { + "name": "retain", + "type": { + "kind": "Optional", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + } + ] + } + ,{ + "kind": "Function", + "name": "call_in_the_background", + "valueParameters": [ + { + "name": "f", + "type": { + "kind": "Struct", + "name": "ArcDynFn0_void" + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Enum", + "comment": { + "lines": [ + "This is a `#[repr(C)]` enum, which leads to a classic enum def." + ] + }, + "name": "SomeReprCEnum", + "cases": [ + { + "comment": { + "lines": [ + "This is some variant." + ] + }, + "rustName": "SomeVariant", + "cName": "SOME_REPR_C_ENUM_SOME_VARIANT" + } + ] + } + ,{ + "kind": "Function", + "name": "check_SomeReprCEnum", + "valueParameters": [ + { + "name": "_baz", + "type": { + "kind": "Enum", + "name": "SomeReprCEnum" + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Function", + "name": "check_bar", + "valueParameters": [ + { + "name": "_bar", + "type": { + "kind": "Enum", + "name": "Bar" + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Function", + "comment": { + "lines": [ + "Concatenate the two input strings into a new one.", + "", + "The returned string must be freed using `free_char_p`." + ] + }, + "name": "concat", + "valueParameters": [ + { + "name": "fst", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + }, + { + "name": "snd", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + } + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "char" + } + } + } + } + ,{ + "kind": "Function", + "comment": { + "lines": [ + "Frees a string created by `concat`." + ] + }, + "name": "free_char_p", + "valueParameters": [ + { + "name": "_string", + "type": { + "kind": "Optional", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "char" + } + } + } + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Opaque", + "name": "foo" + } + ,{ + "kind": "Function", + "name": "free_foo", + "valueParameters": [ + { + "name": "foo", + "type": { + "kind": "Optional", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "foo" + } + } + } + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Function", + "comment": { + "lines": [ + "Returns a pointer to the maximum integer of the input slice, or `NULL` if", + "it is empty." + ] + }, + "name": "max", + "valueParameters": [ + { + "name": "xs", + "type": { + "kind": "DynamicArray", + "backingTypeName": "slice_ref_int32", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "i32" + } + } + } + } + } + ], + "returnType": { + "kind": "Optional", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "i32" + } + } + } + } + } + ,{ + "kind": "Function", + "name": "my_renamed_ptr_api", + "valueParameters": [ + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + } + ,{ + "kind": "Function", + "name": "new_foo", + "valueParameters": [ + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "foo" + } + } + } + } + ,{ + "kind": "Function", + "name": "read_foo", + "valueParameters": [ + { + "name": "foo", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "foo" + } + } + } + } + ], + "returnType": { + "kind": "i32" + } + } + ,{ + "kind": "Function", + "name": "returns_a_fn_ptr", + "valueParameters": [ + ], + "returnType": { + "kind": "Function", + "valueParameters": [ + { + "kind": "u8" + } + ], + "returnType": { + "kind": "u16" + } + } + } + ,{ + "kind": "Opaque", + "comment": { + "lines": [ + "The layout of `core::task::wake::Context` is opaque/subject to changes." + ] + }, + "name": "Opaque_Context" + } + ,{ + "kind": "Function", + "name": "rust_future_task_context_get_waker", + "valueParameters": [ + { + "name": "task_context", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Opaque_Context" + } + } + } + } + ], + "returnType": { + "kind": "Struct", + "name": "ArcDynFn0_void" + } + } + ,{ + "kind": "Function", + "name": "rust_future_task_context_wake", + "valueParameters": [ + { + "name": "task_context", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Opaque_Context" + } + } + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Opaque", + "name": "Erased" + } + ,{ + "kind": "Enum", + "comment": { + "lines": [ + "An FFI-safe `Poll<()>`." + ] + }, + "name": "PollFuture", + "cases": [ + { + "value": "0", + "rustName": "Completed", + "cName": "POLL_FUTURE_COMPLETED" + }, + { + "value": "-1", + "rustName": "Pending", + "cName": "POLL_FUTURE_PENDING" + } + ], + "backingType": { + "kind": "i8" + } + } + ,{ + "kind": "Struct", + "name": "FfiFutureVTable", + "fields": [ + { + "name": "release_vptr", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + }, + { + "name": "dyn_poll", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + }, + { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Opaque_Context" + } + } + ], + "returnType": { + "kind": "Enum", + "name": "PollFuture" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureVTable", + "fields": [ + { + "name": "ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + }, + { + "name": "vtable", + "type": { + "kind": "Struct", + "name": "FfiFutureVTable" + } + } + ] + } + ,{ + "kind": "Struct", + "comment": { + "lines": [ + "`Box Ret>`" + ] + }, + "name": "BoxDynFnMut0_void", + "fields": [ + { + "name": "env_ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + }, + { + "name": "call", + "type": { + "kind": "Optional", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + }, + { + "name": "free", + "type": { + "kind": "Optional", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "DropGlueVTable", + "fields": [ + { + "name": "release_vptr", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_DropGlueVTable", + "fields": [ + { + "name": "ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + }, + { + "name": "vtable", + "type": { + "kind": "Struct", + "name": "DropGlueVTable" + } + } + ] + } + ,{ + "kind": "Struct", + "name": "FfiFutureExecutorVTable", + "fields": [ + { + "name": "release_vptr", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + }, + { + "name": "retain_vptr", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + ], + "returnType": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + } + }, + { + "name": "dyn_spawn", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + }, + { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureVTable" + } + ], + "returnType": { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureVTable" + } + } + }, + { + "name": "dyn_spawn_blocking", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + }, + { + "kind": "Struct", + "name": "BoxDynFnMut0_void" + } + ], + "returnType": { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureVTable" + } + } + }, + { + "name": "dyn_block_on", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + }, + { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureVTable" + } + ], + "returnType": { + "kind": "void" + } + } + }, + { + "name": "dyn_enter", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + ], + "returnType": { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_DropGlueVTable" + } + } + } + ] + } + ,{ + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureExecutorVTable", + "fields": [ + { + "name": "ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "Erased" + } + } + } + }, + { + "name": "vtable", + "type": { + "kind": "Struct", + "name": "FfiFutureExecutorVTable" + } + } + ] + } + ,{ + "kind": "Function", + "name": "test_spawner", + "valueParameters": [ + { + "name": "executor", + "type": { + "kind": "Struct", + "name": "VirtualPtr__Erased_ptr_FfiFutureExecutorVTable" + } + } + ], + "returnType": { + "kind": "i32" + } + } + ,{ + "kind": "Struct", + "comment": { + "lines": [ + "`&'lt mut (dyn 'lt + Send + FnMut(A1) -> Ret)`" + ] + }, + "name": "RefDynFnMut1_void_char_const_ptr", + "fields": [ + { + "name": "env_ptr", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + } + }, + { + "name": "call", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "void" + } + } + }, + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + ] + } + ,{ + "kind": "Function", + "comment": { + "lines": [ + "Same as `concat`, but with a callback-based API to auto-free the created", + "string." + ] + }, + "name": "with_concat", + "valueParameters": [ + { + "name": "fst", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + }, + { + "name": "snd", + "type": { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": false, + "type": { + "kind": "char" + } + } + } + }, + { + "name": "cb", + "type": { + "kind": "Struct", + "name": "RefDynFnMut1_void_char_const_ptr" + } + } + ], + "returnType": { + "kind": "void" + } + } + ,{ + "kind": "Function", + "name": "with_foo", + "valueParameters": [ + { + "name": "cb", + "type": { + "kind": "Function", + "valueParameters": [ + { + "kind": "NonNull", + "type": { + "kind": "Pointer", + "isMutable": true, + "type": { + "kind": "Opaque", + "name": "foo" + } + } + } + ], + "returnType": { + "kind": "void" + } + } + } + ], + "returnType": { + "kind": "bool" + } + } +] diff --git a/ffi_tests/src/lib.rs b/ffi_tests/src/lib.rs index 3e2017ad71..cde0c7f7ef 100644 --- a/ffi_tests/src/lib.rs +++ b/ffi_tests/src/lib.rs @@ -229,6 +229,7 @@ fn generate_headers() -> ::std::io::Result<()> { (C, "h"), (CSharp, "cs"), (Lua, "lua"), + (Metadata, "metadata.json"), (Python, "cffi"), ]; diff --git a/js_tests/src/lib.rs b/js_tests/src/lib.rs index e5bab2b230..8da582060d 100644 --- a/js_tests/src/lib.rs +++ b/js_tests/src/lib.rs @@ -191,7 +191,10 @@ const _: () = { cb.make_nodejs_wait_for_this_to_be_dropped(true)?; let (data, call) = cb.as_raw_parts(); unsafe { - call(data, c!("Hello, World!").to_str().as_ptr().cast()); + call( + data, + ::std::mem::transmute(c!("Hello, World!").to_str().as_ptr()), + ); } ctx.env.get_undefined() } @@ -333,3 +336,44 @@ fn my_renamed_ptr_api() -> MyRenamedPtr { bar: (), } } + +#[derive_ReprC(js)] +#[repr(opaque)] +pub struct CallbackData { + value: i32, +} + +#[ffi_export(js)] +fn callback_data_new(value: i32) -> repr_c::Box { + Box::new(CallbackData { value }).into() +} + +#[ffi_export(js)] +fn callback_data_value(data: &CallbackData) -> i32 { + data.value +} + +#[cfg(feature = "js")] +const _: () = { + use ::safer_ffi::js as napi; + use ::safer_ffi::layout; + + type JsCallbackDataHandler = napi::Closure)>; + + #[napi::derive::js_export] + fn invoke_callback_with_opaque( + handler: ::NapiValue + ) -> napi::Result { + let ctx = napi::derive::__js_ctx!(); + let js_handler: JsCallbackDataHandler = napi::ReprNapi::from_napi_value(ctx.env, handler)?; + + let data: repr_c::Box = Box::new(CallbackData { value: 99 }).into(); + + let (cb_ctx, cb_vcall) = js_handler.as_raw_parts(); + unsafe { + cb_vcall(cb_ctx, layout::into_raw(data)); + } + + ctx.env.get_undefined() + } +}; diff --git a/js_tests/tests/tests.mjs b/js_tests/tests/tests.mjs index ffe5557aa8..2b3f0d372b 100644 --- a/js_tests/tests/tests.mjs +++ b/js_tests/tests/tests.mjs @@ -312,6 +312,17 @@ export async function run_tests({ ffi, performance, assert, is_web }) { assert.equal(ffi.my_renamed_ptr_api().addr, 0xbad000); + // ────────────────────────────────────────────────────────── + // Test callback with opaque pointer conversion via layout::into_raw + // ────────────────────────────────────────────────────────── + + assertCheckPointIsCalled((checkPoint) => { + ffi.invoke_callback_with_opaque(wrap_cb_for_ffi((data) => { + assert.equal(ffi.callback_data_value(data), 99); + checkPoint(); + })); + }); + // ────────────────────────────────────────────────────────── // Regression: pointer objects must contain both { addr, type } // ────────────────────────────────────────────────────────── diff --git a/scripts/build_docs.sh b/scripts/build_docs.sh index 3a0bbcda6c..b280379918 100755 --- a/scripts/build_docs.sh +++ b/scripts/build_docs.sh @@ -2,7 +2,7 @@ set -euxo pipefail -BASE_DIR="$(git rev-parse --show-toplevel)" +BASE_DIR="$(git rev-parse --show-toplevel || jj workspace root)" cd $BASE_DIR RUSTC_BOOTSTRAP=1 cargo doc --features docs diff --git a/scripts/cargo_publish.sh b/scripts/cargo_publish.sh index 17108cbdd5..adc18b3424 100755 --- a/scripts/cargo_publish.sh +++ b/scripts/cargo_publish.sh @@ -3,7 +3,7 @@ # Script to cargo publish a new version of safer-ffi set -euo pipefail -cd "$(git rev-parse --show-toplevel)" +cd "$(git rev-parse --show-toplevel || jj workspace root)" (set -x git status >&2 diff --git a/scripts/change_version.sh b/scripts/change_version.sh index 5fbc9029ce..8fbd65c173 100755 --- a/scripts/change_version.sh +++ b/scripts/change_version.sh @@ -3,7 +3,7 @@ # Script to bump the `safer-ffi` version of the repo (both in `.toml` and `.lock` files). set -euo pipefail -cd "$(git rev-parse --show-toplevel)" +cd "$(git rev-parse --show-toplevel || jj workspace root)" usage() { cat >&2 <<-EOF diff --git a/scripts/formatting/setup_cargo_fmt_override.sh b/scripts/formatting/setup_cargo_fmt_override.sh index 6f701b6ea8..1f18fdd7d4 100755 --- a/scripts/formatting/setup_cargo_fmt_override.sh +++ b/scripts/formatting/setup_cargo_fmt_override.sh @@ -18,7 +18,7 @@ # That's why, instead, we just look up which nightly coincides with # our current stable the best (e.g., using https://releases.rs), so as # to pin a `nightly` toolchain matching our stable MSRV one -# (e.g. for `{MSRV}-stable=1.85.0`, we have `{MSRV}-nighly=nightly-2025-01-03`). +# (e.g. for `{MSRV}-stable=1.85.0`, we have `{MSRV}-nightly=nightly-2025-10-30`). # # - the MSRV one being the one defined at the top-level `rust-toolchain.toml` # file. diff --git a/src/_lib.rs b/src/_lib.rs index 00dfdbf0a8..05978fcb4a 100644 --- a/src/_lib.rs +++ b/src/_lib.rs @@ -422,6 +422,7 @@ macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ( }} )} +extern crate core; extern crate self as safer_ffi; #[apply(hidden_export)] @@ -449,7 +450,6 @@ mod __ { pub use ::core::primitive::u64; pub use ::core::primitive::u128; pub use ::core::primitive::usize; - pub use ::core::{self}; pub use ::macro_rules_attribute::apply; pub use ::scopeguard::{self}; #[cfg(feature = "stabby")] diff --git a/src/boxed.rs b/src/boxed.rs index 0013191551..96bead2709 100644 --- a/src/boxed.rs +++ b/src/boxed.rs @@ -71,6 +71,7 @@ use_prelude!(); ReprC! { #[repr(transparent)] + #[no_stabby] /// An FFI-safe representation of a standard-library `Box`, as a thin pointer. /// /// (It is thus the same as [`Box`][`rust::Box`], (_e.g._, same `#[repr(C)]` layout), but @@ -183,3 +184,15 @@ impl FitForCBox for T { impl FitForCBox for [T] { type CBoxWrapped = c_slice::Box; } + +#[cfg(feature = "stabby")] +unsafe impl stabby::IStable for ThinBox { + type Align = as stabby::IStable>::Align; + type Size = as stabby::IStable>::Size; + type UnusedBits = as stabby::IStable>::UnusedBits; + type ForbiddenValues = as stabby::IStable>::ForbiddenValues; + type HasExactlyOneNiche = as stabby::IStable>::HasExactlyOneNiche; + type ContainsIndirections = as stabby::IStable>::ContainsIndirections; + type CType = as stabby::IStable>::CType; + stabby::abi::primitive_report!("safer_ffi::repr_c::ThinBox"); +} diff --git a/src/c_char.rs b/src/c_char.rs index a5373d75cd..6a7f9349e1 100644 --- a/src/c_char.rs +++ b/src/c_char.rs @@ -45,6 +45,10 @@ unsafe impl CType for c_char { Ok(()) } + fn metadata_type_usage() -> String { + r#""kind": "char""#.into() + } + fn render( out: &mut dyn io::Write, language: &dyn HeaderLanguage, diff --git a/src/headers/_mod.rs b/src/headers/_mod.rs index c442c52b0f..5939c010f3 100644 --- a/src/headers/_mod.rs +++ b/src/headers/_mod.rs @@ -348,11 +348,15 @@ impl Builder<'_, WhereTo> { Ok(()) } + fn get_unwrapped_language(&'_ self) -> Language { + self.language.unwrap_or(Language::C) + } + fn write_banner( &'_ self, definer: &'_ mut dyn Definer, ) -> io::Result<()> { - let lang = self.language.unwrap_or(Language::C); + let lang = self.get_unwrapped_language(); let banner: &'_ str = self.banner.unwrap_or(match lang { | Language::Lua => concat!( @@ -360,6 +364,7 @@ impl Builder<'_, WhereTo> { "--\n", "-- Do not manually edit this file.\n", ), + | Language::Metadata => return Ok(()), | _ => concat!( "/*! \\file */\n", "/*******************************************\n", @@ -379,7 +384,7 @@ impl Builder<'_, WhereTo> { &'_ self, definer: &'_ mut dyn Definer, ) -> io::Result<()> { - let lang = self.language.unwrap_or(Language::C); + let lang = self.get_unwrapped_language(); let guard = self.guard(); let text_after_guard = self.text_after_guard(); @@ -399,6 +404,11 @@ impl Builder<'_, WhereTo> { RustLib = Self::lib_name(), ), + | Language::Metadata => write!( + definer.out(), + include_str!("templates/metadata/_prelude.txt"), + ), + | Language::Lua => writeln!(definer.out(), include_str!("templates/lua/_prelude.lua")), #[cfg(feature = "python-headers")] @@ -413,7 +423,7 @@ impl Builder<'_, WhereTo> { definer: &'_ mut dyn Definer, ) -> io::Result<()> { let stable_header = self.stable_header.unwrap_or(true); - let lang = self.language.unwrap_or(Language::C); + let lang = self.get_unwrapped_language(); // skip adding int/bool headers for Lua if lang == Language::Lua { @@ -456,7 +466,7 @@ impl Builder<'_, WhereTo> { &'_ self, definer: &'_ mut dyn Definer, ) -> io::Result<()> { - let lang = self.language.unwrap_or(Language::C); + let lang = self.get_unwrapped_language(); match lang { | Language::C => write!( definer.out(), @@ -477,6 +487,8 @@ impl Builder<'_, WhereTo> { write!(definer.out(), include_str!("templates/lua/epilogue.lua")) }, + | Language::Metadata => writeln!(definer.out(), "]"), + #[cfg(feature = "python-headers")] | Language::Python => Ok(()), } @@ -542,6 +554,9 @@ pub enum Language { /// Lua Lua, + /// A JSON file containing detailed information about the FFI declarations. + Metadata, + /// Python (experimental). #[cfg(feature = "python-headers")] Python, @@ -553,6 +568,7 @@ impl Language { | Language::C => &languages::C, | Language::CSharp => &languages::CSharp, | Language::Lua => &languages::Lua, + | Language::Metadata => &languages::Metadata, #[cfg(feature = "python-headers")] | Language::Python => &languages::Python, } diff --git a/src/headers/languages/c.rs b/src/headers/languages/c.rs index 864bdd74c7..bae23995dc 100644 --- a/src/headers/languages/c.rs +++ b/src/headers/languages/c.rs @@ -27,33 +27,7 @@ impl HeaderLanguage for C { } fn supports_type_aliases(self: &'_ C) -> Option<&'_ dyn HeaderLanguageSupportingTypeAliases> { - return Some(self); - // where - #[expect(non_local_definitions)] - impl HeaderLanguageSupportingTypeAliases for C { - fn declare_type_alias( - self: &'_ Self, - ctx: &'_ mut dyn Definer, - docs: Docs<'_>, - self_ty: &'_ dyn PhantomCType, - inner_ty: &'_ dyn PhantomCType, - ) -> io::Result<()> { - // No `this` in this design yet; let's stick to `this` nonetheless - // for the syntactical search for the `self` antipattern. - let this = self; - let ref indent = Indentation::new(4 /* ctx.indent_width() */); - mk_out!(indent, ctx.out()); - this.emit_docs(ctx, docs, indent)?; - let ref aliaser = self_ty.name(this); - let ref aliasee = inner_ty.name(this); - out!(( - "typedef {aliasee} {aliaser};" - )); - - out!("\n"); - Ok(()) - } - } + Some(self) } fn declare_simple_enum( @@ -430,3 +404,28 @@ impl HeaderLanguage for C { Ok(()) } } + +impl HeaderLanguageSupportingTypeAliases for C { + fn declare_type_alias( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + inner_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> { + // No `this` in this design yet; let's stick to `this` nonetheless + // for the syntactical search for the `self` antipattern. + let this = self; + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + this.emit_docs(ctx, docs, indent)?; + let ref aliaser = self_ty.name(this); + let ref aliasee = inner_ty.name(this); + out!(( + "typedef {aliasee} {aliaser};" + )); + + out!("\n"); + Ok(()) + } +} diff --git a/src/headers/languages/metadata.rs b/src/headers/languages/metadata.rs new file mode 100644 index 0000000000..4976a736da --- /dev/null +++ b/src/headers/languages/metadata.rs @@ -0,0 +1,470 @@ +#![cfg_attr(rustfmt, rustfmt::skip)] + +use super::*; +use crate::layout; +use crate::utils::DisplayFromFn; +use core::fmt::Display; +use std::io::Write; + +pub struct Metadata; + +impl io::Result<()>> DisplayFromFn { + fn indented_lines(&self) -> String { + self.to_string().lines().map(|line| format!(" {line}\n")).collect() + } +} + +impl HeaderLanguage for Metadata { + fn declare_simple_enum( + self: &'_ Self, + this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + backing_integer: Option<&dyn PhantomCType>, + variants: &'_ [EnumVariant<'_>], + ) -> io::Result<()> { + let ref indent = Indentation::new(4); + mk_out!(indent, ctx.out()); + + if let _ = indent.scope() { + out!((",{{")); + + if let _ = indent.scope() { + out!(("\"kind\": \"Enum\",")); + + self.emit_docs(ctx, docs, indent)?; + + let ref short_name = self_ty.short_name(); + + out!(("\"name\": \"{short_name}\",")); + + out!(("\"cases\": [")); + + if let _ = indent.scope() { + for (index, variant) in variants.iter().enumerate() { + out!(("{{")); + + if let _ = indent.scope() { + self.emit_docs(ctx, variant.docs, indent)?; + + if let Some(discriminant) = variant.discriminant { + let formatted_discriminant = format!("{:?}", discriminant); + + out!(("\"value\": \"{formatted_discriminant}\",")); + } + + let variant_short_name = variant.name; + let variant_name = crate::utils::screaming_case(short_name, variant.name); + + out!(("\"rustName\": \"{variant_short_name}\",")); + out!(("\"cName\": \"{variant_name}\"")); + } + + if index + 1 < variants.len() { + out!(("}},")); + } else { + out!(("}}")); + } + } + } + + if let Some(backing_integer) = backing_integer { + out!(("],")); + self.emit_type_usage(this, ctx, indent, "backingType", backing_integer)?; + } else { + out!(("]")); + } + } + + out!(("}}")); + } + + Ok(()) + } + + fn declare_struct ( + self: &'_ Self, + this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + fields: &'_ [StructField<'_>] + ) -> io::Result<()> { + let ref indent = Indentation::new(4); + mk_out!(indent, ctx.out()); + + if let _ = indent.scope() { + out!((",{{")); + + if let _ = indent.scope() { + out!(("\"kind\": \"Struct\",")); + + self.emit_docs(ctx, docs, indent)?; + + let ref short_name = self_ty.short_name(); + + out!(("\"name\": \"{short_name}\",")); + + out!(("\"fields\": [")); + + if let _ = indent.scope() { + let non_empty_fields: Vec<&StructField<'_>> = fields.iter() + .filter(|f| f.ty.size() != 0) + .collect(); + + for (index, field) in non_empty_fields.iter().enumerate() { + out!(("{{")); + + if let _ = indent.scope() { + self.emit_docs(ctx, field.docs, indent)?; + + let field_name = field.name; + + out!(("\"name\": \"{field_name}\",")); + + self.emit_type_usage(this, ctx, indent, "type", field.ty)?; + } + + if index + 1 < non_empty_fields.len() { + out!(("}},")); + } else { + out!(("}}")); + } + } + } + + out!(("]")); + } + + out!(("}}")); + } + + Ok(()) + } + + fn declare_opaque_type ( + self: &'_ Self, + _this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> { + let ref indent = Indentation::new(4); + mk_out!(indent, ctx.out()); + + if let _ = indent.scope() { + out!((",{{")); + + if let _ = indent.scope() { + out!(("\"kind\": \"Opaque\",")); + + self.emit_docs(ctx, docs, indent)?; + + let short_name = self_ty.short_name(); + + out!(("\"name\": \"{short_name}\"")); + } + + out!(("}}")); + } + + Ok(()) + } + + fn declare_function ( + self: &'_ Self, + this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + fname: &'_ str, + args: &'_ [FunctionArg<'_>], + ret_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> { + let ref indent = Indentation::new(4); + mk_out!(indent, ctx.out()); + + if let _ = indent.scope() { + out!((",{{")); + + if let _ = indent.scope() { + out!(("\"kind\": \"Function\",")); + + self.emit_docs(ctx, docs, indent)?; + + out!(("\"name\": \"{fname}\",")); + + out!(("\"valueParameters\": [")); + + if let _ = indent.scope() { + for (index, arg) in args.iter().enumerate() { + out!(("{{")); + + if let _ = indent.scope() { + let arg_name = arg.name; + + out!(("\"name\": \"{arg_name}\",")); + + self.emit_type_usage(this, ctx, indent, "type", arg.ty)?; + } + + if index + 1 < args.len() { + out!(("}},")); + } else { + out!(("}}")); + } + } + } + + out!(("],")); + + self.emit_type_usage(this, ctx, indent, "returnType", ret_ty)?; + } + + out!(("}}")); + } + + Ok(()) + } + + fn emit_primitive_ty( + self: &'_ Self, + out: &mut dyn Write, + primitive: Primitive + ) -> io::Result<()> { + write!( + out, + r#""kind": "{kind}""#, + kind = match primitive { + Primitive::Bool => "bool".into(), + Primitive::CChar => "char".into(), + Primitive::Integer { signed, bitwidth } => match bitwidth { + IntBitWidth::PointerSized => { + let sign_prefix = if signed { "s" } else { "" }; + format!("{sign_prefix}size_t") + }, + IntBitWidth::CInt => { + let sign_prefix = if signed { "" } else { "u" }; + format!("{sign_prefix}int") + }, + IntBitWidth::Fixed(num_bits) => { + let prefix = if signed { "i" } else { "u" }; + let num_bits = num_bits as u8; + format!("{prefix}{num_bits}") + }, + }, + Primitive::Float { bitwidth } => match bitwidth { + | FloatBitWidth::_32 => "f32", + | FloatBitWidth::_64 => "f64", + }.into(), + } + ) + } + + fn emit_pointer_ty( + self: &'_ Self, + this: &dyn HeaderLanguage, + out: &mut dyn Write, + pointee_is_immutable: bool, + pointee: &'_ dyn PhantomCType + ) -> io::Result<()> { + writeln!(out, r#""kind": "Pointer","#)?; + writeln!( + out, + r#""isMutable": {pointee_is_mutable},"#, + pointee_is_mutable = !pointee_is_immutable + )?; + writeln!(out, r#""type": {{"#)?; + write!( + out, + "{}", + F(|out| pointee.render(out, this)).indented_lines(), + )?; + writeln!(out, "}}") + } + + fn emit_void_output_type(self: &'_ Self, out: &mut dyn Write) -> io::Result<()> { + write!(out, r#""kind": "void""#) + } + + fn emit_function_ptr_ty( + self: &'_ Self, + this: &dyn HeaderLanguage, + out: &mut dyn Write, + _newtype_name: &str, + _name: Option<&dyn Display>, + args: &'_ [FunctionArg<'_>], + ret_ty: &'_ dyn PhantomCType + ) -> io::Result<()> { + write!( + out, + r#" + "kind": "Function", + "valueParameters": [ + {value_parameters} + ], + "returnType": {{ + {return_type} + }} + "#, + value_parameters = F(|out| { + let first = &mut true; + for arg in args { + if mem::take(first).not() { + write!(out, ", ")?; + } + + write!( + out, + r#"{{ + {argument} + }}"#, + argument = F(|out| arg.ty.render(out, this)).indented_lines(), + )?; + } + Ok(()) + }).indented_lines(), + return_type = F(|out| ret_ty.render(out, this)) + ) + } + + fn emit_array_ty( + self: &'_ Self, this: &dyn HeaderLanguage, + out: &mut dyn Write, + _var_name: Option<&dyn Display>, + newtype_name: &'_ str, + elem_ty: &'_ dyn PhantomCType, + array_len: usize + ) -> io::Result<()> { + writeln!(out, r#""kind": "StaticArray","#)?; + writeln!(out, r#""backingTypeName": "{newtype_name}","#)?; + writeln!(out, r#""size": {array_len},"#)?; + writeln!(out, r#""type": {{"#)?; + write!( + out, + "{}", + F(|out| elem_ty.render(out, this)).indented_lines(), + )?; + writeln!(out, r#"}}"#) + } + + fn declare_constant ( + self: &'_ Self, + this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + ty: &'_ dyn PhantomCType, + skip_type: bool, + value: &'_ dyn fmt::Debug, + ) -> io::Result<()> { + let ref indent = Indentation::new(4); + mk_out!(indent, ctx.out()); + + if let _ = indent.scope() { + out!((",{{")); + + if let _ = indent.scope() { + out!((r#""kind": "Constant","#)); + + self.emit_docs(ctx, docs, indent)?; + + out!((r#""name": "{name}","#)); + + let constant_type = if skip_type { + let formatted_constant = format!("{:?}", value); + + if formatted_constant.starts_with('"') { + &PhantomData::< as layout::ReprC>::CLayout> as &dyn PhantomCType + } else if formatted_constant.starts_with('\'') { + &PhantomData::<::CLayout> as &dyn PhantomCType + } else if formatted_constant.contains('.') { + &PhantomData::<::CLayout> as &dyn PhantomCType + } else { + &PhantomData::<::CLayout> as &dyn PhantomCType + } + } else { + ty + }; + + self.emit_type_usage(this, ctx, indent, "type", constant_type)?; + } + + out!(("}}")); + } + + Ok(()) + } + + fn emit_docs ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + indent: &'_ Indentation, + ) -> io::Result<()> { + mk_out!(indent, ctx.out()); + + if docs.is_empty() { + return Ok(()); + } + + out!((r#""comment": {{"#)); + + if let _ = indent.scope() { + out!((r#""lines": ["#)); + + if let _ = indent.scope() { + let docs_lines = docs.iter().copied() + .map(str::trim) + .map(|line| line.replace("\\", "\\\\")) + .map(|line| line.replace("\"", "\\\"")); + + for (index, line) in docs_lines.enumerate() { + if index + 1 < docs.len() { + out!((r#""{line}","#)); + } else { + out!((r#""{line}""#)); + } + } + } + + out!(("]")); + } + + out!(("}},")); + + Ok(()) + } + + fn must_declare_built_in_types(self: &'_ Self) -> bool { + false + } +} + +impl Metadata { + + fn emit_type_usage( + self: &'_ Self, + _this: &dyn HeaderLanguage, + ctx: &'_ mut dyn Definer, + indent: &'_ Indentation, + field_name: &'_ str, + ty: &'_ dyn PhantomCType, + ) -> io::Result<()> { + mk_out!(indent, ctx.out()); + + out!((r#""{field_name}": {{"#)); + + if let _ = indent.scope() { + let type_usage = ty.metadata_type_usage(); + + for line in type_usage.lines() { + out!(("{line}")); + } + } + + out!(("}}")); + + Ok(()) + } +} diff --git a/src/headers/languages/mod.rs b/src/headers/languages/mod.rs index 4c87ef2262..53e76f6d69 100644 --- a/src/headers/languages/mod.rs +++ b/src/headers/languages/mod.rs @@ -26,6 +26,9 @@ mod python; pub use lua::Lua; mod lua; +pub use metadata::Metadata; +mod metadata; + pub struct Indentation { depth: ::core::cell::Cell, width: usize, @@ -221,6 +224,10 @@ pub trait HeaderLanguage: UpcastAny { // it is not directly called by the framework. Ok(()) } + + fn must_declare_built_in_types(self: &'_ Self) -> bool { + true + } } pub trait HeaderLanguageSupportingTypeAliases: HeaderLanguage { @@ -295,6 +302,8 @@ pub trait PhantomCType { fn metadata(self: &'_ Self) -> &'static dyn Provider; + fn metadata_type_usage(self: &'_ Self) -> String; + fn size(self: &'_ Self) -> usize; fn align(self: &'_ Self) -> usize; @@ -344,6 +353,10 @@ where T::metadata() } + fn metadata_type_usage(self: &'_ Self) -> String { + T::metadata_type_usage() + } + fn size(self: &'_ Self) -> usize { ::core::mem::size_of::() } diff --git a/src/headers/templates/metadata/_prelude.txt b/src/headers/templates/metadata/_prelude.txt new file mode 100644 index 0000000000..4137f36bf8 --- /dev/null +++ b/src/headers/templates/metadata/_prelude.txt @@ -0,0 +1,13 @@ +[ + {{ + "kind": "Comment", + "lines": [ + "********************************************", + "* *", + "* File auto-generated by `::safer_ffi`. *", + "* *", + "* Do not manually edit this file. *", + "* *", + "********************************************" + ] + }} diff --git a/src/js/closures/node_js.rs b/src/js/closures/node_js.rs index 3908b5acc3..8e2155aab7 100644 --- a/src/js/closures/node_js.rs +++ b/src/js/closures/node_js.rs @@ -80,6 +80,27 @@ mod safety_boundary { unsafe impl Send for ThreadTiedJsFunction {} unsafe impl Sync for ThreadTiedJsFunction {} + impl Drop for ThreadTiedJsFunction { + fn drop(self: &'_ mut ThreadTiedJsFunction) { + // Note: since Self is `Send`, + // this may be called in a non-Node.js thread. + // It appears the ref-counting functions are thread-safe. + let Self { + ref env, + raw_ref_handle, + .. + } = *self; + unsafe { + /* Decrementing the ref-count before destroying it does + * not seem to be necessary. */ + // ::napi::sys::napi_reference_unref( + // env.raw(), raw_ref_handle, &mut 0, + // ); + let _ignored_status = ::napi::sys::napi_delete_reference(env.raw(), raw_ref_handle); + } + } + } + impl ThreadTiedJsFunction { pub fn new( func: &'_ JsFunction, @@ -99,29 +120,6 @@ mod safety_boundary { ); } - #[expect(non_local_definitions)] - impl Drop for ThreadTiedJsFunction { - fn drop(self: &'_ mut ThreadTiedJsFunction) { - // Note: since Self is `Send`, - // this may be called in a non-Node.js thread. - // It appears the ref-counting functions are thread-safe. - let Self { - ref env, - raw_ref_handle, - .. - } = *self; - unsafe { - /* Decrementing the ref-count before destroying it does - * not seem to be necessary. */ - // ::napi::sys::napi_reference_unref( - // env.raw(), raw_ref_handle, &mut 0, - // ); - let _ignored_status = - ::napi::sys::napi_delete_reference(env.raw(), raw_ref_handle); - } - } - } - Self { env, raw_ref_handle, diff --git a/src/js/ffi_helpers.rs b/src/js/ffi_helpers.rs index a1de1d005a..b2113873c8 100644 --- a/src/js/ffi_helpers.rs +++ b/src/js/ffi_helpers.rs @@ -110,7 +110,7 @@ pub fn with_js_buffer_as_slice_uint8_t_ref( | _case if matches!(fst.get_type(), Ok(ValueType::Null)) => { cb.call(None, &[ReprNapi::to_napi_value( crate::slice::slice_raw_Layout:: { - ptr: NULL!(), + ptr: <*mut u8>::into(NULL!()), len: 0xbad000, }, ctx.env, @@ -132,7 +132,9 @@ pub fn char_p_boxed_to_js_string( // return Err(Error::new(Status::InvalidArg, "Got `NULL`".into())); ctx.env.get_null()?.into_unknown() } else { - let p: crate::prelude::char_p::Box = unsafe { crate::layout::from_raw_unchecked(p) }; + let p: crate::prelude::char_p::Box = unsafe { + crate::layout::from_raw_unchecked(crate::layout::impls::NonNullCLayout::new(p)) + }; ctx.env.create_string(p.to_str())?.into_unknown() } }) @@ -176,8 +178,9 @@ pub fn char_p_ref_to_js_string( // return Err(Error::new(Status::InvalidArg, "Got `NULL`".into())); ctx.env.get_null()?.into_unknown() } else { - let p: crate::prelude::char_p::Ref<'_> = - unsafe { crate::layout::from_raw_unchecked(p) }; + let p: crate::prelude::char_p::Ref<'_> = unsafe { + crate::layout::from_raw_unchecked(crate::layout::impls::NonNullCLayout::new(p)) + }; ctx.env.create_string(p.to_str())?.into_unknown() } }) @@ -286,7 +289,7 @@ pub fn with_out_byte_slice(cb: JsFunction) -> Result { let ctx = ::safer_ffi::js::derive::__js_ctx!(); let ty = &"slice_boxed_uint8_t"; let mut v = crate::slice::slice_ref_Layout::<()> { - ptr: NULL!(), + ptr: <*const crate::CVoid>::into(NULL!()), len: 0, _lt: unsafe { ::core::mem::transmute(()) }, }; @@ -297,7 +300,10 @@ pub fn with_out_byte_slice(cb: JsFunction) -> Result { &format!("{} *", ty), )?])?; let mut v_js = ctx.env.create_object()?; - v_js.set_named_property("ptr", wrap_ptr(ctx.env, v.ptr as _, "uint8_t *")?)?; + v_js.set_named_property( + "ptr", + wrap_ptr(ctx.env, v.ptr.wrappedCLayout as _, "uint8_t *")?, + )?; v_js.set_named_property("len", ReprNapi::to_napi_value(v.len as usize, ctx.env)?)?; v_js.into_unknown() }) @@ -314,7 +320,7 @@ pub fn with_out_vec_of_ptrs( let ref vec_ty: String = vec_ty.into_utf8()?.into_owned()?; let ref ty: String = ty.into_utf8()?.into_owned()?; let mut v = crate::vec::Vec_Layout::<()> { - ptr: NULL!(), + ptr: <*mut crate::CVoid>::into(NULL!()), len: 0, cap: 0, }; @@ -327,7 +333,7 @@ pub fn with_out_vec_of_ptrs( let mut v_js = ctx.env.create_object()?; v_js.set_named_property( "ptr", - wrap_ptr(ctx.env, v.ptr.cast(), &format!("{} *", ty))?, + wrap_ptr(ctx.env, v.ptr.wrappedCLayout.cast(), &format!("{} *", ty))?, )?; v_js.set_named_property("len", ReprNapi::to_napi_value(v.len as usize, ctx.env)?)?; v_js.set_named_property("cap", ReprNapi::to_napi_value(v.cap as usize, ctx.env)?)?; diff --git a/src/layout/_mod.rs b/src/layout/_mod.rs index 4580104741..f78c84752e 100644 --- a/src/layout/_mod.rs +++ b/src/layout/_mod.rs @@ -31,7 +31,7 @@ type_level_enum! { /// That's why **manually implementing this trait is strongly discouraged**, /// although not forbidden: /// -/// - If you trully want a manual implementation of `CType` (_e.g._, for an "opaque type" pattern, +/// - If you truly want a manual implementation of `CType` (_e.g._, for an "opaque type" pattern, /// _i.e._, a forward declaration), then, to implement the trait so that it works no matter the /// status of the `safer_ffi/headers` feature, one must define the methods as if feature was /// present, but with a `#[::safer_ffi::cfg_headers]` gate slapped on _each_ method. @@ -141,9 +141,12 @@ pub unsafe trait CType: Sized + Copy { /// "Foo".into() /// } /// - /// type OPAQUE_KIND = OpaqueKind::Concrete; + /// #[::safer_ffi::cfg_headers] + /// fn metadata_type_usage() -> String { + /// String::new() + /// } /// - /// // ... + /// type OPAQUE_KIND = OpaqueKind::Concrete; /// } /// ``` #[allow(nonstandard_style)] @@ -347,6 +350,9 @@ pub unsafe trait CType: Sized + Copy { fn metadata() -> &'static dyn Provider { &None } + + #[apply(__cfg_headers__!)] + fn metadata_type_usage() -> String; } /// The meat of the crate. _The_ trait. diff --git a/src/layout/impls.rs b/src/layout/impls.rs index e9d9ff1d2d..1f401d59e6 100644 --- a/src/layout/impls.rs +++ b/src/layout/impls.rs @@ -191,6 +191,34 @@ const _: () = { Self::render_wrapping_var(out, language, None) } + fn metadata_type_usage() -> String { + let return_type = metadata_nested_type_usage::(); + + #[allow(unused_mut)] + let mut value_parameters = String::new(); + + $( + let n_type = metadata_n_nested_type_usage::<$An>(2); + value_parameters.push_str("\n {\n"); + value_parameters.push_str(&n_type); + + $( + let i_type = metadata_n_nested_type_usage::<$Ai>(2); + value_parameters.push_str("\n },\n {\n"); + value_parameters.push_str(&i_type); + )* + + value_parameters.push_str("\n }\n"); + )? + + format!( + "\"kind\": \"{}\",\n\"valueParameters\": [{}],\n\"returnType\": {{\n{}\n}}", + "Function", + value_parameters, + return_type, + ) + } + fn render_wrapping_var( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -365,6 +393,10 @@ const _: () = { ) } + fn metadata_type_usage() -> String { + format!(r#""kind": "{}""#, stringify!($RustInt)) + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -417,6 +449,10 @@ const _: () = { Ok(()) } + fn metadata_type_usage() -> String { + format!(r#""kind": "{}""#, stringify!($fN)) + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -461,6 +497,17 @@ const _: () = { T::define_self(language, definer) } + fn metadata_type_usage() -> String { + let nested_type = metadata_nested_type_usage::(); + + format!( + "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", + "Pointer", + "false", + nested_type, + ) + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -511,6 +558,17 @@ const _: () = { T::define_self(language, definer) } + fn metadata_type_usage() -> String { + let nested_type = metadata_nested_type_usage::(); + + format!( + "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", + "Pointer", + "true", + nested_type, + ) + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -680,6 +738,10 @@ unsafe impl CType for Bool { ) } + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\"", "bool") + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -740,6 +802,10 @@ unsafe impl CType for c_int { Ok(()) } + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\"", "int") + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -751,8 +817,7 @@ unsafe impl CType for c_int { signed: true, bitwidth: primitives::IntBitWidth::CInt, }, - )?; - Ok(()) + ) } fn metadata() -> &'static dyn Provider { @@ -765,6 +830,159 @@ unsafe impl CType for c_int { } } +#[derive(Debug, Clone, Copy)] +pub struct NonNullCLayout { + pub(crate) wrappedCLayout: T, +} + +#[cfg(feature = "js")] +impl NonNullCLayout { + #[inline] + pub(crate) fn new(wrappedCLayout: T) -> Self { + NonNullCLayout { wrappedCLayout } + } +} + +impl From for NonNullCLayout { + #[inline] + fn from(wrappedCLayout: T) -> Self { + NonNullCLayout { wrappedCLayout } + } +} + +#[cfg(feature = "js")] +const _: () = { + use crate::js::*; + + impl ReprNapi for NonNullCLayout { + type NapiValue = T::NapiValue; + + fn to_napi_value( + self: Self, + env: &'_ Env, + ) -> Result { + T::to_napi_value(self.wrappedCLayout, env) + } + + fn from_napi_value( + env: &'_ Env, + napi_value: Self::NapiValue, + ) -> Result { + T::from_napi_value(env, napi_value).map(|wrapped| NonNullCLayout { + wrappedCLayout: wrapped, + }) + } + } +}; + +unsafe impl CType for NonNullCLayout { + type OPAQUE_KIND = T::OPAQUE_KIND; + + __cfg_headers__! { + fn short_name() -> String { + T::short_name() + } + + fn render( + out: &'_ mut dyn io::Write, + language: &'_ dyn HeaderLanguage, + ) -> io::Result<()> { + T::render(out, language) + } + + fn render_wrapping_var( + out: &'_ mut dyn io::Write, + language: &'_ dyn HeaderLanguage, + // Either a `&&str`, or a `&fmt::Arguments<'_>`, for instance. + var_name: Option<&dyn ::core::fmt::Display>, + ) -> io::Result<()> { + T::render_wrapping_var(out, language, var_name) + } + + fn define_self__impl(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { + T::define_self__impl(language, definer) + } + + fn define_self(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { + T::define_self(language, definer) + } + + fn name(language: &'_ dyn HeaderLanguage) -> String { + T::name(language) + } + + fn name_wrapping_var(language: &'_ dyn HeaderLanguage, var_name: Option<&dyn fmt::Display>) -> String { + T::name_wrapping_var(language, var_name) + } + + fn metadata() -> &'static dyn Provider { + T::metadata() + } + + fn metadata_type_usage() -> String { + let nested_type = metadata_nested_type_usage::(); + + format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "NonNull", nested_type) + } + } +} + +unsafe impl ReprC for NonNullCLayout { + type CLayout = T::CLayout; + + fn is_valid(it: &'_ Self::CLayout) -> bool { + T::is_valid(it) + } +} + +impl NonNullCLayout<*mut T> { + #[inline] + pub fn is_null(self) -> bool { + self.wrappedCLayout.is_null() + } + + pub fn as_ptr(&self) -> *const T { + self.wrappedCLayout + } + + pub fn align_offset( + &self, + align: usize, + ) -> usize { + let addr = self.as_ptr() as usize; + let misalignment = addr % align; + if misalignment == 0 { + 0 + } else { + align - misalignment + } + } +} + +impl NonNullCLayout<*const T> { + #[inline] + pub fn is_null(self) -> bool { + self.wrappedCLayout.is_null() + } + + pub fn as_ptr(&self) -> *const T { + self.wrappedCLayout + } + + pub fn align_offset( + &self, + align: usize, + ) -> usize { + let addr = self.as_ptr() as usize; + let misalignment = addr % align; + if misalignment == 0 { + 0 + } else { + align - misalignment + } + } +} + impl_ReprC_for! { unsafe { bool => |ref byte: Bool| (byte.0 & !0b1) == 0 @@ -772,44 +990,44 @@ impl_ReprC_for! { unsafe { @for[T : ReprC] ptr::NonNull - => |ref it: *mut T::CLayout| { + => |ref it: NonNullCLayout<*mut T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullRef - => |ref it: *const T::CLayout| { + => |ref it: NonNullCLayout<*const T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullMut - => |ref it: *mut T::CLayout| { + => |ref it: NonNullCLayout<*mut T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullOwned - => |ref it: *mut T::CLayout| { + => |ref it: NonNullCLayout<*mut T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , @for['a, T : 'a + ReprC] &'a T - => |ref it: *const T::CLayout| { + => |ref it: NonNullCLayout<*const T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , @for['a, T : 'a + ReprC] &'a mut T - => |ref it: *mut T::CLayout| { + => |ref it: NonNullCLayout<*mut T::CLayout>| { it.is_null().not() && - (*it as usize) % ::core::mem::align_of::() == 0 + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 } , }} @@ -819,8 +1037,8 @@ impl_ReprC_for! { unsafe { impl_ReprC_for! { unsafe { @for['out, T : 'out + Sized + ReprC] Out<'out, T> - => |ref it: *mut T::CLayout| { - (*it as usize) % ::core::mem::align_of::() == 0 + => |ref it: NonNullCLayout<*mut T::CLayout>| { + (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 }, }} @@ -859,6 +1077,10 @@ unsafe impl CType for OpaqueLayout { &PhantomData::, ) } + + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name()) + } } } @@ -1023,6 +1245,17 @@ unsafe impl CType for [Item; N] { N, ) } + + fn metadata_type_usage() -> String { + let nested_type = metadata_nested_type_usage::(); + + format!("\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"size\": {},\n\"type\": {{\n{}\n}}", + "StaticArray", + Self::short_name() + "_t", + N, + nested_type, + ) + } } } @@ -1034,3 +1267,22 @@ unsafe impl ReprC for [Item; N] { it.iter().all(Item::is_valid) } } + +__cfg_headers__! { + + pub(super) + fn metadata_nested_type_usage() -> String { + metadata_n_nested_type_usage::(1) + } + + pub(super) + fn metadata_n_nested_type_usage(nesting: usize) -> String { + let nested_type = Type::metadata_type_usage(); + + nested_type + .lines() + .map(|line| format!("{}{}", " ".repeat(nesting), line)) + .collect::>() + .join("\n") + } +} diff --git a/src/layout/macros.rs b/src/layout/macros.rs index d058bee62d..e911cd70aa 100644 --- a/src/layout/macros.rs +++ b/src/layout/macros.rs @@ -219,14 +219,14 @@ macro_rules! CType {( let xs = buf; $crate::js::Result::Ok(unsafe { $crate::ඞ::mem::transmute_copy(&{ $crate::slice::slice_raw_Layout:: { - ptr: xs.as_ptr() as _, + ptr: (xs.as_ptr() as *mut u8).into(), len: xs.len(), } })}) } else { // it's NULL $crate::js::Result::Ok(unsafe { $crate::ඞ::mem::transmute_copy::<_, Self>(&{ $crate::slice::slice_raw_Layout:: { - ptr: $crate::NULL!(), + ptr: <*mut u8>::into($crate::NULL!()), len: 0xbad000, } })}) @@ -316,6 +316,59 @@ macro_rules! CType {( #[macro_export] #[cfg_attr(rustfmt, rustfmt::skip)] macro_rules! ReprC {( + $( + @[doc = $doc:expr] + )? + $( + #[doc = $doc2:expr] + )* + #[repr( + $C_or_transparent:ident $(, + $($(@$if_js:tt)? + js $(,)? + )? + )? + )] + #[no_stabby] + $( + #[$attr:meta] + )* + $pub:vis + struct $StructName:ident $([$($generics:tt)*])? + $( + where { $($wc:tt)* } + )? + $({ + $($body:tt)* + })? + $(( + $($body2:tt)* + );)? +) => ( + #[$crate::prelude::derive_ReprC2($($($($if_js)? js)?)?)] + $( + #[doc = $doc] + )? + $( + #[doc = $doc2] + )* + #[repr($C_or_transparent)] + $( + #[$attr] + )* + $pub + struct $StructName $(<$($generics)*>)? + $( + where $($wc)* + )? + $({ + $($body)* + })? + $(( + $($body2)* + );)? +); +( $( @[doc = $doc:expr] )? @@ -367,7 +420,8 @@ macro_rules! ReprC {( $(( $($body2)* );)? -)} +) +} #[cfg(test)] #[crate::derive_ReprC] diff --git a/src/layout/niche.rs b/src/layout/niche.rs index ec7fd2bbd1..8987fe7443 100644 --- a/src/layout/niche.rs +++ b/src/layout/niche.rs @@ -1,6 +1,12 @@ use_prelude!(); + use crate::prelude::c_slice; +__cfg_headers__! { + use crate::__::{Definer, HeaderLanguage}; + use crate::layout::impls::metadata_nested_type_usage; +} + pub unsafe trait HasNiche: ReprC { fn is_niche(it: &'_ ::CLayout) -> bool { // default implementation (the `is_niche()` heuristic does not need to @@ -10,14 +16,104 @@ pub unsafe trait HasNiche: ReprC { } unsafe impl ReprC for Option { - type CLayout = ::CLayout; + type CLayout = OptionCLayout; #[inline] fn is_valid(it: &'_ Self::CLayout) -> bool { - T::is_niche(it) || ::is_valid(it) + T::is_niche(&it.wrappedCLayout) || ::is_valid(&it.wrappedCLayout) } } +#[derive(Debug, Clone, Copy)] +pub struct OptionCLayout { + pub(crate) wrappedCLayout: T, +} + +unsafe impl CType for OptionCLayout { + type OPAQUE_KIND = T::OPAQUE_KIND; + + __cfg_headers__! { + fn short_name() -> String { + T::short_name() + } + + fn render( + out: &'_ mut dyn io::Write, + language: &'_ dyn HeaderLanguage, + ) -> io::Result<()> { + T::render(out, language) + } + + fn render_wrapping_var( + out: &'_ mut dyn io::Write, + language: &'_ dyn HeaderLanguage, + // Either a `&&str`, or a `&fmt::Arguments<'_>`, for instance. + var_name: Option<&dyn ::core::fmt::Display>, + ) -> io::Result<()> { + T::render_wrapping_var(out, language, var_name) + } + + fn define_self__impl(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { + T::define_self__impl(language, definer) + } + + fn define_self(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { + T::define_self(language, definer) + } + + fn name(_language: &'_ dyn HeaderLanguage) -> String { + T::name(_language) + } + + fn name_wrapping_var(language: &'_ dyn HeaderLanguage, var_name: Option<&dyn fmt::Display>) -> String { + T::name_wrapping_var(language, var_name) + } + + fn metadata() -> &'static dyn Provider { + T::metadata() + } + + fn metadata_type_usage() -> String { + let nested_type = metadata_nested_type_usage::(); + + format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "Optional", nested_type) + } + } +} + +unsafe impl ReprC for OptionCLayout { + type CLayout = T::CLayout; + + fn is_valid(it: &'_ Self::CLayout) -> bool { + T::is_valid(it) + } +} + +#[cfg(feature = "js")] +const _: () = { + use crate::js::*; + + impl ReprNapi for OptionCLayout { + type NapiValue = T::NapiValue; + + fn to_napi_value( + self: Self, + env: &'_ Env, + ) -> Result { + T::to_napi_value(self.wrappedCLayout, env) + } + + fn from_napi_value( + env: &'_ Env, + napi_value: Self::NapiValue, + ) -> Result { + T::from_napi_value(env, napi_value).map(|wrapped| OptionCLayout { + wrappedCLayout: wrapped, + }) + } + } +}; + #[cfg_attr(rustfmt, rustfmt::skip)] macro_rules! unsafe_impls {( $( diff --git a/src/proc_macro/_mod.rs b/src/proc_macro/_mod.rs index 0fd5b3f95a..16a1a691b7 100644 --- a/src/proc_macro/_mod.rs +++ b/src/proc_macro/_mod.rs @@ -164,3 +164,11 @@ fn respan( }) .collect() } + +#[proc_macro_attribute] +pub fn ffi_metadata( + _attrs: TokenStream, + input: TokenStream, +) -> TokenStream { + input +} diff --git a/src/proc_macro/derives/c_type/struct_.rs b/src/proc_macro/derives/c_type/struct_.rs index f146c0a44f..bf3c65cf62 100644 --- a/src/proc_macro/derives/c_type/struct_.rs +++ b/src/proc_macro/derives/c_type/struct_.rs @@ -45,18 +45,16 @@ pub(crate) fn derive( // invoke the legacy `CType!` macro which is the one currently featuring // the js FFI glue generating logic. let (params, bounds) = generics.my_split(); - ret.extend(quote!( - ::safer_ffi::layout::CType! { - #[repr(C, js)] - #pub_ - struct #StructName - [#params] - where { - #(#bounds ,)* - } - #fields + ret.extend(quote!(::safer_ffi::layout::CType! { + #[repr(C, js)] + #pub_ + struct #StructName + [#params] + where { + #(#bounds ,)* } - )) + #fields + })) } let mut impl_body = quote!( @@ -107,6 +105,58 @@ pub(crate) fn derive( }) })?; + let ffi_metadata = attrs + .iter() + .find(|attr| attr.path().is_ident("ffi_metadata")); + + if let Some(ffi_metadata) = ffi_metadata { + let ptr_type = fields + .iter() + .find(|field| field.ident.as_ref().map_or(false, |ident| ident == "ptr")) + .map(|field| &field.ty) + .ok_or_else(|| { + syn::Error::new_spanned( + ffi_metadata, + "Struct annotated with ffi_metadata attribute does not have field 'ptr'.", + ) + })?; + + let result = ffi_metadata.parse_args::(); + + if let Some(kind) = result.ok() { + let kind_string = kind.to_string(); + + impl_body.extend(quote_spanned!(Span::mixed_site()=> + fn metadata_type_usage() -> String { + let nested_type = <#ptr_type as #CType>::metadata_type_usage(); + + let indented_nested_type = nested_type + .lines() + .map(|line| format!(" {}", line)) + .collect::>() + .join("\n"); + + format!( + "\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"type\": {{\n{}\n}}", + #kind_string, + Self::short_name(), + indented_nested_type, + ) + } + )); + } else { + bail!("Failed to parse ffi_metadata attribute."); + } + } else { + impl_body.extend(quote_spanned!(Span::mixed_site()=> + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Struct", Self::short_name()) + } + )); + } + + let is_built_in_struct = ffi_metadata.is_some(); + impl_body.extend(quote_spanned!(Span::mixed_site()=> #[allow(nonstandard_style)] fn define_self__impl ( @@ -117,6 +167,10 @@ pub(crate) fn derive( #( < #EachFieldTy as #CType >::define_self(language, definer)?; )* + if #is_built_in_struct && !language.must_declare_built_in_types() { + return Ok(()) + } + language.declare_struct( language, definer, @@ -213,7 +267,7 @@ pub(crate) fn derive_transparent( definer: &'_ mut dyn #ඞ::Definer, ) -> #ඞ::io::Result<()> { - ::core::unimplemented!("directly handled in `define_self()`"); + #ඞ::unimplemented!("directly handled in `define_self()`"); } fn define_self ( @@ -258,6 +312,10 @@ pub(crate) fn derive_transparent( Ok(()) } + fn metadata_type_usage() -> String { + <#CFieldTy as #ඞ::CType>::metadata_type_usage() + } + fn name ( language: &'_ dyn #ඞ::HeaderLanguage, ) -> #ඞ::String @@ -334,7 +392,7 @@ pub(crate) fn derive_transparent( ) -> #js::Result { let inner = <#CFieldTy as #js::ReprNapi>::from_napi_value(env, napi_value)?; - #js::Result::Ok(unsafe { #ඞ::core::mem::transmute::<#CFieldTy, Self>(inner) }) + #js::Result::Ok(unsafe { #ඞ::mem::transmute::<#CFieldTy, Self>(inner) }) } } )); diff --git a/src/proc_macro/derives/repr_c/enum_.rs b/src/proc_macro/derives/repr_c/enum_.rs index 2f9ef7e3bf..590b528f99 100644 --- a/src/proc_macro/derives/repr_c/enum_.rs +++ b/src/proc_macro/derives/repr_c/enum_.rs @@ -166,6 +166,10 @@ pub(crate) fn derive( &[#(#each_enum_variant),*], ) } + + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Enum", Self::short_name()) + } )); } diff --git a/src/proc_macro/derives/repr_c/struct_.rs b/src/proc_macro/derives/repr_c/struct_.rs index f9725b87d2..79852d435f 100644 --- a/src/proc_macro/derives/repr_c/struct_.rs +++ b/src/proc_macro/derives/repr_c/struct_.rs @@ -76,12 +76,18 @@ pub(crate) fn derive( .cloned() .chain([ parse_quote!( - #[allow(nonstandard_style)] - ), + #[allow(nonstandard_style)] + ), parse_quote!( - #[repr(C)] - ), + #[repr(C)] + ), ]) + .chain( + attrs + .iter() + .filter(|a| a.path().is_ident("ffi_metadata")) + .cloned(), + ) .collect(), vis: { let pub_ = crate::respan( @@ -220,8 +226,8 @@ pub(crate) fn derive_transparent( let ref impl_generics = generics.clone().also(|g| { g.make_where_clause().predicates.push(parse_quote!( - #FieldTy : #ඞ::ReprC - )); + #FieldTy : #ඞ::ReprC + )); }); let (intro_generics, fwd_generics, where_clauses) = impl_generics.split_for_impl(); @@ -237,11 +243,11 @@ pub(crate) fn derive_transparent( .cloned() .chain([ parse_quote!( - #[repr(transparent)] - ), + #[repr(transparent)] + ), parse_quote!( - #[allow(nonstandard_style)] - ), + #[allow(nonstandard_style)] + ), ]) .collect(), vis: { @@ -256,7 +262,7 @@ pub(crate) fn derive_transparent( generics: impl_generics.clone(), fields: Fields::Unnamed(parse_quote!(( #ඞ::CLayoutOf<#FieldTy>, - #ඞ::CLayoutOf<::core::marker::PhantomData &mut Self>>, + #ඞ::CLayoutOf<#ඞ::PhantomData &mut Self>>, ))), semi_token: Some(parse_quote!( ; @@ -434,7 +440,7 @@ pub(crate) fn derive_opaque( #[cfg(feature = "headers")] let header_generation = { drop(header_generation); - let ref short_name: Quote![ String ] = match args.rename { + let ref short_name: Quote![String] = match args.rename { | Some(string_expr) => quote!( #ඞ::From::from(#string_expr) ), @@ -476,6 +482,10 @@ pub(crate) fn derive_opaque( &#ඞ::PhantomData::, ) } + + fn metadata_type_usage() -> String { + format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name()) + } ) }; diff --git a/src/proc_macro/ffi_export/const_.rs b/src/proc_macro/ffi_export/const_.rs index d4bfecf76f..ba511a8127 100644 --- a/src/proc_macro/ffi_export/const_.rs +++ b/src/proc_macro/ffi_export/const_.rs @@ -82,6 +82,7 @@ pub(super) fn handle( | Language::C => &languages::C, | Language::CSharp => &languages::CSharp, | Language::Lua => &languages::Lua, + | Language::Metadata => &languages::Metadata, $($($if_cfg_python)? | Language::Python => &languages::Python, )? diff --git a/src/proc_macro/utils/_mod.rs b/src/proc_macro/utils/_mod.rs index 66cdf6ce9e..6872e5e224 100644 --- a/src/proc_macro/utils/_mod.rs +++ b/src/proc_macro/utils/_mod.rs @@ -171,6 +171,7 @@ pub(crate) fn compile_warning( #[allow(nonstandard_style)] struct safer_ffi_ { #[deprecated(note = #message)] + #[allow(dead_code)] #warning: () } // fst lst diff --git a/src/slice.rs b/src/slice.rs index df9431750e..b472ad9310 100644 --- a/src/slice.rs +++ b/src/slice.rs @@ -2,6 +2,7 @@ use_prelude!(); use ::core::slice; +use safer_ffi_proc_macros::ffi_metadata; #[doc(no_inline)] pub use self::slice_mut as Mut; @@ -17,6 +18,7 @@ type PhantomCovariantLifetime<'lt> = PhantomData<&'lt ()>; ReprC! { #[repr(C, js)] + #[ffi_metadata(DynamicArray)] /// Like [`slice_ref`] and [`slice_mut`], but with any lifetime attached /// whatsoever. /// @@ -83,6 +85,7 @@ impl slice_raw { cfg_alloc! { ReprC! { #[repr(C, js)] + #[ffi_metadata(DynamicArray)] #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "alloc")))] /// [`Box`][`rust::Box`]`<[T]>` (fat pointer to a slice), /// but with a guaranteed `#[repr(C)]` layout. @@ -247,6 +250,7 @@ cfg_alloc! { ReprC! { #[repr(C, js)] + #[ffi_metadata(DynamicArray)] /// `&'lt [T]` but with a guaranteed `#[repr(C)]` layout. /// /// # C layout (for some given type T) @@ -341,6 +345,7 @@ impl<'lt, T: 'lt> From> for slice_raw { ReprC! { #[repr(C)] + #[ffi_metadata(DynamicArray)] /// `&'lt mut [T]` but with a guaranteed `#[repr(C)]` layout. /// /// # C layout (for some given type T) diff --git a/src/tuple.rs b/src/tuple.rs index 32a49fe30d..194e206962 100644 --- a/src/tuple.rs +++ b/src/tuple.rs @@ -31,6 +31,10 @@ unsafe impl CType for CVoid { Ok(()) } + fn metadata_type_usage() -> String { + r#""kind": "void""#.into() + } + fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, diff --git a/src/utils/macros.rs b/src/utils/macros.rs index 2fc6313cf8..54c0b55e15 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -114,6 +114,7 @@ macro_rules! const_assert { [$($($pre:tt)+)?] => [$($post:tt)*] ) => ( const _: () = { + #[allow(dead_code)] fn check<$($generics)*> () where $($($pre)+)? diff --git a/src/utils/markers.rs b/src/utils/markers.rs index c737da3590..3518ce6606 100644 --- a/src/utils/markers.rs +++ b/src/utils/markers.rs @@ -1,10 +1,12 @@ #![allow(missing_debug_implementations)] #[cfg_attr(feature = "stabby", stabby::stabby)] +#[repr(transparent)] #[derive(Default, Clone, Copy)] pub struct PhantomCovariantLifetime<'lt>(pub ::core::marker::PhantomData<&'lt ()>); #[cfg_attr(feature = "stabby", stabby::stabby)] +#[repr(transparent)] pub struct PhantomInvariant(pub ::core::marker::PhantomData &T>); impl Default for PhantomInvariant { diff --git a/src/vec.rs b/src/vec.rs index 69fcff0a7d..349cbb1b1b 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,10 +1,12 @@ use_prelude!(); use ::core::slice; +use safer_ffi_proc_macros::ffi_metadata; use crate::slice::*; ReprC! { #[repr(C)] + #[ffi_metadata(Vector)] #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "alloc")))] /// Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout pub diff --git a/tests/test_kotlin.rs b/tests/test_kotlin.rs new file mode 100644 index 0000000000..49fc764f43 --- /dev/null +++ b/tests/test_kotlin.rs @@ -0,0 +1,122 @@ +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt::skip)] +#![allow(unused_imports)] + +#[macro_use] +extern crate macro_rules_attribute; + +use ::std::{ + collections::HashSet as Set, + convert::TryInto, + io, + ptr, + ops::Not as _, +}; +use ::safer_ffi::{ + closure::*, + prelude::*, + layout::{ + CType, + ReprC, + derive_ReprC, + }, + tuple::Tuple2, +}; + +#[derive_ReprC] +#[cfg_attr(feature = "stabby", stabby::stabby)] +#[repr(C)] +pub struct SomeStruct { + a: Option>, +} + +#[derive_ReprC] +#[repr(opaque)] +pub struct SomeOpaqueStruct { + _a: Option>, +} + +#[derive_ReprC(rename = "dittoffi_result")] +#[repr(C, js)] +pub struct FfiResult { + /// Non-`NULL` pointer to opaque object on error, `NULL` otherwise. + pub error: Option>, + + /// When no error occurred, the success value payload can be retrieved here. + /// + /// Otherwise, the value is to be ignored. + pub success: Option>, + foo: Ok::CLayout, +} + +#[derive_ReprC(rename = "RenamedStruct")] +#[repr(transparent)] +pub struct TransparentStruct { + pub i: BasicEnum +} + +#[ffi_export] +/// Some comment +/// Some comment 2 +/// Some comment 3 +pub unsafe fn free_vec ( + _optional_parameter: Option>, + _required_parameter: repr_c::Box, + _foo: FfiResult>, + _optional_foo: FfiResult>>, + _bar: repr_c::Box, + _be: BasicEnum, + _ed: EnumWithExplicitDiscriminant, + _static_array: [u64; 16], + _dynamic_array: repr_c::Box<[u64]>, + _vector: repr_c::Vec, + _transparent: TransparentStruct, + _string: char_p::Ref<'_>, + _raw_pointer: *mut i32, +) { +} + +#[ffi_export] +/// Some comment +/// Some comment 2 +/// Some comment 3 +pub const SOME_CONSTANT: u32 = 4 * 1024; + +#[ffi_export(untyped)] +pub const SOME_STRING: &str = "SOME_STRING"; + +#[ffi_export(untyped)] +pub const SOME_INT: u32 = 1; + +#[ffi_export(untyped)] +pub const SOME_DOUBLE: f64 = 1.0; + +#[derive_ReprC] +#[repr(u8)] +/// Some comment +/// Some comment 2 +/// Some comment 3 +pub enum BasicEnum { + True, + False, +} + +#[derive_ReprC] +#[repr(C)] +pub enum EnumWithExplicitDiscriminant { + // Some comment + // Some comment 2 + True = 42, + False = 43, +} + +#[cfg(feature = "headers")] +#[test] +fn test_kotlin () -> io::Result<()> {Ok({ + use ::safer_ffi::headers::Language::*; + + safer_ffi::headers::builder() + .with_language(Metadata) + .to_writer(&mut io::stderr()) + .generate()? +})} From f31cdd0d891d08259dc5f8e3a6810b74e98ede66 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 13:39:43 +0100 Subject: [PATCH 2/6] post-cherry-pick cleanup Change-Id: I75dca2010b76ffb321739f6da1e3933d6a6a6964 JJ-Change-Id: sumnpxzyzostkkowxyswqktmpylwqwwm --- Cargo.lock | 36 ++- Cargo.toml | 2 + ffi_tests/Cargo.lock | 36 ++- js_tests/Cargo.lock | 36 ++- js_tests/src/lib.rs | 6 +- src/c_char.rs | 12 +- src/headers/languages/c.rs | 53 ++-- src/headers/languages/metadata.rs | 11 +- src/headers/languages/mod.rs | 7 +- src/js/closures/node_js.rs | 44 +-- src/js/ffi_helpers.rs | 22 +- src/layout/_mod.rs | 10 +- src/layout/impls.rs | 328 +++++++++++++---------- src/layout/macros.rs | 2 +- src/layout/niche.rs | 15 +- src/proc_macro/_mod.rs | 8 - src/proc_macro/derives/_mod.rs | 6 +- src/proc_macro/derives/c_type/_mod.rs | 4 +- src/proc_macro/derives/c_type/struct_.rs | 99 +++++-- src/proc_macro/derives/repr_c/enum_.rs | 11 +- src/proc_macro/derives/repr_c/struct_.rs | 15 +- src/proc_macro/utils/_mod.rs | 6 + src/ptr.rs | 22 ++ src/slice.rs | 69 +++-- src/tuple.rs | 12 +- src/vec.rs | 23 +- 26 files changed, 559 insertions(+), 336 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3dc39429c3..b036c57565 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,7 +90,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" dependencies = [ - "ext-trait-proc_macros", + "ext-trait-proc_macros 1.0.1", +] + +[[package]] +name = "ext-trait" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c24fe28375ffabb5479233d60a5d99930a3983ed3aa6db66dd03b830fc41b2" +dependencies = [ + "ext-trait-proc_macros 2.0.1", ] [[package]] @@ -104,13 +113,33 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ext-trait-proc_macros" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad551ddce9af58215158c84e1e655b2011f6355b655c13b56d88986b14d3db98" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "extension-traits" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" dependencies = [ - "ext-trait", + "ext-trait 1.0.1", +] + +[[package]] +name = "extension-traits" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fea67d50388b3db0e51e65815ed7293703607ff9dc50d86f93e1abcc67b572" +dependencies = [ + "ext-trait 2.0.1", ] [[package]] @@ -701,6 +730,7 @@ version = "0.2.0-alpha.0" dependencies = [ "async-compat", "cratesio-placeholder-package", + "extension-traits 2.0.1", "extern-c", "futures", "inventory", @@ -985,7 +1015,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" dependencies = [ - "extension-traits", + "extension-traits 1.0.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d5c56ef6b0..5feb66df5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,8 @@ async-compat.version = "0.2.1" extern-c.version = "0.1.0" +extension-traits.version = "2.0.1" + futures.optional = true futures.version = "0.3.24" diff --git a/ffi_tests/Cargo.lock b/ffi_tests/Cargo.lock index a548751909..22bc6265f3 100644 --- a/ffi_tests/Cargo.lock +++ b/ffi_tests/Cargo.lock @@ -26,7 +26,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" dependencies = [ - "ext-trait-proc_macros", + "ext-trait-proc_macros 1.0.1", +] + +[[package]] +name = "ext-trait" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c24fe28375ffabb5479233d60a5d99930a3983ed3aa6db66dd03b830fc41b2" +dependencies = [ + "ext-trait-proc_macros 2.0.1", ] [[package]] @@ -40,13 +49,33 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ext-trait-proc_macros" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad551ddce9af58215158c84e1e655b2011f6355b655c13b56d88986b14d3db98" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "extension-traits" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" dependencies = [ - "ext-trait", + "ext-trait 1.0.1", +] + +[[package]] +name = "extension-traits" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fea67d50388b3db0e51e65815ed7293703607ff9dc50d86f93e1abcc67b572" +dependencies = [ + "ext-trait 2.0.1", ] [[package]] @@ -332,6 +361,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" name = "safer-ffi" version = "0.2.0-alpha.0" dependencies = [ + "extension-traits 2.0.1", "extern-c", "futures", "inventory", @@ -492,7 +522,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" dependencies = [ - "extension-traits", + "extension-traits 1.0.1", ] [[package]] diff --git a/js_tests/Cargo.lock b/js_tests/Cargo.lock index 27b1a72bbf..da40443afc 100644 --- a/js_tests/Cargo.lock +++ b/js_tests/Cargo.lock @@ -78,7 +78,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" dependencies = [ - "ext-trait-proc_macros", + "ext-trait-proc_macros 1.0.1", +] + +[[package]] +name = "ext-trait" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c24fe28375ffabb5479233d60a5d99930a3983ed3aa6db66dd03b830fc41b2" +dependencies = [ + "ext-trait-proc_macros 2.0.1", ] [[package]] @@ -92,13 +101,33 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ext-trait-proc_macros" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad551ddce9af58215158c84e1e655b2011f6355b655c13b56d88986b14d3db98" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "extension-traits" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" dependencies = [ - "ext-trait", + "ext-trait 1.0.1", +] + +[[package]] +name = "extension-traits" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fea67d50388b3db0e51e65815ed7293703607ff9dc50d86f93e1abcc67b572" +dependencies = [ + "ext-trait 2.0.1", ] [[package]] @@ -645,6 +674,7 @@ name = "safer-ffi" version = "0.2.0-alpha.0" dependencies = [ "cratesio-placeholder-package", + "extension-traits 2.0.1", "extern-c", "inventory", "libc", @@ -907,7 +937,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" dependencies = [ - "extension-traits", + "extension-traits 1.0.1", ] [[package]] diff --git a/js_tests/src/lib.rs b/js_tests/src/lib.rs index 8da582060d..0cfa971269 100644 --- a/js_tests/src/lib.rs +++ b/js_tests/src/lib.rs @@ -193,7 +193,11 @@ const _: () = { unsafe { call( data, - ::std::mem::transmute(c!("Hello, World!").to_str().as_ptr()), + c!("Hello, World!") + .to_str_with_null() + .as_ptr() + .cast::<::safer_ffi::c_char>() + .into(), ); } ctx.env.get_undefined() diff --git a/src/c_char.rs b/src/c_char.rs index 6a7f9349e1..4a15f02b0a 100644 --- a/src/c_char.rs +++ b/src/c_char.rs @@ -1,3 +1,7 @@ +__cfg_headers__! { + use crate::headers::languages::MetadataTypeData; +} + use_prelude!(); /// A `ReprC` _standalone_ type with the same layout and ABI as @@ -45,8 +49,12 @@ unsafe impl CType for c_char { Ok(()) } - fn metadata_type_usage() -> String { - r#""kind": "char""#.into() + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + MetadataTypeData(r#""kind": "char""#.into()) + }); + }) } fn render( diff --git a/src/headers/languages/c.rs b/src/headers/languages/c.rs index bae23995dc..864bdd74c7 100644 --- a/src/headers/languages/c.rs +++ b/src/headers/languages/c.rs @@ -27,7 +27,33 @@ impl HeaderLanguage for C { } fn supports_type_aliases(self: &'_ C) -> Option<&'_ dyn HeaderLanguageSupportingTypeAliases> { - Some(self) + return Some(self); + // where + #[expect(non_local_definitions)] + impl HeaderLanguageSupportingTypeAliases for C { + fn declare_type_alias( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + self_ty: &'_ dyn PhantomCType, + inner_ty: &'_ dyn PhantomCType, + ) -> io::Result<()> { + // No `this` in this design yet; let's stick to `this` nonetheless + // for the syntactical search for the `self` antipattern. + let this = self; + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, ctx.out()); + this.emit_docs(ctx, docs, indent)?; + let ref aliaser = self_ty.name(this); + let ref aliasee = inner_ty.name(this); + out!(( + "typedef {aliasee} {aliaser};" + )); + + out!("\n"); + Ok(()) + } + } } fn declare_simple_enum( @@ -404,28 +430,3 @@ impl HeaderLanguage for C { Ok(()) } } - -impl HeaderLanguageSupportingTypeAliases for C { - fn declare_type_alias( - self: &'_ Self, - ctx: &'_ mut dyn Definer, - docs: Docs<'_>, - self_ty: &'_ dyn PhantomCType, - inner_ty: &'_ dyn PhantomCType, - ) -> io::Result<()> { - // No `this` in this design yet; let's stick to `this` nonetheless - // for the syntactical search for the `self` antipattern. - let this = self; - let ref indent = Indentation::new(4 /* ctx.indent_width() */); - mk_out!(indent, ctx.out()); - this.emit_docs(ctx, docs, indent)?; - let ref aliaser = self_ty.name(this); - let ref aliasee = inner_ty.name(this); - out!(( - "typedef {aliasee} {aliaser};" - )); - - out!("\n"); - Ok(()) - } -} diff --git a/src/headers/languages/metadata.rs b/src/headers/languages/metadata.rs index 4976a736da..db1c57de99 100644 --- a/src/headers/languages/metadata.rs +++ b/src/headers/languages/metadata.rs @@ -8,6 +8,8 @@ use std::io::Write; pub struct Metadata; +pub struct MetadataTypeData(pub String); + impl io::Result<()>> DisplayFromFn { fn indented_lines(&self) -> String { self.to_string().lines().map(|line| format!(" {line}\n")).collect() @@ -442,7 +444,6 @@ impl HeaderLanguage for Metadata { } impl Metadata { - fn emit_type_usage( self: &'_ Self, _this: &dyn HeaderLanguage, @@ -456,10 +457,10 @@ impl Metadata { out!((r#""{field_name}": {{"#)); if let _ = indent.scope() { - let type_usage = ty.metadata_type_usage(); - - for line in type_usage.lines() { - out!(("{line}")); + if let Some(MetadataTypeData(type_usage)) = ty.metadata().dyn_request() { + for line in type_usage.lines() { + out!(("{line}")); + } } } diff --git a/src/headers/languages/mod.rs b/src/headers/languages/mod.rs index 53e76f6d69..7f2c63eb61 100644 --- a/src/headers/languages/mod.rs +++ b/src/headers/languages/mod.rs @@ -27,6 +27,7 @@ pub use lua::Lua; mod lua; pub use metadata::Metadata; +pub use metadata::MetadataTypeData; mod metadata; pub struct Indentation { @@ -302,8 +303,6 @@ pub trait PhantomCType { fn metadata(self: &'_ Self) -> &'static dyn Provider; - fn metadata_type_usage(self: &'_ Self) -> String; - fn size(self: &'_ Self) -> usize; fn align(self: &'_ Self) -> usize; @@ -353,10 +352,6 @@ where T::metadata() } - fn metadata_type_usage(self: &'_ Self) -> String { - T::metadata_type_usage() - } - fn size(self: &'_ Self) -> usize { ::core::mem::size_of::() } diff --git a/src/js/closures/node_js.rs b/src/js/closures/node_js.rs index 8e2155aab7..3908b5acc3 100644 --- a/src/js/closures/node_js.rs +++ b/src/js/closures/node_js.rs @@ -80,27 +80,6 @@ mod safety_boundary { unsafe impl Send for ThreadTiedJsFunction {} unsafe impl Sync for ThreadTiedJsFunction {} - impl Drop for ThreadTiedJsFunction { - fn drop(self: &'_ mut ThreadTiedJsFunction) { - // Note: since Self is `Send`, - // this may be called in a non-Node.js thread. - // It appears the ref-counting functions are thread-safe. - let Self { - ref env, - raw_ref_handle, - .. - } = *self; - unsafe { - /* Decrementing the ref-count before destroying it does - * not seem to be necessary. */ - // ::napi::sys::napi_reference_unref( - // env.raw(), raw_ref_handle, &mut 0, - // ); - let _ignored_status = ::napi::sys::napi_delete_reference(env.raw(), raw_ref_handle); - } - } - } - impl ThreadTiedJsFunction { pub fn new( func: &'_ JsFunction, @@ -120,6 +99,29 @@ mod safety_boundary { ); } + #[expect(non_local_definitions)] + impl Drop for ThreadTiedJsFunction { + fn drop(self: &'_ mut ThreadTiedJsFunction) { + // Note: since Self is `Send`, + // this may be called in a non-Node.js thread. + // It appears the ref-counting functions are thread-safe. + let Self { + ref env, + raw_ref_handle, + .. + } = *self; + unsafe { + /* Decrementing the ref-count before destroying it does + * not seem to be necessary. */ + // ::napi::sys::napi_reference_unref( + // env.raw(), raw_ref_handle, &mut 0, + // ); + let _ignored_status = + ::napi::sys::napi_delete_reference(env.raw(), raw_ref_handle); + } + } + } + Self { env, raw_ref_handle, diff --git a/src/js/ffi_helpers.rs b/src/js/ffi_helpers.rs index b2113873c8..766789a67f 100644 --- a/src/js/ffi_helpers.rs +++ b/src/js/ffi_helpers.rs @@ -110,7 +110,10 @@ pub fn with_js_buffer_as_slice_uint8_t_ref( | _case if matches!(fst.get_type(), Ok(ValueType::Null)) => { cb.call(None, &[ReprNapi::to_napi_value( crate::slice::slice_raw_Layout:: { - ptr: <*mut u8>::into(NULL!()), + // FIXME: how are we ending up in a situation with a "non-null" `0`!?? + // Semantics of `slice_raw` need clarification. + // Or rather, we'll probably need to introduce `option_slice_raw` or smth. + ptr: crate::ptr::NonNullPtrCLayout(0 as _), len: 0xbad000, }, ctx.env, @@ -133,7 +136,7 @@ pub fn char_p_boxed_to_js_string( ctx.env.get_null()?.into_unknown() } else { let p: crate::prelude::char_p::Box = unsafe { - crate::layout::from_raw_unchecked(crate::layout::impls::NonNullCLayout::new(p)) + crate::layout::from_raw_unchecked(crate::ptr::NonNullPtrCLayout::from(p)) }; ctx.env.create_string(p.to_str())?.into_unknown() } @@ -179,7 +182,7 @@ pub fn char_p_ref_to_js_string( ctx.env.get_null()?.into_unknown() } else { let p: crate::prelude::char_p::Ref<'_> = unsafe { - crate::layout::from_raw_unchecked(crate::layout::impls::NonNullCLayout::new(p)) + crate::layout::from_raw_unchecked(crate::ptr::NonNullPtrCLayout::from(p)) }; ctx.env.create_string(p.to_str())?.into_unknown() } @@ -289,7 +292,8 @@ pub fn with_out_byte_slice(cb: JsFunction) -> Result { let ctx = ::safer_ffi::js::derive::__js_ctx!(); let ty = &"slice_boxed_uint8_t"; let mut v = crate::slice::slice_ref_Layout::<()> { - ptr: <*const crate::CVoid>::into(NULL!()), + // Same FIXME as for `with_js_buffer_as_slice_uint8_t_ref` + ptr: crate::ptr::NonNullPtrCLayout(0 as _), len: 0, _lt: unsafe { ::core::mem::transmute(()) }, }; @@ -300,10 +304,7 @@ pub fn with_out_byte_slice(cb: JsFunction) -> Result { &format!("{} *", ty), )?])?; let mut v_js = ctx.env.create_object()?; - v_js.set_named_property( - "ptr", - wrap_ptr(ctx.env, v.ptr.wrappedCLayout as _, "uint8_t *")?, - )?; + v_js.set_named_property("ptr", wrap_ptr(ctx.env, v.ptr.0 as _, "uint8_t *")?)?; v_js.set_named_property("len", ReprNapi::to_napi_value(v.len as usize, ctx.env)?)?; v_js.into_unknown() }) @@ -320,7 +321,8 @@ pub fn with_out_vec_of_ptrs( let ref vec_ty: String = vec_ty.into_utf8()?.into_owned()?; let ref ty: String = ty.into_utf8()?.into_owned()?; let mut v = crate::vec::Vec_Layout::<()> { - ptr: <*mut crate::CVoid>::into(NULL!()), + // Same FIXME remarks as for `with_js_buffer_as_slice_uint8_t_ref` + ptr: crate::ptr::NonNullPtrCLayout(0 as _), len: 0, cap: 0, }; @@ -333,7 +335,7 @@ pub fn with_out_vec_of_ptrs( let mut v_js = ctx.env.create_object()?; v_js.set_named_property( "ptr", - wrap_ptr(ctx.env, v.ptr.wrappedCLayout.cast(), &format!("{} *", ty))?, + wrap_ptr(ctx.env, v.ptr.0.cast(), &format!("{} *", ty))?, )?; v_js.set_named_property("len", ReprNapi::to_napi_value(v.len as usize, ctx.env)?)?; v_js.set_named_property("cap", ReprNapi::to_napi_value(v.cap as usize, ctx.env)?)?; diff --git a/src/layout/_mod.rs b/src/layout/_mod.rs index f78c84752e..0b6310a782 100644 --- a/src/layout/_mod.rs +++ b/src/layout/_mod.rs @@ -141,12 +141,9 @@ pub unsafe trait CType: Sized + Copy { /// "Foo".into() /// } /// - /// #[::safer_ffi::cfg_headers] - /// fn metadata_type_usage() -> String { - /// String::new() - /// } - /// /// type OPAQUE_KIND = OpaqueKind::Concrete; + /// + /// // ... /// } /// ``` #[allow(nonstandard_style)] @@ -350,9 +347,6 @@ pub unsafe trait CType: Sized + Copy { fn metadata() -> &'static dyn Provider { &None } - - #[apply(__cfg_headers__!)] - fn metadata_type_usage() -> String; } /// The meat of the crate. _The_ trait. diff --git a/src/layout/impls.rs b/src/layout/impls.rs index 1f401d59e6..f1e1ebddfc 100644 --- a/src/layout/impls.rs +++ b/src/layout/impls.rs @@ -1,9 +1,11 @@ use super::*; +use crate::ptr::NonNullPtrCLayout; __cfg_headers__! { use crate::headers::languages::{ CSharpMarshaler, FunctionArg, + MetadataTypeData, }; } @@ -191,34 +193,6 @@ const _: () = { Self::render_wrapping_var(out, language, None) } - fn metadata_type_usage() -> String { - let return_type = metadata_nested_type_usage::(); - - #[allow(unused_mut)] - let mut value_parameters = String::new(); - - $( - let n_type = metadata_n_nested_type_usage::<$An>(2); - value_parameters.push_str("\n {\n"); - value_parameters.push_str(&n_type); - - $( - let i_type = metadata_n_nested_type_usage::<$Ai>(2); - value_parameters.push_str("\n },\n {\n"); - value_parameters.push_str(&i_type); - )* - - value_parameters.push_str("\n }\n"); - )? - - format!( - "\"kind\": \"{}\",\n\"valueParameters\": [{}],\n\"returnType\": {{\n{}\n}}", - "Function", - value_parameters, - return_type, - ) - } - fn render_wrapping_var( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -254,6 +228,33 @@ const _: () = { // `UnmanagedFunctionPointer` attribute. CSharpMarshaler("UnmanagedType.FunctionPtr".into()) }); + request.give_if_requested::(|| { + let return_type = metadata_nested_type_usage::(); + + #[allow(unused_mut)] + let mut value_parameters = String::new(); + + $( + let n_type = metadata_n_nested_type_usage::<$An>(2); + value_parameters.push_str("\n {\n"); + value_parameters.push_str(&n_type); + + $( + let i_type = metadata_n_nested_type_usage::<$Ai>(2); + value_parameters.push_str("\n },\n {\n"); + value_parameters.push_str(&i_type); + )* + + value_parameters.push_str("\n }\n"); + )? + + MetadataTypeData(format!( + "\"kind\": \"{}\",\n\"valueParameters\": [{}],\n\"returnType\": {{\n{}\n}}", + "Function", + value_parameters, + return_type, + )) + }); }) } } @@ -393,8 +394,14 @@ const _: () = { ) } - fn metadata_type_usage() -> String { - format!(r#""kind": "{}""#, stringify!($RustInt)) + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request + .give_if_requested::(|| { + MetadataTypeData(format!(r#""kind": "{}""#, stringify!($RustInt))) + }) + ; + }) } fn render( @@ -449,8 +456,12 @@ const _: () = { Ok(()) } - fn metadata_type_usage() -> String { - format!(r#""kind": "{}""#, stringify!($fN)) + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + MetadataTypeData(format!(r#""kind": "{}""#, stringify!($fN))) + }); + }) } fn render( @@ -497,15 +508,19 @@ const _: () = { T::define_self(language, definer) } - fn metadata_type_usage() -> String { - let nested_type = metadata_nested_type_usage::(); - - format!( - "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", - "Pointer", - "false", - nested_type, - ) + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + let nested_type = metadata_nested_type_usage::(); + + MetadataTypeData(format!( + "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", + "Pointer", + "false", + nested_type, + )) + }); + }) } fn render( @@ -558,15 +573,19 @@ const _: () = { T::define_self(language, definer) } - fn metadata_type_usage() -> String { - let nested_type = metadata_nested_type_usage::(); - - format!( - "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", - "Pointer", - "true", - nested_type, - ) + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + let nested_type = metadata_nested_type_usage::(); + + MetadataTypeData(format!( + "\"kind\": \"{}\",\n\"isMutable\": {},\n\"type\": {{\n{}\n}}", + "Pointer", + "true", + nested_type, + )) + }); + }) } fn render( @@ -738,10 +757,6 @@ unsafe impl CType for Bool { ) } - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\"", "bool") - } - fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -756,9 +771,14 @@ unsafe impl CType for Bool { fn metadata() -> &'static dyn Provider { &provide_with(|request| { - request.give_if_requested::(|| { - CSharpMarshaler("UnmanagedType.U1") - }); + request + .give_if_requested::(|| { + CSharpMarshaler("UnmanagedType.U1") + }) + .give_if_requested::(|| { + MetadataTypeData(format!("\"kind\": \"{}\"", "bool")) + }) + ; }) } } @@ -802,10 +822,6 @@ unsafe impl CType for c_int { Ok(()) } - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\"", "int") - } - fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, @@ -817,36 +833,38 @@ unsafe impl CType for c_int { signed: true, bitwidth: primitives::IntBitWidth::CInt, }, - ) + )?; + Ok(()) } fn metadata() -> &'static dyn Provider { &provide_with(|request| { - request.give_if_requested::(|| { - CSharpMarshaler("UnmanagedType.SysInt") - }); + request + .give_if_requested::(|| { + CSharpMarshaler("UnmanagedType.SysInt") + }) + .give_if_requested::(|| { + MetadataTypeData(format!("\"kind\": \"{}\"", "int")) + }) + ; }) } } } -#[derive(Debug, Clone, Copy)] -pub struct NonNullCLayout { - pub(crate) wrappedCLayout: T, -} - -#[cfg(feature = "js")] -impl NonNullCLayout { +impl From<*const T> for NonNullPtrCLayout<*const T> { #[inline] - pub(crate) fn new(wrappedCLayout: T) -> Self { - NonNullCLayout { wrappedCLayout } + fn from(ptr: *const T) -> Self { + debug_assert!(ptr.is_null().not()); + NonNullPtrCLayout(ptr) } } -impl From for NonNullCLayout { +impl From<*mut T> for NonNullPtrCLayout<*mut T> { #[inline] - fn from(wrappedCLayout: T) -> Self { - NonNullCLayout { wrappedCLayout } + fn from(ptr: *mut T) -> Self { + debug_assert!(ptr.is_null().not()); + NonNullPtrCLayout(ptr) } } @@ -854,40 +872,54 @@ impl From for NonNullCLayout { const _: () = { use crate::js::*; - impl ReprNapi for NonNullCLayout { - type NapiValue = T::NapiValue; + impl ReprNapi for NonNullPtrCLayout { + type NapiValue = Ptr::NapiValue; fn to_napi_value( self: Self, env: &'_ Env, ) -> Result { - T::to_napi_value(self.wrappedCLayout, env) + Ptr::to_napi_value(self.0, env) } fn from_napi_value( env: &'_ Env, napi_value: Self::NapiValue, ) -> Result { - T::from_napi_value(env, napi_value).map(|wrapped| NonNullCLayout { - wrappedCLayout: wrapped, - }) + Ptr::from_napi_value(env, napi_value).map(NonNullPtrCLayout) } } }; -unsafe impl CType for NonNullCLayout { - type OPAQUE_KIND = T::OPAQUE_KIND; +unsafe impl CType for NonNullPtrCLayout { + type OPAQUE_KIND = Ptr::OPAQUE_KIND; __cfg_headers__! { + // THE MAIN POINT OF THIS WHOLE WRAPPER: + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + // Note that this shadows / trumps whatever `T::metadata()` would have provided for + // `MetadataTypeData`. + request.give_if_requested::(|| { + let nested_type = metadata_nested_type_usage::(); + + MetadataTypeData(format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "NonNull", nested_type)) + }); + Ptr::metadata().provide_to(request); + }) + } + + // -- From here on, we just delegate. + fn short_name() -> String { - T::short_name() + Ptr::short_name() } fn render( out: &'_ mut dyn io::Write, language: &'_ dyn HeaderLanguage, ) -> io::Result<()> { - T::render(out, language) + Ptr::render(out, language) } fn render_wrapping_var( @@ -896,53 +928,44 @@ unsafe impl CType for NonNullCLayout { // Either a `&&str`, or a `&fmt::Arguments<'_>`, for instance. var_name: Option<&dyn ::core::fmt::Display>, ) -> io::Result<()> { - T::render_wrapping_var(out, language, var_name) + Ptr::render_wrapping_var(out, language, var_name) } fn define_self__impl(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { - T::define_self__impl(language, definer) + Ptr::define_self__impl(language, definer) } fn define_self(language: &'_ dyn HeaderLanguage, definer: &'_ mut dyn Definer) -> io::Result<()> { - T::define_self(language, definer) + Ptr::define_self(language, definer) } fn name(language: &'_ dyn HeaderLanguage) -> String { - T::name(language) + Ptr::name(language) } fn name_wrapping_var(language: &'_ dyn HeaderLanguage, var_name: Option<&dyn fmt::Display>) -> String { - T::name_wrapping_var(language, var_name) - } - - fn metadata() -> &'static dyn Provider { - T::metadata() - } - - fn metadata_type_usage() -> String { - let nested_type = metadata_nested_type_usage::(); - - format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "NonNull", nested_type) + Ptr::name_wrapping_var(language, var_name) } } } -unsafe impl ReprC for NonNullCLayout { - type CLayout = T::CLayout; +// Interestingly enough, `make -C ffi_tests` ICEs without this… +unsafe impl ReprC for NonNullPtrCLayout<*mut T> { + type CLayout = *mut T; - fn is_valid(it: &'_ Self::CLayout) -> bool { - T::is_valid(it) + fn is_valid(&ptr: &*mut T) -> bool { + ptr.is_null().not() && ptr.is_aligned() } } -impl NonNullCLayout<*mut T> { +impl NonNullPtrCLayout<*mut T> { #[inline] pub fn is_null(self) -> bool { - self.wrappedCLayout.is_null() + self.0.is_null() } pub fn as_ptr(&self) -> *const T { - self.wrappedCLayout + self.0 } pub fn align_offset( @@ -959,14 +982,23 @@ impl NonNullCLayout<*mut T> { } } -impl NonNullCLayout<*const T> { +// Interestingly enough, `make -C ffi_tests` ICEs without this… +unsafe impl ReprC for NonNullPtrCLayout<*const T> { + type CLayout = *const T; + + fn is_valid(&ptr: &*const T) -> bool { + ptr.is_null().not() && ptr.is_aligned() + } +} + +impl NonNullPtrCLayout<*const T> { #[inline] pub fn is_null(self) -> bool { - self.wrappedCLayout.is_null() + self.0.is_null() } pub fn as_ptr(&self) -> *const T { - self.wrappedCLayout + self.0 } pub fn align_offset( @@ -990,44 +1022,44 @@ impl_ReprC_for! { unsafe { @for[T : ReprC] ptr::NonNull - => |ref it: NonNullCLayout<*mut T::CLayout>| { + => |ref it: NonNullPtrCLayout<*mut T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullRef - => |ref it: NonNullCLayout<*const T::CLayout>| { + => |ref it: NonNullPtrCLayout<*const T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullMut - => |ref it: NonNullCLayout<*mut T::CLayout>| { + => |ref it: NonNullPtrCLayout<*mut T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , @for[T : ReprC] ptr::NonNullOwned - => |ref it: NonNullCLayout<*mut T::CLayout>| { + => |ref it: NonNullPtrCLayout<*mut T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , @for['a, T : 'a + ReprC] &'a T - => |ref it: NonNullCLayout<*const T::CLayout>| { + => |ref it: NonNullPtrCLayout<*const T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , @for['a, T : 'a + ReprC] &'a mut T - => |ref it: NonNullCLayout<*mut T::CLayout>| { + => |ref it: NonNullPtrCLayout<*mut T::CLayout>| { it.is_null().not() && - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + (it.0 as usize) % ::core::mem::align_of::() == 0 } , }} @@ -1037,8 +1069,8 @@ impl_ReprC_for! { unsafe { impl_ReprC_for! { unsafe { @for['out, T : 'out + Sized + ReprC] Out<'out, T> - => |ref it: NonNullCLayout<*mut T::CLayout>| { - (it.wrappedCLayout as usize) % ::core::mem::align_of::() == 0 + => |ref it: NonNullPtrCLayout<*mut T::CLayout>| { + (it.0 as usize) % ::core::mem::align_of::() == 0 }, }} @@ -1078,8 +1110,12 @@ unsafe impl CType for OpaqueLayout { ) } - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name()) + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + MetadataTypeData(format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name())) + }); + }) } } } @@ -1246,15 +1282,19 @@ unsafe impl CType for [Item; N] { ) } - fn metadata_type_usage() -> String { - let nested_type = metadata_nested_type_usage::(); + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + let nested_type = metadata_nested_type_usage::(); - format!("\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"size\": {},\n\"type\": {{\n{}\n}}", - "StaticArray", - Self::short_name() + "_t", - N, - nested_type, - ) + MetadataTypeData(format!("\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"size\": {},\n\"type\": {{\n{}\n}}", + "StaticArray", + Self::short_name() + "_t", + N, + nested_type, + )) + }); + }) } } } @@ -1277,12 +1317,14 @@ __cfg_headers__! { pub(super) fn metadata_n_nested_type_usage(nesting: usize) -> String { - let nested_type = Type::metadata_type_usage(); - - nested_type - .lines() - .map(|line| format!("{}{}", " ".repeat(nesting), line)) - .collect::>() - .join("\n") + if let Some(MetadataTypeData(nested_type)) = Type::metadata().dyn_request() { + nested_type + .lines() + .map(|line| format!("{}{}", " ".repeat(nesting), line)) + .collect::>() + .join("\n") + } else { + <_>::default() + } } } diff --git a/src/layout/macros.rs b/src/layout/macros.rs index e911cd70aa..ea127e3006 100644 --- a/src/layout/macros.rs +++ b/src/layout/macros.rs @@ -404,7 +404,7 @@ macro_rules! ReprC {( $( #[doc = $doc2] )* - #[$crate::ඞ::apply($crate::ඞ::maybe_stabby!)] + #[::safer_ffi::ඞ::apply(::safer_ffi::ඞ::maybe_stabby!)] #[repr($C_or_transparent)] $( #[$attr] diff --git a/src/layout/niche.rs b/src/layout/niche.rs index 8987fe7443..a7c6c2da35 100644 --- a/src/layout/niche.rs +++ b/src/layout/niche.rs @@ -3,7 +3,7 @@ use_prelude!(); use crate::prelude::c_slice; __cfg_headers__! { - use crate::__::{Definer, HeaderLanguage}; + use crate::headers::{Definer, languages::{HeaderLanguage, MetadataTypeData}}; use crate::layout::impls::metadata_nested_type_usage; } @@ -70,13 +70,14 @@ unsafe impl CType for OptionCLayout { } fn metadata() -> &'static dyn Provider { - T::metadata() - } - - fn metadata_type_usage() -> String { - let nested_type = metadata_nested_type_usage::(); + &provide_with(|request| { + request.give_if_requested::(|| { + let nested_type = metadata_nested_type_usage::(); - format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "Optional", nested_type) + MetadataTypeData(format!("\"kind\": \"{}\",\n\"type\": {{\n{}\n}}", "Optional", nested_type)) + }); + T::metadata().provide_to(request); + }) } } } diff --git a/src/proc_macro/_mod.rs b/src/proc_macro/_mod.rs index 16a1a691b7..0fd5b3f95a 100644 --- a/src/proc_macro/_mod.rs +++ b/src/proc_macro/_mod.rs @@ -164,11 +164,3 @@ fn respan( }) .collect() } - -#[proc_macro_attribute] -pub fn ffi_metadata( - _attrs: TokenStream, - input: TokenStream, -) -> TokenStream { - input -} diff --git a/src/proc_macro/derives/_mod.rs b/src/proc_macro/derives/_mod.rs index 7eddad3e1c..48c91fd770 100644 --- a/src/proc_macro/derives/_mod.rs +++ b/src/proc_macro/derives/_mod.rs @@ -103,8 +103,12 @@ pub(crate) fn derive_ReprC( // Legacy mode: let's tolerate but ignore attribute args: let repr_c::Args { .. } = parse2(args)?; + // // Remove `#[ffi_metadata]` inert attributes. + // attrs.retain(|attr| attr.path().is_ident("ffi_metadata").not()); + input = quote!(#(#attrs)* #rest); - return feed_to_macro_rules(input, parse_quote!(ReprC)); // .map(utils::mb_file_expanded); + return feed_to_macro_rules(input, parse_quote!(ReprC)) + .map(utils::mb_file_expanded); } else { // Otherwise, we might as well not have been covering js to begin with. drop(idents.swap_remove(i)); diff --git a/src/proc_macro/derives/c_type/_mod.rs b/src/proc_macro/derives/c_type/_mod.rs index a8e798b699..953ed8744a 100644 --- a/src/proc_macro/derives/c_type/_mod.rs +++ b/src/proc_macro/derives/c_type/_mod.rs @@ -14,9 +14,9 @@ pub(crate) fn derive( ) -> Result { let args: Args = parse2(args)?; - let input: DeriveInput = parse2(input)?; + let mut input: DeriveInput = parse2(input)?; let DeriveInput { - ref attrs, + ref mut attrs, ref vis, ref ident, ref generics, diff --git a/src/proc_macro/derives/c_type/struct_.rs b/src/proc_macro/derives/c_type/struct_.rs index bf3c65cf62..77bb871518 100644 --- a/src/proc_macro/derives/c_type/struct_.rs +++ b/src/proc_macro/derives/c_type/struct_.rs @@ -3,7 +3,7 @@ use super::*; #[allow(unexpected_cfgs)] pub(crate) fn derive( args: Args, - attrs: &'_ [Attribute], + attrs: &'_ mut Vec, pub_: &'_ Visibility, StructName @ _: &'_ Ident, generics: &'_ Generics, @@ -105,43 +105,73 @@ pub(crate) fn derive( }) })?; - let ffi_metadata = attrs - .iter() - .find(|attr| attr.path().is_ident("ffi_metadata")); + let mut ffi_metadata_attr = None; + let mut errored = None; + attrs.retain_mut(|attr| { + Retain::Keep == { + if attr.path().is_ident("ffi_metadata") { + if ffi_metadata_attr.is_some() { + errored = Some(Error::new_spanned( + &attr, + "duplicate `#[ffi_metadata]` attribute", + )); + } else { + ffi_metadata_attr = Some(attr.clone()); + } + Retain::Drop + } else { + Retain::Keep + } + } + }); + if let Some(err) = errored { + return Err(err); + } - if let Some(ffi_metadata) = ffi_metadata { + if let Some(ffi_metadata_attr) = &ffi_metadata_attr { let ptr_type = fields .iter() .find(|field| field.ident.as_ref().map_or(false, |ident| ident == "ptr")) .map(|field| &field.ty) .ok_or_else(|| { syn::Error::new_spanned( - ffi_metadata, - "Struct annotated with ffi_metadata attribute does not have field 'ptr'.", + ffi_metadata_attr, + "expected `.ptr` field on `#[ffi_metadata]`-annotated `struct`", ) })?; - let result = ffi_metadata.parse_args::(); + let result = ffi_metadata_attr.parse_args::(); if let Some(kind) = result.ok() { let kind_string = kind.to_string(); impl_body.extend(quote_spanned!(Span::mixed_site()=> - fn metadata_type_usage() -> String { - let nested_type = <#ptr_type as #CType>::metadata_type_usage(); - - let indented_nested_type = nested_type - .lines() - .map(|line| format!(" {}", line)) - .collect::>() - .join("\n"); - - format!( - "\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"type\": {{\n{}\n}}", - #kind_string, - Self::short_name(), - indented_nested_type, - ) + fn metadata() -> &'static dyn #headers::provider::Provider { + &#headers::provider::provide_with(|request| { + request.give_if_requested::<#headers::languages::MetadataTypeData>(|| { + let nested_type = + <#ptr_type as #CType>::metadata() + .dyn_request() + .map_or_else( + || "".into(), + |#headers::languages::MetadataTypeData(it)| it, + ) + ; + + let indented_nested_type = nested_type + .lines() + .map(|line| format!(" {}", line)) + .collect::>() + .join("\n"); + + #headers::languages::MetadataTypeData(#ඞ::format!( + "\"kind\": \"{}\",\n\"backingTypeName\": \"{}\",\n\"type\": {{\n{}\n}}", + #kind_string, + Self::short_name(), + indented_nested_type, + )) + }); + }) } )); } else { @@ -149,13 +179,19 @@ pub(crate) fn derive( } } else { impl_body.extend(quote_spanned!(Span::mixed_site()=> - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Struct", Self::short_name()) + fn metadata() -> &'static dyn #headers::provider::Provider { + &#headers::provider::provide_with(|request| { + request.give_if_requested::<#headers::languages::MetadataTypeData>(|| { + #headers::languages::MetadataTypeData( + #ඞ::format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Struct", Self::short_name()), + ) + }); + }) } )); } - let is_built_in_struct = ffi_metadata.is_some(); + let is_built_in_struct = ffi_metadata_attr.is_some(); impl_body.extend(quote_spanned!(Span::mixed_site()=> #[allow(nonstandard_style)] @@ -180,6 +216,9 @@ pub(crate) fn derive( ) } )); + } else { + // Remove `#[ffi_metadata]` inert attributes. + attrs.retain(|attr| attr.path().is_ident("ffi_metadata").not()); } ret.extend({ @@ -227,7 +266,7 @@ pub(crate) fn derive_transparent( #[rustfmt::skip] #[apply(let_quote)] - use ::safer_ffi::ඞ; + use ::safer_ffi::{ඞ, headers}; let mut ret = quote!(); @@ -312,8 +351,10 @@ pub(crate) fn derive_transparent( Ok(()) } - fn metadata_type_usage() -> String { - <#CFieldTy as #ඞ::CType>::metadata_type_usage() + fn metadata() -> &'static dyn #headers::provider::Provider { + &#headers::provider::provide_with(|request| { + <#CFieldTy as #ඞ::CType>::metadata().provide_to(request); + }) } fn name ( diff --git a/src/proc_macro/derives/repr_c/enum_.rs b/src/proc_macro/derives/repr_c/enum_.rs index 590b528f99..418bdba8b8 100644 --- a/src/proc_macro/derives/repr_c/enum_.rs +++ b/src/proc_macro/derives/repr_c/enum_.rs @@ -59,6 +59,7 @@ pub(crate) fn derive( ඞ::{ mem, }, + headers, layout::{ // __HasNiche__, CLayoutOf, @@ -167,8 +168,14 @@ pub(crate) fn derive( ) } - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Enum", Self::short_name()) + fn metadata() -> &'static dyn #headers::provider::Provider { + &#headers::provider::provide_with(|request| { + request.give_if_requested::<#headers::languages::MetadataTypeData>(|| { + #headers::languages::MetadataTypeData(format!( + "\"kind\": \"{}\",\n\"name\": \"{}\"", "Enum", Self::short_name() + )) + }); + }) } )); } diff --git a/src/proc_macro/derives/repr_c/struct_.rs b/src/proc_macro/derives/repr_c/struct_.rs index 79852d435f..a7341e5726 100644 --- a/src/proc_macro/derives/repr_c/struct_.rs +++ b/src/proc_macro/derives/repr_c/struct_.rs @@ -186,6 +186,9 @@ pub(crate) fn derive( ) }); + // Remove `#[ffi_metadata]` inert attributes. + attrs.retain(|attr| attr.path().is_ident("ffi_metadata").not()); + // Add docs about C layout. attrs.extend_::([ parse_quote!( @@ -390,7 +393,7 @@ pub(crate) fn derive_opaque( ) -> Result { #[rustfmt::skip] #[apply(let_quote)] - use ::safer_ffi::ඞ; + use ::safer_ffi::{ඞ, headers}; // Strip the `repr(opaque)` attrs.retain(|attr| { @@ -483,8 +486,14 @@ pub(crate) fn derive_opaque( ) } - fn metadata_type_usage() -> String { - format!("\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name()) + fn metadata() -> &'static dyn #headers::provider::Provider { + &#headers::provider::provide_with(|request| { + request.give_if_requested::<#headers::languages::MetadataTypeData>(|| { + #headers::languages::MetadataTypeData(format!( + "\"kind\": \"{}\",\n\"name\": \"{}\"", "Opaque", Self::short_name(), + )) + }); + }) } ) }; diff --git a/src/proc_macro/utils/_mod.rs b/src/proc_macro/utils/_mod.rs index 6872e5e224..7830f7a432 100644 --- a/src/proc_macro/utils/_mod.rs +++ b/src/proc_macro/utils/_mod.rs @@ -15,6 +15,12 @@ mod mb_file_expanded; pub(crate) use trait_impl_shenanigans::*; mod trait_impl_shenanigans; +#[derive(PartialEq)] +pub(crate) enum Retain { + Drop, + Keep, +} + pub(crate) trait MySplit { type Ret; fn my_split(self: &'_ Self) -> Self::Ret; diff --git a/src/ptr.rs b/src/ptr.rs index 6ed491f9f4..6444730d09 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -7,6 +7,28 @@ use_prelude!(); #[doc(no_inline)] pub use ::core::ptr::*; +/// `CType` Wrapper around `*{const,mut} T` to convey the notion of it being expected to be +/// non-null. +/// +/// Note that `CType`s are expected to be always-valid, this is just a hack for `ReprC` types whose +/// `CLayout` is this to be able to express so in the generated headers. +/// +/// Ideally, header generation would work off the `ReprC` types themselves (which could default to +/// delegating to its `CType` logic), so they could hook themselves into whichever machinery would +/// allow them to express extra, high-level, properties, such as that of being non-null. +/// +/// - This would be a huge improvement to `safer-ffi`, tbh. +#[derive(Debug, Clone, Copy)] +pub struct NonNullPtrCLayout(pub(crate) Ptr); + +pub fn non_null() -> NonNullPtrCLayout<*const T> { + NonNullPtrCLayout(0xbad000 as _) +} + +pub fn non_null_mut() -> NonNullPtrCLayout<*mut T> { + NonNullPtrCLayout(0xbad000 as _) +} + #[cfg_attr(feature = "stabby", stabby::stabby)] #[repr(transparent)] pub struct NonNullRef( diff --git a/src/slice.rs b/src/slice.rs index b472ad9310..aeac5452bd 100644 --- a/src/slice.rs +++ b/src/slice.rs @@ -2,7 +2,6 @@ use_prelude!(); use ::core::slice; -use safer_ffi_proc_macros::ffi_metadata; #[doc(no_inline)] pub use self::slice_mut as Mut; @@ -16,42 +15,38 @@ cfg_alloc! { /// The phantoms from the crate are not `ReprC`. type PhantomCovariantLifetime<'lt> = PhantomData<&'lt ()>; -ReprC! { - #[repr(C, js)] - #[ffi_metadata(DynamicArray)] - /// Like [`slice_ref`] and [`slice_mut`], but with any lifetime attached - /// whatsoever. - /// - /// It is only intended to be used as the parameter of a **callback** that - /// locally borrows it, due to limitations of the [`ReprC`][ - /// `trait@crate::layout::ReprC`] design _w.r.t._ higher-rank trait bounds. - /// - /// # C layout (for some given type T) - /// - /// ```c - /// typedef struct { - /// // Cannot be NULL - /// T * ptr; - /// size_t len; - /// } slice_T; - /// ``` - /// - /// # Nullable pointer? - /// - /// If you want to support the above typedef, but where the `ptr` field is - /// allowed to be `NULL` (with the contents of `len` then being undefined) - /// use the `Option< slice_ptr<_> >` type. - #[derive(Debug)] - pub - struct slice_raw[T] { - /// Pointer to the first element (if any). - pub - ptr: ptr::NonNull, - - /// Element count - pub - len: usize, - } +#[derive_ReprC] +#[repr(C, js)] +#[ffi_metadata(DynamicArray)] +/// Like [`slice_ref`] and [`slice_mut`], but with any lifetime attached +/// whatsoever. +/// +/// It is only intended to be used as the parameter of a **callback** that +/// locally borrows it, due to limitations of the [`ReprC`][ +/// `trait@crate::layout::ReprC`] design _w.r.t._ higher-rank trait bounds. +/// +/// # C layout (for some given type T) +/// +/// ```c +/// typedef struct { +/// // Cannot be NULL +/// T * ptr; +/// size_t len; +/// } slice_T; +/// ``` +/// +/// # Nullable pointer? +/// +/// If you want to support the above typedef, but where the `ptr` field is +/// allowed to be `NULL` (with the contents of `len` then being undefined) +/// use the `Option< slice_ptr<_> >` type. +#[derive(Debug)] +pub struct slice_raw { + /// Pointer to the first element (if any). + pub ptr: ptr::NonNull, + + /// Element count + pub len: usize, } impl slice_raw { diff --git a/src/tuple.rs b/src/tuple.rs index 194e206962..7f5e25c074 100644 --- a/src/tuple.rs +++ b/src/tuple.rs @@ -15,6 +15,10 @@ mod void { } pub(crate) use void::CVoid; +__cfg_headers__! { + use crate::headers::languages::MetadataTypeData; +} + unsafe impl CType for CVoid { type OPAQUE_KIND = crate::layout::OpaqueKind::Concrete; @@ -31,8 +35,12 @@ unsafe impl CType for CVoid { Ok(()) } - fn metadata_type_usage() -> String { - r#""kind": "void""#.into() + fn metadata() -> &'static dyn Provider { + &provide_with(|request| { + request.give_if_requested::(|| { + MetadataTypeData(r#""kind": "void""#.into()) + }); + }) } fn render( diff --git a/src/vec.rs b/src/vec.rs index 349cbb1b1b..4302c3af4b 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,21 +1,18 @@ use_prelude!(); use ::core::slice; -use safer_ffi_proc_macros::ffi_metadata; use crate::slice::*; -ReprC! { - #[repr(C)] - #[ffi_metadata(Vector)] - #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "alloc")))] - /// Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout - pub - struct Vec[T] { - ptr: ptr::NonNullOwned, - len: usize, - - cap: usize, - } +#[derive_ReprC] +#[repr(C)] +#[ffi_metadata(Vector)] +#[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "alloc")))] +/// Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout +pub struct Vec { + ptr: ptr::NonNullOwned, + len: usize, + + cap: usize, } impl Vec { From d29f9d0b40c28bd924afad11c257b77caffd9335 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 17:16:15 +0100 Subject: [PATCH 3/6] Complete transition to `dyn HeaderLanguage` for downstream to use Change-Id: I5d6a430cdb2548875cf6eeaf830b2ec16a6a6964 JJ-Change-Id: umtpvwznmoxuvrrsunktllpkrwzoxlny --- ffi_tests/src/lib.rs | 11 +-- src/headers/_mod.rs | 136 +++++++--------------------- src/headers/languages/c.rs | 38 ++++++++ src/headers/languages/csharp.rs | 30 ++++++ src/headers/languages/lua.rs | 28 ++++++ src/headers/languages/metadata.rs | 23 +++++ src/headers/languages/mod.rs | 25 ++++- src/headers/languages/python.rs | 4 + src/proc_macro/ffi_export/const_.rs | 16 +--- tests/test_kotlin.rs | 3 +- 10 files changed, 185 insertions(+), 129 deletions(-) diff --git a/ffi_tests/src/lib.rs b/ffi_tests/src/lib.rs index cde0c7f7ef..44e93a9770 100644 --- a/ffi_tests/src/lib.rs +++ b/ffi_tests/src/lib.rs @@ -222,15 +222,14 @@ pub struct SpecificConstGenericContainer { #[test] fn generate_headers() -> ::std::io::Result<()> { use ::safer_ffi::headers::Language; - use ::safer_ffi::headers::Language::*; #[rustfmt::skip] const LANGUAGES: &[(Language, &str)] = &[ - (C, "h"), - (CSharp, "cs"), - (Lua, "lua"), - (Metadata, "metadata.json"), - (Python, "cffi"), + (Language::C, "h"), + (Language::CSharp, "cs"), + (Language::Lua, "lua"), + (Language::Metadata, "metadata.json"), + (Language::Python, "cffi"), ]; for &(language, ext) in LANGUAGES { diff --git a/src/headers/_mod.rs b/src/headers/_mod.rs index 5939c010f3..b2f3e029b7 100644 --- a/src/headers/_mod.rs +++ b/src/headers/_mod.rs @@ -358,24 +358,9 @@ impl Builder<'_, WhereTo> { ) -> io::Result<()> { let lang = self.get_unwrapped_language(); - let banner: &'_ str = self.banner.unwrap_or(match lang { - | Language::Lua => concat!( - "-- File auto-generated by `::safer_ffi`.\n", - "--\n", - "-- Do not manually edit this file.\n", - ), - | Language::Metadata => return Ok(()), - | _ => concat!( - "/*! \\file */\n", - "/*******************************************\n", - " * *\n", - " * File auto-generated by `::safer_ffi`. *\n", - " * *\n", - " * Do not manually edit this file. *\n", - " * *\n", - " *******************************************/\n", - ), - }); + let Some::<&str>(banner) = self.banner.or_else(|| lang.as_dyn().default_banner()) else { + return Ok(()); + }; writeln!(definer.out(), "{}", banner) } @@ -386,35 +371,15 @@ impl Builder<'_, WhereTo> { ) -> io::Result<()> { let lang = self.get_unwrapped_language(); - let guard = self.guard(); - let text_after_guard = self.text_after_guard(); - - match lang { - | Language::C => writeln!( - definer.out(), - include_str!("templates/c/_prelude.h"), - guard = guard, - text_after_guard = text_after_guard, - ), - - | Language::CSharp => writeln!( - definer.out(), - include_str!("templates/csharp/_prelude.cs"), - NameSpace = Self::pascal_cased_lib_name(), - RustLib = Self::lib_name(), - ), - - | Language::Metadata => write!( - definer.out(), - include_str!("templates/metadata/_prelude.txt"), - ), - - | Language::Lua => writeln!(definer.out(), include_str!("templates/lua/_prelude.lua")), - - #[cfg(feature = "python-headers")] - // CHECKME - | Language::Python => Ok(()), - } + lang.as_dyn().write_prelude(definer, self) + } + + fn write_epilogue( + &'_ self, + definer: &'_ mut dyn Definer, + ) -> io::Result<()> { + let lang = self.get_unwrapped_language(); + lang.as_dyn().write_epilogue(definer, self) } /// Heart of safer ffi: write the items in the header @@ -425,12 +390,6 @@ impl Builder<'_, WhereTo> { let stable_header = self.stable_header.unwrap_or(true); let lang = self.get_unwrapped_language(); - // skip adding int/bool headers for Lua - if lang == Language::Lua { - definer.insert("__int_headers__"); - definer.insert("bool"); - } - let _naming_convention = self .naming_convention .as_ref() @@ -462,38 +421,6 @@ impl Builder<'_, WhereTo> { Ok(()) } - fn write_epilogue( - &'_ self, - definer: &'_ mut dyn Definer, - ) -> io::Result<()> { - let lang = self.get_unwrapped_language(); - match lang { - | Language::C => write!( - definer.out(), - include_str!("templates/c/epilogue.h"), - guard = self.guard(), - ), - - | Language::CSharp => { - let pkg_name = Self::pascal_cased_lib_name(); - write!( - definer.out(), - include_str!("templates/csharp/epilogue.cs"), - PkgName = pkg_name, - ) - }, - - | Language::Lua => { - write!(definer.out(), include_str!("templates/lua/epilogue.lua")) - }, - - | Language::Metadata => writeln!(definer.out(), "]"), - - #[cfg(feature = "python-headers")] - | Language::Python => Ok(()), - } - } - fn guard(&'_ self) -> String { self.guard.map_or_else( || format!("__RUST_{}__", Self::lib_name().to_ascii_uppercase()), @@ -543,36 +470,35 @@ impl Builder<'_, WhereTo> { } /// Language of the generated headers. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Language { +/// +/// Mere façade over a \&\'static dyn [HeaderLanguage]. +#[derive(Clone, Copy)] +pub struct Language(pub &'static dyn HeaderLanguage); + +impl Language { + /// Convenience getter to be prettier than `.0`. + pub fn as_dyn(&self) -> &'static dyn HeaderLanguage { + self.0 + } +} + +#[allow(nonstandard_style)] +impl Language { /// C, _lingua franca_ of FFI interop. - C, + pub const C: Self = Self(&languages::C); /// C# - CSharp, + pub const CSharp: Self = Self(&languages::CSharp); /// Lua - Lua, + pub const Lua: Self = Self(&languages::Lua); /// A JSON file containing detailed information about the FFI declarations. - Metadata, + pub const Metadata: Self = Self(&languages::Metadata); /// Python (experimental). #[cfg(feature = "python-headers")] - Python, -} - -impl Language { - pub(crate) fn as_dyn(self) -> &'static dyn HeaderLanguage { - match self { - | Language::C => &languages::C, - | Language::CSharp => &languages::CSharp, - | Language::Lua => &languages::Lua, - | Language::Metadata => &languages::Metadata, - #[cfg(feature = "python-headers")] - | Language::Python => &languages::Python, - } - } + pub const Python: Self = Self(&languages::Python); } /// Allow user to specify diff --git a/src/headers/languages/c.rs b/src/headers/languages/c.rs index 864bdd74c7..e8ea80f11a 100644 --- a/src/headers/languages/c.rs +++ b/src/headers/languages/c.rs @@ -429,4 +429,42 @@ impl HeaderLanguage for C { write!(out, "void")?; Ok(()) } + + fn write_prelude( + &'_ self, + definer: &'_ mut dyn Definer, + header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + writeln!( + definer.out(), + include_str!("../templates/c/_prelude.h"), + guard = header_builder.guard(), + text_after_guard = header_builder.text_after_guard(), + ) + } + + fn write_epilogue( + &'_ self, + definer: &'_ mut dyn Definer, + header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + write!( + definer.out(), + include_str!("../templates/c/epilogue.h"), + guard = header_builder.guard(), + ) + } + + fn default_banner(&self) -> Option<&'static str> { + Some(concat!( + "/*! \\file */\n", + "/*******************************************\n", + " * *\n", + " * File auto-generated by `::safer_ffi`. *\n", + " * *\n", + " * Do not manually edit this file. *\n", + " * *\n", + " *******************************************/\n", + )) + } } diff --git a/src/headers/languages/csharp.rs b/src/headers/languages/csharp.rs index 0c85f9bde3..c5d8281df1 100644 --- a/src/headers/languages/csharp.rs +++ b/src/headers/languages/csharp.rs @@ -523,4 +523,34 @@ impl HeaderLanguage for CSharp { write!(out, "void")?; Ok(()) } + + fn write_prelude( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + writeln!( + definer.out(), + include_str!("../templates/csharp/_prelude.cs"), + NameSpace = Builder::::pascal_cased_lib_name(), + RustLib = Builder::::lib_name(), + ) + } + + fn write_epilogue( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + let pkg_name = Builder::::pascal_cased_lib_name(); + write!( + definer.out(), + include_str!("../templates/csharp/epilogue.cs"), + PkgName = pkg_name, + ) + } + + fn default_banner(&self) -> Option<&'static str> { + super::C.default_banner() + } } diff --git a/src/headers/languages/lua.rs b/src/headers/languages/lua.rs index a9ebe30a44..daad5efbd3 100644 --- a/src/headers/languages/lua.rs +++ b/src/headers/languages/lua.rs @@ -218,4 +218,32 @@ impl HeaderLanguage for Lua { ) -> io::Result<()> { C.emit_void_output_type(out) } + + fn write_prelude( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + writeln!(definer.out(), include_str!("../templates/lua/_prelude.lua"))?; + // Hack: skip adding int/bool headers for Lua + definer.insert("__int_headers__"); + definer.insert("bool"); + Ok(()) + } + + fn write_epilogue( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + write!(definer.out(), include_str!("../templates/lua/epilogue.lua")) + } + + fn default_banner(&self) -> Option<&'static str> { + Some(concat!( + "-- File auto-generated by `::safer_ffi`.\n", + "--\n", + "-- Do not manually edit this file.\n", + )) + } } diff --git a/src/headers/languages/metadata.rs b/src/headers/languages/metadata.rs index db1c57de99..f0b0e85d8a 100644 --- a/src/headers/languages/metadata.rs +++ b/src/headers/languages/metadata.rs @@ -441,6 +441,29 @@ impl HeaderLanguage for Metadata { fn must_declare_built_in_types(self: &'_ Self) -> bool { false } + + fn write_prelude( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + write!( + definer.out(), + include_str!("../templates/metadata/_prelude.txt"), + ) + } + + fn write_epilogue( + &'_ self, + definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + writeln!(definer.out(), "]") + } + + fn default_banner(&self) -> Option<&'static str> { + None + } } impl Metadata { diff --git a/src/headers/languages/mod.rs b/src/headers/languages/mod.rs index 7f2c63eb61..8b666d943a 100644 --- a/src/headers/languages/mod.rs +++ b/src/headers/languages/mod.rs @@ -1,6 +1,7 @@ #![allow(irrefutable_let_patterns)] use_prelude!(); + use ::std::io::Write as _; use ::std::io::{self}; @@ -10,6 +11,8 @@ use self::primitives::IntBitWidth; use self::primitives::Primitive; use super::Definer; use super::provider::Provider; +use crate::headers::Builder; +use crate::headers::WhereTo; use crate::utils::DisplayFromFn as F; pub mod primitives; @@ -67,11 +70,31 @@ impl ::core::fmt::Display for Indentation { type Docs<'lt> = &'lt [&'lt str]; -pub trait HeaderLanguage: UpcastAny { +pub trait HeaderLanguage: Sync + UpcastAny { fn language_name(self: &'_ Self) -> &'static str { ::core::any::type_name::() } + /// Prelude to insert to the generated header file, if any. + fn write_prelude( + &'_ self, + _definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + Ok(()) + } + + /// Epilogue to insert to the generated header file, if any. + fn write_epilogue( + &'_ self, + _definer: &'_ mut dyn Definer, + _header_builder: &'_ Builder<'_, WhereTo>, + ) -> io::Result<()> { + Ok(()) + } + + fn default_banner(&self) -> Option<&'static str>; + fn supports_type_aliases( self: &'_ Self ) -> Option<&'_ dyn HeaderLanguageSupportingTypeAliases> { diff --git a/src/headers/languages/python.rs b/src/headers/languages/python.rs index a1a1fe5dcd..a01ae69cb6 100644 --- a/src/headers/languages/python.rs +++ b/src/headers/languages/python.rs @@ -177,4 +177,8 @@ impl HeaderLanguage for Python { ) -> io::Result<()> { C.emit_void_output_type(out) } + + fn default_banner(&self) -> Option<&'static str> { + super::C.default_banner() + } } diff --git a/src/proc_macro/ffi_export/const_.rs b/src/proc_macro/ffi_export/const_.rs index ba511a8127..3323ea2b4f 100644 --- a/src/proc_macro/ffi_export/const_.rs +++ b/src/proc_macro/ffi_export/const_.rs @@ -75,21 +75,7 @@ pub(super) fn handle( Language, languages::{self, HeaderLanguage}, }; - let header_builder: &'static dyn HeaderLanguage = - #krate::__with_cfg_python__!(|$if_cfg_python| { - { - match lang { - | Language::C => &languages::C, - | Language::CSharp => &languages::CSharp, - | Language::Lua => &languages::Lua, - | Language::Metadata => &languages::Metadata, - $($($if_cfg_python)? - | Language::Python => &languages::Python, - )? - } - } - }) - ; + let header_builder: &'static dyn HeaderLanguage = lang.as_dyn(); <#ඞ::CLayoutOf<#Ty> as #ඞ::CType>::define_self( header_builder, diff --git a/tests/test_kotlin.rs b/tests/test_kotlin.rs index 49fc764f43..a09d2481d2 100644 --- a/tests/test_kotlin.rs +++ b/tests/test_kotlin.rs @@ -113,10 +113,9 @@ pub enum EnumWithExplicitDiscriminant { #[cfg(feature = "headers")] #[test] fn test_kotlin () -> io::Result<()> {Ok({ - use ::safer_ffi::headers::Language::*; safer_ffi::headers::builder() - .with_language(Metadata) + .with_language(&languages::Metadata) .to_writer(&mut io::stderr()) .generate()? })} From 340265d8e1dbe7cb47a84b07a4892535a9c95455 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 17:36:05 +0100 Subject: [PATCH 4/6] rc1 Change-Id: I8e5cb7970992a5426147d68c7b98d9c26a6a6964 JJ-Change-Id: rlunosqszqqxpuvxtyvsmtrnsoqrmqnx --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- ffi_tests/Cargo.lock | 4 ++-- js_tests/Cargo.lock | 4 ++-- src/proc_macro/Cargo.toml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b036c57565..d9efef0093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,7 +726,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safer-ffi" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "async-compat", "cratesio-placeholder-package", @@ -763,7 +763,7 @@ dependencies = [ [[package]] name = "safer_ffi-proc_macros" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "macro_rules_attribute", "prettyplease", diff --git a/Cargo.toml b/Cargo.toml index 5feb66df5a..51ab9678df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ path = "src/_lib.rs" [package] name = "safer-ffi" -version = "0.2.0-alpha.0" # Keep in sync +version = "0.2.0-rc1" # Keep in sync authors = ["Daniel Henry-Mantilla "] edition = "2024" rust-version = "1.85.0" @@ -146,7 +146,7 @@ version = "0.0.3" [dependencies.safer_ffi-proc_macros] path = "src/proc_macro" -version = "=0.2.0-alpha.0" # Keep in sync +version = "=0.2.0-rc1" # Keep in sync [workspace] members = [ diff --git a/ffi_tests/Cargo.lock b/ffi_tests/Cargo.lock index 22bc6265f3..af12f5e6a3 100644 --- a/ffi_tests/Cargo.lock +++ b/ffi_tests/Cargo.lock @@ -359,7 +359,7 @@ checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "safer-ffi" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "extension-traits 2.0.1", "extern-c", @@ -380,7 +380,7 @@ dependencies = [ [[package]] name = "safer_ffi-proc_macros" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "macro_rules_attribute", "prettyplease", diff --git a/js_tests/Cargo.lock b/js_tests/Cargo.lock index da40443afc..b6426f96b4 100644 --- a/js_tests/Cargo.lock +++ b/js_tests/Cargo.lock @@ -671,7 +671,7 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safer-ffi" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "cratesio-placeholder-package", "extension-traits 2.0.1", @@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "safer_ffi-proc_macros" -version = "0.2.0-alpha.0" +version = "0.2.0-rc1" dependencies = [ "macro_rules_attribute", "prettyplease", diff --git a/src/proc_macro/Cargo.toml b/src/proc_macro/Cargo.toml index 3a610cc43e..fd09619a05 100644 --- a/src/proc_macro/Cargo.toml +++ b/src/proc_macro/Cargo.toml @@ -4,7 +4,7 @@ proc-macro = true [package] name = "safer_ffi-proc_macros" -version = "0.2.0-alpha.0" # Keep in sync +version = "0.2.0-rc1" # Keep in sync authors = ["Daniel Henry-Mantilla "] edition = "2024" From 9f3bad90f1e7520bbd8a023da8078fe94f9e5af3 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 17:37:58 +0100 Subject: [PATCH 5/6] Package exclude improvements Change-Id: I001b236a7fc5b1ee5ff9666fae1d6f6b6a6a6964 JJ-Change-Id: zzyoxwtpsknuoyllukkqtttkplymtkto --- Cargo.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 51ab9678df..f134773007 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,11 @@ repository = "https://github.com/getditto/safer_ffi" readme = "README.md" -exclude = ["/guide"] +exclude = [ + "/guide", + "/ffi_tests", + "/js_tests", +] [features] default = ["std"] From 3575bc3e38717ee687b139bf730561f41b7e34e1 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Fri, 16 Jan 2026 17:40:49 +0100 Subject: [PATCH 6/6] fixup! post-cherry-pick cleanup --- src/layout/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/macros.rs b/src/layout/macros.rs index ea127e3006..e911cd70aa 100644 --- a/src/layout/macros.rs +++ b/src/layout/macros.rs @@ -404,7 +404,7 @@ macro_rules! ReprC {( $( #[doc = $doc2] )* - #[::safer_ffi::ඞ::apply(::safer_ffi::ඞ::maybe_stabby!)] + #[$crate::ඞ::apply($crate::ඞ::maybe_stabby!)] #[repr($C_or_transparent)] $( #[$attr]