Skip to content

Support default method bodies in #[devirt] trait definitions#20

Merged
Kab1r merged 2 commits intomainfrom
claude/default-method-bodies-YELZY
Apr 12, 2026
Merged

Support default method bodies in #[devirt] trait definitions#20
Kab1r merged 2 commits intomainfrom
claude/default-method-bodies-YELZY

Conversation

@Kab1r
Copy link
Copy Markdown
Owner

@Kab1r Kab1r commented Apr 12, 2026

Trait methods with default bodies (e.g. fn is_large(&self) -> bool { self.area() > 100.0 }) are now accepted by the proc-macro attribute.
The default body is placed on the inner trait's __spec_* provided
method, with self.method() calls rewritten to self.__spec_method()
via syn::visit_mut so sibling calls resolve within the inner trait.
Macro invocations (format!, write!, etc.) are handled via token-level
rewriting since syn doesn't parse macro token streams.

Trait methods with default bodies (e.g. `fn is_large(&self) -> bool {
self.area() > 100.0 }`) are now accepted by the proc-macro attribute.
The default body is placed on the inner trait's `__spec_*` provided
method, with `self.method()` calls rewritten to `self.__spec_method()`
via `syn::visit_mut` so sibling calls resolve within the inner trait.
Macro invocations (format!, write!, etc.) are handled via token-level
rewriting since syn doesn't parse macro token streams.

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

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cd610a52-ab5b-4e76-a840-4358d0cf67ec

📥 Commits

Reviewing files that changed from the base of the PR and between e5f6142 and d73ec03.

📒 Files selected for processing (2)
  • crates/core/tests/equivalence.rs
  • crates/macros/src/lib.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Traits can now include default method implementations that work seamlessly with devirtualization.
    • Default methods correctly dispatch through trait objects and common trait bounds (e.g., Send, Sync).
    • Implementations may override default methods and have those overrides respected.
  • Tests

    • Added comprehensive tests and UI cases verifying default-method dispatch, overrides, and trait-object behavior.

Walkthrough

The PR enables trait methods with default bodies in the devirt macro by emitting inner-trait __spec_* declarations for required methods and rewriting self.method() call sites inside default bodies to invoke the inner-trait versions, plus applying the same rewrite to impl method blocks.

Changes

Cohort / File(s) Summary
Dependency Updates
Cargo.toml
Enabled syn feature visit-mut alongside full to allow AST mutation via syn::visit_mut::VisitMut.
Macro Core Implementation
crates/macros/src/lib.rs
Removed validation that rejected default-bodied trait methods; generate inner __spec_* declarations for required methods and emit default method bodies rewritten to call self.__spec_*; added RewriteSelfCalls (uses syn::visit_mut::VisitMut) and token-level rewriting for macro token streams; apply same rewriting to impl method blocks.
Tests — integration/unit
crates/core/tests/equivalence.rs
Added attr_defaults test module (behind #[cfg(feature = "macros")]) with structs/trait/tests exercising default-method dispatch, overrides, and trait-object combinations.
Tests — trybuild / UI
crates/core/tests/ui_attr.rs, crates/core/tests/ui_attr/attr_default_*.rs
Registered three new passing UI tests and added three new test files validating default-body dispatch, default override behavior, and default methods with Send trait-object bounds.

Sequence Diagram

sequenceDiagram
    participant User as User Code
    participant Trait as Trait Def<br/>(with default methods)
    participant Macro as devirt Macro
    participant Inner as Inner Trait<br/>(__spec_*)
    participant Impl as Type Impl
    participant Runtime as Runtime Dispatch

    User->>Trait: Define trait with default method calling `self.get()`
    Trait->>Macro: #[devirt::devirt] annotation
    Macro->>Macro: Parse trait & default bodies
    Macro->>Macro: Rewrite `self.method()` → `self.__spec_method()` in default bodies
    Macro->>Inner: Emit `__spec_*` declarations for required signatures
    Macro->>Impl: Generate impls, rewriting impl blocks likewise
    User->>Runtime: Create &dyn Trait from instance
    Runtime->>Impl: Call default method
    Impl->>Impl: Execute rewritten body using `self.__spec_*()`
    Impl-->>Runtime: Return result
    Runtime-->>User: Result delivered
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through traits by moonlit code,
Rewrote each self-call down the road.
From self.call() to __spec_call() they glide,
Devirt packs defaults with pride. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.63% 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 clearly and specifically describes the main change: adding support for default method bodies in the #[devirt] trait attribute macro.
Description check ✅ Passed The description is directly related to the changeset, explaining the implementation details of how default method bodies are handled, including the rewriting mechanism for self method calls.

✏️ 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/default-method-bodies-YELZY

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

🤖 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/tests/equivalence.rs`:
- Around line 149-206: The test attr_default_body_dispatch only exercises the
plain default-body path; add two additional runtime cases inside the same test:
(1) a case that exercises an overridden default implementation (create a new
struct e.g. DefOver with an impl Defaulted for DefOver that overrides is_big and
assert the overridden behavior via &dyn Defaulted), and (2) a case that
exercises a default that uses write!/format!-style code (create a struct e.g.
DefFmt that relies on the trait's default method which uses format!/write!
internally and assert its behavior via &dyn Defaulted and &(dyn Defaulted +
Send) to ensure macro-invocation rewriting is executed). Reference the existing
symbols Defaulted, DefHot/DefCold, and the test attr_default_body_dispatch when
adding these assertions.

In `@crates/macros/src/lib.rs`:
- Around line 320-328: expand_impl currently copies impl override method bodies
verbatim, so calls like self.area() inside an impl block don't get rewritten to
the generated sibling names; apply the same RewriteSelfCalls transformation used
in generate_spec_decls to those impl method blocks. Locate expand_impl and where
it captures the impl method body (the code that uses the impl method's
block/tokenstream), clone the Block into a mutable variable, instantiate
RewriteSelfCalls with the existing method_names collection, call
rewriter.visit_block_mut(&mut block) and then use the rewritten block when
quoting the generated __spec_* method instead of the original verbatim body so
sibling calls are renamed consistently.
🪄 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: e82e59df-f8b5-488b-a38f-e81c825d2693

📥 Commits

Reviewing files that changed from the base of the PR and between d316853 and e5f6142.

📒 Files selected for processing (7)
  • Cargo.toml
  • crates/core/tests/equivalence.rs
  • crates/core/tests/ui_attr.rs
  • crates/core/tests/ui_attr/attr_default_body.rs
  • crates/core/tests/ui_attr/attr_default_override.rs
  • crates/core/tests/ui_attr/attr_default_send.rs
  • crates/macros/src/lib.rs

Comment thread crates/core/tests/equivalence.rs
Comment thread crates/macros/src/lib.rs
Apply the same RewriteSelfCalls transformation to impl method bodies
in expand_impl, so self.method() inside a #[devirt] impl block is
rewritten to self.__spec_method(). Without this, sibling calls in impl
bodies fail to compile because the public trait is an empty marker.

Extend attr_default_body_dispatch with three additional runtime cases:
- DefOver: overrides the default is_big with a sibling call (self.get()
  > 1000), exercising impl-body rewriting
- DefFmt: relies on the default describe() that uses format!() with
  self.get(), exercising macro-invocation token rewriting
- Assertions via &dyn Defaulted and &(dyn Defaulted + Send)

https://claude.ai/code/session_01BJen3TbKCFohcjNFeYwBiA
@Kab1r Kab1r merged commit 2de270a into main Apr 12, 2026
7 checks passed
@Kab1r Kab1r deleted the claude/default-method-bodies-YELZY branch April 12, 2026 19:07
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