Skip to content

Add FooBase trait so cold types can implement devirt traits without #[devirt]#40

Open
Kab1r wants to merge 7 commits into
mainfrom
claude/check-proc-macro-cold-types-M8mER
Open

Add FooBase trait so cold types can implement devirt traits without #[devirt]#40
Kab1r wants to merge 7 commits into
mainfrom
claude/check-proc-macro-cold-types-M8mER

Conversation

@Kab1r
Copy link
Copy Markdown
Owner

@Kab1r Kab1r commented May 23, 2026

When a library exposes a devirt trait, downstream crates previously had
to depend on devirt and use #[devirt] on their impl blocks even for cold
types. This was because the public trait is a marker with no methods —
the actual methods live on a hidden __FooImpl trait with _spec* names.

This commit adds a public FooBase trait with the original method names,
plus a blanket impl bridging FooBase → __FooImpl. Cold types can now
implement FooBase with standard Rust, no devirt dependency needed:

impl my_lib::ShapeBase for MyColdType {
    fn area(&self) -> f64 { ... }
}

Hot types continue using #[devirt] for the optimized direct __FooImpl
path. The change is fully backward-compatible — all existing code using
#[devirt] on impl blocks works identically.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey

…[devirt]

When a library exposes a devirt trait, downstream crates previously had
to depend on devirt and use #[devirt] on their impl blocks even for cold
types. This was because the public trait is a marker with no methods —
the actual methods live on a hidden __FooImpl trait with __spec_* names.

This commit adds a public FooBase trait with the original method names,
plus a blanket impl bridging FooBase → __FooImpl. Cold types can now
implement FooBase with standard Rust, no devirt dependency needed:

    impl my_lib::ShapeBase for MyColdType {
        fn area(&self) -> f64 { ... }
    }

Hot types continue using #[devirt] for the optimized direct __FooImpl
path. The change is fully backward-compatible — all existing code using
#[devirt] on impl blocks works identically.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Devirtualization now exposes an explicit base-trait layer so downstream crates can implement base traits directly for cold types, improving extensibility for trait-object use.
  • Documentation

    • Examples and README updated to show implementing generated base traits (including a Hexagon example) and clarified hot/cold dispatch guidance.
  • Tests

    • Test fixtures and UI expectations updated to reflect the new base-trait generation and dispatch behavior.

Walkthrough

The PR refactors the devirt macro system to introduce an explicit base trait layer that separates method declarations from hot-type dispatch logic. The macro now generates a {TraitName}Base trait containing method signatures, updates all dispatch chains to thread both raw and derived data pointers for safer hot-path casting, and modifies all invocation sites to pass the base-trait identifier to the macro machinery.

Changes

Base trait dispatch layer refactor

