Skip to content

Unify devirt APIs: devirt! macro + #[devirt] attribute (0.2.0)#14

Merged
Kab1r merged 6 commits intomainfrom
claude/unify-devirt-apis-gEEJ1
Apr 12, 2026
Merged

Unify devirt APIs: devirt! macro + #[devirt] attribute (0.2.0)#14
Kab1r merged 6 commits intomainfrom
claude/unify-devirt-apis-gEEJ1

Conversation

@Kab1r
Copy link
Copy Markdown
Owner

@Kab1r Kab1r commented Apr 12, 2026

Replace devirt::r#trait! / devirt::r#impl! with a single devirt
name that works as either a proc-macro attribute (default) or a
declarative macro (with default-features = false).

Both APIs delegate to __devirt_define!, a #[doc(hidden)] internal
macro that holds all dispatch expansion logic. The [hot] marker on
impls is removed — hot-path specialization is driven entirely by the
trait's hot-type list.

Breaking changes:

  • devirt::r#trait! and devirt::r#impl! removed
  • [hot] marker on impl blocks no longer accepted
  • Version bumped to 0.2.0

New crate structure:

  • crates/macros (devirt-macros): proc-macro crate with #[devirt] attr
  • crates/core: gains optional macros feature (on by default)

Also updates LTO guidance: LTO is no longer required since all dispatch
logic expands via macros into the user's crate.

claude added 2 commits April 12, 2026 11:56
Replace `devirt::r#trait!` / `devirt::r#impl!` with a single `devirt`
name that works as either a proc-macro attribute (default) or a
declarative macro (with `default-features = false`).

Both APIs delegate to `__devirt_define!`, a #[doc(hidden)] internal
macro that holds all dispatch expansion logic. The `[hot]` marker on
impls is removed — hot-path specialization is driven entirely by the
trait's hot-type list.

Breaking changes:
- `devirt::r#trait!` and `devirt::r#impl!` removed
- `[hot]` marker on impl blocks no longer accepted
- Version bumped to 0.2.0

New crate structure:
- crates/macros (devirt-macros): proc-macro crate with #[devirt] attr
- crates/core: gains optional `macros` feature (on by default)

Also updates LTO guidance: LTO is no longer required since all dispatch
logic expands via macros into the user's crate.

https://claude.ai/code/session_01XRfaF7hqTVzJwR8tBrnkom
The __devirt_define! macro used bare `T` as the generic parameter in
__devirt_vtable_for and the blanket impl. When the user's trait was
also named `T`, the generic parameter shadowed the trait name, causing
a compile error. Rename both to `__DevirtT` to avoid collisions.

Also updates the equivalence test to use trait name `T` to exercise
this edge case.

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

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Attribute-based devirtualization workflow and new procedural-macro crate; workspace bumped to v0.2.0
  • Tests

    • Added comprehensive UI, equivalence, compile-fail, and benchmark-targeted tests covering both attribute and declarative flows
  • Documentation

    • Updated README and architecture docs with new usage examples, clarified performance/LTO guidance, and added build/test instructions

Walkthrough

Unifies devirtualization behind an internal __devirt_define! macro, adds a new crates/macros proc-macro exporting #[devirt] (gated by a default macros feature), migrates examples/tests to attribute/declarative forms, adds many UI tests, and bumps workspace to 0.2.0.

Changes

