diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index f5a5f0f957..810ed16a6c 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -22,7 +22,7 @@ jobs: - macos-latest - windows-latest rust: - - 1.52.0 + - 1.57.0 - stable # - nightly steps: @@ -51,10 +51,10 @@ jobs: needs: check strategy: matrix: - feature--proc_macros: ['', '--features proc_macros'] feature--std: ['', '--features std'] feature--alloc: ['', '--features alloc'] - feature--log: ['', '--features log'] + feature--async-fn: ['', '--features async-fn'] + # feature--log: ['', '--features log'] feature--out-refs: ['', '--features out-refs'] feature--node-js: ['', '--features node-js'] steps: @@ -62,7 +62,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.52.0 + toolchain: 1.57.0 override: true - name: Clone repo @@ -73,10 +73,9 @@ jobs: command: check args: | --no-default-features - ${{ matrix.feature--proc_macros }} ${{ matrix.feature--std }} ${{ matrix.feature--alloc }} - ${{ matrix.feature--log }} + ${{ matrix.feature--async-fn }} ${{ matrix.feature--out-refs }} ${{ matrix.feature--node-js }} @@ -93,7 +92,7 @@ jobs: - macos-latest # - windows-latest rust: - # - 1.52.0 + # - 1.57.0 - stable - nightly steps: @@ -116,7 +115,7 @@ jobs: RUST_BACKTRACE: full with: command: test - args: --no-default-features --features derives,std + args: --no-default-features --features std - name: FFI test (C & C#?) run: make -C ffi_tests @@ -158,7 +157,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: doc - args: --all-features + args: --features docs - name: Build guide and documentation run: | diff --git a/Cargo.toml b/Cargo.toml index ade21a96d9..8785dc2ea6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,9 @@ path = "src/_lib.rs" [package] name = "safer-ffi" -# Keep in sync with `[dependencies.proc_macro]` and `src/proc_macro/Cargo.toml` -version = "0.0.5" +version = "0.1.0" # Keep in sync authors = ["Daniel Henry-Mantilla "] -edition = "2018" +edition = "2021" description = "Write safer FFI code in Rust without polluting it with unsafe code" keywords = ["ffi", "no_std", "framework", "safety", "bindings"] @@ -18,36 +17,52 @@ readme = "README.md" [features] default = [ "std", + # FIXME: REMOVE! + "dyn-traits", "csharp-headers", +] + +# Document under the following features: all but for debug or experimental. +docs = [ + "csharp-headers", + "default", + "nightly", + "uninit", ] nightly = [] alloc = [] -std = [ "alloc" ] +std = [ + "alloc", +] + +# Deprecated +proc_macros = [] async-fn = [ "safer_ffi-proc_macros/async-fn", ] -derives = ["proc_macros"] # alias -proc_macros = [ - "safer_ffi-proc_macros/proc_macros", - "require_unsafe_in_body", - "proc-macro-hack", -] debug_proc_macros = [ - "proc_macros", "safer_ffi-proc_macros/verbose-expansions", ] -out-refs = ["uninit"] +dyn-traits = [ + "safer_ffi-proc_macros/dyn-traits", +] + +out-refs = [ + "uninit", +] headers = [ "inventory", "safer_ffi-proc_macros/headers", "std", ] -csharp-headers = ["headers"] +csharp-headers = [ + "headers", +] node-js = [ "async-fn", @@ -57,23 +72,26 @@ node-js = [ # "napi-derive", "safer_ffi-proc_macros/node-js", # For convenience - "proc_macros", "std", + "std", ] [dev-dependencies] -macro_rules_attribute = "0.0.1" +macro_rules_attribute = "0.1.0" [target.'cfg(not(target = "wasm32-unknown-unknown"))'.dependencies] libc = { version = "0.2.66", default-features = false } [dependencies] paste = { package = "mini_paste", version = "0.1.*" } -log = { version = "0.4.8", optional = true } -require_unsafe_in_body = { version = "0.2.1", optional = true } -inventory = { version = "0.1.6", optional = true } -proc-macro-hack = { version = "0.5.15", optional = true } -unwind_safe = "0.0.1" -scopeguard = "1.1.0" +log.version = "0.4.8" +log.optional = true +inventory.version = "0.1.6" +inventory.optional = true +scopeguard.version = "1.1.0" +unwind_safe.version = "0.1.0" +fstrings = "0.2.3" +with_builtin_macros.version = "0.0.3" +macro_rules_attribute = "0.1.0" [dependencies.napi] package = "napi-dispatcher" @@ -88,9 +106,8 @@ path = "napi-dispatcher" # branch = "ditto/closure-into-jsfunction" [dependencies.safer_ffi-proc_macros] -# package = "safer_ffi-proc_macros" path = "src/proc_macro" -version = "0.0.5" +version = "0.1.0-rc1" # Keep in sync [dependencies.uninit] optional = true @@ -111,5 +128,4 @@ members = [ ] [package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docs"] +features = ["docs"] diff --git a/README.md b/README.md index 04fc1bdc3f..279f7907cb 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ https://github.com/getditto/safer_ffi) ## Prerequisites -Minimum Supported Rust Version: `1.52.0` +Minimum Supported Rust Version: `1.57.0` ## Quickstart diff --git a/docs/layouts.png b/docs/layouts.png new file mode 100644 index 0000000000..9de815d8eb Binary files /dev/null and b/docs/layouts.png differ diff --git a/ffi_tests/Cargo.toml b/ffi_tests/Cargo.toml index ee4a5347c0..fe2ee5a678 100644 --- a/ffi_tests/Cargo.toml +++ b/ffi_tests/Cargo.toml @@ -15,7 +15,7 @@ edition = "2018" [dependencies.safer-ffi] path = ".." -features = ["async-fn", "proc_macros"] +features = ["async-fn"] [features] generate-headers = ["safer-ffi/csharp-headers"] diff --git a/ffi_tests/generated.cs b/ffi_tests/generated.cs index 2d1874437a..27947b84f1 100644 --- a/ffi_tests/generated.cs +++ b/ffi_tests/generated.cs @@ -32,6 +32,9 @@ public unsafe struct AnUnusedStruct_t { public Wow_t are_you_still_there; } +/// +/// Hello, World! +/// public enum Triforce_t : byte { Din = 3, Farore = 1, diff --git a/ffi_tests/generated.h b/ffi_tests/generated.h index e8af298c65..3eac4155f5 100644 --- a/ffi_tests/generated.h +++ b/ffi_tests/generated.h @@ -39,6 +39,9 @@ typedef struct { } AnUnusedStruct_t; +/** \brief + * Hello, `World`! + */ /** \remark Has the same ABI as `uint8_t` **/ #ifdef DOXYGEN typedef enum Triforce @@ -46,11 +49,11 @@ typedef enum Triforce typedef uint8_t Triforce_t; enum #endif { - /** . */ + /** */ TRIFORCE_DIN = 3, - /** . */ + /** */ TRIFORCE_FARORE = 1, - /** . */ + /** */ TRIFORCE_NARYU, } #ifdef DOXYGEN @@ -73,6 +76,7 @@ typedef enum SomeReprCEnum { void check_SomeReprCEnum ( SomeReprCEnum_t _baz); +/** */ /** \remark Has the same ABI as `uint8_t` **/ #ifdef DOXYGEN typedef enum Bar @@ -80,7 +84,7 @@ typedef enum Bar typedef uint8_t Bar_t; enum #endif { - /** . */ + /** */ BAR_A, } #ifdef DOXYGEN diff --git a/ffi_tests/src/lib.rs b/ffi_tests/src/lib.rs index 75520ca711..aaa9858aa9 100644 --- a/ffi_tests/src/lib.rs +++ b/ffi_tests/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(rustfmt, rustfmt::skip)] + use ::safer_ffi::prelude::*; /// Concatenate the two input strings into a new one. @@ -91,7 +92,7 @@ mod foo { mod bar { use super::*; - #[derive_ReprC] + #[derive_ReprC2] #[repr(u8)] pub enum Bar { A } @@ -127,15 +128,26 @@ pub enum Wow { Jenkins, } +/// Hello, `World`! #[ffi_export] -#[derive_ReprC] +#[derive_ReprC2] #[repr(u8)] pub enum Triforce { Din = 3, - Farore = 1, + Farore = Triforce::Din as u8 - 2, Naryu, } +/// Hello, `World`! +// #[ffi_export] +#[derive_ReprC2] +pub struct Next { + /// I test some `gen`-eration. + gen: bar::Bar, + /// with function pointers and everything! + cb: extern "C" fn(u8) -> i8, +} + #[ffi_export] #[derive_ReprC] #[repr(C)] @@ -148,8 +160,8 @@ pub struct AnUnusedStruct { fn generate_headers () -> ::std::io::Result<()> {Ok({ - use ::safer_ffi::headers::Language::*; - for &(language, ext) in &[(C, "h"), (CSharp, "cs")] { + use ::safer_ffi::headers::Language::{self, *}; + for &(language, ext) in &[(C, "h"), (Language::CSharp, "cs")][..1] { let builder = ::safer_ffi::headers::builder() .with_language(language) diff --git a/ffi_tests/tests/csharp/Tests.csproj b/ffi_tests/tests/csharp/Tests.csproj index 14cc317251..148801f72a 100644 --- a/ffi_tests/tests/csharp/Tests.csproj +++ b/ffi_tests/tests/csharp/Tests.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + netcoreapp6.0 true diff --git a/js_tests/Cargo.toml b/js_tests/Cargo.toml index 7803361b44..0557bce50b 100644 --- a/js_tests/Cargo.toml +++ b/js_tests/Cargo.toml @@ -20,7 +20,6 @@ futures = "0.3.15" path = ".." features = [ # "debug_proc_macros", - "proc_macros", "out-refs", ] diff --git a/js_tests/src/lib.rs b/js_tests/src/lib.rs index 06c37f5489..d6088c1c54 100644 --- a/js_tests/src/lib.rs +++ b/js_tests/src/lib.rs @@ -63,7 +63,7 @@ fn concat (s1: char_p::Ref<'_>, s2: char_p::Ref<'_>) } #[ffi_export(node_js)] -fn concat_bytes ( +fn concat_byte_slices ( xs1: Option>, xs2: Option>, ) -> Option> diff --git a/js_tests/tests/tests.mjs b/js_tests/tests/tests.mjs index a5e7347f36..184c655fbb 100644 --- a/js_tests/tests/tests.mjs +++ b/js_tests/tests/tests.mjs @@ -122,7 +122,7 @@ export async function run_tests({ ffi, performance, assert, is_web }) { ); assert.deepEqual( - Uint8Array.from(ffi.boxCBytesIntoBuffer(ffi.concat_bytes( + Uint8Array.from(ffi.boxCBytesIntoBuffer(ffi.concat_byte_slices( Uint8Array.from('Hello, ', c => c.charCodeAt(0)), Uint8Array.from('World!', c => c.charCodeAt(0)), ))), diff --git a/rust-toolchain b/rust-toolchain index a63cb35e6f..4d5fde5bd1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.52.0 +1.60.0 diff --git a/src/_lib.rs b/src/_lib.rs index b7fa4d5959..82993d242e 100644 --- a/src/_lib.rs +++ b/src/_lib.rs @@ -2,7 +2,7 @@ #![allow(clippy::all)] #![cfg_attr(rustfmt, rustfmt::skip)] #![cfg_attr(feature = "nightly", - feature(doc_cfg, external_doc, trivial_bounds) + feature(doc_cfg, trivial_bounds) )] #![cfg_attr(not(feature = "std"), no_std, @@ -19,72 +19,210 @@ unconditional_recursion, unused_must_use, )] - -#![cfg_attr(feature = "nightly", - doc(include = "../README.md") -)] -#![cfg_attr(not(feature = "nightly"), - doc = "See the [user guide](https://getditto.github.io/safer_ffi)." -)] +#![doc = include_str!("../README.md")] #![cfg(not(rustfmt))] +#![feature(rustc_attrs)] #![allow(warnings)] + +#[macro_use] +extern crate fstrings; + +#[macro_use] +extern crate macro_rules_attribute; + +#[macro_use] +extern crate with_builtin_macros; + #[macro_use] #[path = "utils/_mod.rs"] #[doc(hidden)] /** Not part of the public API **/ pub mod __utils__; use __utils__ as utils; -#[cfg(feature = "proc_macros")] -#[macro_use] -extern crate require_unsafe_in_body; - -hidden_export! { - use ::paste; -} - -pub use ::safer_ffi_proc_macros::{ffi_export, cfg_headers}; -cfg_proc_macros! { - #[::proc_macro_hack::proc_macro_hack] - /// Creates a compile-time checked [`char_p::Ref`]`<'static>` out of a - /// string literal. - /// - /// # Example - /// - /// ```rust - /// use ::safer_ffi::prelude::*; - /// - /// #[ffi_export] - /// fn concat (s1: char_p::Ref<'_>, s2: char_p::Ref<'_>) - /// -> char_p::Box - /// { - /// format!("{}{}", s1.to_str(), s2.to_str()) - /// .try_into() - /// .unwrap() // No inner nulls in our format string - /// } - /// - /// fn main () - /// { - /// assert_eq!( - /// concat(c!("Hello, "), c!("World!")).as_ref(), - /// c!("Hello, World!"), - /// ); - /// } - /// ``` - /// - /// If the string literal contains an inner null byte, then the macro - /// will detect it at compile time and thus cause a compile-time error - /// (allowing to skip the then unnecessary runtime check!): - /// - /// ```rust,compile_fail - /// let _ = ::safer_ffi::c!("Hell\0, World!"); // <- Compile error - /// ``` - /// - /// [`char_p::Ref`]: `crate::prelude::char_p::Ref` - pub use ::safer_ffi_proc_macros::c_str as c; - - #[doc(inline)] - pub use ::safer_ffi_proc_macros::derive_ReprC; -} +#[apply(hidden_export)] +use ::paste; + +/// Export a function to be callable by C. +/// +/// # Example +/// +/// ```rust +/// use ::safer_ffi::prelude::ffi_export; +/// +/// #[ffi_export] +/// /// Add two integers together. +/// fn add (x: i32, y: i32) -> i32 +/// { +/// x + y +/// } +/// ``` +/// +/// - ensures that [the generated headers](/safer_ffi/headers/) will include the +/// following definition: +/// +/// ```C +/// #include +/// +/// /* \brief +/// * Add two integers together. +/// */ +/// int32_t add (int32_t x, int32_t y); +/// ``` +/// +/// - exports an `add` symbol pointing to the C-ABI compatible +/// `int32_t (*)(int32_t x, int32_t y)` function. +/// +/// (The crate type needs to be `cdylib` or `staticlib` for this to work, +/// and, of course, the C compiler invocation needs to include +/// `-L path/to/the/compiled/library -l name_of_your_crate`) +/// +/// - when in doubt, use `staticlib`. +/// +/// # `ReprC` +/// +/// You can use any Rust types in the singature of an `#[ffi_export]`- +/// function, provided each of the types involved in the signature is [`ReprC`]. +/// +/// Otherwise the layout of the involved types in the C world is **undefined**, +/// which `#[ffi_export]` will detect, leading to a compilation error. +/// +/// To have custom structs implement [`ReprC`], it suffices to annotate the +/// `struct` definitions with the #\[[derive_ReprC]\] +/// (on top of the obviously required `#[repr(C)]`). +pub use ::safer_ffi_proc_macros::ffi_export; + +pub use ::safer_ffi_proc_macros::cfg_headers; + +/// Creates a compile-time checked [`char_p::Ref`]`<'static>` out of a +/// string literal. +/// +/// # Example +/// +/// ```rust +/// use ::safer_ffi::prelude::*; +/// +/// #[ffi_export] +/// fn concat (s1: char_p::Ref<'_>, s2: char_p::Ref<'_>) +/// -> char_p::Box +/// { +/// format!("{}{}", s1.to_str(), s2.to_str()) +/// .try_into() +/// .unwrap() // No inner nulls in our format string +/// } +/// +/// fn main () +/// { +/// assert_eq!( +/// concat(c!("Hello, "), c!("World!")).as_ref(), +/// c!("Hello, World!"), +/// ); +/// } +/// ``` +/// +/// If the string literal contains an inner null byte, then the macro +/// will detect it at compile time and thus cause a compile-time error +/// (allowing to skip the then unnecessary runtime check!): +/// +/// ```rust,compile_fail +/// let _ = ::safer_ffi::c!("Hell\0, World!"); // <- Compile error +/// ``` +/// +/// [`char_p::Ref`]: `crate::prelude::char_p::Ref` +pub use ::safer_ffi_proc_macros::c_str as c; + +/// Safely implement [`ReprC`] +/// for a `#[repr(C)]` struct **when all its fields are [`ReprC`]**. +/// +/// # Examples +/// +/// ### Simple `struct` +/// +/// ```rust +/// use ::safer_ffi::prelude::*; +/// +/// #[derive_ReprC] +/// #[repr(C)] +/// struct Instant { +/// seconds: u64, +/// nanos: u32, +/// } +/// ``` +/// +/// - corresponding to the following C definition: +/// +/// ```C +/// typedef struct { +/// uint64_t seconds; +/// uint32_t nanos; +/// } Instant_t; +/// ``` +/// +/// ### Field-less `enum` +/// +/// ```rust +/// use ::safer_ffi::prelude::*; +/// +/// #[derive_ReprC] +/// #[repr(u8)] +/// enum Status { +/// Ok = 0, +/// Busy, +/// NotInTheMood, +/// OnStrike, +/// OhNo, +/// } +/// ``` +/// +/// - corresponding to the following C definition: +/// +/// ```C +/// typedef uint8_t Status_t; enum { +/// STATUS_OK = 0, +/// STATUS_BUSY, +/// STATUS_NOT_IN_THE_MOOD, +/// STATUS_ON_STRIKE, +/// STATUS_OH_NO, +/// } +/// ``` +/// +/// ### Generic `struct` +/// +/// In that case, it is required that the struct's generic types carry a +/// `: ReprC` bound each: +/// +/// ```rust +/// use ::safer_ffi::prelude::*; +/// +/// #[derive_ReprC] +/// #[repr(C)] +/// struct Point { +/// x: Coordinate, +/// y: Coordinate, +/// } +/// # +/// # fn main() {} +/// ``` +/// +/// Each monomorphization leads to its own C definition: +/// +/// - **`Point`** +/// +/// ```C +/// typedef struct { +/// int32_t x; +/// int32_t y; +/// } Point_int32_t; +/// ``` +/// +/// - **`Point`** +/// +/// ```C +/// typedef struct { +/// double x; +/// double y; +/// } Point_double_t; +/// ``` +pub use ::safer_ffi_proc_macros::derive_ReprC; #[macro_use] #[path = "layout/_mod.rs"] @@ -115,6 +253,8 @@ __cfg_headers__! { } cfg_alloc! { + #[doc(hidden)] pub + extern crate alloc as __alloc; extern crate alloc; } @@ -133,6 +273,11 @@ mod char_p; pub mod closure; +#[cfg(feature = "dyn-traits")] +#[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "dyn-traits")))] +pub +mod dyn_traits; + const _: () = { #[path = "ffi_export.rs"] mod ffi_export; @@ -164,37 +309,45 @@ cfg_alloc! { pub mod vec; } -macro_rules! reexport_primitive_types {( - $($ty:ident)* -) => ( - $( - #[doc(hidden)] - pub use $ty; - )* -)} reexport_primitive_types! { +match_! {( u8 u16 u32 u64 u128 i8 i16 i32 i64 i128 f32 f64 char bool str -} +) {( + $($ty:ident)* +) => ( + $( + #[doc(hidden)] + pub use $ty; + )* +)}} -hidden_export! { - use ::core; -} +#[apply(hidden_export)] +use ::core; -hidden_export! { - use ::scopeguard; -} +#[apply(hidden_export)] +use ::scopeguard; -cfg_std! { - hidden_export! { - use ::std; - } +#[apply(cfg_std!)] +#[apply(hidden_export)] +use ::std; + +#[apply(hidden_export)] +/// Hack needed to `feature(trivial_bounds)` in stable Rust: +/// +/// Instead of `where Ty : Bounds…`, it suffices to write: +/// `where for<'hrtb> TrivialBound<'hrtb, Ty> : Bounds…`. +type __TrivialBound<'hrtb, T> = >::ItSelf; + +mod __private { + pub trait Ignoring<'__> { type ItSelf : ?Sized; } + impl Ignoring<'_> for T { type ItSelf = Self; } } -#[doc(hidden)] /** Not part of the public API **/ pub +#[apply(hidden_export)] use layout::impls::c_int; #[derive(Clone, Copy)] @@ -261,19 +414,19 @@ mod prelude { pub use crate::string::str_boxed as Box; } } - cfg_proc_macros! { - #[doc(no_inline)] - pub use crate::layout::derive_ReprC; - #[doc(no_inline)] - pub use c; - } + #[doc(no_inline)] - pub use ::core::{ - convert::{ - TryFrom as _, - TryInto as _, + pub use { + crate::layout::derive_ReprC, + ::safer_ffi_proc_macros::derive_ReprC2, + crate::c, + ::core::{ + convert::{ + TryFrom as _, + TryInto as _, + }, + ops::Not as _, }, - ops::Not as _, }; #[cfg(feature = "out-refs")] @@ -288,6 +441,12 @@ mod prelude { // Helper trait to have `AsOut` when `T : !Copy` ManuallyDropMut, }; + + #[cfg(feature = "dyn-traits")] + #[cfg_attr(all(docs, feature = "nightly"), + doc(cfg(feature = "dyn-traits")) + )] + pub use crate::dyn_traits::VirtualPtr; } #[macro_export] @@ -296,54 +455,50 @@ macro_rules! NULL {() => ( )} #[cfg(feature = "log")] -hidden_export! { - use ::log; -} +#[apply(hidden_export)] +use ::log; #[cfg(feature = "node-js")] -// hidden_export! { - #[path = "node_js/_mod.rs"] - pub mod node_js; -// } - -hidden_export! { - #[allow(missing_copy_implementations, missing_debug_implementations)] - struct __PanicOnDrop__; impl Drop for __PanicOnDrop__ { - fn drop (self: &'_ mut Self) - { - panic!() - } +// #[apply(hidden_export)] +#[path = "node_js/_mod.rs"] +pub mod node_js; + +#[apply(hidden_export)] +#[allow(missing_copy_implementations, missing_debug_implementations)] +struct __PanicOnDrop__; impl Drop for __PanicOnDrop__ { + fn drop (self: &'_ mut Self) + { + panic!() } } #[cfg(feature = "log")] -hidden_export! { - macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ - $crate::log::error!($($tt)*); - let _panic_on_drop = $crate::__PanicOnDrop__; - $crate::core::panic!($($tt)*); - })} -} +#[apply(hidden_export)] +macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ + $crate::log::error!($($tt)*); + let _panic_on_drop = $crate::__PanicOnDrop__; + $crate::core::panic!($($tt)*); +})} + #[cfg(all( not(feature = "log"), feature = "std", ))] -hidden_export! { - macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ - $crate::std::eprintln!($($tt)*); - $crate::std::process::abort(); - })} -} +#[apply(hidden_export)] +macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ + $crate::std::eprintln!($($tt)*); + $crate::std::process::abort(); +})} + #[cfg(all( not(feature = "log"), not(feature = "std"), ))] -hidden_export! { - macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ - let _panic_on_drop = $crate::__PanicOnDrop__; - $crate::core::panic!($($tt)*); - })} -} +#[apply(hidden_export)] +macro_rules! __abort_with_msg__ { ($($tt:tt)*) => ({ + let _panic_on_drop = $crate::__PanicOnDrop__; + $crate::core::panic!($($tt)*); +})} #[cfg(target_arch = "wasm32")] #[allow(dead_code)] @@ -356,3 +511,47 @@ mod libc { use ::libc; extern crate self as safer_ffi; + +#[apply(hidden_export)] +use __ as ඞ; + +#[apply(hidden_export)] +mod __ { + pub use { + ::core::{ + self, + primitive::{ + u8, u16, u32, usize, u64, u128, + i8, i16, i32, isize, i64, i128, + bool, + char, + str, + }, + }, + ::std::{ + self, + *, + prelude::rust_2021::*, + }, + crate::{ + headers::{ + Definer, + languages::{ + self, + EnumVariant, + FunctionArg, + HeaderLanguage, + StructField, + }, + }, + layout::{ + CLayoutOf, + ConcreteReprC, + CType, + OpaqueKind, + ReprC, + __HasNiche__, + }, + } + }; +} diff --git a/src/c_char.rs b/src/c_char.rs index 05b019af5a..459665fd59 100644 --- a/src/c_char.rs +++ b/src/c_char.rs @@ -38,7 +38,7 @@ const _: () = { }; unsafe -impl CType +impl LegacyCType for c_char { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) @@ -59,7 +59,21 @@ impl CType ) } + fn c_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + __cfg_csharp__! { + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + fn csharp_ty () -> rust::String { diff --git a/src/char_p.rs b/src/char_p.rs index 0ce8a68ae4..49cdf5c00e 100644 --- a/src/char_p.rs +++ b/src/char_p.rs @@ -275,12 +275,7 @@ ReprC! { ); } -#[cfg_attr(feature = "proc_macros", - require_unsafe_in_bodies, -)] -#[cfg_attr(not(feature = "proc_macros"), - allow(unused_unsafe), -)] +#[deny(unsafe_op_in_unsafe_fn)] impl char_p_raw { /// # Safety /// @@ -447,7 +442,7 @@ cfg_alloc! { @for['lt] &'lt str => rust::String, // @for['lt] str::Ref<'lt> => rust::String, rust::String => rust::String, - String => rust::String, + repr_c::String => rust::String, } cfg_std! { derive_MyFrom_from! { diff --git a/src/dyn_traits.rs b/src/dyn_traits.rs new file mode 100644 index 0000000000..acdeeb1dc3 --- /dev/null +++ b/src/dyn_traits.rs @@ -0,0 +1,207 @@ +use_prelude!(); + +hidden_export! { + trait __AssocConst { + const CONST: Ty; + } +} +hidden_export! { + #[cfg(feature = "alloc")] + trait __assert_dyn_safe { + fn m(self: rust::Box); + } +} + +pub use self::ty::{ + Erased as ErasedTy, +}; + +#[super::derive_ReprC] +#[repr(transparent)] +#[allow(missing_debug_implementations)] +pub +struct ErasedRef<'a>( + ptr::NonNull, + ::core::marker::PhantomData<&'a ()>, +); + +// #[macro_export] +// macro_rules! const_ {( +// $( +// for $generics:tt $(where { $($wc:tt)* })? , +// )? +// $VALUE:block : $T:ty +// ) => ({ +// struct __Generics $generics ( +// *mut Self, +// ) +// // where +// // $($($wc)*)? +// ; + +// impl $generics +// $crate::dyn_traits::__AssocConst<$T> +// for +// __Generics $generics +// where +// $($($wc)*)? +// { +// const CONST: $T = $VALUE; +// } + +// <__Generics $generics as $crate::dyn_traits::__AssocConst<$T>>::CONST +// })} + +pub +trait ReprCTrait { + type VTable : ConcreteReprC; + + unsafe + fn drop_ptr ( + ptr: ptr::NonNullOwned, + vtable: &'_ Self::VTable, + ) + ; + + // fn type_name ( + // vtable: &'_ Self::VTable, + // ) -> &'static str + // ; +} + +mod ty { + #![allow(warnings)] + + #[super::derive_ReprC] + #[repr(transparent)] + // #[ReprC::opaque] + pub + struct Erased(crate::tuple::CVoid); // { _private: () } +} + +pub +trait VirtualPtrFromBox : ReprCTrait { // DynTrait : ?Sized + ReprCTrait > : Sized { + fn boxed_into_virtual_ptr ( + this: rust::Box, + ) -> VirtualPtr + ; +} + +impl< + T, + DynTrait : ?Sized + VirtualPtrFromBox, // + ReprCTrait, + // T : BoxedIntoVirtualPtr, +> + From> +for + VirtualPtr +{ + fn from (boxed: rust::Box) + -> VirtualPtr + { + DynTrait::boxed_into_virtual_ptr(boxed) + } +} + +use hack::VirtualPtr_; +mod hack { + #[super::derive_ReprC] + #[repr(C)] + #[allow(missing_debug_implementations)] + pub + struct VirtualPtr_ { + pub(in super) ptr: Ptr, + pub(in super) vtable: VTable, + } +} + +#[derive_ReprC] +#[repr(transparent)] +pub +struct VirtualPtr( + VirtualPtr_< + /* ptr: */ ptr::NonNullOwned, + /* vtable: */ ptr::NonNullRef, + > +); + +impl + Drop +for + VirtualPtr +{ + fn drop (self: &'_ mut VirtualPtr) + { + unsafe { + DynTrait::drop_ptr(self.0.ptr.copy(), self.__vtable()) + } + } +} + +impl VirtualPtr { + pub + unsafe + fn from_raw_parts ( + ptr: ptr::NonNullOwned, + vtable: ptr::NonNullRef, + ) -> VirtualPtr + { + Self(VirtualPtr_ { ptr, vtable }) + } + + pub + fn __ptr (self: &'_ VirtualPtr) + -> ptr::NonNull + { + self.0.ptr.0 + } + + pub + fn __vtable<'vtable> (self: &'_ VirtualPtr) + -> &'vtable DynTrait::VTable + { + unsafe { &*self.0.vtable.as_ptr() } + } +} + +unsafe +impl + Send +for + VirtualPtr +where + DynTrait : Send, +{} + +unsafe +impl + Sync +for + VirtualPtr +where + DynTrait : Sync, +{} + +impl + ::core::fmt::Debug +for + VirtualPtr +{ + fn fmt ( + self: &'_ VirtualPtr, + fmt: &'_ mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result + { + fmt .debug_struct(::core::any::type_name::()) + .field("ptr", &format_args!("{:p}", self.__ptr())) + .field("vtable", &format_args!( + concat!( + "{:p}", + // " ({})", + ), + self.__vtable(), + // DynTrait::type_name(self.__vtable()), + )) + .finish() + } +} diff --git a/src/ffi_export.rs b/src/ffi_export.rs index 5203f36d2a..3f4fe42037 100644 --- a/src/ffi_export.rs +++ b/src/ffi_export.rs @@ -18,6 +18,7 @@ macro_rules! __ffi_export__ { { $($tt)* } + $crate::__cfg_headers__! { $crate::inventory::submit! { #![crate = $crate] @@ -97,12 +98,7 @@ macro_rules! __ffi_export__ { { fn __return_type__ (_: T) where - T : $crate::layout::ReprC, - ::CLayout - : - $crate::layout::CType< - OPAQUE_KIND = $crate::layout::OpaqueKind::Concrete, - >, + T : $crate::layout::ConcreteReprC, {} let _ = __return_type__::<$Ret>; } @@ -114,12 +110,7 @@ macro_rules! __ffi_export__ { pub(in super) fn $arg_name (_: T) where - T : $crate::layout::ReprC, - ::CLayout - : - $crate::layout::CType< - OPAQUE_KIND = $crate::layout::OpaqueKind::Concrete, - >, + T : $crate::layout::ConcreteReprC, {} } let _ = __parameter__::$arg_name::<$arg_ty>; @@ -159,11 +150,12 @@ macro_rules! __ffi_export__ { ret }} + $crate::paste::item! { /// Define the N-API wrapping function. #[cfg(any( $( all(), - __hack = $node_js_arg_count, + [< __hack_ $node_js_arg_count >] = "", )? ))] const _: () = { @@ -259,6 +251,7 @@ macro_rules! __ffi_export__ { } } }; + } }; #[cfg(not(target_arch = "wasm32"))] diff --git a/src/headers/_mod.rs b/src/headers/_mod.rs index 243cd41e00..7b419f4def 100644 --- a/src/headers/_mod.rs +++ b/src/headers/_mod.rs @@ -112,6 +112,9 @@ use ::std::{ use_prelude!(); use rust::{String}; +pub // (in crate) +mod languages; + pub use definer::{Definer, HashSetDefiner}; mod definer; @@ -472,11 +475,11 @@ hidden_export! { { match lang { | Language::C => { - ::c_define_self(definer) + ::define_self(&crate::headers::languages::C, definer) }, #[cfg(feature = "csharp-headers")] | Language::CSharp => { - ::csharp_define_self(definer) + ::define_self(&crate::headers::languages::CSharp, definer) }, } } @@ -522,12 +525,12 @@ hidden_export! { } match lang { | Language::C => write!(out, - "\n {}", Arg::CLayout::c_var(arg_name), + "\n {}", Arg::CLayout::name_wrapping_var(&crate::headers::languages::C, arg_name), ), #[cfg(feature = "csharp-headers")] | Language::CSharp => write!(out, - "\n {marshaler}{}", Arg::CLayout::csharp_var(arg_name), + "\n {marshaler}{}", Arg::CLayout::name_wrapping_var(&crate::headers::languages::CSharp, arg_name), marshaler = Arg::CLayout::csharp_marshaler() .map(|m| format!("[MarshalAs({})]\n ", m)) @@ -553,7 +556,7 @@ hidden_export! { } writeln!(out, "{});\n", - Ret::CLayout::c_var(&fname_and_args), + Ret::CLayout::name_wrapping_var(&crate::headers::languages::C, &fname_and_args), ) }, @@ -567,7 +570,7 @@ hidden_export! { " {});\n", "}}\n", ), - Ret::CLayout::csharp_var(&fname_and_args), + Ret::CLayout::name_wrapping_var(&crate::headers::languages::CSharp, &fname_and_args), mb_marshaler = Ret::CLayout::csharp_marshaler() .map(|m| format!("[return: MarshalAs({})]\n ", m)) diff --git a/src/headers/definer.rs b/src/headers/definer.rs index 740c5cedbd..f1583b856a 100644 --- a/src/headers/definer.rs +++ b/src/headers/definer.rs @@ -50,7 +50,7 @@ mod define_once_seal { self: &'_ mut Self, name: &'_ str, write_typedef: &'_ mut dyn - FnMut (&'_ mut dyn Definer) -> io::Result<()> + FnMut(&'_ mut dyn Definer) -> io::Result<()> , ) -> io::Result<()> ; @@ -63,7 +63,7 @@ mod define_once_seal { self: &'_ mut Self, name: &'_ str, write_typedef: &'_ mut dyn - FnMut (&'_ mut dyn Definer) -> io::Result<()> + FnMut(&'_ mut dyn Definer) -> io::Result<()> , ) -> io::Result<()> { diff --git a/src/headers/languages/c.rs b/src/headers/languages/c.rs new file mode 100644 index 0000000000..196a9dacd3 --- /dev/null +++ b/src/headers/languages/c.rs @@ -0,0 +1,148 @@ +use super::*; + +pub +struct C; + +impl HeaderLanguage for C { + fn emit_docs ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + indent: &'_ Indentation, + ) -> io::Result<()> + { + mk_out!(indent, "{indent}", ctx.out()); + + if docs.is_empty() { + out!(("/** */")); + return Ok(()); + } + + out!(("/** \\brief")); + for line in docs.iter().copied().map(str::trim) { + let sep = if line.is_empty() { "" } else { " " }; + out!((" *{sep}{line}")); + } + out!((" */")); + + Ok(()) + } + + fn emit_simple_enum ( + self: &'_ C, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + enum_name: &'_ str, + size: Option<(bool, u8)>, + variants: &'_ [EnumVariant<'_>], + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, "{indent}", ctx.out()); + + let ref intn_t = + size.map(|(signed, bitwidth)| format!( + "{}int{bitwidth}_t", if signed { "" } else { "u" }, + )) + ; + + self.emit_docs(ctx, docs, indent)?; + + if let Some(intn_t) = intn_t { + out!(( + "/** \\remark Has the same ABI as `{intn_t}` **/" + "#ifdef DOXYGEN" + "typedef enum {enum_name}" + "#else" + "typedef {intn_t} {enum_name}_t; enum" + "#endif" + "{{" + )); + } else { + out!(("typedef enum {enum_name} {{")); + } + + if let _ = indent.scope() { + for v in variants { + self.emit_docs(ctx, v.docs, indent)?; + let variant_name = crate::utils::screaming_case(enum_name, v.name) /* ctx.adjust_variant_name( + Language::C, + enum_name, + v.name, + ) */; + if let Some(value) = v.discriminant { + out!(("{variant_name} = {value:?},")); + } else { + out!(("{variant_name},")); + } + } + } + + if intn_t.is_some() { + out!(( + "}}" + "#ifdef DOXYGEN" + "{enum_name}_t" + "#endif" + ";" + )); + } else { + out!(("}} {enum_name}_t;")); + } + + out!("\n"); + Ok(()) + } + + fn emit_struct ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + size: usize, + fields: &'_ [StructField<'_>] + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, "{indent}", ctx.out()); + + if size == 0 { + panic!("C does not support zero-sized structs!") + } + + self.emit_docs(ctx, docs, indent)?; + + out!(("typedef struct {name} {{")); + if let _ = indent.scope() { + let ref mut first = true; + for f in fields { + if f.layout.size() == 0 && f.layout.align() > 1 { + panic!("Zero-sized fields must have an alignment of `1`"); + } + if mem::take(first).not() { + out!("\n"); + } + self.emit_docs(ctx, f.docs, indent)?; + out!("{indent}"); + (f.emit_unindented)(self, ctx)?; + out!(";\n"); + } + } + out!(("}} {name}_t;")); + + out!("\n"); + Ok(()) + } + + fn emit_function ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + fname: &'_ str, + arg_names: &'_ [FunctionArg<'_>], + ret_ty: &'_ str, + ) -> io::Result<()> + { + todo!() + } +} diff --git a/src/headers/languages/csharp.rs b/src/headers/languages/csharp.rs new file mode 100644 index 0000000000..9c5374f960 --- /dev/null +++ b/src/headers/languages/csharp.rs @@ -0,0 +1,121 @@ +use super::*; + +pub +struct CSharp; + +impl HeaderLanguage for CSharp { + fn emit_docs ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + indent: &'_ Indentation, + ) -> io::Result<()> + { + mk_out!(indent, "{indent}", ctx.out()); + + if docs.is_empty() { + // out!(("/// No documentation available> ")); + return Ok(()); + } + + out!(("/// ")); + for mut line in docs.iter().copied().map(str::trim) { + let mut storage = None; + if line.contains('`') { + let s = storage.get_or_insert_with(rust::String::new); + let mut parity = 0..; + line.chars().for_each(|c| match c { + | '`' => s.push_str(["", ""][parity.next().unwrap() % 2]), + | _ => s.push(c), + }); + line = s; + } + let sep = if line.is_empty() { "" } else { " " }; + out!(("///{sep}{line}")); + } + out!(("/// ")); + + Ok(()) + } + + fn emit_simple_enum ( + self: &'_ CSharp, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + enum_name: &'_ str, + size: Option<(bool, u8)>, + variants: &'_ [EnumVariant<'_>], + ) -> io::Result<()> + { + let ref indent = Indentation::new(4 /* ctx.indent_width() */); + mk_out!(indent, "{indent}", ctx.out()); + + let ref IntN = + size.map(|(signed, bitwidth)| if bitwidth != 8 { + format!( + "{}Int{bitwidth}", if signed { "" } else { "U" }, + ) + } else { + format!( + "{}byte", if signed { "s" } else { "" }, + ) + }) + ; + + self.emit_docs(ctx, docs, indent)?; + + out!( + ("public enum {enum_name}_t {super} {{"), + super = if let Some(IntN) = IntN { + format!(": {IntN}") + } else { + "".into() + }, + ); + + if let _ = indent.scope() { + for v in variants { + self.emit_docs(ctx, v.docs, indent)?; + let variant_name = v.name /* ctx.adjust_variant_name( + Language::CSharp, + enum_name, + v.name, + ) */; + if let Some(value) = v.discriminant { + out!(("{variant_name} = {value:?},")); + } else { + out!(("{variant_name},")); + } + } + } + + out!(("}}")); + + out!("\n"); + Ok(()) + } + + fn emit_struct ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + size: usize, + fields: &'_ [StructField<'_>] + ) -> io::Result<()> + { + todo!() + } + + fn emit_function ( + self: &'_ Self, + ctx: &'_ mut dyn Definer, + docs: Docs<'_>, + fname: &'_ str, + arg_names: &'_ [FunctionArg<'_>], + ret_ty: &'_ str, + ) -> io::Result<()> + { + todo!() + } +} diff --git a/src/headers/languages/mod.rs b/src/headers/languages/mod.rs new file mode 100644 index 0000000000..462f751813 --- /dev/null +++ b/src/headers/languages/mod.rs @@ -0,0 +1,265 @@ +#![allow(irrefutable_let_patterns)] + +use_prelude!(); +use { + ::std::io::{ + self, + Write as _, + }, + super::{ + Definer, + Language, + }, +}; + +pub use c::C; +mod c; + +pub use csharp::CSharp; +mod csharp; + +pub +struct Indentation { + depth: ::core::cell::Cell, + width: usize, +} + +impl Indentation { + pub + fn new (width: usize) + -> Indentation + { + Self { depth: 0.into(), width } + } + + pub + fn scope (self: &'_ Self) + -> impl '_ + Sized + { + self.depth.set(self.depth.get() + 1); + ::scopeguard::guard((), move |()| { + self.depth.set(self.depth.get() - 1); + }) + } +} + +impl ::core::fmt::Display for Indentation { + fn fmt ( + self: &'_ Indentation, + fmt: &'_ mut ::core::fmt::Formatter<'_>, + ) -> ::core::fmt::Result + { + write!(fmt, "{: = &'lt [&'lt str]; + +pub +trait HeaderLanguage : UpcastAny { + fn emit_docs ( + self: &'_ Self, + out: &'_ mut dyn Definer, + docs: Docs<'_>, + indentation: &'_ Indentation, + ) -> io::Result<()> + ; + + fn emit_simple_enum ( + self: &'_ Self, + out: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + // `(is_signed, bitwidth)` + size: Option<(bool, u8)>, + variants: &'_ [EnumVariant<'_>], + ) -> io::Result<()> + ; + + fn emit_struct ( + self: &'_ Self, + out: &'_ mut dyn Definer, + docs: Docs<'_>, + name: &'_ str, + size: usize, + fields: &'_ [StructField<'_>] + ) -> io::Result<()> + ; + + fn emit_function ( + self: &'_ Self, + out: &'_ mut dyn Definer, + docs: Docs<'_>, + fname: &'_ str, + arg_names: &'_ [FunctionArg<'_>], + ret_ty: &'_ str, + ) -> io::Result<()> + ; +} + +pub +struct EnumVariant<'lt> { + pub + docs: Docs<'lt>, + + pub + name: &'lt str, + + pub + discriminant: Option<&'lt dyn ::core::fmt::Debug>, +} + +pub +struct StructField<'lt> { + pub + docs: Docs<'lt>, + + pub + name: &'lt str, + + pub + emit_unindented: &'lt dyn + Fn(&'_ dyn HeaderLanguage, &'_ mut dyn Definer) -> io::Result<()> + , + + pub + layout: ::std::alloc::Layout, +} + +pub +struct FunctionArg<'lt> { + pub + docs: Docs<'lt>, + + pub + name: &'lt str, + + pub + emit_unindented: &'lt dyn + Fn(&'_ dyn HeaderLanguage, &'_ mut dyn Definer) -> io::Result<()> + , +} + +/// Generates an `out!` macro. +/// +/// Important: the `out!` macro accepts a `("foo" "bar" "baz")` shorthand +/// for the format literal parameter, to automatically convert it to: +/// +/** ```rust ,ignore +concat!( + "{indent}foo\n", + "{indent}bar\n", + "{indent}baz\n", +) +``` */ +/// +/// where `"{indent}"` is the first parameter passed to `mk_out!`, +/// and the second parameter is the `impl Write` the `write!`s will +/// be outputting to. +macro_rules! mk_out { + ( + $indent_name:ident, + $indent:tt, + $out:expr $(,)? + ) => ( + mk_out! { $indent_name $indent $out $ } + ); + + ( + $indent_name:tt $indent:tt $out:tt $_:tt + ) => ( + macro_rules! out { + ( + ($_( + $line:tt + )*) $_($rest:tt)* + ) => ( + with_builtin! { + let $concat = concat!($_( + $indent, + $line, + "\n", + )*) in { + ::safer_ffi_proc_macros::__respan! { + ( $_($line)* ) + ( + write!( + $out, + $concat + // , $indent_name = $indent_name + $_($rest)* + )? + ) + } + } + } + // write!( + // $out, + // concat!($_( + // // "{", stringify!($indent), "}", + // $indent, + // $line, + // "\n", + // )*) + // , $indent_name = $indent_name + // $_($rest)* + // ) + ); + + ( $_($tt:tt)* ) => ( + write!($out, $_($tt)*)? + ) + } + ); +} use mk_out; + +pub +trait UpcastAny : 'static { + fn upcast_any (self: &'_ Self) + -> &dyn ::core::any::Any + ; +} +impl UpcastAny for T { + fn upcast_any (self: &'_ Self) + -> &dyn ::core::any::Any + { + self + } +} + +impl dyn HeaderLanguage { + pub + fn is ( + self: &'_ Self, + ) -> bool + { + self.upcast_any().is::() + } + + pub + fn downcast_ref ( + self: &'_ Self, + ) -> Option<&'_ Concrete> + { + self.upcast_any() + .downcast_ref() + } +} diff --git a/src/layout/_mod.rs b/src/layout/_mod.rs index a8a8cdb715..24bf88ca9b 100644 --- a/src/layout/_mod.rs +++ b/src/layout/_mod.rs @@ -4,7 +4,10 @@ use_prelude!(); __cfg_headers__! { - use crate::headers::Definer; + use crate::headers::{ + Definer, + languages::*, + }; } #[macro_use] @@ -13,11 +16,9 @@ mod macros; #[doc(inline)] pub use crate::{from_CType_impl_ReprC, ReprC, CType}; -cfg_proc_macros! { - pub use crate::{ - derive_ReprC, - }; -} +pub use crate::{ + derive_ReprC, +}; type_level_enum! { pub @@ -28,10 +29,116 @@ type_level_enum! { } pub -fn __assert_concrete__() where - T : ReprC, - ::CLayout : CType, -{} +unsafe +trait CType +: + Sized + + Copy + +{ + type OPAQUE_KIND : OpaqueKind::T; + + __cfg_headers__! { + fn short_name () + -> String + ; + + #[inline] + fn define_self ( + language: &'_ dyn HeaderLanguage, + definer: &'_ mut dyn Definer, + ) -> io::Result<()> + ; + + fn name ( + _language: &'_ dyn HeaderLanguage, + ) -> String + { + format!("{}_t", Self::short_name()) + } + + fn name_wrapping_var ( + language: &'_ dyn HeaderLanguage, + var_name: &'_ str, + ) -> String + { + let sep = if var_name.is_empty() { "" } else { " " }; + format!("{}{sep}{var_name}", Self::name(language)) + } + + /// Optional marshaler attached to the type (_e.g._, + /// `[MarshalAs(UnmanagedType.FunctionPtr)]`) + fn csharp_marshaler () + -> Option + { + None + } + } +} + +unsafe +impl CType for T { + type OPAQUE_KIND = ::OPAQUE_KIND; + + #[inline] + fn short_name () + -> String + { + ::c_short_name().to_string() + } + + #[inline] + fn define_self ( + language: &'_ dyn HeaderLanguage, + definer: &'_ mut dyn Definer, + ) -> io::Result<()> + { + match () { + | _case if language.is::() => { + ::c_define_self(definer) + }, + | _case if language.is::() => { + ::csharp_define_self(definer) + }, + | _ => unimplemented!(), + } + } + + #[inline] + fn name ( + language: &'_ dyn HeaderLanguage, + ) -> String + { + Self::name_wrapping_var(language, "") + } + + #[inline] + fn name_wrapping_var ( + language: &'_ dyn HeaderLanguage, + var_name: &'_ str, + ) -> String + { + match () { + | _case if language.is::() => { + ::c_var(var_name).to_string() + }, + | _case if language.is::() => { + let sep = if var_name.is_empty() { "" } else { " " }; + format!("{}{sep}{var_name}", Self::csharp_ty()) + }, + | _ => unimplemented!(), + } + } + + #[inline] + fn csharp_marshaler () + -> Option + { + ::legacy_csharp_marshaler() + } +} + +pub +type CLayoutOf = ::CLayout; /// One of the two core traits of this crate (with [`ReprC`][`trait@ReprC`]). /// @@ -64,7 +171,7 @@ fn __assert_concrete__() where /// /// - This crates provides as many of these implementations as possible. /// -/// - an recursively, a non-zero-sized `#[repr(C)]` struct of `CType` fields. +/// - and recursively, a non-zero-sized `#[repr(C)]` struct of `CType` fields. /// /// - the [`CType!`] macro can be used to wrap a `#[repr(C)]` struct /// definition to _safely_ and automagically implement the trait @@ -76,10 +183,11 @@ fn __assert_concrete__() where /// /// For such types, see the [`ReprC`][`trait@ReprC`] trait. pub -unsafe trait CType +unsafe trait LegacyCType : Sized + Copy + + CType + { type OPAQUE_KIND : OpaqueKind::T; __cfg_headers__! { @@ -132,7 +240,21 @@ unsafe trait CType fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) -> fmt::Result ; - + // { + // Self::short_name_fmt(&C, fmt) + // } + + // fn short_name_fmt ( + // language: &'_ dyn HeaderLanguage, + // fmt: &'_ mut fmt::Formatter<'_>, + // ) -> fmt::Result + // { + // match () { + // | _case if language.is::() => Self::c_short_name_fmt(fmt), + // // | _case if language.is::() => Self::csharp_short_name_fmt(fmt), + // | _ => unimplemented!(), + // } + // } /// Convenience function for _callers_ / users of types implementing /// [`CType`][`trait@CType`]. @@ -242,10 +364,20 @@ unsafe trait CType #[inline] fn c_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> - { - let _ = definer; - Ok(()) - } + ; + // { + // Self::define_self(&C, definer) + // } + + // #[inline] + // fn define_self ( + // language: &'_ dyn HeaderLanguage, + // definer: &'_ mut dyn Definer, + // ) -> io::Result<()> + // { + // let _ = (language, definer); + // Ok(()) + // } /// The core method of the trait: it provides the implementation to be /// used by [`CType::c_var`], by bringing a `Formatter` in scope. @@ -366,14 +498,17 @@ unsafe trait CType /// Extra typedef code (_e.g._ `[LayoutKind.Sequential] struct ...`) fn csharp_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> - { - let _ = definer; - Ok(()) - } + ; + // { + // Self::define_self( + // &CSharp, + // definer, + // ) + // } /// Optional marshaler attached to the type (_e.g._, /// `[MarshalAs(UnmanagedType.FunctionPtr)]`) - fn csharp_marshaler () + fn legacy_csharp_marshaler () -> Option { None @@ -408,7 +543,7 @@ __cfg_headers__! { #[allow(missing_debug_implementations)] pub - struct ImplDisplay<'__, T : CType> { + struct ImplDisplay<'__, T : LegacyCType> { pub(in super) var_name: &'__ str, @@ -416,7 +551,7 @@ __cfg_headers__! { _phantom: ::core::marker::PhantomData, } - impl Display + impl Display for ImplDisplay<'_, T> { #[inline] @@ -434,12 +569,12 @@ __cfg_headers__! { #[allow(missing_debug_implementations)] pub - struct ImplDisplay { + struct ImplDisplay { pub(in super) _phantom: ::core::marker::PhantomData, } - impl Display + impl Display for ImplDisplay { #[inline] @@ -752,12 +887,7 @@ fn from_raw_unchecked (c_layout: T::CLayout) } } -#[cfg_attr(all(feature = "proc_macros", not(docs)), - require_unsafe_in_body, -)] -#[cfg_attr(not(feature = "proc_macros"), - allow(unused_unsafe), -)] +#[deny(unsafe_op_in_unsafe_fn)] #[inline] pub unsafe @@ -777,12 +907,7 @@ fn from_raw (c_layout: T::CLayout) } } -#[cfg_attr(all(feature = "proc_macros", not(docs)), - require_unsafe_in_body, -)] -#[cfg_attr(not(feature = "proc_macros"), - allow(unused_unsafe), -)] +#[deny(unsafe_op_in_unsafe_fn)] #[inline] pub unsafe // May not be sound when input has uninit bytes that the output does not @@ -802,5 +927,35 @@ mod impls; mod niche; -#[doc(hidden)] /* Not part of the public API */ pub +#[apply(hidden_export)] use niche::HasNiche as __HasNiche__; + +#[apply(hidden_export)] +trait Is { type EqTo : ?Sized; } +impl Is for T { type EqTo = Self; } + +/// Alias for `ReprC where Self::CLayout::OPAQUE_KIND = OpaqueKind::Concrete` +pub +trait ConcreteReprC +where + Self : ReprC, +{ + type ConcreteCLayout + : + Is> + + CType + + ; +} +impl ConcreteReprC for T +where + Self : ReprC, + CLayoutOf : CType, +{ + type ConcreteCLayout = CLayoutOf; +} + +#[apply(hidden_export)] +fn __assert_concrete__ () +where + T : ConcreteReprC, +{} diff --git a/src/layout/impls.rs b/src/layout/impls.rs index 457ed90b6f..886ef3bf2b 100644 --- a/src/layout/impls.rs +++ b/src/layout/impls.rs @@ -1,6 +1,8 @@ #![cfg_attr(rustfmt, rustfmt::skip)] use super::*; +// use crate::layout::CTypeFmt; + const_assert! { ::core::mem::size_of::() == @@ -93,12 +95,12 @@ const _: () = { macro_rules! impl_CTypes { @arrays $($N:tt)* ) => ($( - // CType + // LegacyCType /// Simplified for lighter documentation, but the actual impls /// range **from `1` up to `32`, plus a bunch of significant /// lengths up to `1024`**. unsafe // Safety: Rust arrays _are_ `#[repr(C)]` - impl CType + impl LegacyCType for [Item; $N] { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) @@ -107,7 +109,7 @@ const _: () = { macro_rules! impl_CTypes { // item_N_array write!(fmt, concat!("{}_", stringify!($N), "_array"), - Item::c_short_name(), + Item::short_name(), ) } @@ -118,14 +120,14 @@ const _: () = { macro_rules! impl_CTypes { definer.define_once( me, &mut |definer| { - Item::c_define_self(definer)?; + Item::define_self(&crate::headers::languages::C, definer)?; writeln!(definer.out(), concat!( "typedef struct {{\n", " {inline_array};\n", "}} {me};\n", ), - inline_array = Item::c_var(concat!( + inline_array = Item::name_wrapping_var(&crate::headers::languages::C, concat!( "idx[", stringify!($N), "]", )), me = me, @@ -153,7 +155,7 @@ const _: () = { macro_rules! impl_CTypes { -> io::Result<()> { let ref me = Self::csharp_ty(); - Item::csharp_define_self(definer)?; + Item::define_self(&crate::headers::languages::CSharp, definer)?; definer.define_once(me, &mut |definer| { let array_items = { // Poor man's specialization to use `fixed` arrays. @@ -166,7 +168,7 @@ const _: () = { macro_rules! impl_CTypes { { format!( " public fixed {ItemTy} arr[{N}];\n", - ItemTy = Item::csharp_ty(), + ItemTy = Item::name(&crate::headers::languages::CSharp), N = $N, // no need for a marshaler here ) @@ -178,7 +180,7 @@ const _: () = { macro_rules! impl_CTypes { " \ {marshaler}\ public {ItemTy} _{i};\n", - ItemTy = Item::csharp_ty(), + ItemTy = Item::name(&crate::headers::languages::CSharp), i = i, marshaler = Item::csharp_marshaler() @@ -241,7 +243,7 @@ const _: () = { macro_rules! impl_CTypes { } )? - // CType + // LegacyCType /// Simplified for lighter documentation, but the actual impls include /// **up to 9 function parameters**. unsafe // Safety: this is the "blessed" type recommended across Rust @@ -251,25 +253,25 @@ const _: () = { macro_rules! impl_CTypes { Ret : CType, $( $An : CType, $( $Ai : CType, - )*)?> CType + )*)?> LegacyCType for Option Ret> { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) -> fmt::Result { // ret_arg1_arg2_fptr - Ret::c_short_name_fmt(fmt)?; $( - write!(fmt, "_{}", $An::c_short_name())?; $( - write!(fmt, "_{}", $Ai::c_short_name())?; )*)? + fmt.write_str(&Ret::short_name())?; $( + write!(fmt, "_{}", $An::short_name())?; $( + write!(fmt, "_{}", $Ai::short_name())?; )*)? fmt.write_str("_fptr") } fn c_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> { - Ret::c_define_self(definer)?; $( - $An::c_define_self(definer)?; $( - $Ai::c_define_self(definer)?; )*)? + Ret::define_self(&crate::headers::languages::C, definer)?; $( + $An::define_self(&crate::headers::languages::C, definer)?; $( + $Ai::define_self(&crate::headers::languages::C, definer)?; )*)? Ok(()) } @@ -278,12 +280,12 @@ const _: () = { macro_rules! impl_CTypes { var_name: &'_ str, ) -> fmt::Result { - write!(fmt, "{} ", Ret::c_var(""))?; + write!(fmt, "{} ", Ret::name(&crate::headers::languages::C))?; write!(fmt, "(*{})(", var_name)?; let _empty = true; $( let _empty = false; - write!(fmt, "{}", $An::c_var(""))?; $( - write!(fmt, ", {}", $Ai::c_var(""))?; )*)? + write!(fmt, "{}", $An::name(&crate::headers::languages::C))?; $( + write!(fmt, ", {}", $Ai::name(&crate::headers::languages::C))?; )*)? if _empty { fmt.write_str("void")?; } @@ -294,10 +296,10 @@ const _: () = { macro_rules! impl_CTypes { fn csharp_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> { - Ret::csharp_define_self(definer)?; $( - $An::csharp_define_self(definer)?; $( - $Ai::csharp_define_self(definer)?; )*)? - let ref me = Self::csharp_ty(); + Ret::define_self(&crate::headers::languages::CSharp, definer)?; $( + $An::define_self(&crate::headers::languages::CSharp, definer)?; $( + $Ai::define_self(&crate::headers::languages::CSharp, definer)?; )*)? + let ref me = Self::name(&crate::headers::languages::CSharp).to_string(); let ref mut _arg = { let mut iter = (0 ..).map(|c| format!("_{}", c)); move || iter.next().unwrap() @@ -337,9 +339,9 @@ const _: () = { macro_rules! impl_CTypes { .as_deref() .unwrap_or("") , - Ret = Ret::csharp_ty(), $( - $An = $An::csharp_var(&_arg()), $( - $Ai = $Ai::csharp_var(&_arg()), )*)? + Ret = Ret::name(&crate::headers::languages::CSharp), $( + $An = $An::name_wrapping_var(&crate::headers::languages::CSharp, &_arg()), $( + $Ai = $Ai::name_wrapping_var(&crate::headers::languages::CSharp, &_arg()), )*)? )) } @@ -349,7 +351,7 @@ const _: () = { macro_rules! impl_CTypes { Self::c_short_name().to_string() } - fn csharp_marshaler () + fn legacy_csharp_marshaler () -> Option { // This assumes the calling convention from the above @@ -503,7 +505,7 @@ const _: () = { macro_rules! impl_CTypes { )* ) => ($( $unsafe // Safety: guaranteed by the caller of the macro - impl CType + impl LegacyCType for $RustInt { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) @@ -541,6 +543,13 @@ const _: () = { macro_rules! impl_CTypes { } __cfg_csharp__! { + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + fn csharp_ty () -> rust::String { @@ -558,7 +567,7 @@ const _: () = { macro_rules! impl_CTypes { )* ) => ($( $unsafe // Safety: guaranteed by the caller of the macro - impl CType + impl LegacyCType for $fN { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) @@ -579,7 +588,21 @@ const _: () = { macro_rules! impl_CTypes { ) } + fn c_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + __cfg_csharp__! { + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + fn csharp_ty () -> rust::String { @@ -594,19 +617,19 @@ const _: () = { macro_rules! impl_CTypes { @pointers ) => ( unsafe - impl CType + impl LegacyCType for *const T { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "{}_const_ptr", T::c_short_name()) + write!(fmt, "{}_const_ptr", T::short_name()) } fn c_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> { - T::c_define_self(definer) + T::define_self(&crate::headers::languages::C, definer) } fn c_var_fmt ( @@ -616,7 +639,7 @@ const _: () = { macro_rules! impl_CTypes { { write!(fmt, "{} const *{sep}{}", - T::c_var(""), + T::name(&crate::headers::languages::C), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -626,7 +649,7 @@ const _: () = { macro_rules! impl_CTypes { fn csharp_define_self (definer: &'_ mut dyn $crate::headers::Definer) -> $crate::std::io::Result<()> { - T::csharp_define_self(definer)?; + T::define_self(&crate::headers::languages::CSharp, definer)?; // definer.define_once("Const", &mut |definer| { // definer.out().write_all(concat!( // "[StructLayout(LayoutKind.Sequential)]\n", @@ -641,7 +664,7 @@ const _: () = { macro_rules! impl_CTypes { fn csharp_ty () -> rust::String { - format!("{} /*const*/ *", T::csharp_ty()) + format!("{} /*const*/ *", T::name(&crate::headers::languages::CSharp)) } } } type OPAQUE_KIND = OpaqueKind::Concrete; } @@ -661,19 +684,19 @@ const _: () = { macro_rules! impl_CTypes { } unsafe - impl CType + impl LegacyCType for *mut T { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "{}_ptr", T::c_short_name()) + write!(fmt, "{}_ptr", T::short_name()) } fn c_define_self (definer: &'_ mut dyn Definer) -> io::Result<()> { - T::c_define_self(definer) + T::define_self(&crate::headers::languages::C, definer) } fn c_var_fmt ( @@ -683,7 +706,7 @@ const _: () = { macro_rules! impl_CTypes { { write!(fmt, "{} *{sep}{}", - T::c_var(""), + T::name(&crate::headers::languages::C), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -693,13 +716,13 @@ const _: () = { macro_rules! impl_CTypes { fn csharp_define_self (definer: &'_ mut dyn $crate::headers::Definer) -> $crate::std::io::Result<()> { - T::csharp_define_self(definer) + T::define_self(&crate::headers::languages::CSharp, definer) } fn csharp_ty () -> rust::String { - format!("{} *", T::csharp_ty()) + format!("{} *", T::name(&crate::headers::languages::CSharp)) } } } type OPAQUE_KIND = OpaqueKind::Concrete; } @@ -830,7 +853,7 @@ const _: () = { }; unsafe - impl CType + impl LegacyCType for Bool { __cfg_headers__! { @@ -866,7 +889,14 @@ unsafe } __cfg_csharp__! { - fn csharp_marshaler () + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + + fn legacy_csharp_marshaler () -> Option { Some("UnmanagedType.U1".into()) @@ -898,7 +928,7 @@ impl ::core::fmt::Debug for c_int { } unsafe - impl CType + impl LegacyCType for c_int { __cfg_headers__! { @@ -920,14 +950,28 @@ unsafe ) } + fn c_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + __cfg_csharp__! { + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + fn csharp_ty () -> rust::String { "int".into() } - fn csharp_marshaler () + fn legacy_csharp_marshaler () -> Option { Some("UnmanagedType.SysInt".into()) diff --git a/src/layout/macros.rs b/src/layout/macros.rs index ef41457232..12bd167cc9 100644 --- a/src/layout/macros.rs +++ b/src/layout/macros.rs @@ -74,7 +74,7 @@ macro_rules! __with_doc__ {( struct $($rest)* )} -/// Safely implement [`CType`][`trait@crate::layout::CType`] +/// Safely implement [`CType`][`trait@crate::layout::LegacyCType`] /// for a `#[repr(C)]` struct **when all its fields are `CType`**. /// /// Note: you rarely need to call this macro directly. Instead, look for the @@ -234,7 +234,7 @@ macro_rules! CType {( } unsafe // Safety: struct is `#[repr(C)]` and contains `CType` fields - impl $(<$($lt ,)* $($($generics),+)?>)? $crate::layout::CType + impl $(<$($lt ,)* $($($generics),+)?>)? $crate::layout::LegacyCType for $StructName$(<$($lt ,)* $($($generics),+)?>)? where $( @@ -258,7 +258,7 @@ macro_rules! CType {( <$generics as $crate::layout::ReprC>::CLayout as $crate::layout::CType - >::c_short_name() + >::short_name() )?; )+ )?)? @@ -273,14 +273,14 @@ macro_rules! CType {( "C does not support zero-sized structs!", ); let ref me = - ::c_var("") + ::name(&$crate::headers::languages::C) .to_string() ; definer.define_once( me, &mut |definer| { $( - <$field_ty as $crate::layout::CType>::c_define_self(definer)?; + <$field_ty as $crate::layout::CType>::define_self(&$crate::headers::languages::C, definer)?; )* let out = definer.out(); $( @@ -295,7 +295,8 @@ macro_rules! CType {( $(#[$($field_meta)*])* ); $crate::core::writeln!(out, " {};\n", - <$field_ty as $crate::layout::CType>::c_var( + <$field_ty as $crate::layout::CType>::name_wrapping_var( + &$crate::headers::languages::C, $crate::core::stringify!($field_name), ), )?; @@ -322,7 +323,7 @@ macro_rules! CType {( { $crate::core::write!(fmt, "{}_t{sep}{}", - ::c_short_name(), + ::short_name(), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -336,9 +337,9 @@ macro_rules! CType {( $crate::core::mem::size_of::(), 0, "C# does not support zero-sized structs!", ); - let ref me = ::csharp_ty(); + let ref me = ::name(&$crate::headers::languages::CSharp).to_string(); $( - <$field_ty as $crate::layout::CType>::csharp_define_self(definer)?; + <$field_ty as $crate::layout::CType>::define_self(&$crate::headers::languages::CSharp, definer)?; )* definer.define_once(me, &mut |definer| $crate::core::writeln!(definer.out(), $crate::core::concat!( @@ -362,7 +363,8 @@ macro_rules! CType {( if $crate::core::mem::size_of::<$field_ty>() > 0 { format!( " public {};\n", - <$field_ty as $crate::layout::CType>::csharp_var( + <$field_ty as $crate::layout::CType>::name_wrapping_var( + &$crate::headers::languages::CSharp, $crate::core::stringify!($field_name), ), ) @@ -422,7 +424,7 @@ macro_rules! CType {( | _ => $crate::std::panic!( "ill-formed enum variant ({:?}) for type `{}`", &self.0, - <$Enum_Layout as $crate::layout::CType>::c_short_name(), + <$Enum_Layout as $crate::layout::CType>::short_name(), ), }) } @@ -842,7 +844,7 @@ macro_rules! ReprC { impl $(<$($generics)*>)? $crate::layout::__HasNiche__ for $StructName $(<$($generics)*>)? where - $field_ty : $crate::layout::__HasNiche__, + for<'hack> $crate::__TrivialBound<'hack, $field_ty> : $crate::layout::__HasNiche__, $($( $($bounds)* )?)? @@ -912,7 +914,7 @@ macro_rules! ReprC { } unsafe - impl $crate::layout::CType + impl $crate::layout::LegacyCType for [< $EnumName _Layout >] { $crate::__cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut $crate::core::fmt::Formatter<'_>) @@ -925,7 +927,7 @@ macro_rules! ReprC { -> $crate::std::io::Result<()> { let ref me = - ::c_var("") + ::name(&$crate::headers::languages::C) .to_string() ; definer.define_once( @@ -933,7 +935,7 @@ macro_rules! ReprC { &mut |definer| { let me_t = me; let ref me = - ::c_short_name() + ::short_name() .to_string() ; let out = definer.out(); @@ -988,7 +990,7 @@ macro_rules! ReprC { { $crate::core::write!(fmt, "{}_t{sep}{}", - ::c_short_name(), + ::short_name(), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -998,7 +1000,7 @@ macro_rules! ReprC { fn csharp_define_self (definer: &'_ mut dyn $crate::headers::Definer) -> $crate::std::io::Result<()> { - let ref me = ::csharp_ty(); + let ref me = ::name(&$crate::headers::languages::CSharp).to_string(); definer.define_once(me, &mut |definer| $crate::core::writeln!(definer.out(), $crate::core::concat!( "public enum {me} {{\n", @@ -1135,7 +1137,7 @@ macro_rules! ReprC { } unsafe - impl $crate::layout::CType + impl $crate::layout::LegacyCType for [< $EnumName _Layout >] { $crate::__cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut $crate::core::fmt::Formatter<'_>) @@ -1148,7 +1150,7 @@ macro_rules! ReprC { -> $crate::std::io::Result<()> { let ref me = - ::c_var("") + ::name(&$crate::headers::languages::C) .to_string() ; definer.define_once( @@ -1156,10 +1158,10 @@ macro_rules! ReprC { &mut |definer| { let me_t = me; let ref me = - ::c_short_name() + ::short_name() .to_string() ; - <$crate::$Int as $crate::layout::CType>::c_define_self( + <$crate::$Int as $crate::layout::CType>::define_self(&$crate::headers::languages::C, definer, )?; let out = definer.out(); @@ -1212,8 +1214,9 @@ macro_rules! ReprC { )* me = me, me_t = me_t, - int = <$crate::$Int as $crate::layout::CType>::c_var(""), - int__me_t = <$crate::$Int as $crate::layout::CType>::c_var( + int = <$crate::$Int as $crate::layout::CType>::name(&$crate::headers::languages::C), + int__me_t = <$crate::$Int as $crate::layout::CType>::name_wrapping_var( + &$crate::headers::languages::C, me_t, ), ) @@ -1228,7 +1231,7 @@ macro_rules! ReprC { { $crate::core::write!(fmt, "{}_t{sep}{}", - ::c_short_name(), + ::short_name(), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -1238,7 +1241,7 @@ macro_rules! ReprC { fn csharp_define_self (definer: &'_ mut dyn $crate::headers::Definer) -> $crate::std::io::Result<()> { - let ref me = ::csharp_ty(); + let ref me = ::name(&$crate::headers::languages::CSharp).to_string(); definer.define_once(me, &mut |definer| $crate::core::writeln!(definer.out(), $crate::core::concat!( "public enum {me} : {int} {{\n", @@ -1260,7 +1263,7 @@ macro_rules! ReprC { )? )* me = me, - int = <$crate::$Int as $crate::layout::CType>::csharp_ty(), + int = <$crate::$Int as $crate::layout::CType>::name(&$crate::headers::languages::CSharp), )) } } @@ -1423,7 +1426,7 @@ macro_rules! ReprC { unsafe impl $(<$($lt ,)* $($($generics),+)?>)? - $crate::layout::CType + $crate::layout::LegacyCType for __safer_ffi_Opaque__ $(<$($lt ,)* $($($generics),+)?>)? $( @@ -1449,7 +1452,7 @@ macro_rules! ReprC { -> $crate::std::io::Result<()> { let ref me = - ::c_var("") + ::name(&$crate::headers::languages::C) .to_string() ; definer.define_once(me, &mut |definer| { @@ -1460,7 +1463,7 @@ macro_rules! ReprC { ))); $crate::core::write!(definer.out(), "typedef struct {} {};\n\n", - ::c_short_name(), + ::short_name(), me, ) }) @@ -1473,7 +1476,7 @@ macro_rules! ReprC { { $crate::core::write!(fmt, "{}_t{sep}{}", - ::c_short_name(), + ::short_name(), var_name, sep = if var_name.is_empty() { "" } else { " " }, ) @@ -1483,7 +1486,7 @@ macro_rules! ReprC { fn csharp_define_self (definer: &'_ mut dyn $crate::headers::Definer) -> $crate::std::io::Result<()> { - let ref me = ::csharp_ty(); + let ref me = ::name(&$crate::headers::languages::CSharp).to_string(); definer.define_once(me, &mut |definer| { $crate::std::writeln!(definer.out(), concat!( @@ -1691,7 +1694,7 @@ mod test { } } - cfg_proc_macros! { doc_test! { derive_ReprC_supports_generics: + doc_test! { derive_ReprC_supports_generics: fn main () {} use ::safer_ffi::prelude::*; @@ -1715,7 +1718,7 @@ mod test { { inner: &'lifetime T, } - }} + } mod opaque { doc_test! { unused: diff --git a/src/layout/niche.rs b/src/layout/niche.rs index f7851976b7..00bbacfdc1 100644 --- a/src/layout/niche.rs +++ b/src/layout/niche.rs @@ -27,7 +27,7 @@ impl ReprC macro_rules! unsafe_impls {( $( $(@for[$($generics:tt)*])? - $T:ty => |$it:pat| $expr:expr + $T:ty => |$it:pat_param| $expr:expr ),* $(,)? ) => ( $( @@ -81,14 +81,14 @@ unsafe_impls! { cfg_alloc! { unsafe_impls! { // @for[T : ReprC] - // Box => |it| it.is_null(), + // repr_c::Box => |it| it.is_null(), @for[T : ReprC] c_slice::Box => |it| it.ptr.is_null(), @for[T : ReprC] - Vec => |it| it.ptr.is_null(), + repr_c::Vec => |it| it.ptr.is_null(), // str::Box => |it| it.ptr.is_null(), - // String => |it| it.ptr.is_null(), + // repr_c::String => |it| it.ptr.is_null(), // char_p::Box => |it| it.is_null(), } diff --git a/src/proc_macro/Cargo.toml b/src/proc_macro/Cargo.toml index 6f2224b347..8a61be8b0a 100644 --- a/src/proc_macro/Cargo.toml +++ b/src/proc_macro/Cargo.toml @@ -4,18 +4,20 @@ proc-macro = true [package] name = "safer_ffi-proc_macros" -# Keep in sync with `/Cargo.toml` -version = "0.0.5" +version = "0.1.0-rc1" # Keep in sync authors = ["Daniel Henry-Mantilla "] -edition = "2018" +edition = "2021" description = "Procedural macro internals of `::safer_ffi`" license = "MIT" [dependencies] -proc-macro2 = { version = "1.0", optional = true } -quote = { version = "1.0", optional = true } -proc-macro-hack = { version = "0.5.15", optional = true } +proc-macro2.version = "1.0" +quote.version = "1.0" + +# prettyplease.optional = true +prettyplease.version = "0.1.7" +macro_rules_attribute = "0.1.0" [dependencies.syn] version = "1.0" @@ -24,11 +26,15 @@ features = [ "full", "visit-mut", ] -optional = true [features] -async-fn = ["proc_macros"] +async-fn = [] +dyn-traits = [] headers = [] -node-js = ["async-fn", "proc_macros"] -proc_macros = [ "proc-macro2", "proc-macro-hack", "quote", "syn" ] -verbose-expansions = [] +js = [ + "async-fn", +] +node-js = ["js"] # legacy alias. +verbose-expansions = [ + # "prettyplease", +] diff --git a/src/proc_macro/_mod.rs b/src/proc_macro/_mod.rs index dbf4d82baa..2e620d87f7 100644 --- a/src/proc_macro/_mod.rs +++ b/src/proc_macro/_mod.rs @@ -1,70 +1,166 @@ #![allow(clippy::all)] #![cfg_attr(rustfmt, rustfmt::skip)] #![allow(nonstandard_style, unused_imports)] +#![allow(warnings)] // #![feature(proc_macro_span)] -extern crate proc_macro; - -use ::proc_macro::{Span, TokenStream}; - -#[cfg(feature = "proc_macros")] -use ::{ - proc_macro2::{ - Span as Span2, +use { + ::core::{ + ops::Not as _, + }, + ::proc_macro::{ + TokenStream, + }, + ::proc_macro2::{ + Span, TokenStream as TokenStream2, + TokenTree as TT, }, - quote::{ + ::quote::{ format_ident, quote, quote_spanned, ToTokens, }, - syn::{*, + ::syn::{*, parse::{ Parse, Parser, + ParseStream, }, punctuated::Punctuated, spanned::Spanned, - visit_mut::VisitMut, Result, }, + crate::utils::{ + *, + }, }; -use ::core::ops::Not as _; - -macro_rules! inline_mod {($modname:ident) => ( - include! { concat!(stringify!($modname), ".rs") } +macro_rules! todo {( + $( $fmt:expr $(, $($rest:tt)* )? )? +) => ( + ::core::todo! { + concat!(file!(), ":", line!(), ":", column!(), " ({})"), + ::core::format_args!( + concat!($($fmt)?), + $($( $($rest)* )?)? + ), + } )} -inline_mod!(utils); +#[macro_use] +extern crate macro_rules_attribute; -#[cfg(feature = "proc_macros")] -inline_mod!(derives); +mod c_str; -#[cfg(feature = "proc_macros")] -inline_mod!(c_str); +#[path = "derives/_mod.rs"] +mod derives; -include!("ffi_export/_mod.rs"); +#[path = "ffi_export/_mod.rs"] +mod ffi_export; + +#[path = "utils/_mod.rs"] +mod utils; -#[cfg(feature = "headers")] #[proc_macro_attribute] pub -fn cfg_headers (attrs: TokenStream, input: TokenStream) - -> TokenStream +fn cfg_headers ( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream { - if let Some(unexpected_tt) = attrs.into_iter().next() { - return compile_error("Unexpected parameter", unexpected_tt.span()); + parse_macro_input!(attrs as parse::Nothing); + if cfg!(feature = "headers") { + input + } else { + <_>::default() } - input } -#[cfg(not(feature = "headers"))] +#[proc_macro] pub +fn c_str (input: TokenStream) + -> TokenStream +{ + unwrap!(c_str::c_str(input.into())) +} + #[proc_macro_attribute] pub -fn cfg_headers (attrs: TokenStream, input: TokenStream) +fn ffi_export (attrs: TokenStream, input: TokenStream) -> TokenStream { - if let Some(unexpected_tt) = attrs.into_iter().next() { - return compile_error("Unexpected parameter", unexpected_tt.span()); + unwrap!(ffi_export::ffi_export(attrs.into(), input.into())) +} + +#[proc_macro_attribute] pub +fn derive_ReprC ( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream +{ + unwrap!(derives::derive_ReprC(attrs.into(), input.into())) +} + +#[proc_macro_attribute] pub +fn derive_CType ( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream +{ + unwrap!(derives::derive_CType(attrs.into(), input.into())) +} + +#[proc_macro_attribute] pub +fn derive_ReprC2 ( + attrs: TokenStream, + input: TokenStream, +) -> TokenStream +{ + unwrap!( + derives::repr_c::derive(attrs.into(), input.into()) + .map(utils::mb_file_expanded) + ) +} + +#[doc(hidden)] /** Not part of the public API */ #[proc_macro] pub +fn __respan ( + input: TokenStream, +) -> TokenStream +{ + // use ::proc_macro::*; + use ::proc_macro2::{*, TokenStream as TokenStream2}; + let parser = |input: ParseStream<'_>| Result::Ok({ + let mut contents; + ({ + parenthesized!(contents in input); + let tt: TokenTree = contents.parse()?; + let _: TokenStream2 = contents.parse()?; + tt.span() + }, { + parenthesized!(contents in input); + contents.parse::()? + }) + }); + let (span, tts) = parse_macro_input!(input with parser); + return respan(span, tts.into()).into(); + // where: + fn respan ( + span: Span, + tts: TokenStream2, + ) -> TokenStream2 + { + tts.into_iter().map(|mut tt| { + if let TokenTree::Group(ref mut g) = tt { + let g_span = g.span(); + *g = Group::new( + g.delimiter(), + respan(span, g.stream()), + ); + g.set_span(/* span.located_at */ g_span) + } else { + tt.set_span( + span //.located_at(tt.span()) + ); + } + tt + }).collect() } - let _ = input; - TokenStream::new() } diff --git a/src/proc_macro/c_str.rs b/src/proc_macro/c_str.rs index d5a5acc319..360925a7ce 100644 --- a/src/proc_macro/c_str.rs +++ b/src/proc_macro/c_str.rs @@ -1,12 +1,19 @@ -#[::proc_macro_hack::proc_macro_hack] pub -fn c_str (input: TokenStream) - -> TokenStream +use super::*; + +pub(in crate) +fn c_str ( + input: TokenStream2, +) -> Result { - let input: LitStr = if let Some(it) = parse_macro_input!(input) { it } else { - return ::quote::quote!( - ::safer_ffi::char_p::char_p_ref::EMPTY - ).into(); - }; + let input: LitStr = + if let Some(it) = parse2(input)? { + it + } else { + return Ok(::quote::quote!( + ::safer_ffi::char_p::char_p_ref::EMPTY + )); + } + ; let bytes = input.value(); let mut bytes = bytes.as_bytes(); let mut v; @@ -19,18 +26,18 @@ fn c_str (input: TokenStream) }, | Some(n) if n == bytes.len() - 1 => {}, | Some(bad_idx) => { - return Error::new_spanned(input, &format!( + return Err(Error::new_spanned(input, &format!( "Error, encountered inner nul byte at position {}", bad_idx, - )).to_compile_error().into(); + ))); }, } let byte_str = LitByteStr::new(bytes, input.span()); - ::quote::quote!( + Ok(::quote::quote!( unsafe { const STATIC_BYTES: &'static [u8] = #byte_str; ::safer_ffi::char_p::char_p_ref::from_ptr_unchecked( ::safer_ffi::ptr::NonNull::new_unchecked(STATIC_BYTES.as_ptr() as _) ) } - ).into() + )) } diff --git a/src/proc_macro/derives.rs b/src/proc_macro/derives.rs deleted file mode 100644 index a4dd2486d6..0000000000 --- a/src/proc_macro/derives.rs +++ /dev/null @@ -1,176 +0,0 @@ -inline_mod!(handle_fptr); - -fn feed_to_macro_rules (input: TokenStream, name: Ident) - -> TokenStream -{ - let input = parse_macro_input!(input as DeriveInput); - if let Some(expansion) = try_handle_fptr(&input) { - return expansion; - } - let DeriveInput { - attrs, - vis, - ident, - generics, - data, - } = input; - let ret = TokenStream::from(match data { - | Data::Enum(DataEnum { - enum_token: ref enum_, - ref variants, - .. - }) => quote! { - ::safer_ffi::layout::ReprC! { - #(#attrs)* - #vis - #enum_ #ident { - #variants - } - } - }, - | Data::Struct(DataStruct { - struct_token: ref struct_, - ref fields, - semi_token: ref maybe_semi_colon, - }) => { - let (params, bounds) = generics.my_split(); - quote! { - ::safer_ffi::layout::#name! { - #(#attrs)* - #vis - #struct_ #ident - [#params] - where { - #(#bounds ,)* - } - #fields - #maybe_semi_colon - } - } - }, - | Data::Union(ref union_) => { - Error::new_spanned( - union_.union_token, - "`union`s are not supported yet." - ).to_compile_error() - }, - }); - #[cfg(feature = "verbose-expansions")] - println!("{}", ret.to_string()); - ret -} - -/// Safely implement [`ReprC`] -/// for a `#[repr(C)]` struct **when all its fields are [`ReprC`]**. -/// -/// [`ReprC`]: /safer_ffi/layout/trait.ReprC.html -/// -/// # Examples -/// -/// ### Simple `struct` -/// -/// ```rust -/// use ::safer_ffi::prelude::*; -/// -/// #[derive_ReprC] -/// #[repr(C)] -/// struct Instant { -/// seconds: u64, -/// nanos: u32, -/// } -/// ``` -/// -/// - corresponding to the following C definition: -/// -/// ```C -/// typedef struct { -/// uint64_t seconds; -/// uint32_t nanos; -/// } Instant_t; -/// ``` -/// -/// ### Field-less `enum` -/// -/// ```rust -/// use ::safer_ffi::prelude::*; -/// -/// #[derive_ReprC] -/// #[repr(u8)] -/// enum Status { -/// Ok = 0, -/// Busy, -/// NotInTheMood, -/// OnStrike, -/// OhNo, -/// } -/// ``` -/// -/// - corresponding to the following C definition: -/// -/// ```C -/// typedef uint8_t Status_t; enum { -/// STATUS_OK = 0, -/// STATUS_BUSY, -/// STATUS_NOT_IN_THE_MOOD, -/// STATUS_ON_STRIKE, -/// STATUS_OH_NO, -/// } -/// ``` -/// -/// ### Generic `struct` -/// -/// In that case, it is required that the struct's generic types carry a -/// `: ReprC` bound each: -/// -/// ```rust -/// use ::safer_ffi::prelude::*; -/// -/// #[derive_ReprC] -/// #[repr(C)] -/// struct Point { -/// x: Coordinate, -/// y: Coordinate, -/// } -/// ``` -/// -/// Each monomorphization leads to its own C definition: -/// -/// - **`Point`** -/// -/// ```C -/// typedef struct { -/// int32_t x; -/// int32_t y; -/// } Point_int32_t; -/// ``` -/// -/// - **`Point`** -/// -/// ```C -/// typedef struct { -/// double x; -/// double y; -/// } Point_double_t; -/// ``` -#[cfg(feature = "proc_macros")] -#[proc_macro_attribute] pub -fn derive_ReprC (attrs: TokenStream, input: TokenStream) - -> TokenStream -{ - if let Some(tt) = TokenStream2::from(attrs).into_iter().next() { - return Error::new_spanned(tt, - "Unexpected parameter", - ).to_compile_error().into(); - } - feed_to_macro_rules(input, parse_quote!(ReprC)) -} - -#[proc_macro_attribute] pub -fn derive_CType (attrs: TokenStream, input: TokenStream) - -> TokenStream -{ - if let Some(unexpected_tt) = attrs.into_iter().next() { - return compile_error("Unexpected parameter", unexpected_tt.span()); - } - feed_to_macro_rules(input, parse_quote!(CType)) -} diff --git a/src/proc_macro/derives/_mod.rs b/src/proc_macro/derives/_mod.rs new file mode 100644 index 0000000000..20b42fed7c --- /dev/null +++ b/src/proc_macro/derives/_mod.rs @@ -0,0 +1,129 @@ +use super::*; + +#[path = "c_type/_mod.rs"] +mod c_type; + +#[path = "repr_c/_mod.rs"] +pub(in crate) +mod repr_c; + +#[cfg(feature = "dyn-traits")] +#[path = "dyn_trait/_mod.rs"] +mod dyn_trait; + +mod handle_fptr; + +fn feed_to_macro_rules ( + input: TokenStream2, + name: Ident, +) -> Result +{ + let input = parse2::(input)?; + if let Some(expansion) = handle_fptr::try_handle_fptr(&input) { + return Ok(expansion); + } + let DeriveInput { + attrs, + vis, + ident, + generics, + data, + } = input; + let ret = match data { + | Data::Enum(DataEnum { + enum_token: ref enum_, + ref variants, + .. + }) => quote! { + ::safer_ffi::layout::ReprC! { + #(#attrs)* + #vis + #enum_ #ident { + #variants + } + } + }, + | Data::Struct(DataStruct { + struct_token: ref struct_, + ref fields, + semi_token: ref maybe_semi_colon, + }) => { + let (params, bounds) = generics.my_split(); + quote! { + ::safer_ffi::layout::#name! { + #(#attrs)* + #vis + #struct_ #ident + [#params] + where { + #(#bounds ,)* + } + #fields + #maybe_semi_colon + } + } + }, + | Data::Union(ref union_) => { + Error::new_spanned( + union_.union_token, + "`union`s are not supported yet." + ).to_compile_error() + }, + }; + #[cfg(feature = "verbose-expansions")] + println!("{}", ret); + Ok(ret) +} + +pub(in crate) +fn derive_ReprC ( + mut attrs: TokenStream2, + mut input: TokenStream2, +) -> Result +{ + #![cfg_attr(not(feature = "dyn-traits"), allow(unused_mut))] + #[cfg(feature = "dyn-traits")] + if let Some(output) = dyn_trait::try_handle_trait(&mut attrs, &mut input)? { + return Ok(utils::mb_file_expanded(output)); + } + if let Some(tt) = TokenStream2::from(attrs).into_iter().next() { + return Err(Error::new_spanned(tt, "Unexpected parameter")); + } + return feed_to_macro_rules(input, parse_quote!(ReprC)); // .map(utils::mb_file_expanded); + repr_c::derive(attrs, input) + // // | Err(mut err) => { + // // // Prefix error messages with `derive_ReprC`. + // // { + // // let mut errors = + // // err .into_iter() + // // .map(|err| Error::new_spanned( + // // err.to_compile_error(), + // // format_args!("`#[safer_ffi::derive_ReprC]`: {}", err), + // // )) + // // ; + // // err = errors.next().unwrap(); + // // errors.for_each(|cur| err.combine(cur)); + // // } + // // input.extend(TokenStream::from(err.to_compile_error())); + // // return input; + // // }, + // // | Ok(None) => {}, + // // } + // if let Some(tt) = TokenStream2::from(attrs).into_iter().next() { + // return Err(Error::new_spanned(tt, "Unexpected parameter")); + // } + // feed_to_macro_rules(input, parse_quote!(ReprC)) +} + +pub(in crate) +fn derive_CType ( + attrs: TokenStream2, + input: TokenStream2, +) -> Result +{ + if let Some(tt) = TokenStream2::from(attrs).into_iter().next() { + return Err(Error::new_spanned(tt, "Unexpected parameter")); + } + return feed_to_macro_rules(input, parse_quote!(CType)); + c_type::derive(attrs, input) +} diff --git a/src/proc_macro/derives/c_type/_mod.rs b/src/proc_macro/derives/c_type/_mod.rs new file mode 100644 index 0000000000..8b4c92483d --- /dev/null +++ b/src/proc_macro/derives/c_type/_mod.rs @@ -0,0 +1,84 @@ +use super::*; + +#[cfg(feature = "js")] +pub(in crate) +mod js; + +pub(in crate) +mod struct_; + +pub +struct Args { + rename: Option, +} + +impl Parse for Args { + fn parse (input: ParseStream<'_>) + -> Result + { + let mut ret = Args { + rename: None, + }; + + let snoopy = input.lookahead1(); + while input.is_empty().not() { + mod kw { + ::syn::custom_keyword!(rename); + } + match () { + | _case if snoopy.peek(kw::rename) => { + let _: kw::rename = input.parse().unwrap(); + let _: Token![=] = input.parse()?; + if ret.rename.replace(input.parse()?).is_some() { + return Err(input.error("duplicate attribute")); + } + }, + | _default => return Err(snoopy.error()), + } + let _: Option = input.parse()?; + } + Ok(ret) + } +} + +pub(in crate) +fn derive ( + args: TokenStream2, + input: TokenStream2 +) -> Result +{ + let args: Args = parse2(args)?; + + let input: DeriveInput = parse2(input)?; + let DeriveInput { + ref attrs, + ref vis, + ref ident, + ref generics, + ref data, + } = input; + let mut ret = match data { + | Data::Struct(DataStruct { fields, .. }) => struct_::derive( + args, + attrs, + vis, + ident, + generics, + fields, + ), + | Data::Enum(DataEnum { enum_token, .. }) => bail! { + "\ + an `enum` does not have a *fully safe* backing `CType`; \ + did you mean to implement `ReprC` instead?\ + " => enum_token + }, + | Data::Union(DataUnion { union_token, .. }) => bail! { + "`union`s are not supported yet" => union_token + }, + }?; + Ok(quote!( + #input + + #ret + )) +} diff --git a/src/proc_macro/derives/c_type/js.rs b/src/proc_macro/derives/c_type/js.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/proc_macro/derives/c_type/struct_.rs b/src/proc_macro/derives/c_type/struct_.rs new file mode 100644 index 0000000000..c16ef3a4d1 --- /dev/null +++ b/src/proc_macro/derives/c_type/struct_.rs @@ -0,0 +1,247 @@ +use super::*; + +pub(in crate) +fn derive ( + args: Args, + attrs: &'_ [Attribute], + vis: &'_ Visibility, + StructName @ _: &'_ Ident, + generics: &'_ Generics, + fields: &'_ Fields, +) -> Result +{ + if matches!(fields, Fields::Unnamed { .. } | Fields::Unit { .. }) { + bail!("only braced structs are supported"); + } + + #[apply(let_quote!)] + use ::safer_ffi::{ + ඞ, + headers, + layout::{ + CLayoutOf, + CType as CType, + OpaqueKind, + ReprC, + }, + }; + + let mut ret = quote!(); + + let impl_body = quote!( + type OPAQUE_KIND = #OpaqueKind::Concrete; + ); + + #[cfg(feature = "headers")] + let impl_body = { + let EachGenericTy = + generics.type_params().map(|it| &it.ident) + ; + let ref EachFieldTy = + fields.iter().vmap(|Field { ty, .. }| ty) + ; + let ref each_field_name = + fields.iter().vmap(|f| f.ident.as_ref().unwrap()) + ; + let ref each_field_name_str = + each_field_name.iter().vmap(ToString::to_string) + ; + let ref StructName_str = StructName.to_string(); + + #[cfg(feature = "csharp")] + let impl_body = quote!( + fn csharp_define_self ( + definer: &'_ mut dyn #headers::Definer, + ) -> #io::Result<()> + { + use #io::Write as _; + #core::assert_ne!( + #mem::size_of::(), 0, + "C# does not support zero-sized structs!", + ); + let ref me = ::csharp_ty(); + #( + <#EachFieldTy as #CType>::csharp_define_self(definer)?; + )* + definer.define_once(me, &mut |definer| #writeln!(definer.out(), + #concat!( + "[StructLayout(LayoutKind.Sequential, Size = {size})]\n", + "public unsafe struct {me} {{\n", + #( + "{}{", #each_field_name_str, "}", + )* + "}}\n", + ), + #( + <#EachFieldTy as #CType>::csharp_marshaler() + .map(|m| #format!(" [MarshalAs({})]\n", m)) + .as_deref() + .unwrap_or("") + , + )* + size = #mem::size_of::(), + #( + #each_field_name = { + if #mem::size_of::<#EachFieldTy>() > 0 { + #format!( + " public {};\n", + <#EachFieldTy as #CType>::csharp_var( + #each_field_name_str, + ), + ) + } else { + #assert_eq!( + #mem::align_of::<#EachFieldTy>(), + 1, + "\ + Zero-sized fields must have an \ + alignment of `1`.\ + ", + ); + String::new() + } + }, + )* + me = me, + )) + } + ); + + let mut impl_body = impl_body; + impl_body.extend(quote!( + fn short_name () + -> #ඞ::String + { + let mut _ret: #ඞ::String = #StructName_str.into(); + #( + #ඞ::fmt::Write::write_fmt( + &mut _ret, + #ඞ::format_args!( + "_{}", + <#CLayoutOf<#EachGenericTy> as #CType>::short_name(), + ), + ).unwrap(); + )* + _ret + } + )); + + let each_field = + fields.try_vmap(|f| Result::Ok({ + let ref docs = utils::extract_docs(&f.attrs)?; + let ref name = f.ident.as_ref().expect("BRACED STRUCT").to_string(); + let FieldTy = &f.ty; + let emit_unindented = quote!( + &|language, definer| #ඞ::io::Write::write_fmt( + definer.out(), + #ඞ::format_args!( + "{}", + <#FieldTy as #CType>::name_wrapping_var( + language, + #name, + ), + ), + ) + ); + quote!( + #ඞ::StructField { + docs: &[#(#docs),*], + name: #name, + emit_unindented: #emit_unindented, + layout: #ඞ::std::alloc::Layout::new::(), + } + ) + }))? + ; + + let ref docs = utils::extract_docs(attrs)?; + + let me = args.rename.as_ref().unwrap_or(StructName).to_string(); + impl_body.extend(quote!( + fn define_self ( + language: &'_ dyn #headers::languages::HeaderLanguage, + definer: &'_ mut dyn #headers::Definer, + ) -> #ඞ::io::Result<()> + { + definer.define_once(#me, &mut |definer| { + #( + < #EachFieldTy as #CType >::define_self(language, definer)?; + )* + language.emit_struct( + definer, + &[#(#docs),*], + #me, + #ඞ::mem::size_of::(), + &[#(#each_field),*], + ) + }) + } + )); + + impl_body + }; + + ret.extend({ + let (intro_generics, fwd_generics, where_clauses) = + generics.split_for_impl() + ; + + quote!( + impl #intro_generics + #ඞ::Clone + for + #StructName #fwd_generics + #where_clauses + { + #[inline] + fn clone (self: &'_ Self) + -> Self + { + *self + } + } + + impl #intro_generics + #ඞ::Copy + for + #StructName #fwd_generics + #where_clauses + {} + + unsafe + impl #intro_generics + #CType + for + #StructName #fwd_generics + #where_clauses + { + #impl_body + } + + // If it is CType, it trivially is ReprC. + unsafe + impl #intro_generics + #ReprC + for + #StructName #fwd_generics + #where_clauses + { + type CLayout = Self; + + #[inline] + fn is_valid ( + _: &'_ Self::CLayout, + ) -> #ඞ::bool + { + true + } + } + ) + }); + + #[cfg(feature = "js")] { + ret.extend(super::js::handle(/* … */)?); + } + + Ok(ret) +} diff --git a/src/proc_macro/derives/dyn_trait/_mod.rs b/src/proc_macro/derives/dyn_trait/_mod.rs new file mode 100644 index 0000000000..6e32ffb2e0 --- /dev/null +++ b/src/proc_macro/derives/dyn_trait/_mod.rs @@ -0,0 +1,366 @@ +use { + ::core::{ + mem, slice, + }, + super::*, + self::{ + vtable_entry::{ + VTableEntry, + vtable_entries, + }, + } +}; + +enum Input { + Trait(ItemTrait), + TokenStream(TokenStream2), +} + +mod vtable_entry; + +impl Parse for Input { + fn parse (input: ParseStream<'_>) + -> Result + {Ok({ + let ref fork = input.fork(); + fork.parse::() + .map(|trait_| { + ::syn::parse::discouraged::Speculative::advance_to(input, fork); + Input::Trait(trait_) + }) + .unwrap_or_else(|_| Input::TokenStream(input.parse().unwrap())) + })} +} + +pub(in super) +fn try_handle_trait ( + attrs: &'_ TokenStream2, + input: &'_ mut TokenStream2, +) -> Result< Option > +{ + let ErasedTy @ _ = quote!( + ::safer_ffi::dyn_traits::ErasedTy + ); + let Box @ _ = quote!( + ::safer_ffi::__alloc::boxed::Box + ); + + let ref mut trait_ = match parse2(mem::take(input)).unwrap() { + | Input::TokenStream(ts) => { + *input = ts.into(); + return Ok(None); + }, + | Input::Trait(it) => { + *input = it.to_token_stream().into(); + it + }, + }; + let mut ret = TokenStream2::new(); + let ItemTrait { + attrs: _, + vis: ref pub_, + ref unsafety, // FIXME + auto_token: _, + trait_token: _, + ident: ref TraitName @ _, + ref generics, + colon_token: _, + supertraits: _, + brace_token: _, + ref mut items, + } = *trait_; + + let (_, fwd_trait_generics, trait_where_clause) = + generics.split_for_impl() + ; + + let ref each_vtable_entry = vtable_entries(items, &mut ret)?; + let each_method_def = each_vtable_entry.vmap(VTableEntry::virtual_forwarding); + let each_vtable_entry_name = each_vtable_entry.vmap(VTableEntry::name); + let each_vtable_entry_attrs = each_vtable_entry.vmap(VTableEntry::attrs); + let (EachVTableEntryType @ _, each_vtable_entry_value_f) = + each_vtable_entry + .iter() + .map(VTableEntry::type_and_value) + .unzip::<_, _, Vec<_>, Vec<_>>() + ; + let VTableName @ _ = format_ident!("{}Vtable", TraitName); + let impl_Trait = format_ident!("__impl_{}", TraitName); + + // Original generics but for introducing the `'usability` lifetime param. + let mut trait_generics_and_lt = generics.clone(); + let lifetime = quote_spanned!(Span::mixed_site()=> + '__usability + ); + trait_generics_and_lt.params.insert(0, parse_quote!( + #lifetime + )); + let ref trait_generics_and_lt = trait_generics_and_lt; + let (intro_trait_generics_and_lt, fwd_trait_generics_and_lt, _) = + trait_generics_and_lt.split_for_impl() + ; + + let EachToBeInvariantParam @ _ = + Iterator::chain( + trait_generics_and_lt.lifetimes().map(|LifetimeDef { lifetime, .. }| quote!( + &#lifetime () + )), + trait_generics_and_lt.type_params().map(|ty| { + ty.ident.to_token_stream() + }) + ) + ; + + // Emit the vtable type definition + ret.extend(quote!( + #[cfg_eval] + #[::safer_ffi::prelude::derive_ReprC] + #[repr(C)] + #pub_ + struct #VTableName #intro_trait_generics_and_lt + #trait_where_clause + { + release_vptr: + unsafe + extern "C" + fn ( + _: ::safer_ffi::ptr::NonNullOwned< #ErasedTy >, + ) + , + retain_vptr: ::core::option::Option< + unsafe + extern "C" + fn ( + _: ::safer_ffi::ptr::NonNullRef< #ErasedTy >, + ) + >, + #( + #(#each_vtable_entry_attrs)* + #each_vtable_entry_name: #EachVTableEntryType, + )* + // __type_name__debug: ::core::option::Option< + // extern "C" + // fn() -> ::safer_ffi::string::str_ref<'static> + // >, + _invariant: + ::core::marker::PhantomData< + // &#lifetime mut Self, + // &#lifetime mut #VTableName #fwd_trait_generics_and_lt, + fn(&()) -> &mut (#( + #EachToBeInvariantParam, + )*) + > + , + } + )); + + let Send @ _ = &[quote!(::core::marker::Send)][..]; + let Sync @ _ = &[quote!(::core::marker::Sync)][..]; + let ref mut must_emit_generic_vtable_reference = true; + for &(is_send, is_sync) in &[ + (false, false), + (true, true), + (true, false), + (false, true), + ] + { + let MbSend @ _ = if is_send { Send } else { &[] }; + let MbSync @ _ = if is_sync { Sync } else { &[] }; + let Trait @ _ = quote!( + #lifetime #(+ #MbSend)* #(+ #MbSync)* + #TraitName #fwd_trait_generics + ); + + // trait_generics_and_lt + `impl_Trait` generic parameter. + let mut all_generics = trait_generics_and_lt.clone(); + let param_count = ::add( + all_generics.lifetimes().count(), + all_generics.type_params().count(), + ); + all_generics.params.insert(param_count, parse_quote!( + #impl_Trait : #Trait + )); + let (intro_all_generics, fwd_all_generics, where_clause) = + all_generics.split_for_impl() + ; + + let QSelf @ _ = quote!( + <#impl_Trait as #TraitName #fwd_trait_generics> + ); + + let EACH_VTABLE_ENTRY_VALUE @ _ = + each_vtable_entry_value_f.iter().map(|f| f(&QSelf, &all_generics)) + ; + if mem::take(must_emit_generic_vtable_reference) { + ret.extend(quote_spanned!(Span::mixed_site()=> + struct __GenericConst #intro_all_generics ( + *mut Self, + ); + + impl #intro_all_generics + // ::safer_ffi::dyn_traits::__AssocConst< + // &#lifetime < + // dyn #Trait + // as + // ::safer_ffi::dyn_traits::ReprCTrait + // >::VTable + // > + // for + __GenericConst #fwd_all_generics + #where_clause + { + #[allow(unused_parens)] + const VALUE + : ( + &#lifetime < + dyn #Trait + as + ::safer_ffi::dyn_traits::ReprCTrait + >::VTable + ) = ( + &#VTableName { + // __type_name__debug: Some({ + // extern "C" + // fn __type_name__debug<#impl_Trait> () + // -> ::safer_ffi::string::str_ref<'static> + // { + // let s: &'static ::core::primitive::str = + // ::core::any::type_name::<#impl_Trait>() + // ; + // let ptr = s.as_bytes().as_ptr() as *mut u8; + // let len = s.len(); + // unsafe { + // ::core::mem::transmute::< + // ::safer_ffi::slice::slice_raw_Layout, + // ::safer_ffi::string::str_ref<'static>, + // >( + // safer_ffi::slice::slice_raw_Layout { + // ptr, + // len, + // } + // ) + // } + // } + // __type_name__debug::<#impl_Trait> + // }), + release_vptr: { + unsafe extern "C" + fn release_vptr<#impl_Trait : #TraitName #fwd_trait_generics> ( + ptr: ::safer_ffi::ptr::NonNullOwned< #ErasedTy >, + ) + { + let ptr = ptr.cast::<#impl_Trait>(); + ::core::mem::drop( + #Box::from_raw( + { ptr }.as_mut_ptr() + ) + ) + } + release_vptr::<#impl_Trait> // as … + }, + retain_vptr: None, + #( + #(#each_vtable_entry_attrs)* + #each_vtable_entry_name: #EACH_VTABLE_ENTRY_VALUE, + )* + _invariant: ::core::marker::PhantomData, + } + ); + } + )); + } + ret.extend(quote_spanned!(Span::mixed_site()=> + impl #intro_trait_generics_and_lt + ::safer_ffi::dyn_traits::ReprCTrait + for + dyn #Trait + { + type VTable = #VTableName #fwd_trait_generics_and_lt; + + unsafe + fn drop_ptr ( + ptr: ::safer_ffi::ptr::NonNullOwned<#ErasedTy>, + &Self::VTable { release_vptr, .. }: &'_ Self::VTable, + ) + { + release_vptr(ptr) + } + + // fn type_name ( + // &Self::VTable { __type_name__debug, .. }: &'_ Self::VTable, + // ) -> &'static ::core::primitive::str + // { + // if let ::core::option::Option::Some(f) = __type_name__debug { + // f().as_str() + // } else { + // "" + // } + // } + } + + impl #intro_trait_generics_and_lt + #TraitName #fwd_trait_generics + for + ::safer_ffi::dyn_traits::VirtualPtr + { + #(#each_method_def)* + } + + impl #intro_all_generics + ::safer_ffi::dyn_traits::VirtualPtrFromBox<#impl_Trait> + for + dyn #Trait + { + fn boxed_into_virtual_ptr ( + boxed: #Box<#impl_Trait> + ) -> ::safer_ffi::dyn_traits::VirtualPtr + { + unsafe { + ::safer_ffi::dyn_traits::VirtualPtr:::: + from_raw_parts( + ::core::mem::transmute(boxed), + ::core::convert::Into::into( + < + __GenericConst #fwd_all_generics + // as + // ::safer_ffi::dyn_traits::__AssocConst< + // &#lifetime < + // dyn #Trait + // as + // ::safer_ffi::dyn_traits::ReprCTrait + // >::VTable + // > + >::VALUE + ), + ) + } + } + } + )); + } + let _: parse::Nothing = parse2(attrs.clone())?; + drop(each_vtable_entry_value_f); + ret = quote!( + #trait_ + #[allow(warnings, clippy::all)] + const _: () = { #ret }; + ); + Ok(Some(ret)) +} + +fn CType (ty: &'_ Type) + -> TokenStream2 +{ + /* replace lifetimes inside `T` with … `'static`?? */ + let mut T = ty.clone(); + ::syn::visit_mut::VisitMut::visit_type_mut( + &mut RemapNonStaticLifetimesTo { new_lt_name: "static" }, + &mut T, + ); + quote!( + < + #T + as + ::safer_ffi::layout::ReprC + >::CLayout + ) +} diff --git a/src/proc_macro/derives/dyn_trait/vtable_entry.rs b/src/proc_macro/derives/dyn_trait/vtable_entry.rs new file mode 100644 index 0000000000..72ae345812 --- /dev/null +++ b/src/proc_macro/derives/dyn_trait/vtable_entry.rs @@ -0,0 +1,464 @@ +use super::*; + +pub(in super) +enum VTableEntry<'trait_> { + VirtualMethod { + src: &'trait_ TraitItemMethod, + name: &'trait_ Ident, + each_for_lifetime: Vec<&'trait_ Lifetime>, + each_arg_name: Vec, + ErasedSelf: Type, + EachArgTy: Vec<&'trait_ Type>, + OutputTy: &'trait_ [Type], + }, +} + +impl<'trait_> VTableEntry<'trait_> { + pub(in super) + fn name (self: &'_ VTableEntry<'trait_>) + -> &'trait_ Ident + { + match self { + | Self::VirtualMethod { + name, + .. + } => name, + } + } + + pub(in super) + fn virtual_forwarding<'r> ( + self: &'r VTableEntry<'trait_> + ) -> TokenStream2 + { + match *self { + | Self::VirtualMethod { + name, + each_for_lifetime: _, + ref each_arg_name, + ErasedSelf: _, + EachArgTy: _, + OutputTy: _, + src: &TraitItemMethod { + sig: ref full_signature, + ref attrs, + .. + }, + } => { + let mut signature = full_signature.clone(); + signature + .inputs + .iter_mut() + .skip(1) + .zip(each_arg_name) + .for_each(|(fn_arg, arg_name)| match *fn_arg { + | FnArg::Typed(PatType { ref mut pat, .. }) => { + // let arg_name = format_ident!("__arg_{}", i, span = ty.span()); + **pat = parse_quote!( #arg_name ); + }, + | _ => unreachable!(), + }) + ; + quote!( + #(#attrs)* + #[inline] + #signature + { + unsafe { + ::core::mem::transmute( + (self.__vtable().#name)( + ::core::mem::transmute(self.__ptr()), + #( + ::core::mem::transmute(#each_arg_name), + )* + ) + ) + } + } + ) + }, + } + } + + pub(in super) + fn attrs<'r> ( + self: &'r VTableEntry<'trait_> + ) -> &'trait_ Vec + { + match self { + | Self::VirtualMethod { + src: &TraitItemMethod { + ref attrs, + .. + }, + .. + } => attrs, + } + } + + pub(in super) + fn type_and_value<'r> ( + self: &'r VTableEntry<'trait_>, + ) -> ( + TokenStream2, + impl 'r + Fn( + /* QSelf: */ &dyn ToTokens, + /* trait_generics: */ &'_ Generics, + ) -> TokenStream2, + ) + { + match self { + | Self::VirtualMethod { + name, + each_for_lifetime, + each_arg_name, + ErasedSelf, + EachArgTy, + OutputTy, + src: _, + } => { + let span = Span::mixed_site().located_at(name.span()); + let EachArgTy @ _ = EachArgTy.iter().copied().map(CType).collect::>(); + let OutputTy @ _ = CType(OutputTy.get(0).unwrap_or(&parse_quote!( () ))); + let type_ = quote_spanned!(span=> + for<#(#each_for_lifetime),*> + unsafe + extern "C" + fn( + #ErasedSelf, + #(#EachArgTy ,)* + ) -> #OutputTy + + ); + let value = { + // let type_ = type_.clone(); /* may not be necessary */ + move + | + QSelf @ _: &dyn ToTokens, + trait_generics: &Generics, + | { + // What happens here is quite subtle: + // 1. we are dealing with the function signature of a trait's method + // 2. the trait may have generic lifetime params, + // 3. and the method may have its own generic lifetime params + // - which we'll currently assume to be higher-order / late-bound + // since they'll most likely be (and writing a heuristic to detect + // these with a macro seems overkill to begin with) + // 4. we want to end up with a `for<'higher_order_lts…> fn…` kind of + // function pointer, but still be able to name the types of the method + // signature, even if those may refer to the trait's generic. + // 5. since it has to be an `extern fn` pointer, we can't use closures + // to implicitly get access to those, so we need to: + // - REINJECT the trait generics into the helper fn def; + // - TURBOFISH-FEED those immediately after, when instanciating it. + // 6. But… the outer lifetime generics will be problematic: + // - They are not to be higher-order / late-bound, but early-bound. + // - if a combination of both such kinds lifetime params occurs, + // no lifetime parameter may be turbofished, at all. + // 7. We tackle the former problem by ensuring the outer lifetime parameters + // are early-bound by ensuring there is a `:` after their definition 😅 + // 8. And tackle the latter by not turbofishing the early-bound lifetimes, + // since those can always be left inferred. + let mut vfn_generics = trait_generics.clone(); + // Step 7: ensure the outer generics introduce early-bound lifetimes. + vfn_generics + .make_where_clause() + .predicates + .extend( + trait_generics + .lifetimes() + .map(|LifetimeDef { lifetime, .. }| -> WherePredicate { + parse_quote!( + #lifetime : + ) + }) + ) + ; + vfn_generics.params = Iterator::chain( + each_for_lifetime.iter().map(|lt| -> GenericParam { + parse_quote!( #lt ) + }), + ::core::mem::take(&mut vfn_generics.params) + ).collect(); + // we can't use fwd_generics since we want to skip the lts. + let (intro_generics, _, where_clause) = + vfn_generics.split_for_impl() + ; + let fwd_generics = Iterator::chain( + vfn_generics.type_params().map(|it| &it.ident), + vfn_generics.const_params().map(|it| &it.ident), + ); + quote_spanned!(span=> { + unsafe + extern "C" + fn #name #intro_generics ( + __this: #ErasedSelf, + #(#each_arg_name: #EachArgTy ,)* + ) -> #OutputTy + #where_clause + { + ::safer_ffi::layout::into_raw(#QSelf::#name( + ::core::mem::transmute(__this) #(, + ::safer_ffi::layout::from_raw_unchecked( + #each_arg_name + ) )* + )) + } + + #name ::< #(#fwd_generics),* > // as #type_ + }) + } + }; + (type_, value) + }, + } + } +} + +pub(in super) +fn vtable_entries<'trait_> ( + trait_items: &'trait_ mut [TraitItem], + emit: &mut TokenStream2, +) -> Result>> +{ + use ::quote::format_ident as ident; + // let mut Sized @ _ = None; + // let mut skip_attrs_found = vec![]; + macro_rules! failwith {( $err_msg:expr => $at:expr $(,)? ) => ( + return Some(Err(Error::new_spanned($at, $err_msg))) + )} + macro_rules! continue_ {() => ( + return None + )} + trait_items.iter_mut().filter_map(|it| Some(Result::Ok(match *it { + | TraitItem::Method(ref trait_item_method @ TraitItemMethod { + attrs: _, + sig: ref sig @ Signature { + constness: _, // ref const_, + asyncness: _, // ref async_, + unsafety: _, // ref unsafe_, + abi: _, // ref extern_, + fn_token: _, + ident: ref method_name, + ref generics, + ref paren_token, + ref inputs, + variadic: _, // ref variadic, + output: ref RetTy @ _, + }, + default: _, + semi_token: _, + }) => { + // Is there a `Self : Sized` opt-out-of-`dyn` clause? + if matches!( + generics.where_clause, Some(ref where_clause) + if where_clause.predicates.iter().any(|clause| matches!( + *clause, WherePredicate::Type(PredicateType { + lifetimes: ref _for, + bounded_ty: Type::Path(TypePath { + qself: None, + path: ref BoundedTy @ _, + }), + colon_token: _, + ref bounds, + }) + if BoundedTy.is_ident("Self") + && bounds.iter().any(|Bound @ _| matches!( + *Bound, TypeParamBound::Trait(TraitBound { + path: ref Super @ _, + .. + }) + if Super.is_ident("Sized") + )) + )) + ) + { + // If so, skip it, it did opt out after all. + continue_!() + } + let ref mut storage = None; + let lifetime_of_and = move |and: &Token![&], mb_lt| { + let _: &Option = mb_lt; + mb_lt.as_ref().unwrap_or_else(|| { + { storage }.get_or_insert( + Lifetime::new("'_", and.span) + ) + }) + }; + let self_lt: Option<&Lifetime> = if let Some(receiver) = sig.receiver() { + let is_Self = |T: &Type| matches!( + *T, Type::Path(TypePath { + qself: None, ref path, + }) if path.is_ident("Self") + ); + loop { + match *receiver { + | FnArg::Receiver(Receiver { + attrs: _, + reference: /* heh */ ref ref_, + // thanks to raw pointers, we can disregard mutability + mutability: _, + self_token: _, + }) => { + break ref_.as_ref().map(|(and, mb_lt)| lifetime_of_and(and, mb_lt)); + }, + | FnArg::Typed(PatType { ref ty, ..}) => { + match **ty { + | Type::Reference(TypeReference { + and_token: ref and, + lifetime: ref mb_lt, + elem: ref Pointee @ _, + .. + }) + if is_Self(Pointee) + => { + break Some(lifetime_of_and(and, mb_lt)); + }, + + | _ if is_Self(ty) => { + failwith!("owned `Self` receiver is not `dyn` safe" => ty); + }, + + | Type::Path(TypePath { + qself: None, + path: ref BoxedSelf @ _, + }) + if BoxedSelf.segments.last().unwrap().ident == "Box" + => { + // generate a compile-time assertion checking + // that the path is indeed a Box. + emit.extend(quote!( + const _: () = { + enum __Self {} + impl + ::safer_ffi::dyn_traits::__assert_dyn_safe + for + __Self + { + fn m(self: #BoxedSelf) {} + } + }; + )); + break None; + }, + | _ => {}, + } + failwith!("arbitrary `Self` types are not supported" => ty); + }, + } + } + } else { + return Some(Err(Error::new( + paren_token.span, + "\ + `dyn` trait requires a `self` receiver on this method. \ + Else opt-out of `dyn` trait support by adding a \ + `where Self : Sized` clause.\ + ", + ))); + }; + /* From now on, we'll assume "no funky stuff", _e.g._, no generics, etc. + * since at the time of this writing, this kind of funky stuff is denied for + * `dyn Trait`s, and we're gonna emit a `dyn_safe(true)` assertion beforehand. + * we can thus allow to skip checks when we consider the resulting diagnostic + * noise to be bearable. */ + VTableEntry::VirtualMethod { + name: method_name, + each_for_lifetime: if false { + // Since CTypes are `'static`, we shouldn't need those lifetimes + // when writing the function pointer definitions. + generics + .lifetimes() + .map(|it| &it.lifetime) + .collect() + } else { + vec![] + }, + each_arg_name: + inputs + .iter() + .enumerate() + .skip(1) + .map(|(i, arg)| ident!("__arg{}", i, span = match *arg { + | FnArg::Receiver(_) => { + unreachable!("Skipped receiver") + }, + | FnArg::Typed(PatType { ref pat, .. }) => { + pat.span() + }, + })) + .collect() + , + ErasedSelf: if let Some(lt) = self_lt { + parse_quote!( + // ::safer_ffi::dyn_traits::ErasedRef<#lt> + ::safer_ffi::ptr::NonNull< + ::safer_ffi::dyn_traits::ErasedTy, + > + ) + } else { + parse_quote!( + ::safer_ffi::ptr::NonNull< + ::safer_ffi::dyn_traits::ErasedTy, + > + ) + }, + EachArgTy: + inputs + .iter() + .skip(1) + .map(|it| match *it { + | FnArg::Receiver(_) => unreachable!(), + | FnArg::Typed(PatType { ref ty, .. }) => &**ty, + }) + .collect() + , + OutputTy: match RetTy { + | ReturnType::Type(_, it) => ::core::slice::from_ref(it), + | ReturnType::Default => &[], + }, + src: trait_item_method, + } + }, + + // | TraitItem::Const(TraitItemConst { ref mut attrs, .. }) + // | TraitItem::Type(TraitItemType { ref mut attrs, .. }) + // if { + // skip_attrs_found = + // attrs + // .iter() + // .enumerate() + // .filter_map(|(i, attr)| ( + // attr.path.is_ident("safer_ffi") + // && + // attr.parse_args_with(|input: ParseStream<'_>| { + // ::syn::custom_keyword!(skip); + // let _: skip = input.parse()?; + // let _: Option = input.parse()?; + // Ok(()) + // }).is_ok() + // ).then(|| i)) + // .collect() + // ; + // skip_attrs_found.is_empty().not() + // } + // => { + // // perform the drain (hack needed since you can't mutate a binding in an `if` guard) + // let mut enumerate = 0..; + // attrs.retain(|_| skip_attrs_found.contains(&enumerate.next().unwrap()).not()); + // // skip the current item from the `repr(c) dyn` processing altogether. + // continue_!(); + // }, + + | TraitItem::Type(_) => { + failwith!("not supported yet (TBD)" => it); + }, + + | TraitItem::Const(_) + | TraitItem::Macro(_) + | TraitItem::Verbatim(_) + | _ + => failwith!("unsupported" => it), + }))) + .collect() +} diff --git a/src/proc_macro/handle_fptr.rs b/src/proc_macro/derives/handle_fptr.rs similarity index 84% rename from src/proc_macro/handle_fptr.rs rename to src/proc_macro/derives/handle_fptr.rs index 091c73c825..aa39803ab3 100644 --- a/src/proc_macro/handle_fptr.rs +++ b/src/proc_macro/derives/handle_fptr.rs @@ -1,7 +1,16 @@ -fn try_handle_fptr (input: &'_ DeriveInput) - -> Option +use { + ::syn::{ + visit_mut::VisitMut, + }, + super::*, +}; + +pub(in super) +fn try_handle_fptr ( + input: &'_ DeriveInput, +) -> Option { - let span = Span2::call_site(); + let span = Span::call_site(); macro_rules! error {( $msg:expr $(=> $span:expr)? $(,)? @@ -79,25 +88,32 @@ fn try_handle_fptr (input: &'_ DeriveInput) } /* == VALIDATION PASSED, TIME TO EXPAND == */ - quote_use! { - // Fully-qualified paths to be robust to an weird/antagonistic - // namespace (except for `::safer_ffi`; that's our path-resolution - // keystone. - use ::safer_ffi::layout::ReprC; - use ::safer_ffi::layout::CType; - use ::safer_ffi::layout::__HasNiche__; - use ::safer_ffi::headers::Definer; - use ::safer_ffi::layout::OpaqueKind::Concrete; - use ::safer_ffi::__cfg_headers__; - use ::safer_ffi::__cfg_csharp__; - use ::safer_ffi::bool; - use ::safer_ffi::str; - use ::safer_ffi::std; - use ::safer_ffi::core; - use ::safer_ffi::core::fmt; - use ::safer_ffi::core::write; - use ::safer_ffi::core::option::Option; - } + // Fully-qualified paths to be robust to an weird/antagonistic + // namespace (except for `::safer_ffi`; that's our path-resolution + // keystone. + #[apply(let_quote!)] + use ::safer_ffi::{ + layout::{ + CType, + CType, + LegacyCType, + OpaqueKind, + ReprC, + __HasNiche__, + }, + headers::Definer, + __cfg_headers__, + __cfg_csharp__, + ඞ::{ + bool, + core, + fmt, + Option, + std, + str, + write, + }, + }; let (intro, fwd, where_) = input.generics.split_for_impl(); let mut ret = quote!( @@ -202,7 +218,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) // // for<#(#lifetimes),*> < #ty // <#ty_no_lt as #ReprC>::CLayout // : - // #CType + // #LegacyCType // ); // Iterator::chain( ::core::iter::once(is_ReprC) @@ -235,7 +251,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) ).unwrap() }; // Add a PhantomData field to account for unused lifetime params. - // (given that we've had to strip them to become `CType`) + // (given that we've had to strip them to become `LegacyCType`) if generics.lifetimes().next().is_some() { // non-empty. let fields_mut = match input_Layout_data.fields { | Fields::Unnamed(FieldsUnnamed { @@ -280,7 +296,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) let (intro, fwd, where_) = input_Layout.generics.split_for_impl(); ret.extend(quote!( unsafe - impl#intro + impl #intro #ReprC for #StructName #fwd @@ -298,7 +314,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) #[allow(nonstandard_style)] #input_Layout - impl#intro + impl #intro #core::clone::Clone for #StructName_Layout #fwd @@ -307,7 +323,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) fn clone (self: &'_ Self) -> Self { - impl#intro + impl #intro #core::marker::Copy for #StructName_Layout #fwd @@ -318,8 +334,8 @@ fn try_handle_fptr (input: &'_ DeriveInput) } unsafe - impl#intro - #CType + impl #intro + #LegacyCType for #StructName_Layout #fwd #where_ @@ -329,16 +345,16 @@ fn try_handle_fptr (input: &'_ DeriveInput) { // fmt.write_str(stringify!(#StructName)) // ret_arg1_arg2_fptr - <#RetCType as #CType>::c_short_name_fmt(fmt)?; #( - #write!(fmt, "_{}", <#EachArgCType as #CType>::c_short_name())?; )* + fmt.write_str(&<#RetCType as #CType>::short_name())?; #( + #write!(fmt, "_{}", <#EachArgCType as #CType>::short_name())?; )* fmt.write_str("_fptr") } fn c_define_self (definer: &'_ mut dyn #Definer) -> #std::io::Result<()> { - <#RetCType as #CType>::c_define_self(definer)?; #( - <#EachArgCType as #CType>::c_define_self(definer)?; )* + <#RetCType as #CType>::define_self(&::safer_ffi::headers::languages::C, definer)?; #( + <#EachArgCType as #CType>::define_self(&::safer_ffi::headers::languages::C, definer)?; )* Ok(()) } @@ -347,13 +363,13 @@ fn try_handle_fptr (input: &'_ DeriveInput) var_name: &'_ #str, ) -> #fmt::Result { - #write!(fmt, "{} ", <#RetCType as #CType>::c_var(""))?; + #write!(fmt, "{} ", <#RetCType as #CType>::name_wrapping_var(&::safer_ffi::headers::languages::C, ""))?; #write!(fmt, "(*{})(", var_name)?; let _empty = true; #( #write!(fmt, "{comma}{arg}", - arg = <#EachArgCType as #CType>::c_var(""), + arg = <#EachArgCType as #CType>::name_wrapping_var(&::safer_ffi::headers::languages::C, ""), comma = if _empty { "" } else { ", " }, )?; let _empty = false; @@ -368,9 +384,9 @@ fn try_handle_fptr (input: &'_ DeriveInput) fn csharp_define_self (definer: &'_ mut dyn #Definer) -> #std::io::Result<()> { - <#RetCType as #CType>::csharp_define_self(definer)?; #( - <#EachArgCType as #CType>::csharp_define_self(definer)?; )* - let ref me = Self::csharp_ty(); + <#RetCType as #CType>::define_self(&::safer_ffi::headers::languages::CSharp, definer)?; #( + <#EachArgCType as #CType>::define_self(&::safer_ffi::headers::languages::CSharp, definer)?; )* + let ref me = ::name(&::safer_ffi::headers::languages::CSharp).to_string(); let ref mut _forge_arg_name = { let mut iter = (0 ..).map(|c| #std::format!("_{}", c)); move || iter.next().unwrap() @@ -398,7 +414,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) .as_deref() .unwrap_or("") , - <#EachArgCType as #CType>::csharp_var(&_forge_arg_name()), + <#EachArgCType as #CType>::name_wrapping_var(&::safer_ffi::headers::languages::CSharp, &_forge_arg_name()), )* me = me, ret_marshaler = @@ -407,7 +423,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) .as_deref() .unwrap_or("") , - Ret = <#RetCType as #CType>::csharp_ty(), + Ret = <#RetCType as #CType>::name(&::safer_ffi::headers::languages::CSharp), )) } @@ -417,7 +433,7 @@ fn try_handle_fptr (input: &'_ DeriveInput) Self::c_short_name().to_string() } - fn csharp_marshaler () + fn legacy_csharp_marshaler () -> #Option<#std::string::String> { // This assumes the calling convention from the above @@ -425,10 +441,10 @@ fn try_handle_fptr (input: &'_ DeriveInput) #Option::Some("UnmanagedType.FunctionPtr".into()) } } - } type OPAQUE_KIND = #Concrete; } + } type OPAQUE_KIND = #OpaqueKind::Concrete; } unsafe - impl#intro + impl #intro #__HasNiche__ for #StructName #fwd @@ -461,8 +477,11 @@ const _: () = { macro_rules! ELIDED_LIFETIME_TEMPLATE {() => ( "__elided_{}" )} - impl VisitMut for UnelideLifetimes<'_, '_> { - fn visit_lifetime_mut (self: &'_ mut Self, lifetime: &'_ mut Lifetime) + impl ::syn::visit_mut::VisitMut for UnelideLifetimes<'_, '_> { + fn visit_lifetime_mut ( + self: &'_ mut Self, + lifetime: &'_ mut Lifetime, + ) { let Self { lifetime_params, counter } = self; if lifetime.ident == "_" { @@ -474,7 +493,10 @@ const _: () = { } } - fn visit_type_mut (self: &'_ mut Self, ty: &'_ mut Type) + fn visit_type_mut ( + self: &'_ mut Self, + ty: &'_ mut Type, + ) { ::syn::visit_mut::visit_type_mut(self, ty); let Self { lifetime_params, counter } = self; @@ -493,7 +515,7 @@ const _: () = { ), counter.next().unwrap(), ), - Span2::call_site(), + Span::call_site(), )) ; lifetime_params.to_mut().push(parse_quote!( @@ -508,14 +530,22 @@ const _: () = { /// Pretty self-explanatory: since we can't do ` Ty as Tr>::Assoc` /// (technically the result value could depend on `'lt`, but not in our case, -/// since `CType` is `'static`) -struct StripLifetimeParams; impl VisitMut for StripLifetimeParams { - fn visit_lifetime_mut (self: &'_ mut Self, lifetime: &'_ mut Lifetime) +/// since `LegacyCType` is `'static`) +struct StripLifetimeParams; + +impl VisitMut for StripLifetimeParams { + fn visit_lifetime_mut ( + self: &'_ mut Self, + lifetime: &'_ mut Lifetime, + ) { - *lifetime = Lifetime::new("'static", Span2::call_site()); + *lifetime = Lifetime::new("'static", Span::call_site()); } - fn visit_type_mut (self: &'_ mut Self, ty: &'_ mut Type) + fn visit_type_mut ( + self: &'_ mut Self, + ty: &'_ mut Type, + ) { ::syn::visit_mut::visit_type_mut(self, ty); match *ty { @@ -524,7 +554,7 @@ struct StripLifetimeParams; impl VisitMut for StripLifetimeParams { .. }) => { *implicitly_elided_lifetime = Some( - Lifetime::new("'static", Span2::call_site()) + Lifetime::new("'static", Span::call_site()) ); }, | _ => {}, diff --git a/src/proc_macro/derives/repr_c/_mod.rs b/src/proc_macro/derives/repr_c/_mod.rs new file mode 100644 index 0000000000..f6d38c3503 --- /dev/null +++ b/src/proc_macro/derives/repr_c/_mod.rs @@ -0,0 +1,53 @@ +use super::*; + +pub(in crate) +mod enum_; + +#[cfg(feature = "js")] +pub(in crate) +mod js; + +pub(in crate) +mod struct_; + +pub(in crate) +fn derive ( + attrs: TokenStream2, + input: TokenStream2 +) -> Result +{ + let _: parse::Nothing = parse2(attrs)?; + + let mut input: DeriveInput = parse2(input)?; + let DeriveInput { + ref mut attrs, + ref vis, + ref ident, + ref generics, + ref data, + } = input; + let mut ret = match *data { + | Data::Struct(DataStruct { ref fields, .. }) => struct_::derive( + attrs, + vis, + ident, + generics, + fields, + ), + | Data::Enum(DataEnum { ref variants, .. }) => enum_::derive( + attrs, + vis, + ident, + generics, + variants, + ), + | Data::Union(DataUnion { ref union_token, .. }) => bail! { + "`union`s are not supported yet" => union_token + }, + }?; + Ok(quote!( + #input + + #ret + )) +} diff --git a/src/proc_macro/derives/repr_c/enum_.rs b/src/proc_macro/derives/repr_c/enum_.rs new file mode 100644 index 0000000000..9058887e14 --- /dev/null +++ b/src/proc_macro/derives/repr_c/enum_.rs @@ -0,0 +1,341 @@ +use super::*; + +pub(in crate) +fn derive ( + attrs: &'_ mut Vec, + pub_: &'_ Visibility, + EnumName @ _: &'_ Ident, + generics: &'_ Generics, + variants: &'_ Punctuated, +) -> Result +{ + let mut ret = quote!(); + + if let Some(payload) = + variants + .iter() + .find(|Variant { fields, .. }| matches!( + fields, + Fields::Unit, + ).not()) + { + bail! { + "Non field-less `enum`s are not supported yet." => payload, + } + } + + let (Int @ _, repr) = parse_discriminant_type(attrs, &mut ret)?; + + let EnumName_Layout @ _ = format_ident!("{}_Layout", EnumName); + + #[apply(let_quote!)] + use ::safer_ffi::{ + ඞ, + ඞ::{ + mem, + }, + layout::{ + __HasNiche__, + CLayoutOf, + CType as CType, + OpaqueKind, + ReprC, + }, + }; + + ret.extend(quote!( + #[allow(nonstandard_style)] + #[repr(transparent)] + #[#ඞ::derive( + #ඞ::Clone, #ඞ::Copy, + // #ඞ::PartialEq, #ඞ::Eq, + )] + #pub_ + struct #EnumName_Layout { + #pub_ + discriminant: #Int, + } + + impl + #ඞ::From<#Int> + for + #EnumName_Layout + { + #[inline] + fn from (discriminant: #Int) + -> Self + { + Self { discriminant } + } + } + )); + + let impl_body = quote!( + type OPAQUE_KIND = #OpaqueKind::Concrete; + ); + + let ref each_doc = utils::extract_docs(attrs)?; + + #[cfg(feature = "headers")] + let impl_body = { + #[apply(let_quote!)] + use ::safer_ffi::{ + ඞ::fmt, + headers::{ + Definer, + languages::{ + HeaderLanguage, + EnumVariant, + }, + }, + }; + + let mut impl_body = impl_body; + + let ref EnumName_str = EnumName.to_string(); + let enum_size = repr.to_enum_size(); + let each_enum_variant = + variants.iter().map(|v| { + let ref VariantName_str = v.ident.to_string(); + let discriminant = if let Some((_eq, disc)) = &v.discriminant { + quote!( + #ඞ::Some(&(#disc, ).0 as _) + ) + } else { + quote!( + #ඞ::None + ) + }; + quote!( + #EnumVariant { + docs: &[], // FIXME TODO + name: #VariantName_str, + discriminant: #discriminant, + } + ) + }) + ; + + let ref EnumName_t_str = format!("{EnumName}_t"); + impl_body.extend(quote!( + fn short_name () + -> #ඞ::String + { + #EnumName_str.into() + } + + fn define_self ( + language: &'_ dyn #HeaderLanguage, + definer: &'_ mut dyn #Definer, + ) -> #ඞ::io::Result<()> + { + definer.define_once( + #EnumName_t_str, + &mut |definer| { + <#Int as #CType>::define_self(language, definer)?; + language.emit_simple_enum( + definer, + &[#(#each_doc),*], + #EnumName_str, + #enum_size, + &[#(#each_enum_variant),*], + ) + }, + ) + } + )); + + impl_body + }; + + ret.extend(quote!( + unsafe + impl + #CType + for + #EnumName_Layout + { + #impl_body + } + )); + + ret.extend({ + let ref EachVariant @ _ = variants.iter().vmap(|it| &it.ident); + quote!( + unsafe + impl #ReprC for #EnumName { + type CLayout = #EnumName_Layout; + + #[inline] + fn is_valid ( + &#EnumName_Layout { discriminant }: &'_ Self::CLayout, + ) -> #ඞ::bool + { + #![allow(nonstandard_style)] + #( + const #EachVariant: #Int = #EnumName::#EachVariant as _; + )* + #ඞ::matches!(discriminant, #( #EachVariant )|*) + } + } + ) + }); + + ret.extend(quote!( + unsafe + impl #__HasNiche__ + for + #EnumName + { + #[inline] + fn is_niche ( + &#EnumName_Layout { discriminant }: &'_ #CLayoutOf, + ) -> #ඞ::bool + { + /// Safety: this is either well-defined, or fails to compile. + const DISCRIMINANT_OF_NONE: #Int = unsafe { + #ඞ::mem::transmute::<_, #Int>( + #ඞ::None::<#EnumName> + ) + }; + + #ඞ::matches!(discriminant, DISCRIMINANT_OF_NONE) + } + } + )); + + Ok(ret) +} + +fn parse_discriminant_type ( + attrs: &'_ [Attribute], + out_warnings: &mut TokenStream2, +) -> Result<(TokenStream2, Repr)> +{ + let repr_attr = + attrs + .iter() + .find(|attr| attr.path.is_ident("repr")) + .ok_or(()) + .or_else(|()| bail!("missing `#[repr(…)]` annotation"))? + ; + let ref reprs = repr_attr.parse_args_with( + Punctuated::::parse_terminated, + )?; + if reprs.is_empty() { + bail!("expected an integral `repr` specifier" => repr_attr); + } + let parsed_reprs = reprs.iter().try_vmap(Repr::try_from)?; + let (c_type, repr) = + match + ::core::iter::zip(parsed_reprs, reprs) + .find(|(parsed, ident)| matches!(parsed, Repr::C).not()) + { + | Some((repr, ident)) => { + ( + quote!( + ::safer_ffi::ඞ::#ident + ), + repr, + ) + }, + | None if reprs.iter().any(|repr| repr == "C") => { + out_warnings.extend(utils::compile_warning( + &repr_attr, + "\ + `#[repr(C)]` enums are not well-defined in C; \ + it is thus ill-advised to use them \ + in a multi-compiler scenario such as FFI\ + ", + )); + ( + quote!( + ::safer_ffi::c_int + ), + Repr::C, + ) + }, + | None => bail! { + "expected an integral `repr` annotation" + }, + } + ; + + Ok((c_type, repr)) +} + +match_! {( + C, + u8, u16, u32, u64, u128, + i8, i16, i32, i64, i128, +) {( + $($repr:ident),* $(,)? +) => ( + enum Repr { + $($repr),* + } + + impl Repr { + fn try_from (ident: &'_ Ident) + -> Result + { + match &ident.to_string()[..] { + $( + | stringify!($repr) => Ok(Self::$repr), + )* + | _ => bail! { + "unsupported `repr` annotation" => ident, + }, + } + } + + fn to_enum_size (&self) + -> Quote![Option<(bool, u8)>] + { + use { + ::{ + core::primitive as builtin, + }, + self::{ + Repr::*, + }, + }; + let mb_signed_bitwidth: Option<(bool, builtin::u8)> = match *self { + | C => None, + | u8 | u16 | u32 | u64 | u128 => Some(( + false, + match *self { + | u8 => 8, + | u16 => 16, + | u32 => 32, + | u64 => 64, + | u128 => 128, + | _ => unreachable!(), + }, + )), + | i8 | i16 | i32 | i64 | i128 => Some(( + true, + match *self { + | i8 => 8, + | i16 => 16, + | i32 => 32, + | i64 => 64, + | i128 => 128, + | _ => unreachable!(), + }, + )), + }; + let ret: Quote![Option<(bool, builtin::u8)>] = { + match mb_signed_bitwidth { + | None => quote!( + ::safer_ffi::ඞ::None + ), + | Some((signed, bitwidth)) => quote!( + ::safer_ffi::ඞ::Some((#signed, #bitwidth)) + ), + } + }; + ret + } + } +)}} diff --git a/src/proc_macro/derives/repr_c/js.rs b/src/proc_macro/derives/repr_c/js.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/proc_macro/derives/repr_c/struct_.rs b/src/proc_macro/derives/repr_c/struct_.rs new file mode 100644 index 0000000000..3d8264c0f9 --- /dev/null +++ b/src/proc_macro/derives/repr_c/struct_.rs @@ -0,0 +1,161 @@ +use super::*; + +pub(in crate) +fn derive ( + attrs: &'_ mut Vec, + vis: &'_ Visibility, + StructName @ _: &'_ Ident, + generics: &'_ Generics, + fields: &'_ Fields, +) -> Result +{ + if fields.is_empty() { + bail!("C requires that structs have at least one field"); + } + if attrs.iter().any(|attr| { + mod kw { ::syn::custom_keyword!(transparent); } + attr.path.is_ident("repr") + && + attr.parse_args::().is_ok() + }) + { + return derive_transparent(attrs, vis, StructName, generics, fields); + } + + let docs = + attrs + .iter() + .filter(|a| a.path.is_ident("doc")) + .cloned() + .vec() + ; + + // Add docs about C layout. + attrs.extend_::([ + parse_quote!( + /// # C Layout + ), + parse_quote!( + /// + ), + { + let line = format!( + "{} - [`{StructName}_Layout`](#impl-ReprC)", " ", + ); + parse_quote!(#[doc = #line]) + }, + ]); + + let mut ret = quote!(); + + #[apply(let_quote!)] + use ::safer_ffi::{ + ඞ, + layout::{ + ConcreteReprC, + CLayoutOf, + ReprC, + }, + }; + + let EachFieldTy @ _ = || fields.iter().map(|Field { ty, .. }| ty); + let each_field_name = || (0..).zip(fields).map(|(i, f)| match f.ident { + | Some(ref ident) => ident.to_token_stream(), + | None => Index { index: i, span: f.ty.span() }.into_token_stream(), + }); + + let ref StructName_Layout @ _ = format_ident!("{}_Layout", StructName); + + let ref ctype_generics = + utils::ctype_generics(generics, &mut EachFieldTy()) + ; + // define the CType + ret.extend({ + let c_type_def = ItemStruct { + attrs: docs.also(|v| v.push(parse_quote!( + #[allow(nonstandard_style)] + ))), + vis: parse_quote!(pub), + struct_token: parse_quote!(struct), + ident: StructName_Layout.clone(), + generics: ctype_generics.clone(), + fields: Fields::Named({ + let EachFieldTy = EachFieldTy(); + let each_field_name = (0_u8..).zip(fields).map(|(i, f)| { + match f.ident { + | Some(ref ident) => ident.clone(), + | None => format_ident!("_{}", i), + } + }); + let each_docs = fields.iter().map(|f| { + f .attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .vec() + }); + parse_quote!({ + #( + #(#each_docs)* + pub + #each_field_name: #CLayoutOf<#EachFieldTy> + ),* + }) + }), + semi_token: None, + }; + + crate::derives::c_type::derive( + quote!(rename = #StructName), + c_type_def.into_token_stream(), + )? + }); + + // Impl ReprC to point to the just defined type + ret.extend({ + let EachFieldTy @ _ = EachFieldTy(); + let each_field_name = each_field_name(); + let (intro_generics, fwd_generics, where_clauses) = + ctype_generics.split_for_impl() + ; + quote!( + #[allow(trivial_bounds)] + unsafe + impl #intro_generics + #ReprC + for + #StructName #fwd_generics + #where_clauses + { + type CLayout = #StructName_Layout #fwd_generics; + + #[inline] + fn is_valid (it: &'_ Self::CLayout) + -> #ඞ::bool + { + let _ = it; + true #(&& ( + #ඞ::mem::size_of::<#EachFieldTy>() == 0 + || + <#EachFieldTy as #ReprC>::is_valid( + &it.#each_field_name + ) + ))* + } + } + ) + }); + + Ok(ret) +} + +pub(in crate) +fn derive_transparent ( + attrs: &'_ mut Vec, + vis: &'_ Visibility, + StructName @ _: &'_ Ident, + generics: &'_ Generics, + fields: &'_ Fields, +) -> Result +{ + todo!("transparent struct"); +} diff --git a/src/proc_macro/ffi_export/_mod.rs b/src/proc_macro/ffi_export/_mod.rs index b43f35e711..6f372febed 100644 --- a/src/proc_macro/ffi_export/_mod.rs +++ b/src/proc_macro/ffi_export/_mod.rs @@ -1,75 +1,27 @@ -/// Export a function to be callable by C. -/// -/// # Example -/// -/// ```rust -/// use ::safer_ffi::prelude::ffi_export; -/// -/// #[ffi_export] -/// /// Add two integers together. -/// fn add (x: i32, y: i32) -> i32 -/// { -/// x + y -/// } -/// ``` -/// -/// - ensures that [the generated headers](/safer_ffi/headers/) will include the -/// following definition: -/// -/// ```C -/// #include -/// -/// /* \brief -/// * Add two integers together. -/// */ -/// int32_t add (int32_t x, int32_t y); -/// ``` -/// -/// - exports an `add` symbol pointing to the C-ABI compatible -/// `int32_t (*)(int32_t x, int32_t y)` function. -/// -/// (The crate type needs to be `cdylib` or `staticlib` for this to work, -/// and, of course, the C compiler invocation needs to include -/// `-L path/to/the/compiled/library -l name_of_your_crate`) -/// -/// - when in doubt, use `staticlib`. -/// -/// # `ReprC` -/// -/// [`ReprC`]: /safer_ffi/layout/trait.ReprC.html -/// -/// You can use any Rust types in the singature of an `#[ffi_export]`- -/// function, provided each of the types involved in the signature is [`ReprC`]. -/// -/// Otherwise the layout of the involved types in the C world is **undefined**, -/// which `#[ffi_export]` will detect, leading to a compilation error. -/// -/// To have custom structs implement [`ReprC`], it suffices to annotate the -/// `struct` definitions with the [`#[derive_ReprC]`]( -/// /safer_ffi/layout/attr.derive_ReprC.html) -/// (on top of the obviously required `#[repr(C)]`). -#[proc_macro_attribute] pub -fn ffi_export (attrs: TokenStream, input: TokenStream) - -> TokenStream +use super::*; + +macro_rules! emit {( $($tt:tt)* ) => ( $($tt)* )} + +pub(in super) +fn ffi_export ( + attrs: TokenStream2, + input: TokenStream2, +) -> Result { - use ::proc_macro::{*, TokenTree as TT}; - #[cfg(feature = "proc_macros")] - if let Ok(input) = parse::(input.clone()) { - parse_macro_input!(attrs as parse::Nothing); - return ::quote::quote!( + use ::proc_macro2::*; + + if let Ok(input) = parse2::(input.clone()) { + let _: parse::Nothing = parse2(attrs)?; + return Ok(::quote::quote!( ::safer_ffi::__ffi_export__! { #input } - ).into(); + )); } - #[cfg(feature = "async-fn")] - let fun: ItemFn = { - let input = input.clone(); - parse_macro_input!(input) - }; - #[cfg(feature = "async-fn")] { - let attrs = attrs.clone(); - match parse::(attrs) { + + #[cfg(feature = "async-fn")] emit! { + let fun: ItemFn = parse2(input.clone())?; + match parse2::(attrs.clone()) { | Ok(attrs) if attrs.block_on.is_some() || fun.sig.asyncness.is_some() @@ -95,10 +47,10 @@ fn ffi_export (attrs: TokenStream, input: TokenStream) | Some(TT::Ident(id)) if id.to_string() == "async_worker" => { is_async_worker = true; }, - | Some(extraneous_tt) => return compile_error( + | Some(extraneous_tt) => return Err(Error::new_spanned( + extraneous_tt, "Unexpected parameter", - extraneous_tt.span(), - ), + )), } if matches!( tts.peek(), @@ -119,16 +71,15 @@ fn ffi_export (attrs: TokenStream, input: TokenStream) is_async_worker, )); if prev.is_some() { - return compile_error( - "Duplicate `nodejs` parameter", - kw.span(), - ); + bail! { + "Duplicate `nodejs` parameter" => kw + } } } }, - | Some(unexpected_tt) => return compile_error( - "Unexpected parameter", unexpected_tt.span(), - ), + | Some(unexpected_tt) => bail! { + "Unexpected parameter" => unexpected_tt + }, | None => break, } if matches!(attr_tokens.peek(), Some(TT::Punct(p)) if p.as_char() == ',') { @@ -153,7 +104,7 @@ fn ffi_export (attrs: TokenStream, input: TokenStream) input }; let span = Span::call_site(); - >::from_iter(vec![ + Ok(TokenStream2::from_iter(vec![ TT::Punct(Punct::new(':', Spacing::Joint)), TT::Punct(Punct::new(':', Spacing::Alone)), @@ -170,7 +121,7 @@ fn ffi_export (attrs: TokenStream, input: TokenStream) Delimiter::Brace, input.into_iter().collect(), )), - ]) + ])) } #[cfg(feature = "async-fn")] diff --git a/src/proc_macro/ffi_export/async_fn.rs b/src/proc_macro/ffi_export/async_fn.rs index f2a747d442..647479dd8b 100644 --- a/src/proc_macro/ffi_export/async_fn.rs +++ b/src/proc_macro/ffi_export/async_fn.rs @@ -1,28 +1,33 @@ -use super::*; +use { + ::syn::{ + visit_mut::VisitMut, + }, + super::{ + *, + }, +}; -use ::proc_macro2::{Span, TokenStream as TokenStream2}; - -pub(in crate) +pub(in super) fn export ( Attrs { block_on, node_js }: Attrs, fun: &'_ ItemFn, -) -> TokenStream +) -> Result { let block_on = match (block_on, fun.sig.asyncness) { | (Some(block_on), Some(_asyncness)) => block_on, - | (Some(block_on), None) => { - return Error::new_spanned(block_on, "\ + | (Some(block_on), None) => bail!( + "\ `#[ffi_export(…)]`'s `executor` attribute \ can only be applied to an `async fn`. \ - ").into_compile_error().into(); - }, - | (None, Some(asyncness)) => { - return Error::new_spanned(asyncness, "\ + " => block_on + ), + | (None, Some(asyncness)) => bail!( + "\ In order for `#[ffi_export(…)]` to support `async fn`, you \ need to feed it an `executor = …` parameter and then use \ `ffi_await!(…)` as the last expression of the function's body.\ - ").into_compile_error().into(); - }, + " => asyncness + ), | (None, None) => unreachable!(), }; // The body of the function is expected to be of the form: @@ -75,12 +80,14 @@ fn export ( }, })() { - return Error::new(err_span, "\ - `#[ffi_export(…, executor = …)]` expects the last \ - expression/statement to be an expression of the form: \ - `ffi_await!()` such as:\n \ - ffi_await!(async move {\n …\n })\n\ - ").into_compile_error().into(); + bail!( + "\ + `#[ffi_export(…, executor = …)]` expects the last \ + expression/statement to be an expression of the form: \ + `ffi_await!()` such as:\n \ + ffi_await!(async move {\n …\n })\n\ + " => spanned!(err_span) + ); } (stmts, async_body.unwrap()) }; @@ -90,7 +97,7 @@ fn export ( let ret = if cfg!(feature = "node-js") { if node_js.is_none() { // Nothing to do in this branch: - return fun.into_token_stream().into(); + return Ok(fun.into_token_stream()); } let fname = &fun.sig.ident; let mut fun_signature = fun.sig.clone(); @@ -275,7 +282,7 @@ fn export ( } ) }; - ret.into() + Ok(ret) } use ::syn::parse::{Parse, ParseStream}; @@ -293,8 +300,9 @@ mod kw { } impl Parse for Attrs { - fn parse (input: ParseStream<'_>) - -> Result + fn parse ( + input: ParseStream<'_>, + ) -> Result { let mut ret = Attrs::default(); while input.is_empty().not() { @@ -322,61 +330,20 @@ impl Parse for Attrs { } } -fn respan (span: Span, tokens: TokenStream2) - -> TokenStream2 +fn respan ( + span: Span, + tokens: TokenStream2, +) -> TokenStream2 { - use ::proc_macro2::{Group, TokenTree as TT}; - tokens.into_iter().map(|tt| match tt { - | TT::Group(g) => TT::Group( - Group::new(g.delimiter(), respan(span, g.stream())) - ), - | mut tt => { - tt.set_span(tt.span().resolved_at(span)); - tt - }, - }).collect() -} + use ::proc_macro2::*; -struct RemapNonStaticLifetimesTo<'__> { - new_lt_name: &'__ str, -} -use visit_mut::VisitMut; -impl VisitMut for RemapNonStaticLifetimesTo<'_> { - fn visit_lifetime_mut ( - self: &'_ mut Self, - lifetime: &'_ mut Lifetime, - ) - { - if lifetime.ident != "static" { - lifetime.ident = Ident::new( - self.new_lt_name, - lifetime.ident.span(), - ); - } - } - - fn visit_type_reference_mut ( - self: &'_ mut Self, - ty_ref: &'_ mut TypeReference, - ) - { - // 1 – sub-recurse - visit_mut::visit_type_reference_mut(self, ty_ref); - // 2 – handle the implicitly elided case. - if ty_ref.lifetime.is_none() { - ty_ref.lifetime = Some(Lifetime::new( - &["'", self.new_lt_name].concat(), - ty_ref.and_token.span, - )); - } - } - - fn visit_parenthesized_generic_arguments_mut ( - self: &'_ mut Self, - _: &'_ mut ParenthesizedGenericArguments, - ) - { - // Elided lifetimes in `fn(…)` or `Fn…(…)` are higher order: - /* do not subrecurse */ - } + tokens.into_iter().map(|tt| match tt { + | TT::Group(g) => TT::Group( + Group::new(g.delimiter(), respan(span, g.stream())) + ), + | mut tt => { + tt.set_span(tt.span().resolved_at(span)); + tt + }, + }).collect() } diff --git a/src/proc_macro/utils.rs b/src/proc_macro/utils/_mod.rs similarity index 56% rename from src/proc_macro/utils.rs rename to src/proc_macro/utils/_mod.rs index 8af16e0284..46fb0e81a5 100644 --- a/src/proc_macro/utils.rs +++ b/src/proc_macro/utils/_mod.rs @@ -1,25 +1,21 @@ -fn compile_error (err_msg: &'_ str, span: Span) - -> TokenStream -{ - use ::proc_macro::{*, TokenTree as TT}; - macro_rules! spanned {($expr:expr) => ({ - let mut it = $expr; - it.set_span(span); - it - })} - >::from_iter(vec![ - TT::Ident(Ident::new("compile_error", span)), - TT::Punct(spanned!(Punct::new('!', Spacing::Alone))), - TT::Group(spanned!(Group::new( - Delimiter::Brace, - ::core::iter::once(TT::Literal( - spanned!(Literal::string(err_msg)) - )).collect(), - ))), - ]) -} +#![allow(unused)] +#![warn(unused_must_use)] + +use super::*; + +pub(in crate) use extension_traits::*; +mod extension_traits; -#[cfg(feature = "proc_macros")] +pub(in crate) use macros::*; +mod macros; + +pub(in crate) use mb_file_expanded::*; +mod mb_file_expanded; + +pub(in crate) use trait_impl_shenanigans::*; +mod trait_impl_shenanigans; + +pub(in crate) trait MySplit { type Ret; fn my_split (self: &'_ Self) @@ -27,7 +23,6 @@ trait MySplit { ; } -#[cfg(feature = "proc_macros")] impl MySplit for Generics { type Ret = (TokenStream2, Vec); @@ -138,3 +133,98 @@ fn pretty_print_tokenstream ( } } } + +pub(in crate) +struct RemapNonStaticLifetimesTo<'__> { + pub(in crate) + new_lt_name: &'__ str, +} + +impl ::syn::visit_mut::VisitMut + for RemapNonStaticLifetimesTo<'_> +{ + fn visit_lifetime_mut ( + self: &'_ mut Self, + lifetime: &'_ mut Lifetime, + ) + { + if lifetime.ident != "static" { + lifetime.ident = Ident::new( + self.new_lt_name, + lifetime.ident.span(), + ); + } + } + + fn visit_type_reference_mut ( + self: &'_ mut Self, + ty_ref: &'_ mut TypeReference, + ) + { + // 1 – sub-recurse + visit_mut::visit_type_reference_mut(self, ty_ref); + // 2 – handle the implicitly elided case. + if ty_ref.lifetime.is_none() { + ty_ref.lifetime = Some(Lifetime::new( + &["'", self.new_lt_name].concat(), + ty_ref.and_token.span, + )); + } + } + + fn visit_parenthesized_generic_arguments_mut ( + self: &'_ mut Self, + _: &'_ mut ParenthesizedGenericArguments, + ) + { + // Elided lifetimes in `fn(…)` or `Fn…(…)` are higher order: + /* do not subrecurse */ + } +} + +pub(in crate) +fn compile_warning ( + span: &dyn ToTokens, + message: &str, +) -> TokenStream2 +{ + let mut spans = span.to_token_stream().into_iter().map(|tt| tt.span()); + let fst = spans.next().unwrap_or_else(|| Span::call_site()); + let lst = spans.fold(fst, |cur, _| cur); + let safer_ffi_ = Ident::new("safer_ffi_", fst); + let warning = Ident::new("warning", fst); + let ref message = ["\n", message].concat(); + quote!( + const _: () = { + mod safer_ffi_ { + #[deprecated(note = #message)] + pub fn warning() {} + } + let _ = #safer_ffi_::#warning; + }; + ) +} + +pub(in crate) +fn extract_docs ( + attrs: &'_ [Attribute] +) -> Result> +{ + attrs.iter().filter_map(|attr| { + attr.path + .is_ident("doc") + .then(|| Parser::parse2( + |input: ParseStream<'_>| Result::Ok( + if input.parse::>()?.is_some() { + Some(input.parse::()?) + } else { + None + } + ), + attr.tokens.clone(), + ) + .transpose()) + .flatten() + }) + .collect() +} diff --git a/src/proc_macro/utils/extension_traits.rs b/src/proc_macro/utils/extension_traits.rs new file mode 100644 index 0000000000..2974eb0a73 --- /dev/null +++ b/src/proc_macro/utils/extension_traits.rs @@ -0,0 +1,69 @@ +pub(in crate) +trait CollectVec : Sized + Iterator { + fn vec (self: Self) + -> Vec + { + impl CollectVec for I {} + self.collect() + } +} + +pub(in crate) +trait VMap : Sized + IntoIterator { + fn vmap ( + self: Self, + f: impl FnMut(Self::Item) -> T, + ) -> Vec + { + self.into_iter().map(f).collect() + } + + fn try_vmap ( + self: Self, + f: impl FnMut(Self::Item) -> Result + ) -> Result, E> + { + self.into_iter().map(f).collect() + } +} + +impl VMap for I +where + Self : Sized + IntoIterator, +{} + +pub(in crate) +trait Extend_ { + fn extend_ ( + &mut self, + iterable: I, + ) + where + Self : Extend, + I : IntoIterator, + { + impl Extend_ for T {} + self.extend(iterable) + } + + fn extend_one_ ( + &mut self, + item: A, + ) + where + Self : Extend, + { + self.extend([item]) + } +} + +pub +trait Also : Sized { + fn also (mut self, tweak: impl FnOnce(&mut Self)) + -> Self + { + impl Also for T {} + tweak(&mut self); + self + } +} diff --git a/src/proc_macro/utils/macros.rs b/src/proc_macro/utils/macros.rs new file mode 100644 index 0000000000..b2c14b7392 --- /dev/null +++ b/src/proc_macro/utils/macros.rs @@ -0,0 +1,191 @@ +macro_rules! spanned {( $span:expr $(,)? ) => ( + ::proc_macro2::Ident::new("__", $span) +)} pub(in crate) use spanned; + +macro_rules! bail { + ( + $err_msg:expr $(,)? + ) => ( + $crate::utils::bail! { + $err_msg => $crate::utils::spanned!(::proc_macro2::Span::call_site()) + } + ); + + ( + $err_msg:expr => $spanned:expr $(,)? + ) => ( + return ::syn::Result::Err(::syn::Error::new_spanned( + $spanned, + $err_msg, + )) + ); +} pub(in crate) use bail; + +macro_rules! unwrap {( $proc_macro_result:expr $(,)? ) => ( + $proc_macro_result + .unwrap_or_else(|mut err| { + let mut iter_errors = + err .into_iter() + .map(|err| Error::new_spanned( + err.to_compile_error(), + format_args!( + "`#[::safer_ffi::{}]`: {}", + $crate::utils::function_name!(), + err, + ), + )) + ; + err = iter_errors.next().unwrap(); + iter_errors.for_each(|cur| err.combine(cur)); + err.to_compile_error() + }) + .into() +)} pub(in crate) use unwrap; + +pub(in crate) +fn type_name_of_val (_: T) + -> &'static str +{ + ::core::any::type_name::() +} + +macro_rules! function_name {() => ({ + let mut name = $crate::utils::type_name_of_val({ fn f () {} f }); + name = &name[.. name.len() - "::f".len()].trim_end_matches("::{{closure}}"); + if let ::core::option::Option::Some(i) = name.rfind(':') { + name = &name[i + 1..]; + } + name +})} pub(in crate) use function_name; + +macro_rules! let_quote {( + use $($contents:tt)* +) => ( + __let_quote! { + [ + [] + [] + ] + $($contents)* + } +)} pub(in crate) use let_quote; + +macro_rules! __let_quote { + ( + [ + $fst:tt + $snd:tt + $($deeper:tt)* + ] + { + $($inner:tt)* + } $(, + $($rest:tt)* + )? $(;)? + ) => ( + __let_quote! { + [ + $fst // duplicate fst + $fst + $snd + $($deeper)* + ] + $($inner)* + } + __let_quote! { + [ + $snd // replace fst with duplicate of snd + $snd + $($deeper)* + ] + $($($rest)*)? + } + ); + + ( + [ + [$($path:tt)*] // fst + $snd:tt + $($deeper:tt)* + ] + $last_segment:ident $(as $rename:ident)? $(, + $($rest:tt)* )? $(;)? + ) => ( + let quoted = ::quote::quote!( + $($path)* $last_segment + ); + #[allow(nonstandard_style)] + #[cfg(all( + $($rename = "__if_provided", + any(), + )? + ))] + let $last_segment @ _ = quoted; + $( + #[allow(nonstandard_style)] + let $rename @ _ = quoted; + )? + __let_quote! { + [ + $snd // replace fst with duplicate of snd + $snd + $($deeper)* + ] + $($($rest)*)? + } + ); + + ( + [ + [$($path:tt)*] + $($deeper:tt)* + ] + $mid_segment:tt + $($rest:tt)* + ) => ( + __let_quote! { + [ + [$($path)* $mid_segment] + $($deeper)* + ] + $($rest)* + } + ); + + ( + $path:tt + /* nothing left */ + ) => (); +} pub(in crate) use __let_quote; + +macro_rules! match_ {( + ( $($input:tt)* ) $rules:tt +) => ( + macro_rules! __recurse__ $rules + __recurse__! { $($input)* } +)} pub(in crate) use match_; + +macro_rules! dbg_parse_quote {( + $($code:tt)* +) => ( + (|| { + fn type_of_some (_: Option) + -> &'static str + { + ::core::any::type_name::() + } + + let target_ty = None; if false { return target_ty.unwrap(); } + let code = ::quote::quote!( $($code)* ); + eprintln!( + "[{}:{}:{}:parse_quote!]\n - ty: `{ty}`\n - code: `{code}`", + file!(), line!(), column!(), + ty = type_of_some(target_ty), + ); + ::syn::parse2(code).unwrap() + })() +)} pub(in crate) use dbg_parse_quote; + +macro_rules! Quote {( $T:ty $(,)? ) => ( + ::proc_macro2::TokenStream +)} pub(in crate) use Quote; diff --git a/src/proc_macro/utils/mb_file_expanded.rs b/src/proc_macro/utils/mb_file_expanded.rs new file mode 100644 index 0000000000..6086e98756 --- /dev/null +++ b/src/proc_macro/utils/mb_file_expanded.rs @@ -0,0 +1,62 @@ +use super::*; + +pub(in crate) +fn mb_file_expanded (output: TokenStream2) + -> TokenStream2 +{ + let mut debug_macros_dir = + match ::std::env::var_os("DEBUG_MACROS_LOCATION") { + | Some(it) => ::std::path::PathBuf::from(it), + | None => return output, + } + ; + let hopefully_unique = { + use ::std::hash::*; + let ref mut hasher = + ::std::collections::hash_map::RandomState::new() + .build_hasher() + ; + hasher.finish() + }; + + debug_macros_dir.push("safer-ffi-debugged-proc-macros"); + ::std::fs::create_dir_all(&debug_macros_dir) + .unwrap_or_else(|err| panic!( + "`DEBUG_MACROS_LOCATION`-error: failed to create {}: {}", + debug_macros_dir.display(), err, + )) + ; + let ref file_name = { + debug_macros_dir.push(format!("{:016x}.rs", hopefully_unique)); + debug_macros_dir + .into_os_string() + .into_string() + .expect("`DEBUG_MACROS_LOCATION`-error: \ + non-UTF-8 paths are not supported\ + ") + }; + + ::std::fs::write( + file_name, + ::std::panic::catch_unwind(|| ::prettyplease::unparse(&parse_quote!(#output))) + .unwrap_or_else(|_| quote!(#output).to_string()) + , + ) + .unwrap_or_else(|err| panic!( + "`DEBUG_MACROS_LOCATION`-error: failed to write to `{}`: {}", + file_name, err + )) + ; + let warning = + compile_warning("e!(), &format!( + "Output emitted to {file_name}", + )) + ; + quote!( + #warning + + ::core::include! { + #file_name + } + ) +} diff --git a/src/proc_macro/utils/trait_impl_shenanigans.rs b/src/proc_macro/utils/trait_impl_shenanigans.rs new file mode 100644 index 0000000000..4d54f90d6c --- /dev/null +++ b/src/proc_macro/utils/trait_impl_shenanigans.rs @@ -0,0 +1,61 @@ +use crate::*; + +pub(in crate) +fn allowing_trivial_bound ( + mut where_predicate: WherePredicate +) -> WherePredicate +{ + if let WherePredicate::Type(PredicateType { + ref mut lifetimes, + ref mut bounded_ty, + .. + }) = where_predicate + { + lifetimes + .get_or_insert_with(|| parse_quote!(for<>)) + .lifetimes + .push(parse_quote!('__trivial_bound_hack)) + ; + *bounded_ty = parse_quote!( + ::safer_ffi::__::Identity<'__trivial_bound_hack, #bounded_ty> + ); + } else { + panic!("Invalid `where_predicate` arg"); + } + where_predicate +} + +pub(in crate) +fn ctype_generics ( + generics: &'_ Generics, + EachFieldTy @ _: &mut dyn Iterator, +) -> Generics +{ + #[apply(let_quote!)] + use ::safer_ffi::ඞ::{ + ConcreteReprC, + CLayoutOf, + CType, + OpaqueKind, + ReprC, + }; + generics.clone().also(|it| { + it + .make_where_clause() + .predicates + .extend_::(Iterator::chain( + generics + .type_params() + .map(|TypeParam { ident: T, .. }| parse_quote!( + #T : #ReprC + )) + , + EachFieldTy + .map(|FieldTy @_ | parse_quote!( + #FieldTy : #ConcreteReprC + )) + // .map(utils::allowing_trivial_bound) + , + )) + }) +} diff --git a/src/string/_mod.rs b/src/string/_mod.rs index 15c06c785a..5b3057ebd9 100644 --- a/src/string/_mod.rs +++ b/src/string/_mod.rs @@ -3,8 +3,9 @@ //! compatible (_fat_ pointers). use_prelude!(); +use repr_c::Vec; -pub use self::slice::*; +pub use slice::*; mod slice; cfg_alloc! { diff --git a/src/tuple.rs b/src/tuple.rs index 4ec17da7ad..d7ee1709a8 100644 --- a/src/tuple.rs +++ b/src/tuple.rs @@ -18,7 +18,7 @@ mod void { pub(in crate) use void::CVoid; unsafe -impl CType +impl LegacyCType for CVoid { __cfg_headers__! { fn c_short_name_fmt (fmt: &'_ mut fmt::Formatter<'_>) @@ -39,7 +39,21 @@ impl CType ) } + fn c_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + __cfg_csharp__! { + fn csharp_define_self ( + _: &'_ mut dyn crate::headers::Definer, + ) -> io::Result<()> + { + Ok(()) + } + fn csharp_ty () -> rust::String { diff --git a/src/utils/_mod.rs b/src/utils/_mod.rs index b32b445ec3..a93a3ab211 100644 --- a/src/utils/_mod.rs +++ b/src/utils/_mod.rs @@ -1,5 +1,4 @@ #![cfg_attr(rustfmt, rustfmt::skip)] -#![cfg_attr(rustfmt, rustfmt::skip)] #[macro_use] mod macros; @@ -18,40 +17,35 @@ fn transmute_unchecked (ref it: T) use ::core::ops::{Not as _}; assert!( ::core::mem::size_of::() == ::core::mem::size_of::(), - concat!( - "Error, size mismatch.", - " This is a soundness bug, please report an issue ASAP", - ) + "Error, size mismatch. \ + This is a soundness bug, please report an issue ASAP", ); assert!( ::core::mem::needs_drop::().not(), - concat!( - "Error, input has drop glue.", - " This is a soundness bug, please report an issue ASAP", - ), + "Error, input has drop glue. \ + This is a soundness bug, please report an issue ASAP", ); ::core::mem::transmute_copy(it) } #[allow(warnings)] pub -struct screaming_case<'__>( +struct screaming_case<'__> ( pub &'__ str, pub &'__ str, ); const _: () = { - use ::core::fmt::{self, Display, Write}; + use ::core::{fmt::{self, Display, Write}, ops::Not}; impl Display for screaming_case<'_> { fn fmt (self: &'_ Self, fmt: &'_ mut fmt::Formatter<'_>) - -> fmt::Result + -> fmt::Result { - let mut not_first = false; - [self.0, self.1].iter().copied().flat_map(|s| s.chars()).try_for_each(|c| { - if true - && ::core::mem::replace(&mut not_first, true) - && c.is_ascii_uppercase() + let mut first = true; + [self.0, self.1].iter().flat_map(|&s| s.chars()).try_for_each(|c| { + if ::core::mem::take(&mut first).not() + && c.is_ascii_uppercase() { fmt.write_char('_')?; } diff --git a/src/utils/macros.rs b/src/utils/macros.rs index 1234c6cc9c..14c5410a5b 100644 --- a/src/utils/macros.rs +++ b/src/utils/macros.rs @@ -6,76 +6,60 @@ macro_rules! use_prelude { () => ( use crate::utils::prelude::*; )} -#[macro_use] -mod cfg_alloc { - #[cfg( - feature = "alloc", - )] - macro_rules! cfg_alloc {( - $($item:item)* - ) => ( - $( - #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "alloc")))] - $item - )* - )} - - #[cfg(not( - feature = "alloc", - ))] - macro_rules! cfg_alloc {( - $($item:item)* +/// I really don't get the complexity of `cfg_if!`… +macro_rules! cfg_match { + ( _ => { $($expansion:tt)* } $(,)? ) => ( $($expansion)* ); + ( + $cfg:meta => $expansion:tt $(, + $($rest:tt)* )? ) => ( - // Nothing - )} + #[cfg($cfg)] + cfg_match! { _ => $expansion } $( + #[cfg(not($cfg))] + cfg_match! { $($rest)* } )? + ); + // Bonus: expression-friendly syntax: `cfg_match!({ … })` + ({ $($input:tt)* }) => ({ cfg_match! { $($input)* } }) } -#[macro_use] -mod cfg_std { - #[cfg( - feature = "std", - )] - macro_rules! cfg_std {( - $($item:item)* - ) => ( - $( - #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "std")))] - $item - )* - )} - - #[cfg(not( - feature = "std", - ))] - macro_rules! cfg_std {( - $($item:item)* - ) => ( - // Nothing - )} +cfg_match! { + feature = "alloc" => { + macro_rules! cfg_alloc {( + $($item:item)* + ) => ( + $( + #[cfg_attr(all(docs), doc(cfg(feature = "alloc")))] + $item + )* + )} + }, + _ => { + macro_rules! cfg_alloc {( + $($item:item)* + ) => ( + // Nothing + )} + }, } -#[macro_use] -mod cfg_proc_macros { - #[cfg( - feature = "proc_macros", - )] - macro_rules! cfg_proc_macros {( - $($item:item)* - ) => ( - $( - #[cfg_attr(all(docs, feature = "nightly"), doc(cfg(feature = "proc_macros")))] - $item - )* - )} - - #[cfg(not( - feature = "proc_macros", - ))] - macro_rules! cfg_proc_macros {( - $($item:item)* - ) => ( - // Nothing - )} +cfg_match! { + feature = "std" => { + macro_rules! cfg_std {( + $($item:item)* + ) => ( + $( + #[cfg_attr(all(docs), doc(cfg(feature = "std")))] + $item + )* + )} + }, + _ => { + macro_rules! cfg_std {( + $($item:item)* + ) => ( + // Nothing + )} + }, } macro_rules! cfg_wasm {( $($item:item)* ) => ( @@ -120,6 +104,7 @@ macro_rules! const_assert { } as usize]; ); } + macro_rules! type_level_enum {( $( #[doc = $doc:tt] )* $pub:vis @@ -129,56 +114,56 @@ macro_rules! type_level_enum {( $Variant:ident ),* $(,)? } -) => (type_level_enum! { // This requires the macro to be in scope when called. - with_docs! { - $( #[doc = $doc] )* - /// - /// ### Type-level `enum` - /// - /// Until `const_generics` can handle custom `enum`s, this pattern must be - /// implemented at the type level. - /// - /// We thus end up with: - /// - /// ```rust,ignore - /// #[type_level_enum] - #[doc = ::core::concat!( - " enum ", ::core::stringify!($EnumName), " {", - )] - $( - #[doc = ::core::concat!( - " ", ::core::stringify!($Variant), ",", - )] - )* - #[doc = " }"] - /// ``` - /// - #[doc = ::core::concat!( - "With [`", ::core::stringify!($EnumName), "::T`](#reexports) \ - being the type-level \"enum type\":", - )] - /// - /// ```rust,ignore +) => ( + $( #[doc = $doc] )* + /// + /// ### Type-level `enum` + /// + /// Until `const_generics` can handle custom `enum`s, this pattern must be + /// implemented at the type level. + /// + /// We thus end up with: + /// + /// ```rust,ignore + /// #[type_level_enum] + #[doc = ::core::concat!( + " enum ", ::core::stringify!($EnumName), " {", + )] + $( #[doc = ::core::concat!( - "" + " ", ::core::stringify!($Variant), ",", )] - /// ``` - } + )* + #[doc = " }"] + /// ``` + /// + #[doc = ::core::concat!( + "With [`", ::core::stringify!($EnumName), "::T`](#reexports) \ + being the type-level \"enum type\":", + )] + /// + /// ```rust,ignore + #[doc = ::core::concat!( + "" + )] + /// ``` #[allow(warnings)] $pub mod $EnumName { #[doc(no_inline)] pub use $EnumName as T; - type_level_enum! { - with_docs! { - #[doc = ::core::concat!( - "See [`", ::core::stringify!($EnumName), "`]\ - [super::", ::core::stringify!($EnumName), "]" - )] - } - pub trait $EnumName : __sealed::$EnumName + ::core::marker::Sized + 'static { - const VALUE: __value::$EnumName; - } + #[doc = ::core::concat!( + "See [`", ::core::stringify!($EnumName), "`]", + "[super::", ::core::stringify!($EnumName), "]" + )] + pub + trait $EnumName + : + __sealed::$EnumName + + ::core::marker::Sized + + 'static + + { + const VALUE: __value::$EnumName; } mod __sealed { pub trait $EnumName {} } @@ -193,29 +178,17 @@ macro_rules! type_level_enum {( pub enum $Variant {} impl __sealed::$EnumName for $Variant {} impl $EnumName for $Variant { - const VALUE: __value::$EnumName = __value::$EnumName::$Variant; + const VALUE: __value::$EnumName = + __value::$EnumName::$Variant + ; } impl $Variant { - pub const VALUE: __value::$EnumName = __value::$EnumName::$Variant; + pub const VALUE: __value::$EnumName = + __value::$EnumName::$Variant + ; } )* } -});( - with_docs! { - $( #[doc = $doc:expr] )* - } - $item:item -) => ( - $( #[doc = $doc] )* - $item -)} - -macro_rules! with_doc {( - #[doc = $doc:expr] - $($rest:tt)* -) => ( - #[doc = $doc] - $($rest)* )} macro_rules! doc_test { @@ -223,42 +196,40 @@ macro_rules! doc_test { #![$attr:ident] $($code:tt)* ) => ( - with_doc! { + const _: () = { #[doc = concat!( "```rust,", stringify!($attr), "\n", - stringify!($($code)*), - "\n```\n", + stringify!($($code)*), "\n", )] + /// ``` pub mod $name {} - } + }; ); ($name:ident : $($code:tt)* ) => ( - with_doc! { + const _: () = { #[doc = concat!( "```rust\n", - stringify!($($code)*), - "\n```\n", + stringify!($($code)*), "\n", )] + /// ``` pub mod $name {} - } + }; ); } -cfg_proc_macros! { - doc_test! { c_str: - use ::safer_ffi::prelude::*; +doc_test! { c_str: + use ::safer_ffi::prelude::*; - let _ = c!("Hello, World!"); - } - doc_test! { c_str_inner_nul_byte: - #![compile_fail] - use ::safer_ffi::prelude::*; + let _ = c!("Hello, World!"); +} +doc_test! { c_str_inner_nul_byte: + #![compile_fail] + use ::safer_ffi::prelude::*; - let _ = c!("Hell\0, World!"); - } + let _ = c!("Hell\0, World!"); } /// Items exported through this macro are internal implementation details diff --git a/src/utils/markers.rs b/src/utils/markers.rs index 58dd90fa72..642d0e0d4a 100644 --- a/src/utils/markers.rs +++ b/src/utils/markers.rs @@ -14,15 +14,9 @@ pub struct PhantomInvariant ( pub ::core::marker::PhantomData< - *mut T, + fn(&T) -> &T, >, ); -unsafe impl Send - for PhantomInvariant -{} -unsafe impl Sync - for PhantomInvariant -{} impl Default for PhantomInvariant diff --git a/src/utils/prelude.rs b/src/utils/prelude.rs index 7e92d9ac54..d7a0ce55cd 100644 --- a/src/utils/prelude.rs +++ b/src/utils/prelude.rs @@ -8,13 +8,7 @@ pub(in crate) use crate::{ tuple::*, utils::markers::*, }; -cfg_alloc! { - pub(in crate) use crate::prelude::repr_c::{ - Box, - String, - Vec, - }; -} + pub(in crate) use ::core::{ convert::{TryFrom, TryInto}, ffi::c_void, @@ -26,12 +20,16 @@ pub(in crate) use ::core::{ Not as _, }, }; -#[cfg(not(target_arch = "wasm32"))] -pub(in crate) use ::libc::size_t; -#[cfg(target_arch = "wasm32")] -#[allow(bad_style, dead_code)] -pub(in crate) type size_t = u32; +cfg_match! { + target_arch = "wasm32" => { + #[allow(bad_style, dead_code)] + pub(in crate) type size_t = u32; + }, + _ => { + pub(in crate) use ::libc::size_t; + }, +} cfg_alloc! { pub(in crate) use ::alloc::{ @@ -43,13 +41,12 @@ cfg_alloc! { pub(in crate) mod rust { - cfg_alloc! { - pub(in crate) use ::alloc::{ - boxed::Box, - string::String, - vec::Vec, - }; - } + #[apply(cfg_alloc)] + pub(in crate) use ::alloc::{ + boxed::Box, + string::String, + vec::Vec, + }; } pub(in crate) @@ -58,11 +55,10 @@ mod ptr { pub(in crate) use crate::ptr::*; } -cfg_std! { - pub(in crate) use ::std::{ - io, - }; -} +#[apply(cfg_std)] +pub(in crate) use ::std::{ + io, +}; pub(in crate) use crate::prelude::*;