diff --git a/CLAUDE.md b/CLAUDE.md index 536d4b3..38c105e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -78,10 +78,14 @@ There are two independent expansion paths: For a trait `Foo` with hot types `[A, B]`: 1. Generates hidden inner trait `__FooImpl` with `__spec_*` method declarations. -2. Generates a compile-time assertion that `size_of::<*const dyn Foo>() == 2 * size_of::()`. -3. Generates `impl<'a> dyn Foo + 'a { ... }` with two primitive helpers — `__devirt_raw_parts(&Self) -> [usize; 2]` and `__devirt_vtable_for::() -> usize` — plus inherent methods for each user-declared trait method whose body is the vtable-comparison dispatch shim. -4. Generates public marker trait `Foo: __FooImpl` (no methods of its own). -5. Blanket impl: `impl Foo for T {}`. +2. Generates public base trait `FooBase` with the original method signatures (including default bodies). Cold types implement this directly without depending on `devirt`. +3. Generates blanket impl `impl __FooImpl for T { ... }` bridging base trait methods to `__spec_*` via `#[inline(always)]` delegation. +4. Generates a compile-time assertion that `size_of::<*const dyn Foo>() == 2 * size_of::()`. +5. Generates `impl<'a> dyn Foo + 'a { ... }` with two primitive helpers — `__devirt_raw_parts(&Self) -> [usize; 2]` and `__devirt_vtable_for::() -> usize` — plus inherent methods for each user-declared trait method whose body is the vtable-comparison dispatch shim. +6. Generates public marker trait `Foo: __FooImpl` (no methods of its own). +7. Blanket impl: `impl Foo for T {}`. + +Hot types use `#[devirt]` on impl blocks → generates `impl __FooImpl for T` directly (bypasses the base blanket, enables `#[inline]`). Cold types implement `FooBase` → blanket provides `__FooImpl` → blanket provides `Foo`. ### `__devirt_define! { @impl ... }` Expansion diff --git a/README.md b/README.md index c891d41..c7dba27 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ use std::f64::consts::PI; struct Circle { radius: f64 } struct Rect { w: f64, h: f64 } struct Triangle { a: f64, b: f64, c: f64 } +struct Hexagon { side: f64 } // 1. Define trait — list hot types in the attribute #[devirt::devirt(Circle, Rect)] @@ -36,7 +37,7 @@ pub trait Shape { fn scale(&mut self, factor: f64); } -// 2. Hot type — vtable-cmp match, direct inlined call +// 2. Hot type — #[devirt] generates optimized direct dispatch #[devirt::devirt] impl Shape for Circle { fn area(&self) -> f64 { PI * self.radius * self.radius } @@ -51,7 +52,7 @@ impl Shape for Rect { fn scale(&mut self, factor: f64) { self.w *= factor; self.h *= factor; } } -// 3. Cold type — falls back to vtable +// 3a. Cold type via #[devirt] — also works, adds #[inline] #[devirt::devirt] impl Shape for Triangle { fn area(&self) -> f64 { @@ -64,7 +65,14 @@ impl Shape for Triangle { } } -// 4. Use — completely normal dyn Trait +// 3b. Cold type via ShapeBase — no devirt dependency needed +impl ShapeBase for Hexagon { + fn area(&self) -> f64 { 1.5 * 3f64.sqrt() * self.side * self.side } + fn perimeter(&self) -> f64 { 6.0 * self.side } + fn scale(&mut self, factor: f64) { self.side *= factor; } +} + +// 4. Use — completely normal dyn Trait. Both cold types work identically. fn total_area(shapes: &[Box]) -> f64 { shapes.iter().map(|s| s.area()).sum() } @@ -102,6 +110,24 @@ devirt::devirt! { Both APIs produce identical expanded code. +### Implementing from downstream crates + +The trait definition generates a public `{Trait}Base` trait (e.g., +`ShapeBase`) with the original method signatures. Downstream crates +can implement this trait directly — no `devirt` dependency required: + +```rust +// In a downstream crate — no devirt in Cargo.toml +impl my_shapes::ShapeBase for MyWidget { + fn area(&self) -> f64 { self.w * self.h } + fn perimeter(&self) -> f64 { 2.0 * (self.w + self.h) } + fn scale(&mut self, factor: f64) { self.w *= factor; self.h *= factor; } +} + +// MyWidget now satisfies the Shape bound: +let shapes: Vec> = vec![Box::new(MyWidget { w: 3.0, h: 4.0 })]; +``` + ### `dyn Trait + Send` / `Sync` The proc-macro attribute automatically emits dispatch shims for diff --git a/crates/core/examples/shapes.rs b/crates/core/examples/shapes.rs index 62a9fc6..d38e8e8 100644 --- a/crates/core/examples/shapes.rs +++ b/crates/core/examples/shapes.rs @@ -6,6 +6,7 @@ //! //! Note: this example uses `std`; the `devirt` crate itself is `#![no_std]`. #![expect(clippy::print_stdout, reason = "example intentionally prints output to demonstrate API usage")] +#![expect(clippy::unnecessary_literal_bound, reason = "trait declares &str, not &'static str")] struct Circle { radius: f64 } struct Rect { w: f64, h: f64 } @@ -97,9 +98,9 @@ impl Shape for Triangle { fn name(&self) -> &str { "triangle" } } -// Downstream type — not in the hot list, automatically uses vtable -#[devirt::devirt] -impl Shape for Hexagon { +// Cold type — implements ShapeBase directly, no #[devirt] needed. +// Downstream crates can do this without depending on devirt at all. +impl ShapeBase for Hexagon { fn area(&self) -> f64 { 1.5 * 3.0_f64.sqrt() * self.side * self.side } fn perimeter(&self) -> f64 { 6.0 * self.side } fn scale(&mut self, factor: f64) { self.side *= factor; } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index de934b7..2057211 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -17,6 +17,8 @@ //! The trait definition (`#[devirt::devirt(Hot1, Hot2)]` or //! `devirt::devirt! { trait Foo [Hot1, Hot2] { ... } }`) generates: //! - A hidden inner trait `__XImpl` with `__spec_*` method declarations +//! - A public base trait `XBase` with the original method signatures +//! - A blanket impl bridging `XBase` to `__XImpl` //! - Two `#[doc(hidden)]` inherent helpers on `dyn X`: //! `__devirt_raw_parts` (extracts `[data, vtable]` from a fat pointer) //! and `__devirt_vtable_for::()` (returns the compiler-assigned @@ -24,45 +26,30 @@ //! - Inherent dispatch methods on `dyn X` whose bodies compare the //! runtime vtable pointer against each hot type's vtable and dispatch //! directly on match, or fall through to `__spec_*` otherwise -//! - A blanket impl: `impl X for T {}` +//! - A marker trait `X: __XImpl` and blanket `impl X for T {}` //! -//! The impl (`#[devirt::devirt]` or `devirt::devirt! { impl Foo for T { ... } }`) -//! generates: -//! - `impl __XImpl for ConcreteType { ... }` with the `__spec_*` bodies +//! Hot types use `#[devirt::devirt]` on their impl blocks to generate +//! an optimized direct `impl __XImpl` with `#[inline]`. //! -//! The proc-macro attribute (`#[devirt]`) emits the dispatch code -//! directly via `quote!`, supporting the full range of method -//! signatures that `syn` can parse (lifetimes, `unsafe fn`, -//! supertraits, attributes, `extern "ABI" fn`, etc.). -//! The declarative macro (`devirt!`) delegates to `__devirt_define!`, -//! which has more limited syntax support. +//! Cold types can either use `#[devirt::devirt]` (same as hot types) +//! or implement `XBase` directly with standard Rust — no `devirt` +//! dependency needed. The blanket impl automatically bridges `XBase` +//! to `__XImpl`, satisfying the `X` trait bound. //! //! # Usage //! //! ```ignore -//! // With proc-macro attribute (default): //! #[devirt::devirt(HotType1, HotType2)] //! pub trait MyTrait { //! fn method(&self) -> ReturnType; //! } //! +//! // Hot type — use #[devirt] for optimized direct dispatch //! #[devirt::devirt] -//! impl MyTrait for HotType1 { -//! fn method(&self) -> ReturnType { ... } -//! } -//! -//! // With declarative macro (default-features = false): -//! devirt::devirt! { -//! pub trait MyTrait [HotType1, HotType2] { -//! fn method(&self) -> ReturnType; -//! } -//! } +//! impl MyTrait for HotType1 { ... } //! -//! devirt::devirt! { -//! impl MyTrait for HotType1 { -//! fn method(&self) -> ReturnType { ... } -//! } -//! } +//! // Cold type — implement MyTraitBase directly, no devirt needed +//! impl MyTraitBase for ColdType { ... } //! ``` //! //! # LTO @@ -137,7 +124,7 @@ pub use paste::paste as __paste; macro_rules! __devirt_define { (@trait [$($unsafety:tt)*] $(#[$meta:meta])* - $vis:vis $trait_name:ident [$($hot:ty),+ $(,)?] { + $vis:vis $trait_name:ident $base_name:ident [$($hot:ty),+ $(,)?] { $($methods:tt)* } ) => { @@ -146,58 +133,44 @@ macro_rules! __devirt_define { $vis $($unsafety)* trait [<__ $trait_name Impl>] { $crate::__devirt_define!{@spec_decl $($methods)*} } + } + + /// Base implementation trait: implement this for cold types + /// without depending on `devirt`. + $vis $($unsafety)* trait $base_name { + $($methods)* + } + + $crate::__paste! { + $($unsafety)* impl<__DevirtT: $base_name + ?Sized> [<__ $trait_name Impl>] for __DevirtT { + $crate::__devirt_define!{@blanket_bridge $base_name, $($methods)*} + } + } + + const _: () = assert!( + ::core::mem::size_of::<*const dyn $trait_name>() + == 2 * ::core::mem::size_of::() + ); - // Compile-time sanity check: `*const dyn Trait` must be a fat - // pointer of exactly two `usize`s. If a future Rust edition - // changes this, compilation fails loudly rather than producing - // UB at runtime. - const _: () = assert!( - ::core::mem::size_of::<*const dyn $trait_name>() - == 2 * ::core::mem::size_of::() - ); - - // Inherent helpers on `dyn $trait_name` that expose the fat - // pointer's `(data, vtable)` halves and the compiler-assigned - // vtable address for a concrete hot type. These are `#[inline( - // always)]` so LTO folds them into the dispatch shim. + $crate::__paste! { impl<'__devirt> dyn $trait_name + '__devirt { - /// Split a fat pointer into `[data, vtable]`. #[doc(hidden)] #[inline(always)] pub fn __devirt_raw_parts(this: &Self) -> [usize; 2] { - // SAFETY: `&(dyn $trait_name + '_)` is a two-`usize` - // fat pointer (verified by the compile-time - // `size_of` assertion above) laid out as - // `[data, vtable]`. Transmuting to `[usize; 2]` - // only reinterprets bits — the data half is still - // borrowed for the duration of `this`, so the - // result may not outlive the borrow. unsafe { ::core::mem::transmute::< &Self, [usize; 2], >(this) } } - /// Vtable pointer for the `(T, Self)` pair. #[doc(hidden)] #[inline(always)] pub fn __devirt_vtable_for< __DevirtT: [<__ $trait_name Impl>] + 'static, >() -> usize { - // A dangling, non-null, aligned `*const __DevirtT`. - // We never dereference it — the coercion below only - // reads the vtable metadata the compiler attaches. let fake: *const __DevirtT = ::core::ptr::without_provenance( ::core::mem::align_of::<__DevirtT>(), ); - // Coercion is a metadata-attaching op; the resulting - // fat pointer's vtable half is the - // `(__DevirtT, $trait_name)` vtable selected by the - // compiler. Its data half is `fake`, which we discard. let fat: *const Self = fake; - // SAFETY: `*const Self` (dyn trait fat pointer) is - // two `usize`s by the compile-time assertion above. - // We read only the vtable half; the dangling data - // half is discarded without dereferencing. let __parts: [usize; 2] = unsafe { ::core::mem::transmute::< *const Self, [usize; 2], @@ -207,14 +180,6 @@ macro_rules! __devirt_define { } } - // Inherent dispatch methods on `dyn $trait_name`. These - // contain the vtable-comparison hot-path and take priority - // over the trait's default methods during method resolution, - // so a call like `dyn_trait.method()` reaches this block - // before falling back to the trait method. Putting the cast - // `self as *const dyn $trait_name` here (where `Self = dyn - // $trait_name`) avoids the `Self: Sized` requirement that - // would otherwise arise in a default method body. impl<'__devirt> dyn $trait_name + '__devirt { $crate::__devirt_define!{ @inherent_decl @@ -225,18 +190,6 @@ macro_rules! __devirt_define { } } - // The public trait is a thin marker over the hidden inner - // trait: it carries no methods of its own, so `dyn - // $trait_name` has no trait methods to conflict with the - // inherent dispatch methods emitted above. Methods named - // from the user's declaration resolve unambiguously to the - // inherent block. - // - // Concrete-type callers that want to bypass dispatch - // entirely can either call `<$trait_name>::$method` via - // an explicit dyn coercion `(&t as &dyn $trait_name).$method - // (...)` or call `::__spec_$method - // (&t, ...)` via UFCS. $(#[$meta])* $vis $($unsafety)* trait $trait_name: [<__ $trait_name Impl>] {} @@ -270,6 +223,45 @@ macro_rules! __devirt_define { (@spec_decl) => {}; + // ── @blanket_bridge ──────────────────────────────────────────────────── + // + // Generates method bodies for the blanket + // `impl __FooImpl for T { ... }`. + // Each `__spec_*` method delegates to the corresponding + // `FooBase::method` call. + + // &self + (@blanket_bridge $base_name:ident, + $(#[$_attr:meta])* + fn $method:ident(&self $(, $arg:ident : $argty:ty)*) $(-> $ret:ty)?; + $($rest:tt)* + ) => { + $crate::__paste! { + #[inline(always)] + fn [<__spec_ $method>](&self $(, $arg: $argty)*) $(-> $ret)? { + $base_name::$method(self $(, $arg)*) + } + } + $crate::__devirt_define!{@blanket_bridge $base_name, $($rest)*} + }; + + // &mut self + (@blanket_bridge $base_name:ident, + $(#[$_attr:meta])* + fn $method:ident(&mut self $(, $arg:ident : $argty:ty)*) $(-> $ret:ty)?; + $($rest:tt)* + ) => { + $crate::__paste! { + #[inline(always)] + fn [<__spec_ $method>](&mut self $(, $arg: $argty)*) $(-> $ret)? { + $base_name::$method(self $(, $arg)*) + } + } + $crate::__devirt_define!{@blanket_bridge $base_name, $($rest)*} + }; + + (@blanket_bridge $base_name:ident,) => {}; + // ── @inherent_decl ────────────────────────────────────────────────────── // // Emits inherent methods on `impl dyn $trait_name { ... }` that do @@ -378,34 +370,30 @@ macro_rules! __devirt_define { ) => {{ let __raw: [usize; 2] = ::__devirt_raw_parts($this); + let __data: *const () = ::core::ptr::from_ref::($this).cast::<()>(); $crate::__devirt_define!(@dispatch_ref_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw, __data) }}; (@dispatch_ref_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident + ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident, $data:ident ) => {{ if $raw[1] == ::__devirt_vtable_for::<$first>() { - // Bind the data-half to a local `*const $first` outside - // any `unsafe` block so that no metavariable is expanded - // inside `unsafe { ... }` — clippy's - // `macro_metavars_in_unsafe` lint flags the latter. - let __p: *const $first = $raw[0] as *const $first; + let __p: *const $first = $data.cast::<$first>(); // SAFETY: vtable identity implies type identity. The data - // half is the original `&$first` the caller coerced into - // the fat pointer, valid for at least the lifetime of - // `$this`'s borrow. + // pointer preserves provenance from the original `&dyn` + // reference, valid for at least the lifetime of `$this`. let __concrete: &$first = unsafe { &*__p }; return $crate::__paste! { __concrete.[<__spec_ $method>]($($arg),*) }; } $crate::__devirt_define!(@dispatch_ref_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw, $data) }}; (@dispatch_ref_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [], $raw:ident + ($($arg:expr),*), [], $raw:ident, $data:ident ) => { $crate::__paste! { $inner::[<__spec_ $method>] }($this $(, $arg)*) }; @@ -417,15 +405,16 @@ macro_rules! __devirt_define { ) => {{ let __raw: [usize; 2] = ::__devirt_raw_parts($this); + let __data: *const () = ::core::ptr::from_ref::($this).cast::<()>(); $crate::__devirt_define!(@dispatch_void_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw, __data) }}; (@dispatch_void_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident + ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident, $data:ident ) => {{ if $raw[1] == ::__devirt_vtable_for::<$first>() { - let __p: *const $first = $raw[0] as *const $first; + let __p: *const $first = $data.cast::<$first>(); // SAFETY: see @dispatch_ref_chain above. let __concrete: &$first = unsafe { &*__p }; $crate::__paste! { @@ -434,42 +423,34 @@ macro_rules! __devirt_define { return; } $crate::__devirt_define!(@dispatch_void_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw, $data) }}; (@dispatch_void_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [], $raw:ident + ($($arg:expr),*), [], $raw:ident, $data:ident ) => { $crate::__paste! { $inner::[<__spec_ $method>] }($this $(, $arg)*) }; // ── @dispatch_mut: &mut self, non-void ───────────────────────────────── - // - // The `&mut` arms go through a raw `*mut` dereference rather than - // constructing a named `&mut $hot` binding. This keeps the hot-branch - // reborrow scoped to the single method call expression and avoids - // aliasing the still-live `&mut dyn $trait_name` under Stacked - // Borrows. The cold fallback path uses `self` directly. (@dispatch_mut $inner:ident, $trait_name:ident, $this:tt, $method:ident, ($($arg:expr),*), [$($hot:ty),+] ) => {{ - // Shared reborrow of `&mut dyn $trait_name` to read the fat - // pointer halves. The reborrow is scoped to the call to - // `__devirt_raw_parts` and released before we construct any - // `&mut $first` on the hot path, so there is no overlapping - // mutable alias. + // Shared reborrow to read vtable, then raw pointer from the + // mutable ref to preserve provenance for the hot-path cast. let __raw: [usize; 2] = ::__devirt_raw_parts(&*$this); + let __data: *mut () = ::core::ptr::from_mut::($this).cast::<()>(); $crate::__devirt_define!(@dispatch_mut_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw, __data) }}; (@dispatch_mut_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident + ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident, $data:ident ) => {{ if $raw[1] == ::__devirt_vtable_for::<$first>() { - let __p: *mut $first = $raw[0] as *mut $first; + let __p: *mut $first = $data.cast::<$first>(); // SAFETY: vtable match → the underlying storage is a // `$first`. The reborrow to `&mut $first` is scoped to // this branch (which returns immediately), so the @@ -481,11 +462,11 @@ macro_rules! __devirt_define { }; } $crate::__devirt_define!(@dispatch_mut_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw, $data) }}; (@dispatch_mut_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [], $raw:ident + ($($arg:expr),*), [], $raw:ident, $data:ident ) => { $crate::__paste! { $inner::[<__spec_ $method>] }(&mut *$this $(, $arg)*) }; @@ -497,15 +478,16 @@ macro_rules! __devirt_define { ) => {{ let __raw: [usize; 2] = ::__devirt_raw_parts(&*$this); + let __data: *mut () = ::core::ptr::from_mut::($this).cast::<()>(); $crate::__devirt_define!(@dispatch_mut_void_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($hot),+], __raw, __data) }}; (@dispatch_mut_void_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident + ($($arg:expr),*), [$first:ty $(, $rest:ty)*], $raw:ident, $data:ident ) => {{ if $raw[1] == ::__devirt_vtable_for::<$first>() { - let __p: *mut $first = $raw[0] as *mut $first; + let __p: *mut $first = $data.cast::<$first>(); // SAFETY: see @dispatch_mut_chain above. let __ref: &mut $first = unsafe { &mut *__p }; $crate::__paste! { @@ -514,11 +496,11 @@ macro_rules! __devirt_define { return; } $crate::__devirt_define!(@dispatch_mut_void_chain - $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw) + $inner, $trait_name, $this, $method, ($($arg),*), [$($rest),*], $raw, $data) }}; (@dispatch_mut_void_chain $inner:ident, $trait_name:ident, $this:tt, $method:ident, - ($($arg:expr),*), [], $raw:ident + ($($arg:expr),*), [], $raw:ident, $data:ident ) => { $crate::__paste! { $inner::[<__spec_ $method>] }(&mut *$this $(, $arg)*) }; @@ -578,11 +560,13 @@ macro_rules! devirt { $($methods:tt)* } ) => { - $crate::__devirt_define! { - @trait [] - $(#[$meta])* - $vis $name [$($hot),+] { - $($methods)* + $crate::__paste! { + $crate::__devirt_define! { + @trait [] + $(#[$meta])* + $vis $name [<$name Base>] [$($hot),+] { + $($methods)* + } } } }; @@ -594,11 +578,13 @@ macro_rules! devirt { $($methods:tt)* } ) => { - $crate::__devirt_define! { - @trait [unsafe] - $(#[$meta])* - $vis $name [$($hot),+] { - $($methods)* + $crate::__paste! { + $crate::__devirt_define! { + @trait [unsafe] + $(#[$meta])* + $vis $name [<$name Base>] [$($hot),+] { + $($methods)* + } } } }; @@ -659,7 +645,7 @@ mod primitives { crate::__devirt_define! { @trait [] - pub Probe [Hot, Also] { + pub Probe ProbeBase [Hot, Also] { fn get(&self) -> u64; fn set(&mut self, v: u64); } @@ -778,4 +764,34 @@ mod primitives { let boxed: Box = Box::new(Hot { val: 5 }); assert_eq!(boxed.get(), 5); } + + /// Cold types can implement `ProbeBase` directly (no devirt macro) + /// and still participate in `dyn Probe` dispatch via the blanket. + struct NakedCold { + val: u64, + } + + impl ProbeBase for NakedCold { + fn get(&self) -> u64 { self.val.wrapping_mul(2) } + fn set(&mut self, v: u64) { self.val = v.wrapping_mul(2); } + } + + #[test] + fn cold_type_via_base_trait_ref() { + let nc = NakedCold { val: 5 }; + assert_eq!((&nc as &dyn Probe).get(), 10); + } + + #[test] + fn cold_type_via_base_trait_mut() { + let mut nc = NakedCold { val: 0 }; + (&mut nc as &mut dyn Probe).set(3); + assert_eq!(nc.val, 6); + } + + #[test] + fn cold_type_via_base_trait_box() { + let boxed: Box = Box::new(NakedCold { val: 7 }); + assert_eq!(boxed.get(), 14); + } } diff --git a/crates/core/tests/kani.rs b/crates/core/tests/kani.rs index 881b742..bccf270 100644 --- a/crates/core/tests/kani.rs +++ b/crates/core/tests/kani.rs @@ -26,7 +26,7 @@ mod n1 { devirt::__devirt_define! { @trait [] - pub Trait1 [Hot] { + pub Trait1 Trait1Base [Hot] { fn compute(&self, x: u64) -> u64; fn notify(&self, x: u64); fn transform(&mut self, x: u64) -> u64; @@ -122,7 +122,7 @@ mod n2 { devirt::__devirt_define! { @trait [] - pub Trait2 [HotA, HotB] { + pub Trait2 Trait2Base [HotA, HotB] { fn compute(&self, x: u64) -> u64; fn notify(&self, x: u64); fn transform(&mut self, x: u64) -> u64; @@ -257,7 +257,7 @@ mod n3 { devirt::__devirt_define! { @trait [] - pub Trait3 [HotA, HotB, HotC] { + pub Trait3 Trait3Base [HotA, HotB, HotC] { fn compute(&self, x: u64) -> u64; fn notify(&self, x: u64); fn transform(&mut self, x: u64) -> u64; @@ -405,7 +405,7 @@ mod vt { devirt::__devirt_define! { @trait [] - pub TraitVt [Hot] { + pub TraitVt TraitVtBase [Hot] { fn compute(&self, x: u64) -> u64; } } diff --git a/crates/core/tests/ui/all_arms.rs b/crates/core/tests/ui/all_arms.rs index 785fc5f..a2d86b8 100644 --- a/crates/core/tests/ui/all_arms.rs +++ b/crates/core/tests/ui/all_arms.rs @@ -8,7 +8,7 @@ struct ColdType { devirt::__devirt_define! { @trait [] - pub AllArms [Hot] { + pub AllArms AllArmsBase [Hot] { fn ref_nonvoid(&self, x: f64) -> f64; fn ref_void(&self, x: f64); fn mut_nonvoid(&mut self, x: f64) -> f64; diff --git a/crates/core/tests/ui/method_attrs.rs b/crates/core/tests/ui/method_attrs.rs index fb7ca2f..6313b35 100644 --- a/crates/core/tests/ui/method_attrs.rs +++ b/crates/core/tests/ui/method_attrs.rs @@ -4,7 +4,7 @@ struct Hot { devirt::__devirt_define! { @trait [] - pub Checked [Hot] { + pub Checked CheckedBase [Hot] { /// Computes the value. #[must_use] fn compute(&self) -> f64; diff --git a/crates/core/tests/ui/missing_method.rs b/crates/core/tests/ui/missing_method.rs index 0f88465..857f358 100644 --- a/crates/core/tests/ui/missing_method.rs +++ b/crates/core/tests/ui/missing_method.rs @@ -2,7 +2,7 @@ struct Foo; devirt::__devirt_define! { @trait [] - pub TwoMethods [Foo] { + pub TwoMethods TwoMethodsBase [Foo] { fn first(&self) -> i32; fn second(&self) -> i32; } diff --git a/crates/core/tests/ui/missing_method.stderr b/crates/core/tests/ui/missing_method.stderr index 70f14c4..26675da 100644 --- a/crates/core/tests/ui/missing_method.stderr +++ b/crates/core/tests/ui/missing_method.stderr @@ -3,7 +3,7 @@ error[E0046]: not all trait items implemented, missing: `__spec_second` | 3 | / devirt::__devirt_define! { 4 | | @trait [] - 5 | | pub TwoMethods [Foo] { + 5 | | pub TwoMethods TwoMethodsBase [Foo] { 6 | | fn first(&self) -> i32; ... | 9 | | } diff --git a/crates/core/tests/ui/multi_arg.rs b/crates/core/tests/ui/multi_arg.rs index 491c9f5..71ea331 100644 --- a/crates/core/tests/ui/multi_arg.rs +++ b/crates/core/tests/ui/multi_arg.rs @@ -5,7 +5,7 @@ struct Widget { devirt::__devirt_define! { @trait [] - pub MultiArg [Widget] { + pub MultiArg MultiArgBase [Widget] { fn add(&self, a: f64, b: f64) -> f64; fn set(&mut self, a: f64, b: f64); } diff --git a/crates/core/tests/ui/multi_hot.rs b/crates/core/tests/ui/multi_hot.rs index 0622654..d6c4c7f 100644 --- a/crates/core/tests/ui/multi_hot.rs +++ b/crates/core/tests/ui/multi_hot.rs @@ -4,7 +4,7 @@ struct C; devirt::__devirt_define! { @trait [] - pub MultiHot [A, B, C] { + pub MultiHot MultiHotBase [A, B, C] { fn id(&self) -> u8; } } diff --git a/crates/core/tests/ui/pub_trait.rs b/crates/core/tests/ui/pub_trait.rs index d8b5740..a057b00 100644 --- a/crates/core/tests/ui/pub_trait.rs +++ b/crates/core/tests/ui/pub_trait.rs @@ -5,7 +5,7 @@ struct Inner { devirt::__devirt_define! { @trait [] /// A public trait with documentation. - pub DocTrait [Inner] { + pub DocTrait DocTraitBase [Inner] { /// Returns the inner value. fn get(&self) -> i32; } diff --git a/crates/core/tests/ui/single_hot.rs b/crates/core/tests/ui/single_hot.rs index 2abafeb..6a0cb00 100644 --- a/crates/core/tests/ui/single_hot.rs +++ b/crates/core/tests/ui/single_hot.rs @@ -4,7 +4,7 @@ struct Foo { devirt::__devirt_define! { @trait [] - pub SingleHot [Foo] { + pub SingleHot SingleHotBase [Foo] { fn get(&self) -> f64; } } diff --git a/crates/core/tests/ui/unsafe_trait.rs b/crates/core/tests/ui/unsafe_trait.rs index b6f4c78..c5c57db 100644 --- a/crates/core/tests/ui/unsafe_trait.rs +++ b/crates/core/tests/ui/unsafe_trait.rs @@ -8,7 +8,7 @@ struct Cold { devirt::__devirt_define! { @trait [unsafe] - pub Trusted [Hot] { + pub Trusted TrustedBase [Hot] { fn verify(&self) -> bool; } } diff --git a/crates/core/tests/ui/wrong_signature.rs b/crates/core/tests/ui/wrong_signature.rs index 8256f17..9ebf90e 100644 --- a/crates/core/tests/ui/wrong_signature.rs +++ b/crates/core/tests/ui/wrong_signature.rs @@ -2,7 +2,7 @@ struct Bar; devirt::__devirt_define! { @trait [] - pub WrongSig [Bar] { + pub WrongSig WrongSigBase [Bar] { fn compute(&self, x: f64) -> f64; } } diff --git a/crates/core/tests/ui_attr/attr_args_on_impl.rs b/crates/core/tests/ui_attr/attr_args_on_impl.rs index 995c20b..61dff91 100644 --- a/crates/core/tests/ui_attr/attr_args_on_impl.rs +++ b/crates/core/tests/ui_attr/attr_args_on_impl.rs @@ -2,7 +2,7 @@ struct Foo; devirt::__devirt_define! { @trait [] - pub ArgsOnImpl [Foo] { + pub ArgsOnImpl ArgsOnImplBase [Foo] { fn get(&self) -> i32; } } diff --git a/crates/core/tests/ui_attr/attr_args_on_impl.stderr b/crates/core/tests/ui_attr/attr_args_on_impl.stderr index 9b2f882..d302edf 100644 --- a/crates/core/tests/ui_attr/attr_args_on_impl.stderr +++ b/crates/core/tests/ui_attr/attr_args_on_impl.stderr @@ -7,12 +7,12 @@ error: hot types are specified on the trait definition, not the impl block = note: this error originates in the attribute macro `devirt::devirt` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `Foo: __ArgsOnImplImpl` is not satisfied - --> tests/ui_attr/attr_args_on_impl.rs:5:21 + --> tests/ui_attr/attr_args_on_impl.rs:5:36 | -5 | pub ArgsOnImpl [Foo] { - | ^^^ unsatisfied trait bound +5 | pub ArgsOnImpl ArgsOnImplBase [Foo] { + | ^^^ unsatisfied trait bound | -help: the trait `__ArgsOnImplImpl` is not implemented for `Foo` +help: the trait `ArgsOnImplBase` is not implemented for `Foo` --> tests/ui_attr/attr_args_on_impl.rs:1:1 | 1 | struct Foo; @@ -22,7 +22,18 @@ help: this trait has no implementations, consider adding one | 3 | / devirt::__devirt_define! { 4 | | @trait [] -5 | | pub ArgsOnImpl [Foo] { +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { +6 | | fn get(&self) -> i32; +7 | | } +8 | | } + | |_^ +note: required for `Foo` to implement `__ArgsOnImplImpl` + --> tests/ui_attr/attr_args_on_impl.rs:3:1 + | +3 | / devirt::__devirt_define! { +4 | | @trait [] +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { + | | -------------- unsatisfied trait bound introduced here 6 | | fn get(&self) -> i32; 7 | | } 8 | | } @@ -32,7 +43,7 @@ note: required by a bound in `<(dyn ArgsOnImpl + '__devirt)>::__devirt_vtable_fo | 3 | / devirt::__devirt_define! { 4 | | @trait [] -5 | | pub ArgsOnImpl [Foo] { +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { 6 | | fn get(&self) -> i32; 7 | | } 8 | | } @@ -42,24 +53,50 @@ note: required by a bound in `<(dyn ArgsOnImpl + '__devirt)>::__devirt_vtable_fo | required by this bound in `::__devirt_vtable_for` = note: this error originates in the macro `devirt::__devirt_define` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0599]: no method named `__spec_get` found for reference `&Foo` in the current scope +error[E0599]: the method `__spec_get` exists for reference `&Foo`, but its trait bounds were not satisfied --> tests/ui_attr/attr_args_on_impl.rs:3:1 | +1 | struct Foo; + | ---------- doesn't satisfy `Foo: ArgsOnImplBase` or `Foo: __ArgsOnImplImpl` +2 | +3 | / devirt::__devirt_define! { +4 | | @trait [] +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { +6 | | fn get(&self) -> i32; +7 | | } +8 | | } + | |_^ method cannot be called on `&Foo` due to unsatisfied trait bounds + | +note: the following trait bounds were not satisfied: + `&Foo: ArgsOnImplBase` + `Foo: ArgsOnImplBase` + --> tests/ui_attr/attr_args_on_impl.rs:5:20 + | 3 | / devirt::__devirt_define! { 4 | | @trait [] -5 | | pub ArgsOnImpl [Foo] { +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { + | | ^^^^^^^^^^^^^^ unsatisfied trait bound introduced here 6 | | fn get(&self) -> i32; 7 | | } 8 | | } - | |_^ method not found in `&Foo` + | |_- +note: the trait `ArgsOnImplBase` must be implemented + --> tests/ui_attr/attr_args_on_impl.rs:3:1 | +3 | / devirt::__devirt_define! { +4 | | @trait [] +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { +6 | | fn get(&self) -> i32; +7 | | } +8 | | } + | |_^ = help: items from traits can only be used if the trait is implemented and in scope note: `__ArgsOnImplImpl` defines an item `__spec_get`, perhaps you need to implement it --> tests/ui_attr/attr_args_on_impl.rs:3:1 | 3 | / devirt::__devirt_define! { 4 | | @trait [] -5 | | pub ArgsOnImpl [Foo] { +5 | | pub ArgsOnImpl ArgsOnImplBase [Foo] { 6 | | fn get(&self) -> i32; 7 | | } 8 | | } diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 211c393..5902ab6 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -461,6 +461,113 @@ fn build_delegating_methods( .collect() } +fn build_base_trait_items( + trait_item: &syn::ItemTrait, +) -> Vec { + trait_item + .items + .iter() + .filter_map(|item| { + if let syn::TraitItem::Fn(m) = item { + let attrs = &m.attrs; + let sig = &m.sig; + Some(m.default.as_ref().map_or_else( + || quote! { #(#attrs)* #sig; }, + |body| quote! { #(#attrs)* #sig #body }, + )) + } else { + None + } + }) + .collect() +} + +fn build_base_bridge_items( + trait_item: &syn::ItemTrait, + base_name: &syn::Ident, + trait_ty_generics: &syn::TypeGenerics<'_>, +) -> Vec { + trait_item + .items + .iter() + .filter_map(|item| match item { + syn::TraitItem::Type(t) => { + let type_name = &t.ident; + Some(quote! { + type #type_name = ::#type_name; + }) + } + syn::TraitItem::Fn(m) => { + let cfg_attrs: Vec<_> = m.attrs.iter() + .filter(|a| a.path().is_ident("cfg")) + .collect(); + let method_name = &m.sig.ident; + let spec_name = format_ident!("__spec_{method_name}"); + let (mut bridge_sig, arg_names) = rewrite_sig_with_named_args(&m.sig); + bridge_sig.ident = spec_name; + Some(quote! { + #(#cfg_attrs)* + #[inline(always)] + #bridge_sig { + ::#method_name(self, #(#arg_names),*) + } + }) + } + _ => None, + }) + .collect() +} + +#[expect(clippy::too_many_arguments)] +fn build_base_trait_expansion( + trait_item: &syn::ItemTrait, + unsafety: Option<&syn::token::Unsafe>, + has_trait_generics: bool, + base_name: &syn::Ident, + inner_name: &syn::Ident, + trait_generic_params: &Punctuated, + trait_ty_generics: &syn::TypeGenerics<'_>, + trait_where_clause: Option<&syn::WhereClause>, + inner_supers: &proc_macro2::TokenStream, +) -> proc_macro2::TokenStream { + let vis = &trait_item.vis; + let base_trait_items = build_base_trait_items(trait_item); + let base_bridge_items = build_base_bridge_items(trait_item, base_name, trait_ty_generics); + let assoc_type_decls = collect_assoc_types(trait_item).decls; + let trait_def_generics = if has_trait_generics { + quote! { <#trait_generic_params> } + } else { + quote! {} + }; + let base_blanket = if has_trait_generics { + quote! { + #unsafety impl< + __DevirtT: #base_name #trait_ty_generics + ?Sized, + #trait_generic_params + > #inner_name #trait_ty_generics for __DevirtT #trait_where_clause { + #(#base_bridge_items)* + } + } + } else { + quote! { + #unsafety impl<__DevirtT: #base_name + ?Sized> + #inner_name for __DevirtT #trait_where_clause + { + #(#base_bridge_items)* + } + } + }; + quote! { + /// Base implementation trait: implement this for cold types + /// without depending on `devirt`. + #vis #unsafety trait #base_name #trait_def_generics + #inner_supers #trait_where_clause + { #(#assoc_type_decls)* #(#base_trait_items)* } + + #base_blanket + } +} + fn emit_trait_expansion( trait_item: &syn::ItemTrait, hot_types: &[syn::Type], @@ -535,12 +642,21 @@ fn emit_trait_expansion( ); let assoc_type_decls = &assoc_info.decls; + let base_name = format_ident!("{name}Base"); + let base_expansion = build_base_trait_expansion( + trait_item, unsafety.as_ref(), has_trait_generics, &base_name, + &inner_name, trait_generic_params, &trait_ty_generics, + trait_where_clause.as_ref(), &inner_supers, + ); + quote! { #[doc(hidden)] #vis #unsafety trait #inner_name #trait_def_generics #inner_supers #trait_where_clause { #(#assoc_type_decls)* #(#spec_decls)* } + #base_expansion + #fat_ptr_assertion #vtable_helpers