Layer / File(s) Summary
Macro codegen: base-trait helpers
crates/macros/src/lib.rs
New helper functions construct hidden {TraitName}Base traits with method declarations, generate bridge impls that delegate __spec_* calls into the base trait, and integrate those tokens in emit_trait_expansion.
Core macro: __devirt_define! and dispatch shims
crates/core/src/lib.rs
__devirt_define! accepts a $base_name, emits a public $base_name trait and a blanket bridge impl mapping base-trait calls to __spec_*; dispatch shims (@dispatch_*_chain) now compute and thread __data pointers alongside __raw and cast __data on vtable matches.
Tests, examples, and docs updated to Base trait form
crates/core/tests/*, crates/core/examples/shapes.rs, README.md, CLAUDE.md
All Kani and UI tests, stderr fixtures, the shapes example (Hexagon converted to implement ShapeBase), README examples, and documentation updated to reference/generated Base-trait identifiers; added NakedCold test implementing ProbeBase.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Kab1r/devirt#17: Changes to proc-macro codegen and dyn-trait shims; related to dispatch/vtable delegation.
  • Kab1r/devirt#24: Proc-macro trait expansion changes that interact with base-trait generation and where-clause forwarding.
  • Kab1r/devirt#14: Earlier changes to __devirt_define! expansion and macro invocation patterns related to this rewrite.

Poem

🐰 I stitched a Base trait, neat and spry,
Pointers threaded clean so hot paths fly,
Cold shapes hop in without a bind,
Docs and tests now show the kind,
A rabbit claps — macros hum by!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: introducing a FooBase trait to allow cold types to implement devirt traits without depending on #[devirt].
Description check ✅ Passed The description provides a clear explanation of the problem solved (downstream crates needing devirt dependency even for cold types), the solution (introducing a public FooBase trait with a blanket impl bridge), and demonstrates the new usage pattern with a concrete code example.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/check-proc-macro-cold-types-M8mER

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review

.filter_map(|arg| {
if let syn::FnArg::Typed(pat) = arg
&& let syn::Pat::Ident(pi) = &*pat.pat
{
return Some(&pi.ident);
}

P1 Badge Preserve wildcard args when forwarding base trait methods

validate_trait_method allows wildcard parameters (_) in trait signatures, but this bridge builder only forwards Pat::Ident arguments. For a method like fn foo(&self, _: u32);, the generated __spec_foo body calls Base::foo(self, ...) without the wildcard argument, producing invalid expansion and breaking existing #[devirt] traits that used wildcard parameters.


let attrs = &m.attrs;
let sig = &m.sig;
Some(quote! { #(#attrs)* #sig; })
} else {

P2 Badge Keep default method bodies in generated *Base traits

The generated base trait rewrites every method to a bare signature (#sig;), which drops user-provided default bodies. That means cold implementers of FooBase must reimplement methods that were previously optional overrides, changing trait semantics and defeating the goal of exposing the original trait API through *Base.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'Base implementation trait' pattern to the devirt crate, allowing cold types to implement a base trait directly without a dependency on the devirt macro while still participating in dynamic dispatch. The changes involve significant updates to the __devirt_define! and devirt! macros to generate these base traits and blanket implementations. Review feedback highlighted critical issues in the procedural macro implementation, including the use of unstable Rust syntax (let chains), incorrect handling of wildcard patterns in function arguments, and missing support for generic traits in the generated bridge code.

Comment thread crates/macros/src/lib.rs
Comment on lines +482 to +524
fn build_base_bridge_items(
trait_item: &syn::ItemTrait,
base_name: &syn::Ident,
) -> Vec<proc_macro2::TokenStream> {
trait_item
.items
.iter()
.filter_map(|item| match item {
syn::TraitItem::Type(t) => {
let type_name = &t.ident;
Some(quote! {
type #type_name = <Self as #base_name>::#type_name;
})
}
syn::TraitItem::Fn(m) => {
let method_name = &m.sig.ident;
let spec_name = format_ident!("__spec_{method_name}");
let mut bridge_sig = m.sig.clone();
bridge_sig.ident = spec_name;
let arg_names: Vec<_> = m
.sig
.inputs
.iter()
.filter_map(|arg| {
if let syn::FnArg::Typed(pat) = arg
&& let syn::Pat::Ident(pi) = &*pat.pat
{
return Some(&pi.ident);
}
None
})
.collect();
Some(quote! {
#[inline(always)]
#bridge_sig {
#base_name::#method_name(self, #(#arg_names),*)
}
})
}
_ => None,
})
.collect()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of build_base_bridge_items has several issues that will lead to compilation failures:

  1. Unstable Syntax: It uses "let chains" (&& let), which is currently an unstable Rust feature and will fail on stable compilers.
  2. Wildcard Handling: It skips arguments with wildcard patterns (_). This results in a mismatch between the function signature and the call site in the generated bridge code.
  3. Missing Generics: It does not include trait generics in the UFCS calls or associated type aliases. If the trait is generic (e.g., Trait<T>), the generated code will fail to compile because the base trait name will be used without its required type arguments.

Using the existing rewrite_sig_with_named_args helper and passing trait_ty_generics addresses all these points.

fn build_base_bridge_items(
    trait_item: &syn::ItemTrait,
    base_name: &syn::Ident,
    trait_ty_generics: &syn::TypeGenerics<'_>,
) -> Vec<proc_macro2::TokenStream> {
    trait_item
        .items
        .iter()
        .filter_map(|item| match item {
            syn::TraitItem::Type(t) => {
                let type_name = &t.ident;
                Some(quote! {
                    type #type_name = <Self as #base_name #trait_ty_generics>::#type_name;
                })
            }
            syn::TraitItem::Fn(m) => {
                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! {
                    #[inline(always)]
                    #bridge_sig {
                        <Self as #base_name #trait_ty_generics>::#method_name(self, #(#arg_names),*)
                    }
                })
            }
            _ => None,
        })
        .collect()
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust edition 2024 has been stable for years

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Items 2 and 3 were fixed in 16205cbbuild_base_bridge_items now uses rewrite_sig_with_named_args for proper wildcard handling and takes trait_ty_generics for fully-qualified UFCS calls.


Generated by Claude Code

Comment thread crates/macros/src/lib.rs Outdated
) -> 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The call to build_base_bridge_items must be updated to pass trait_ty_generics to ensure that the generated bridge code correctly handles generic traits.

Suggested change
let base_bridge_items = build_base_bridge_items(trait_item, base_name);
let base_bridge_items = build_base_bridge_items(trait_item, base_name, trait_ty_generics);

The hot-path dispatch previously used `$raw[0] as *const $first` to
recover the data pointer from a transmuted [usize; 2]. This integer-
to-pointer cast strips provenance, causing Miri UB when the hot path
is taken.

Fix: extract the data pointer directly from the trait object reference
via `ptr::from_ref`/`ptr::from_mut` + `.cast::<()>()`, preserving
provenance. The vtable comparison still uses the transmuted usize
(comparing integers is fine). The provenance-carrying data pointer is
threaded through the dispatch chain alongside the vtable value.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/macros/src/lib.rs`:
- Around line 501-513: The current extraction of argument idents from
m.sig.inputs only collects Pat::Ident and thus drops Pat::Wild (`_`) parameters
causing arity mismatch in the bridge call; update the logic that builds
arg_names (used for the bridge invocation `#base_name::`#method_name`(self,
#(`#arg_names`),*)`) to handle Pat::Wild by generating synthetic identifiers (or
simply reuse the existing rewrite_sig_with_named_args helper which already
produces named args for wildcards) so every parameter has a corresponding ident;
ensure this change aligns with validate_trait_method which permits Pat::Wild and
preserves argument ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 73bdeeb8-3c53-4aaa-b403-731b98dbfd68

📥 Commits

Reviewing files that changed from the base of the PR and between a1ab754 and dcb285a.

📒 Files selected for processing (16)
  • crates/core/examples/shapes.rs
  • crates/core/src/lib.rs
  • crates/core/tests/kani.rs
  • crates/core/tests/ui/all_arms.rs
  • crates/core/tests/ui/method_attrs.rs
  • crates/core/tests/ui/missing_method.rs
  • crates/core/tests/ui/missing_method.stderr
  • crates/core/tests/ui/multi_arg.rs
  • crates/core/tests/ui/multi_hot.rs
  • crates/core/tests/ui/pub_trait.rs
  • crates/core/tests/ui/single_hot.rs
  • crates/core/tests/ui/unsafe_trait.rs
  • crates/core/tests/ui/wrong_signature.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.stderr
  • crates/macros/src/lib.rs

Comment thread crates/macros/src/lib.rs Outdated
claude added 3 commits May 23, 2026 12:54
- Use rewrite_sig_with_named_args for wildcard parameter handling
- Preserve default method bodies in generated Base traits
- Pass trait_ty_generics through to UFCS calls in bridge methods

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
Update README, CLAUDE.md, and lib.rs module docs to show both ways
of implementing cold types: via #[devirt] (same as hot types) or via
the generated FooBase trait (no devirt dependency needed).

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
The main example now demonstrates both ways to implement a cold type:
- 3a: #[devirt] on the impl block (adds #[inline], requires devirt dep)
- 3b: impl ShapeBase directly (no devirt dependency needed)

The separate section is refocused on the cross-crate use case.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
Copy link
Copy Markdown
Owner Author

Kab1r commented May 23, 2026

@coderabbitai review


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Kab1r commented May 23, 2026

@codex review


Generated by Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== crates/macros/src/lib.rs ==="
cat crates/macros/src/lib.rs

echo ""
echo "=== crates/core/src/lib.rs ==="
cat crates/core/src/lib.rs

echo ""
echo "=== crates/core/examples/shapes.rs ==="
cat crates/core/examples/shapes.rs

echo ""
echo "=== crates/core/tests/ui_attr/attr_args_on_impl.stderr ==="
cat crates/core/tests/ui_attr/attr_args_on_impl.stderr

echo ""
echo "=== crates/core/tests/ui_attr/attr_args_on_impl.rs ==="
cat crates/core/tests/ui_attr/attr_args_on_impl.rs

echo ""
echo "=== crates/core/tests/kani.rs (first 100 lines) ==="
head -100 crates/core/tests/kani.rs

Length of output: 82409


@Kab1r Here is a full review of PR #40.


Overall Impression

The architecture is sound: introducing {TraitName}Base as the stable public surface lets downstream crates avoid a devirt dependency entirely for cold types, and the blanket bridge correctly routes them through the hidden __XImpl dispatch path. The provenance-safe pointer handling (from_ref/from_mut instead of raw __raw[0]) is a genuine safety improvement. The new tests for NakedCold via ProbeBase (ref, mut, box) give good end-to-end coverage.

A few things are worth addressing before merging.


1. @blanket_bridge ignores $base_name, hardcodes [<$trait_name Base>]

In crates/core/src/lib.rs, the @blanket_bridge arm silently ignores the $base_name argument it was passed and reconstructs the base-trait name via paste:

// `@trait` arm passes $base_name…
$($unsafety)* impl<__DevirtT: $base_name + ?Sized> [<__ $trait_name Impl>] for __DevirtT {
    $crate::__devirt_define!{`@blanket_bridge` $trait_name, $($methods)*}
    //                                       ↑ $base_name is dropped here
}

// …but `@blanket_bridge` hardcodes the name instead:
(`@blanket_bridge` $trait_name:ident,) => {
    [<$trait_name Base>]::$method(self $(, $arg)*)  // always "TraitNameBase"
};

This works today because every caller (both devirt! and the test files) passes exactly {TraitName}Base. However the @trait signature publicly accepts an arbitrary $base_name, so a user calling __devirt_define! directly with a non-conventional base name will silently get a compile error pointing at the wrong spot.

Fix: thread $base_name into @blanket_bridge:

// `@trait` arm:
$crate::__devirt_define!{`@blanket_bridge` $trait_name, $base_name, $($methods)*}

// `@blanket_bridge` arms — add $base_name:ident:
(`@blanket_bridge` $trait_name:ident, $base_name:ident,
    $(#[$_attr:meta])*
    fn $method:ident(&self $(, $arg:ident : $argty:ty)*) $(-> $ret:ty)?;
    $($rest:tt)*
) => {
    #[inline(always)]
    fn [<__spec_ $method>](&self $(, $arg: $argty)*) $(-> $ret)? {
        $base_name::$method(self $(, $arg)*)  // use $base_name directly, no paste
    }
    $crate::__devirt_define!{`@blanket_bridge` $trait_name, $base_name, $($rest)*}
};

// &mut self arm and base case similarly
(`@blanket_bridge` $trait_name:ident, $base_name:ident,) => {};

2. build_base_bridge_items omits self from non-receiver argument list (proc-macro path)

In crates/macros/src/lib.rs, build_base_bridge_items generates:

<Self as `#base_name` `#trait_ty_generics`>::`#method_name`(self, #(`#arg_names`),*)

rewrite_sig_with_named_args skips FnArg::Receiver, so self is not in arg_names — the explicit self here is correct. However, the generated bridge_sig still carries the original &self / &mut self receiver, so self is in scope as the implicit receiver. Passing it explicitly to a UFCS call is fine in Rust, but the pattern is slightly unusual. A brief comment would help future readers understand why self is written explicitly:

Some(quote! {
    #[inline(always)]
    `#bridge_sig` {
        // UFCS call: `self` is the receiver, `arg_names` are the remaining args.
        <Self as `#base_name` `#trait_ty_generics`>::`#method_name`(self, #(`#arg_names`),*)
    }
})

3. build_base_trait_expansion silently reuses inner_supers for the base trait

fn build_base_trait_expansion(
    ...
    inner_supers: &proc_macro2::TokenStream,  // from __XImpl
) -> proc_macro2::TokenStream {
    ...
    quote! {
        `#vis` `#unsafety` trait `#base_name` `#trait_def_generics`
            `#inner_supers` `#trait_where_clause`   // ← same bounds as __XImpl
        { ... }
    }
}

inner_supers is the user's original supertraits, stripped of Self predicates by build_fat_ptr_assertion. For normal traits this is correct, but it's worth verifying: if a user writes a supertrait bound like trait Foo: Bar and Bar itself is #[devirt]-generated (so it expands to : __BarImpl), will FooBase: Bar resolve correctly? Bar would already be in scope since it expands to a plain public trait, so this should be fine — but a test or comment confirming this interaction would add confidence.


4. Error messages now expose the internal {TraitName}Base name

The change to attr_args_on_impl.stderr (+47/-10) shows that error diagnostics now surface ArgsOnImplBase prominently:

help: the trait `ArgsOnImplBase` is not implemented for `Foo`
note: required for `Foo` to implement `__ArgsOnImplImpl`

This is an unavoidable consequence of the blanket bridge, but it is a user-experience regression: a developer using #[devirt::devirt(Foo)] on a trait and forgetting to implement it will now see ArgsOnImplBase in the error, which is a name they never wrote. Consider whether a #[doc(hidden)] attribute or a diagnostic rename (via #[diagnostic::on_unimplemented]) on {TraitName}Base could produce friendlier messages:

#[diagnostic::on_unimplemented(
    message = "implement `{Self}` for `{T}` using `impl {TraitName} for {T}` or `impl {TraitName}Base for {T}`"
)]
pub trait FooBase { ... }

5. Minor: build_base_trait_items — default body self-calls are not rewritten

In generate_spec_decls, default method bodies have their self.method() calls rewritten to self.__spec_method() so they resolve within __XImpl. In build_base_trait_items the original body is preserved as-is:

Some(m.default.as_ref().map_or_else(
    || quote! { #(`#attrs`)* `#sig`; },
    |body| quote! { #(`#attrs`)* `#sig` `#body` },  // body unchanged
))

This is correctFooBase uses the original method names, so self.method() in a default body should resolve to FooBase::method, not __spec_method. Just confirming this is intentional (it is) and adding a comment to that effect would prevent someone from accidentally applying RewriteSelfCalls here in the future.


Nits

  • build_base_bridge_items calls collect_assoc_types on the outer build_base_trait_expansion call, and also collect_assoc_types is called again in emit_trait_expansion. These two calls are on the same trait_item, so the second allocation is redundant — consider passing &assoc_info in.
  • The #[expect(clippy::too_many_arguments)] on build_base_trait_expansion is justified, but breaking the function into a BaseTrait builder struct would be cleaner long-term.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13ec1604fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/core/src/lib.rs Outdated
$crate::__paste! {
#[inline(always)]
fn [<__spec_ $method>](&self $(, $arg: $argty)*) $(-> $ret)? {
[<$trait_name Base>]::$method(self $(, $arg)*)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the declared base trait name in bridge methods

__devirt_define!(@trait ...) now takes an explicit $base_name, but the generated blanket bridge still hardcodes [<$trait_name Base>] when forwarding each __spec_* method. If a caller provides any base name other than the exact {Trait}Base spelling, expansion produces references to a non-existent trait and fails to compile. This is a functional regression in the new macro signature and should use $base_name consistently in bridge generation.

Useful? React with 👍 / 👎.

Comment thread crates/macros/src/lib.rs
);
let assoc_type_decls = &assoc_info.decls;

let base_name = format_ident!("{name}Base");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid forcing a public {Trait}Base symbol name

The proc-macro expansion unconditionally creates a new public trait named {Trait}Base via format_ident!("{name}Base"). That introduces a source-breaking collision for existing modules that already define an item with that name, causing duplicate-definition compile errors after upgrade even when previous #[devirt] usage was valid. This breaks backward compatibility and should be made configurable or collision-safe.

Useful? React with 👍 / 👎.

The @blanket_bridge macro arms were reconstructing the base trait name
via paste ([<$trait_name Base>]) instead of using the $base_name
parameter already accepted by @trait. This made the internal API
inconsistent — callers pass an explicit base name but it was ignored.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
Copy link
Copy Markdown
Owner Author

Kab1r commented May 24, 2026

@gemini-code-assist review


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Kab1r commented May 24, 2026

@coderabbitai review


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Kab1r commented May 24, 2026

@codex review


Generated by Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 2026

@Kab1r Sure! I'll kick off a fresh review of PR #40 right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a public base trait (e.g., FooBase) for devirtualized traits, enabling downstream crates to implement cold types without a direct dependency on the devirt crate. It also refines pointer provenance handling in the dispatch logic by using from_ref and from_mut. Review feedback identifies a potential MSRV increase to 1.76 due to the use of without_provenance, a limitation in the declarative macro where default method bodies are not supported in the new blanket bridge, and the omission of method attributes in the internal implementation trait.

Comment thread crates/core/src/lib.rs
// 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(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of ::core::ptr::without_provenance was stabilized in Rust 1.76. If this crate intends to support older Rust versions (MSRV), this will be a breaking change. However, given the use of other modern features like the expect attribute (stabilized in 1.81 for some uses), this is likely acceptable. If a lower MSRV is required, consider using addr as *const T which is the older way to create a pointer from an address.

Comment thread crates/core/src/lib.rs
Comment on lines +234 to +261
(@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)*}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The @blanket_bridge macro arms in the declarative macro expansion only match method signatures ending in a semicolon (e.g., fn method(&self);). This means that traits defined using the declarative devirt! macro with default method bodies will fail to generate the necessary bridge implementations for cold types. While the declarative macro is documented as having limited syntax support, this is a notable limitation for the new Base trait functionality.

Comment thread crates/macros/src/lib.rs
Comment on lines +506 to +510
#[inline(always)]
#bridge_sig {
<Self as #base_name #trait_ty_generics>::#method_name(self, #(#arg_names),*)
}
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bridge methods generated for the blanket implementation do not preserve attributes from the original trait methods. While #[inline(always)] is added, other functional attributes (like #[must_use]) or documentation are lost on the __FooImpl side. Since __FooImpl is an internal implementation detail used for dispatch, this is likely fine, but ensuring the public FooBase trait (handled in build_base_trait_items) retains them is critical.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad216cdb6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/macros/src/lib.rs
Comment on lines +506 to +509
#[inline(always)]
#bridge_sig {
<Self as #base_name #trait_ty_generics>::#method_name(self, #(#arg_names),*)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve cfg-gated methods in base bridge impl

build_base_bridge_items drops the original method attributes when generating __spec_* methods, so a trait method with #[cfg(...)] is conditionally removed from __{Trait}Impl but still emitted unconditionally in the blanket impl __{Trait}Impl for T. In cfg-disabled builds this produces a hard compile error (method ... is not a member of trait). The bridge method needs to carry through method attributes (at least #[cfg]) to stay structurally consistent with the generated inner trait.

Useful? React with 👍 / 👎.

Without this, a trait method gated by #[cfg(feature = "x")] would be
conditionally removed from __FooImpl but unconditionally emitted in the
blanket bridge, causing a compile error when the cfg is disabled.

Only #[cfg] attributes are carried through — other attributes like
#[must_use] don't belong on internal bridge methods.

https://claude.ai/code/session_018DyxQYLHG2JCYbUvoVA7Ey
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/macros/src/lib.rs`:
- Around line 501-503: The code collects conditional attributes into cfg_attrs
by filtering m.attrs for .path().is_ident("cfg"), but this omits cfg_attr and
can cause mismatched method presence; change the predicate to accept both "cfg"
and "cfg_attr" (e.g., check is_ident("cfg") || is_ident("cfg_attr")) wherever
cfg_attrs is built (referencing the binding cfg_attrs and the iteration over
m.attrs) so generated bridge methods preserve both cfg and cfg_attr attributes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 13827ee8-d455-409d-8211-a3fc1211c44d

📥 Commits

Reviewing files that changed from the base of the PR and between ad216cd and 4480322.

📒 Files selected for processing (1)
  • crates/macros/src/lib.rs

Comment thread crates/macros/src/lib.rs
Comment on lines +501 to +503
let cfg_attrs: Vec<_> = m.attrs.iter()
.filter(|a| a.path().is_ident("cfg"))
.collect();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include cfg_attr when propagating conditional method attributes.

Filtering only cfg drops cfg_attr, so conditionally-present/absent methods can diverge between trait items and generated bridge methods, causing trait-impl signature mismatches at compile time.

Suggested fix
-                let cfg_attrs: Vec<_> = m.attrs.iter()
-                    .filter(|a| a.path().is_ident("cfg"))
-                    .collect();
+                let cfg_attrs: Vec<_> = m
+                    .attrs
+                    .iter()
+                    .filter(|a| {
+                        let p = a.path();
+                        p.is_ident("cfg") || p.is_ident("cfg_attr")
+                    })
+                    .collect();

As per coding guidelines: **/*.rs proc-macro expansion should support method attributes with full signature fidelity.

Also applies to: 509-513

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/macros/src/lib.rs` around lines 501 - 503, The code collects
conditional attributes into cfg_attrs by filtering m.attrs for
.path().is_ident("cfg"), but this omits cfg_attr and can cause mismatched method
presence; change the predicate to accept both "cfg" and "cfg_attr" (e.g., check
is_ident("cfg") || is_ident("cfg_attr")) wherever cfg_attrs is built
(referencing the binding cfg_attrs and the iteration over m.attrs) so generated
bridge methods preserve both cfg and cfg_attr attributes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants