Skip to content

Extend external_derive to cover proc-macro-generated impls - #2641

Open
hz2 wants to merge 2 commits into
verus-lang:mainfrom
hz2:external-derive-proc-macro
Open

Extend external_derive to cover proc-macro-generated impls#2641
hz2 wants to merge 2 commits into
verus-lang:mainfrom
hz2:external-derive-proc-macro

Conversation

@hz2

@hz2 hz2 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

#[verifier::external_derive] had no effect on impls generated by proc-macro crates (e.g., serde's Serialize/Deserialize). Those crates do not emit #[automatically_derived] on the impls they generate, so get_attributes_for_automatic_derive in external.rs returned None immediately, leaving the generated impl subject to normal (failing) classification.

Fixes #1575, #1577, #1956.

Changes

source/rust_verify/src/external.rs

The get_attributes_for_automatic_derive function previously returned None at the top if !is_automatically_derived(attrs). This PR adds a secondary check: when an impl span is from a macro expansion (span.from_expansion()) and the self type carries #[verifier::external_derive] (either AllExternal or SomeExternal with a matching trait name), the impl is now marked external.

If the type has no external_derive annotation at all, macro-expanded impls fall through to None unchanged, preserving all existing behavior.

source/rust_verify_test/tests/std.rs

Two new tests under --no-external-by-default:

  • external_derive_proc_macro_all: uses a macro_rules! that produces an impl without #[automatically_derived] (simulating a proc-macro derive), verifies that #[verifier::external_derive] (AllExternal) suppresses verification of the generated impl.
  • external_derive_proc_macro_named: same, but with #[verifier::external_derive(FakeSerialize)] to test the named-trait path.

Testing

The new tests simulate proc-macro derives with macro_rules!, which produces macro-expanded impls without #[automatically_derived] -- exactly the shape serde emits. The existing external_derive_attr and external_derive_attr_list tests are unchanged and continue to pass.

@hz2
hz2 force-pushed the external-derive-proc-macro branch from fb795ec to 57b3c85 Compare July 9, 2026 23:25
@hz2
hz2 marked this pull request as ready for review July 10, 2026 14:07
@hz2
hz2 marked this pull request as draft July 17, 2026 20:38
@hz2
hz2 force-pushed the external-derive-proc-macro branch from 57b3c85 to 5712db8 Compare July 17, 2026 21:00
@hz2
hz2 marked this pull request as ready for review July 17, 2026 21:03
@hz2
hz2 force-pushed the external-derive-proc-macro branch 2 times, most recently from c5ec020 to 3f83d25 Compare July 19, 2026 14:52
@tjhance
tjhance self-requested a review August 3, 2026 16:06
@hz2
hz2 force-pushed the external-derive-proc-macro branch from 3f83d25 to ff4ed97 Compare August 8, 2026 16:41
@hz2
hz2 force-pushed the external-derive-proc-macro branch from ff4ed97 to 52b1c4d Compare August 16, 2026 17:30
hz2 and others added 2 commits August 22, 2026 17:46
Proc-macro crates such as serde do not emit #[automatically_derived] on
the impls they generate. As a result, #[verifier::external_derive] had no
effect on serde-derived Serialize/Deserialize impls (issues verus-lang#1575, verus-lang#1577,
verus-lang#1956): the gate in get_attributes_for_automatic_derive returned None
immediately for any impl lacking that attribute.

The fix checks span.from_expansion() as a secondary signal. When an impl
is macro-expanded but not #[automatically_derived], and the self type has
#[verifier::external_derive] (with AllExternal or a matching trait name),
the impl is now marked external. If the type has no external_derive
annotation at all, macro-expanded impls fall through to the normal
classification path unchanged, preserving existing behavior.

Tests added in rust_verify_test/tests/std.rs simulate proc-macro derives
with macro_rules! (which produces macro-expanded impls without
#[automatically_derived]) and verify that external_derive AllExternal and
SomeExternal(TraitName) both suppress verification of the generated impl.
The new is_macro_expanded path in get_attributes_for_automatic_derive
runs for any macro-expanded impl, not just #[automatically_derived]
ones. Unlike derive impls, macro-expanded impls can target primitive
types (e.g. vstd's std_specs/ops.rs generates impl ... for u8/usize/...
via macro_rules!), whose Res is PrimTy and has no DefId. Calling
path.res.def_id() on those panics; switch to opt_def_id() and fall
through to normal classification when there's no def_id, matching
the existing fallback arms.
@hz2
hz2 force-pushed the external-derive-proc-macro branch from 52b1c4d to 4b2399a Compare August 23, 2026 00:46
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.

duplicate specification error when deriving Serialize on multiple types

1 participant