Cohort / File(s) Summary
Workspace & docs
Cargo.toml, README.md, CLAUDE.md
Workspace version → 0.2.0; added proc-macro deps (syn,quote,proc-macro2) and usage docs; removed LTO requirement and updated architecture/verification text to describe __devirt_define! and attribute workflow.
Core crate manifest
crates/core/Cargo.toml
Added rust-version = "1.85", optional devirt-macros dep, feature flags (default = ["macros"], macros = ["dep:devirt-macros"]); gated example/test/bench targets on macros.
Core library implementation
crates/core/src/lib.rs
Removed public r#trait!/r#impl!; added exported internal __devirt_define!, conditional devirt! for non-macros mode, and pub use devirt_macros::devirt when macros enabled; renamed internals and propagate method attributes.
Proc-macro crate
crates/macros/Cargo.toml, crates/macros/src/lib.rs
New devirt-macros crate (proc-macro = true) providing #[devirt]; parses trait/impl TokenStreams, validates payloads, preserves method attrs, and emits ::devirt::__devirt_define! { @trait`
Declarative → Attribute migration
crates/core/benches/dispatch.rs, crates/core/examples/shapes.rs, fuzz/fuzz_targets/dispatch.rs
Converted macro-form trait/impls to attribute/declarative forms (devirt::r#trait!/r#impl!#[devirt::devirt(...)] pub trait / #[devirt::devirt] impl), removed per-impl [hot] markers.
Macro-invocation updates in tests
crates/core/tests/ui/*.rs, crates/core/tests/ui/*.stderr, crates/core/tests/kani.rs, crates/core/tests/...
Rewrote many UI and Kani tests to call devirt::__devirt_define! { @trait [] ... } / @impl [] ... or to use the attribute tests; adjusted stderr snapshots and line offsets.
New/updated UI attribute tests
crates/core/tests/ui_attr.rs, crates/core/tests/ui_attr/*, crates/core/tests/ui_attr/*.stderr
Added trybuild-based runner and many new pass/fail UI fixtures exercising the attribute macro (single/multi-hot, method attrs, unsafe trait, diagnostic cases, etc.).
New equivalence & unit tests
crates/core/tests/equivalence.rs
Added equivalence test comparing declarative (devirt!) and attribute (#[devirt]) forms (feature-gated).
Additional UI tests
crates/core/tests/ui/method_attrs.rs, crates/core/tests/ui/unsafe_trait.rs
Added tests ensuring method attributes and unsafe traits behave correctly with the macro form.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Code
    participant Compiler as Rust Compiler
    participant Attr as devirt::devirt (proc-macro)
    participant Def as devirt::__devirt_define!

    User->>Compiler: #[devirt(TypeA,TypeB)] trait T { ... }
    Compiler->>Attr: trait TokenStream
    Attr->>Attr: parse & validate hot types
    Attr->>Def: emit ::devirt::__devirt_define! { `@trait` [...] ... }
    Def->>Def: expand trait -> vtable, helpers, markers
    Def-->>Compiler: expanded tokens
    Compiler-->>User: compiled trait + dispatch

    User->>Compiler: #[devirt] impl T for TypeA { ... }
    Compiler->>Attr: impl TokenStream
    Attr->>Attr: parse & validate (empty attr)
    Attr->>Def: emit ::devirt::__devirt_define! { `@impl` [] T for TypeA { ... } }
    Def->>Def: expand impl -> marker impls / method wiring
    Def-->>Compiler: expanded tokens
    Compiler-->>User: compiled impl
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I nibble tokens, stitch a neat design,

__devirt_define! calls line by line.
Hot types hop in, attribute leads the way,
Tests sprout blossoms, docs bright as day.
A rabbit cheers — macros now at play.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: unifying the devirt API from two separate macros (r#trait! and r#impl!) into a single 'devirt' identifier that works as both a macro and attribute, with version bump to 0.2.0.
Description check ✅ Passed The description clearly explains the key changes: replacement of old macros, removal of [hot] marker, introduction of the new unified API with proc-macro attribute and declarative macro variants, and the reorganized crate structure with the new macros crate.

✏️ 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/unify-devirt-apis-gEEJ1

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

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/core/examples/shapes.rs (1)

16-33: ⚠️ Potential issue | 🟠 Major

Use the public devirt API in the example.

This example is the user-facing cargo run --example shapes path, but it now teaches devirt::__devirt_define!, which is explicitly an internal #[doc(hidden)] hook. That means the example no longer exercises the supported surface for the default feature set, and it invites downstream users to depend on an implementation detail. Please switch this example to #[devirt::devirt] (or cfg-split it if you also want a no-default-features variant).

Also applies to: 37-112

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/core/examples/shapes.rs` around lines 16 - 33, The example currently
invokes the internal macro devirt::__devirt_define! to define the Shape trait
and its variants; replace that with the public API by annotating the trait with
#[devirt::devirt] (or provide a cfg-split variant for no-default-features) so
the example uses the supported surface: remove the devirt::__devirt_define!
wrapper and declare the pub trait Shape { fn area(&self)->f64; fn
perimeter(&self)->f64; fn scale(&mut self,f64); fn try_scale(&mut
self,f64)->bool; fn describe(&self); fn name(&self)->&str; } with
#[devirt::devirt] above it (keeping Circle and Rect implementations as-is) so
the example targets the public devirt API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/core/src/lib.rs`:
- Around line 51-56: The documentation examples for the declarative macro
devirt::devirt! are missing the required trait keyword; update both snippets
that declare MyTrait (the blocks using devirt::devirt! with MyTrait [HotType1,
HotType2] and the second example) to include "trait" (i.e., use "pub trait
MyTrait [...]") so the macro pattern matches and the examples compile when
copy-pasted.

In `@crates/core/tests/equivalence.rs`:
- Around line 54-70: Extend both decl_dispatch and attr_dispatch tests to
exercise all four unsafe dispatch arms instead of only the &self -> value path:
for the Hot and Cold instances used in decl::T and attr::T, add runtime
assertions that invoke the &self->value method (existing get()), the &self->void
method, the &mut->value method, and the &mut->void method so `@dispatch_void`,
`@dispatch_mut`, and `@dispatch_mut_void` code paths run through the proc-macro
surface; locate these additions inside the decl_dispatch (for cfg(not(feature =
"macros"))) and attr_dispatch (for cfg(feature = "macros")) functions
referencing Hot, Cold, and the dyn decl::T / dyn attr::T types.

In `@crates/macros/src/lib.rs`:
- Around line 98-141: expand_impl currently drops trait-path qualifiers, impl
generics/where-clauses, and method attributes when building the output; update
it to forward the full trait path (use the whole trait_path instead of
trait_path.segments.last().ident), include impl_item.generics and its
where_clause when emitting the impl header, and preserve each method's
attributes (include m.attrs when constructing method_bodies, similar to
expand_trait) so qualifiers like super::Trait, generics (impl<T>), where
clauses, and #[inline]/#[cfg] survive expansion; alternatively, if you cannot
support qualified paths or generics, make expand_impl emit a clear syn::Error
for non-plain cases (qualified trait paths, non-empty impl_item.generics, or
methods with attrs) instead of silently dropping them.
- Around line 50-95: expand_trait currently drops generics, where clauses,
supertraits, associated items, and default method bodies (and expand_impl drops
impl generics/where/attrs); add explicit validation in expand_trait (inspect
trait_item.generics, trait_item.supertraits, trait_item.items for
syn::TraitItem::Type/Const and for TraitItem::Fn with Some(block) default
bodies, and trait_item.generics.where_clause) and in expand_impl (inspect
syn::ItemImpl generics, self_ty, attrs, and where_clause) and when any
unsupported form is present return a compile error using syn::Error::new(span,
"...") .to_compile_error().into() with clear messages referencing the
unsupported feature (e.g. "devirt: generic traits are not supported", "devirt:
associated types/constants are not supported", "devirt: default method bodies
are not supported", "devirt: impl generics/where/attrs are not supported") so
the macro fails early rather than silently dropping elements; use the existing
trait_item/impl_item identifiers and proc_macro2::Span from the items for
accurate error spans.

---

Outside diff comments:
In `@crates/core/examples/shapes.rs`:
- Around line 16-33: The example currently invokes the internal macro
devirt::__devirt_define! to define the Shape trait and its variants; replace
that with the public API by annotating the trait with #[devirt::devirt] (or
provide a cfg-split variant for no-default-features) so the example uses the
supported surface: remove the devirt::__devirt_define! wrapper and declare the
pub trait Shape { fn area(&self)->f64; fn perimeter(&self)->f64; fn scale(&mut
self,f64); fn try_scale(&mut self,f64)->bool; fn describe(&self); fn
name(&self)->&str; } with #[devirt::devirt] above it (keeping Circle and Rect
implementations as-is) so the example targets the public devirt API.
🪄 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: b353d6a1-3391-47ed-a3b2-ce092807b2b0

📥 Commits

Reviewing files that changed from the base of the PR and between d62ba77 and d644bb5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • CLAUDE.md
  • Cargo.toml
  • README.md
  • crates/core/Cargo.toml
  • crates/core/benches/dispatch.rs
  • crates/core/examples/shapes.rs
  • crates/core/src/lib.rs
  • crates/core/tests/equivalence.rs
  • crates/core/tests/kani.rs
  • crates/core/tests/ui/all_arms.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/wrong_signature.rs
  • crates/core/tests/ui/wrong_signature.stderr
  • crates/core/tests/ui_attr.rs
  • crates/core/tests/ui_attr/attr_all_arms.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.stderr
  • crates/core/tests/ui_attr/attr_missing_args.rs
  • crates/core/tests/ui_attr/attr_missing_args.stderr
  • crates/core/tests/ui_attr/attr_multi_hot.rs
  • crates/core/tests/ui_attr/attr_on_struct.rs
  • crates/core/tests/ui_attr/attr_on_struct.stderr
  • crates/core/tests/ui_attr/attr_single_hot.rs
  • crates/macros/Cargo.toml
  • crates/macros/src/lib.rs
  • fuzz/fuzz_targets/dispatch.rs

Comment thread crates/core/src/lib.rs
Comment thread crates/core/tests/equivalence.rs
Comment thread crates/macros/src/lib.rs
Comment thread crates/macros/src/lib.rs
- Examples, benchmarks, and fuzz target now use the public
  #[devirt::devirt] attribute API instead of __devirt_define!
- Fix missing `trait` keyword in lib.rs doc example for devirt!
- Extend equivalence test to exercise all four dispatch arms
  (&self->val, &self->void, &mut->val, &mut->void)
- Add proc-macro validation: reject generic traits/impls, where
  clauses, supertraits, associated types/constants, and default
  method bodies with clear error messages
- Forward method attributes (#[inline], #[cfg], etc.) through
  expand_impl instead of silently dropping them

https://claude.ai/code/session_01XRfaF7hqTVzJwR8tBrnkom
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/core/src/lib.rs (1)

238-258: ⚠️ Potential issue | 🟠 Major

Fix @impl, @spec_decl, and @inherent_decl to handle method attributes forwarded by the proc-macro.

The proc-macro forwards trait and impl method attributes to __devirt_define!, but the macro patterns cannot handle them:

  • @impl expects fn $method(...) but receives #[attr] fn $method(...), causing the pattern to fail.
  • @spec_decl silently discards trait method attributes ($(#[$_attr:meta])* is never emitted).
  • @inherent_decl overwrites method attributes with hardcoded #[inline] and #[doc(hidden)], discarding user-provided attributes.

Either thread attributes through to generated methods (preserving user intent) or filter them out in the proc-macro before expanding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/core/src/lib.rs` around lines 238 - 258, The macro patterns must
accept and forward method attributes instead of dropping or overwriting them:
update the `@impl`, `@spec_decl`, and `@inherent_decl` arms in __devirt_define! to
include an optional $(#[$_attr:meta])* matcher on method productions (e.g. match
$(#[$_attr:meta])* fn $method(...)) and emit those attributes when generating
the helper methods (e.g. attach $(#[$_attr])* to the generated [<__spec_
$method>] or impl-method definitions) rather than hardcoding #[inline] and
#[doc(hidden)]; alternatively, if attributes must be filtered earlier, have the
proc-macro strip them before calling __devirt_define! so these macro arms only
see the clean fn form. Ensure the unique symbols `@impl`, `@spec_decl`,
`@inherent_decl`, and the generated names like [<__spec_ $method>] are updated to
preserve and re-emit user-provided attributes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/macros/src/lib.rs`:
- Around line 60-115: Add explicit checks rejecting unsafe traits and unsafe
impls in expand_trait and expand_impl: inspect the ItemTrait::unsafety field in
expand_trait and the ItemImpl::unsafety field in expand_impl at the top of each
function (before the existing generic/supertrait/associated-item validations)
and return a compile_error
(syn::Error::new_spanned(...).to_compile_error().into()) with a message like
"#[devirt] does not support unsafe traits" or "#[devirt] does not support unsafe
impls" when unsafety.is_some(); this prevents silently dropping unsafety when
generating the safe macro output.

---

Outside diff comments:
In `@crates/core/src/lib.rs`:
- Around line 238-258: The macro patterns must accept and forward method
attributes instead of dropping or overwriting them: update the `@impl`,
`@spec_decl`, and `@inherent_decl` arms in __devirt_define! to include an optional
$(#[$_attr:meta])* matcher on method productions (e.g. match $(#[$_attr:meta])*
fn $method(...)) and emit those attributes when generating the helper methods
(e.g. attach $(#[$_attr])* to the generated [<__spec_ $method>] or impl-method
definitions) rather than hardcoding #[inline] and #[doc(hidden)]; alternatively,
if attributes must be filtered earlier, have the proc-macro strip them before
calling __devirt_define! so these macro arms only see the clean fn form. Ensure
the unique symbols `@impl`, `@spec_decl`, `@inherent_decl`, and the generated names
like [<__spec_ $method>] are updated to preserve and re-emit user-provided
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: 8edb48de-f5c7-4140-96d5-dbf87fd9a940

📥 Commits

Reviewing files that changed from the base of the PR and between d644bb5 and ecd0615.

📒 Files selected for processing (6)
  • crates/core/benches/dispatch.rs
  • crates/core/examples/shapes.rs
  • crates/core/src/lib.rs
  • crates/core/tests/equivalence.rs
  • crates/macros/src/lib.rs
  • fuzz/fuzz_targets/dispatch.rs

Comment thread crates/macros/src/lib.rs
Comment on lines +60 to +115
// Reject unsupported trait features.
if !trait_item.generics.params.is_empty() {
return syn::Error::new_spanned(
&trait_item.generics,
"#[devirt] does not support generic traits",
)
.to_compile_error()
.into();
}
if let Some(where_clause) = &trait_item.generics.where_clause {
return syn::Error::new_spanned(
where_clause,
"#[devirt] does not support where clauses on traits",
)
.to_compile_error()
.into();
}
if !trait_item.supertraits.is_empty() {
return syn::Error::new_spanned(
&trait_item.supertraits,
"#[devirt] does not support supertraits",
)
.to_compile_error()
.into();
}
for item in &trait_item.items {
match item {
syn::TraitItem::Type(t) => {
return syn::Error::new_spanned(
t,
"#[devirt] does not support associated types",
)
.to_compile_error()
.into();
}
syn::TraitItem::Const(c) => {
return syn::Error::new_spanned(
c,
"#[devirt] does not support associated constants",
)
.to_compile_error()
.into();
}
syn::TraitItem::Fn(f) => {
if f.default.is_some() {
return syn::Error::new_spanned(
f,
"#[devirt] does not support default method bodies",
)
.to_compile_error()
.into();
}
}
_ => {}
}
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '60,115p' crates/macros/src/lib.rs
sed -n '165,191p' crates/macros/src/lib.rs
sed -n '124,132p' crates/core/src/lib.rs
sed -n '513,524p' crates/core/src/lib.rs

Repository: Kab1r/devirt

Length of output: 3263


Reject unsafe trait and unsafe impl with explicit validation.

The expand_trait and expand_impl functions do not validate the unsafety field on ItemTrait and ItemImpl. Since the generated macros only emit safe forms, #[devirt] unsafe trait T { ... } would silently expand into a safe trait, dropping the safety contract instead of failing. Add checks to reject these unsupported cases before other validations:

Guard checks for unsafety
 fn expand_trait(attr: TokenStream, trait_item: &syn::ItemTrait) -> TokenStream {
     if attr.is_empty() {
         return syn::Error::new(
             proc_macro2::Span::call_site(),
             "expected hot types: #[devirt(Type1, Type2)]",
         )
         .to_compile_error()
         .into();
     }
 
     // Reject unsupported trait features.
+    if let Some(unsafety) = &trait_item.unsafety {
+        return syn::Error::new_spanned(
+            unsafety,
+            "#[devirt] does not support unsafe traits",
+        )
+        .to_compile_error()
+        .into();
+    }
     if !trait_item.generics.params.is_empty() {
     // Reject unsupported impl features.
+    if let Some(unsafety) = &impl_item.unsafety {
+        return syn::Error::new_spanned(
+            unsafety,
+            "#[devirt] does not support unsafe impl blocks",
+        )
+        .to_compile_error()
+        .into();
+    }
     if !impl_item.generics.params.is_empty() {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/macros/src/lib.rs` around lines 60 - 115, Add explicit checks
rejecting unsafe traits and unsafe impls in expand_trait and expand_impl:
inspect the ItemTrait::unsafety field in expand_trait and the ItemImpl::unsafety
field in expand_impl at the top of each function (before the existing
generic/supertrait/associated-item validations) and return a compile_error
(syn::Error::new_spanned(...).to_compile_error().into()) with a message like
"#[devirt] does not support unsafe traits" or "#[devirt] does not support unsafe
impls" when unsafety.is_some(); this prevents silently dropping unsafety when
generating the safe macro output.

Bug 1: `#[devirt(Foo)] unsafe trait Bar` silently dropped `unsafe`,
generating a safe trait. Fix: parameterize `__devirt_define!` with
`[$($unsafety:tt)*]` and emit it on the inner trait, public trait,
blanket impl, and user impl.

Bug 2: Method attributes (`#[must_use]`, doc comments, `#[deprecated]`)
were parsed but not emitted in `@inherent_decl` and `@impl` arms. Fix:
forward `$(#[$attr])*` in all four `@inherent_decl` arms and add
`$(#[$method_attr:meta])*` to the `@impl` arm.

Also:
- Add `unsafe trait`/`unsafe impl` arms to `devirt!` declarative macro
- Forward unsafety through proc-macro `expand_trait`/`expand_impl`
- Gate example/bench on `required-features = ["macros"]` so
  `--no-default-features` doesn't try to compile attribute-using code
- Fix doc example missing `trait` keyword in `devirt!` macro
- Add trybuild tests for both features (ui + ui_attr)
- Update all ~38 existing `__devirt_define!` call sites with `[]`

https://claude.ai/code/session_01XRfaF7hqTVzJwR8tBrnkom
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: 2

♻️ Duplicate comments (1)
crates/macros/src/lib.rs (1)

193-195: ⚠️ Potential issue | 🟡 Minor

Qualified trait paths are silently truncated, causing incorrect scope resolution.

Line 194 extracts only the final identifier from the trait path (trait_path.segments.last()...ident), so impl super::MyTrait for Foo expands to @impl [] MyTrait for Foo { ... }. This refers to MyTrait in the local scope rather than super::MyTrait, potentially causing confusing errors or silent miscompilation if both exist.

Since __devirt_define!'s @impl pattern only accepts $trait_name:ident (not a path), the proc-macro should reject qualified paths explicitly rather than silently truncating them.

🛡️ Proposed fix: reject qualified trait paths
     let Some((_, trait_path, _)) = &impl_item.trait_ else {
         return syn::Error::new(
             proc_macro2::Span::call_site(),
             "#[devirt] requires `impl Trait for Type`, not a bare impl block",
         )
         .to_compile_error()
         .into();
     };

+    // Reject qualified trait paths (e.g., super::Trait, crate::Trait)
+    if trait_path.segments.len() > 1 {
+        return syn::Error::new_spanned(
+            trait_path,
+            "#[devirt] does not support qualified trait paths; use just the trait name",
+        )
+        .to_compile_error()
+        .into();
+    }
+
     // Reject unsupported impl features.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/macros/src/lib.rs` around lines 193 - 195, The code currently
truncates qualified trait paths by taking only trait_path.segments.last() into
trait_name, causing `impl super::MyTrait for Foo` to be treated as `MyTrait`;
change this to explicitly reject qualified paths: in the code that handles the
trait in the impl (referencing impl_item, trait_path and trait_name and the
macro pattern __devirt_define!/@impl with $trait_name:ident), check trait_path
for a leading_colon or more than one segment and, if found, produce a clear
compile-time error (use syn::Error::new_spanned on trait_path or similar)
telling the user that only plain identifiers are allowed rather than silently
truncating to the last segment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/core/Cargo.toml`:
- Line 14: The dependency entry for devirt-macros in Cargo.toml uses only a path
which prevents crates.io publishing; update the devirt-macros dependency entry
(the devirt-macros = { ... } line) to inherit the workspace version by adding
version.workspace = true alongside path and optional fields so it matches the
workspace versioning pattern (workspace version 0.2.0).

In `@crates/core/src/lib.rs`:
- Around line 178-180: Add an explicit minimum supported Rust version to
Cargo.toml to document the stabilization requirement for
core::ptr::without_provenance; update Cargo.toml by adding rust-version = "1.84"
in the package section so the toolchain requirement (Rust 1.84.0+) is clear to
consumers and CI; ensure the version string is exactly "1.84" and run cargo
metadata/build to verify no other MSRV-related issues.

---

Duplicate comments:
In `@crates/macros/src/lib.rs`:
- Around line 193-195: The code currently truncates qualified trait paths by
taking only trait_path.segments.last() into trait_name, causing `impl
super::MyTrait for Foo` to be treated as `MyTrait`; change this to explicitly
reject qualified paths: in the code that handles the trait in the impl
(referencing impl_item, trait_path and trait_name and the macro pattern
__devirt_define!/@impl with $trait_name:ident), check trait_path for a
leading_colon or more than one segment and, if found, produce a clear
compile-time error (use syn::Error::new_spanned on trait_path or similar)
telling the user that only plain identifiers are allowed rather than silently
truncating to the last segment.
🪄 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: 223512c7-b82b-4101-9195-956ed3a79f08

📥 Commits

Reviewing files that changed from the base of the PR and between ecd0615 and 318fc33.

📒 Files selected for processing (20)
  • crates/core/Cargo.toml
  • crates/core/src/lib.rs
  • crates/core/tests/kani.rs
  • crates/core/tests/ui.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.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.rs
  • crates/core/tests/ui_attr/attr_args_on_impl.stderr
  • crates/core/tests/ui_attr/attr_method_attrs.rs
  • crates/core/tests/ui_attr/attr_unsafe_trait.rs
  • crates/macros/src/lib.rs

Comment thread crates/core/Cargo.toml Outdated
Comment thread crates/core/src/lib.rs
Comment on lines +178 to 180
let fake: *const __DevirtT = ::core::ptr::without_provenance(
::core::mem::align_of::<__DevirtT>(),
);
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check MSRV in Cargo.toml
echo "=== Checking rust-version in Cargo.toml files ==="
grep -r "rust-version" Cargo.toml crates/*/Cargo.toml 2>/dev/null || echo "No rust-version specified"

echo ""
echo "=== Checking for toolchain file ==="
cat rust-toolchain.toml 2>/dev/null || cat rust-toolchain 2>/dev/null || echo "No toolchain file"

echo ""
echo "=== Checking CI for Rust version ==="
cat .github/workflows/*.yml 2>/dev/null | grep -A2 -B2 "toolchain\|rust-version" | head -30

Repository: Kab1r/devirt

Length of output: 1892


🌐 Web query:

When was core::ptr::without_provenance stabilized in Rust?

💡 Result:

core::ptr::without_provenance was stabilized in Rust 1.84.0, released on January 9, 2025.

Citations:


Document MSRV in Cargo.toml to reflect core::ptr::without_provenance requirement (1.84.0+).

core::ptr::without_provenance was stabilized in Rust 1.84.0 (January 2025). While the project uses stable toolchain, no explicit MSRV is documented in Cargo.toml. Add rust-version = "1.84" to make this requirement clear.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/core/src/lib.rs` around lines 178 - 180, Add an explicit minimum
supported Rust version to Cargo.toml to document the stabilization requirement
for core::ptr::without_provenance; update Cargo.toml by adding rust-version =
"1.84" in the package section so the toolchain requirement (Rust 1.84.0+) is
clear to consumers and CI; ensure the version string is exactly "1.84" and run
cargo metadata/build to verify no other MSRV-related issues.

- Add devirt-macros as workspace dependency with version for crates.io
  publishing (path-only deps can't be published)
- Add rust-version = "1.85" MSRV (edition 2024 requires 1.85+,
  core::ptr::without_provenance stabilized in 1.84)
- Reject qualified trait paths in #[devirt] expand_impl (e.g.,
  super::MyTrait) with a clear error instead of silently truncating
  to the last segment

https://claude.ai/code/session_01XRfaF7hqTVzJwR8tBrnkom
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 the current code and only fix it if needed.

Inline comments:
In `@crates/macros/src/lib.rs`:
- Around line 210-218: The current loop over impl_item.items only handles
syn::ImplItem::Fn and silently drops other impl items (consts/types/macros);
update the loop in the impl processing logic to detect non-function items and
emit a syn::Error (new_spanned) or otherwise return/collect an error like the
trait-side checks do, referencing the offending item span so the compiler shows
a helpful message; keep processing of functions using the existing
m.attrs/m.sig/m.block -> method_bodies logic but explicitly reject or report all
non-ImplItem::Fn entries instead of ignoring them.
🪄 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: e95fa573-c413-4bf8-a9a7-d80746b28f09

📥 Commits

Reviewing files that changed from the base of the PR and between 318fc33 and 48bc890.

📒 Files selected for processing (3)
  • Cargo.toml
  • crates/core/Cargo.toml
  • crates/macros/src/lib.rs

Comment thread crates/macros/src/lib.rs
Comment on lines +210 to +218
for item in &impl_item.items {
if let syn::ImplItem::Fn(m) = item {
for a in &m.attrs {
a.to_tokens(&mut method_bodies);
}
m.sig.to_tokens(&mut method_bodies);
m.block.to_tokens(&mut method_bodies);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Non-function impl items are silently dropped.

The loop only processes ImplItem::Fn, so any associated constants, types, or macro invocations in the impl block will be silently discarded. A user who writes:

#[devirt::devirt]
impl Shape for Circle {
    const NAME: &'static str = "Circle"; // silently dropped
    fn area(&self) -> f64 { ... }
}

...may be surprised when NAME disappears. Consider adding validation to reject non-function items explicitly, similar to the trait-side checks:

🛡️ Proposed validation
     let mut method_bodies = proc_macro2::TokenStream::new();
     for item in &impl_item.items {
-        if let syn::ImplItem::Fn(m) = item {
+        match item {
+            syn::ImplItem::Fn(m) => {
             for a in &m.attrs {
                 a.to_tokens(&mut method_bodies);
             }
             m.sig.to_tokens(&mut method_bodies);
             m.block.to_tokens(&mut method_bodies);
+            }
+            syn::ImplItem::Const(c) => {
+                return syn::Error::new_spanned(
+                    c,
+                    "#[devirt] does not support associated constants in impl blocks",
+                )
+                .to_compile_error()
+                .into();
+            }
+            syn::ImplItem::Type(t) => {
+                return syn::Error::new_spanned(
+                    t,
+                    "#[devirt] does not support associated types in impl blocks",
+                )
+                .to_compile_error()
+                .into();
+            }
+            _ => {}
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/macros/src/lib.rs` around lines 210 - 218, The current loop over
impl_item.items only handles syn::ImplItem::Fn and silently drops other impl
items (consts/types/macros); update the loop in the impl processing logic to
detect non-function items and emit a syn::Error (new_spanned) or otherwise
return/collect an error like the trait-side checks do, referencing the offending
item span so the compiler shows a helpful message; keep processing of functions
using the existing m.attrs/m.sig/m.block -> method_bodies logic but explicitly
reject or report all non-ImplItem::Fn entries instead of ignoring them.

@Kab1r Kab1r merged commit 31658ec into main Apr 12, 2026
15 checks passed
@Kab1r Kab1r deleted the claude/unify-devirt-apis-gEEJ1 branch April 12, 2026 13:24
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