Skip to content

fulfill #[expect] expectations from derive generated code#158943

Open
Albab-Hasan wants to merge 5 commits into
rust-lang:mainfrom
Albab-Hasan:expect-derive-cfg-experiment
Open

fulfill #[expect] expectations from derive generated code#158943
Albab-Hasan wants to merge 5 commits into
rust-lang:mainfrom
Albab-Hasan:expect-derive-cfg-experiment

Conversation

@Albab-Hasan

@Albab-Hasan Albab-Hasan commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

View all comments

an #[expect] on an item now covers the impls #[derive] generates from it. done at lint level building by reusing the items own expectation ids. no expansion changes at all so the cfg/cfg_attr attr id desync that forced the revert of #152289 in #153055 cant happen.

context in the issue thread starting at #150553 (comment)

fixes #150553

r? @Urgau

an `#[expect]` on an item now covers the impls `#[derive]` generates from it. lint level building recognizes a derive generated impl and applies the derived from items `#[expect]` attrs to it reusing their expectation ids. a lint triggered in the derived code is suppressed and fulfills the original expectation because the id is literally the same one. one written attr is one expectation. fulfilled from either site. reported exactly once when genuinely unfulfilled.

copying the attr during expansion was tried before and reverted because `cfg`/`cfg_attr` processing re-parses the derive input from tokens and attr ids dont survive that. here identity never travels through token streams so theres nothing to desync.

fixes rust-lang#150553
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 8, 2026
@Albab-Hasan
Albab-Hasan marked this pull request as draft July 8, 2026 06:30
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 8, 2026
Comment thread compiler/rustc_lint/src/levels.rs Outdated
@Albab-Hasan
Albab-Hasan requested a review from Urgau July 9, 2026 07:21
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jul 9, 2026
@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

@Urgau hey can u pls look into this? sorry for the bother

@Urgau

Urgau commented Jul 11, 2026

Copy link
Copy Markdown
Member

Thanks, that looks better. I will take a closer look tomorrow or Monday.

You can switch to ready for review.

@Albab-Hasan
Albab-Hasan marked this pull request as ready for review July 11, 2026 15:34
@Urgau

Urgau commented Jul 12, 2026

Copy link
Copy Markdown
Member

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 12, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 12, 2026
…try>

fulfill `#[expect]` expectations from derive generated code
@rust-bors

rust-bors Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: c23f6fe (c23f6feec7b496697f70c4f2fb0777995536ae15)
Base parent: b69e089 (b69e089eb3ec9976ddec602bf18c3d6a8ce72512)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (c23f6fe): comparison URL.

Overall result: ❌✅ regressions and improvements - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.1% [0.0%, 0.1%] 3
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.1% [-0.2%, -0.0%] 4
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results (primary 2.6%, secondary 1.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.6% [2.6%, 2.6%] 1
Regressions ❌
(secondary)
5.0% [4.6%, 5.3%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-1.6% [-2.4%, -0.9%] 2
All ❌✅ (primary) 2.6% [2.6%, 2.6%] 1

Cycles

Results (secondary 5.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
8.9% [1.6%, 25.7%] 9
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.3% [-4.8%, -2.0%] 4
All ❌✅ (primary) - - 0

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 489.358s -> 489.445s (0.02%)
Artifact size: 389.32 MiB -> 389.81 MiB (0.12%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 12, 2026
levels.add_command_line();
}

levels.inherit_derive_expectations(owner);

@Urgau Urgau Jul 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment in

explaining why #[expect] is not on that list and pointing to this code?

View changes since the review

Comment thread compiler/rustc_lint/src/levels.rs Outdated
Comment thread compiler/rustc_lint/src/levels.rs Outdated
}
let hir::OwnerNode::Item(item) = tcx.hir_owner_node(owner) else { return };
let hir::ItemKind::Impl(impl_) = &item.kind else { return };
if !item.span.in_derive_expansion() {

@Urgau Urgau Jul 12, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you tested what happens with a proc-macro?

Could you add a test case with Span::call_site, Span::mixed_site and Span::def_site.


@petrochenkov I know you've worked on spans/hygiene data before. Do you know if we can trust the ExpnData will always be right?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Of course not, procedural macros can assign any spans they want to the produced tokens.

@petrochenkov petrochenkov Jul 13, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The general approach from #152289 seems right to me, compared to what this PR attempts to do.
Maybe in some modified form, with expects downgrated to allows or something.

Not sure why it relied on AttrIds being preserved. The mental model is that any proc macro generates tokens, and the tokens are then parsed. Built-in proc macros do optimize things by generating AST directly and skipping the parsing, but that's only an optimization, and the observable behavior should be as if the tokens were produced and parsed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's what I also though, thanks for confirming it.

@Albab-Hasan unless there is a way to not rely (at all!) on the spans (and it's hygiene data), this approach seems like a dead-end.

@Albab-Hasan Albab-Hasan Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll look into it; see if anything can be done. if not I'll close this pr

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe in some modified form, with expects downgrated to allows or something.

I proposed that to T-lang, but they explicitly wants #[expect] to act as one, since a warning may only be emitted in the derived code.

Not sure why it relied on AttrIds being preserved.

That's how the implementation of expectation works. StableLintExpectationId takes an attr_index.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

#[expect] to act as one, since a warning may only be emitted in the derived code.

Then it does require introducing some new infrastructure to preserve "item grouping", to implement this properly.

In #158980 we have a similar situation for cfg traces for derived impls used by rustdoc, but the traces can be best effort and use hacks.

@petrochenkov petrochenkov self-assigned this Jul 12, 2026
Co-authored-by: Urgau <3616612+Urgau@users.noreply.github.com>
@petrochenkov petrochenkov removed their assignment Jul 13, 2026
… hygiene

Proc macros can assign arbitrary spans to the tokens they produce, so span
expansion data cannot prove an impl came from a derive. Gate expectation
inheritance on is_builtin_derived instead, and change that helper to read
expn_that_defined, the def collector's record of which expansion created
the definition, which token spans cannot influence. Only builtin derive
macros are covered; a new test checks that a proc-macro derive emitting
automatically_derived impls with call_site, mixed_site or def_site spans
never inherits or fulfills expectations.
@Albab-Hasan
Albab-Hasan requested a review from Urgau July 15, 2026 06:38
@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

the impl is now recognized through expn_that_defined, the def collectors record of which expansion created the definition. its recorded compiler side when the expansion output is collected into the AST so the spans a proc macro puts on its tokens have zero influence on it. name resolution already trusts this record for hygienic_eq.

the gate also requires the expansion to be a builtin derive, macro_def_id carrying #[rustc_builtin_macro]. builtin derives always emit impls for exactly the item they were applied to so the self type resolution cant point anywhere else. proc macro derives are out entirely whatever spans they use. added the test you asked for.

this also holds under the tokens-then-parse model @petrochenkov described. if builtin derives went through print and reparse the impl would still be collected under the same derive ExpnId. thats the difference to #152289 which needed AttrIds to survive the token roundtrip.

the check itself is tcx.is_builtin_derived which already exists and is what coherence uses to validate derive(CoercePointee) impls. it read def_span(..).ctxt().outer_expn_data() before. moved it to expn_that_defined so it stops depending on spans there too.

@petrochenkov

Copy link
Copy Markdown
Contributor

I like the idea with expn_that_defined.
It is also used for similar ad-hoc grouping for items produced by the same glob delegation (#157601).

Both this feature and #158980 ideally need some proper design, accounting for macro calls in all positions, not just derives in item positions.
The common thing is that some property from a macro call is propagated to all the code produced by that macro, and the property cannot be expressed as tokens.

#[expect] in particular can be used in non-item positions and should supposedly behave consistently - expect on a macro call is propagated to all nodes produced from that macro as a group. That's what the lang team want really implies.

@Urgau

Urgau commented Jul 16, 2026

Copy link
Copy Markdown
Member

#[expect] in particular can be used in non-item positions and should supposedly behave consistently - expect on a macro call is propagated to all nodes produced from that macro as a group. That's what the lang team want really implies.

I guess that depends if we want user derives to behave the same as the builtin one. We currently only duplicate #[allow], #[warn], ... for builtin derives, we don't do it for user derives (playground). For builtin derives I don't think we need to worry about about non-item, I'm not sure if we need to handle non-impl items either.

@Urgau

Urgau commented Jul 16, 2026

Copy link
Copy Markdown
Member

Both this feature and #158980 ideally need some proper design, accounting for macro calls in all positions, not just derives in item positions.

I guess this would be a case of moving the inheritence to the add function, which is called for all positions, and not gating it on just impl item.

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

checked the other positions on master before answering the scope question. #[expect] directly on a macro invocation is already ignored today (unused_attributes, "will be ignored, since it's applied to the macro invocation") and macro output lexically nested under an attributed node already fulfills through the normal level walk. so derives are the only position where a legal attribute placement has output escaping its lexical scope.

non-impl items: builtin derives only ever emit trait impls (create_derived_impl, coerce_pointee). the DefKind::Impl check is just a fast path in front of is_builtin_derived.

moving it into add has nothing to key on yet: expn_that_defined is DefId keyed so the non-def nodes add visits have no record, and ExpnData carries no attributes from the call. the general design needs that infrastructure first, this pr and #157601 are ad-hoc instances of it.

so id keep this builtin derives only and leave user derives and other positions to the follow-up design. what do you think?

@Urgau

Urgau commented Jul 17, 2026

Copy link
Copy Markdown
Member

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 17, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 17, 2026
…try>

fulfill `#[expect]` expectations from derive generated code
@rust-bors

rust-bors Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 58c0d17 (58c0d17bffec6602b195474e479085c8fcc2b1f7)
Base parent: 4a9d536 (4a9d5368df6450bbd2fc8dde4508c3e5d83bb19d)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (58c0d17): comparison URL.

Overall result: ❌ regressions - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.2% [0.2%, 0.2%] 1
Regressions ❌
(secondary)
0.5% [0.1%, 0.6%] 4
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.2% [0.2%, 0.2%] 1

Max RSS (memory usage)

Results (primary 4.8%, secondary 1.2%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
4.8% [2.7%, 6.9%] 2
Regressions ❌
(secondary)
5.6% [5.6%, 5.6%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.2% [-3.2%, -3.2%] 1
All ❌✅ (primary) 4.8% [2.7%, 6.9%] 2

Cycles

Results (secondary 1.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
5.7% [2.6%, 8.7%] 2
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.5% [-4.2%, -2.8%] 2
All ❌✅ (primary) - - 0

Binary size

Results (primary 0.1%, secondary 0.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.1% [0.0%, 0.1%] 55
Regressions ❌
(secondary)
0.1% [0.0%, 0.1%] 23
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.1% [0.0%, 0.1%] 55

Bootstrap: 491.028s -> 491.058s (0.01%)
Artifact size: 389.42 MiB -> 390.11 MiB (0.18%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 17, 2026
@Urgau

Urgau commented Jul 18, 2026

Copy link
Copy Markdown
Member

The perf regressions are exclusively in the derive benchmark, it has 300 derives. Seems quite unavoidable.

Unless you maybe see a way to optimize it @Albab-Hasan?

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

i think i can roughly halve it by gating first on the impls own #[automatically_derived] from the already-fetched attr map and only running the expn_that_defined check when the adt actually has an #[expect]. thats an estimate tho, ill push the code and well see.

@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

retime pls

@Urgau

Urgau commented Jul 19, 2026

Copy link
Copy Markdown
Member

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 19, 2026
@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Jul 19, 2026
…try>

fulfill `#[expect]` expectations from derive generated code
@rust-bors

rust-bors Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 5f44591 (5f44591224c798c2230d2c7e919375cf84bfb648)
Base parent: c904ba3 (c904ba32e22c9d6fd31140b4283c5cc6c6b2fb5c)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (5f44591): comparison URL.

Overall result: ❌ regressions - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

@rustbot label: -S-waiting-on-perf -perf-regression

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
0.2% [0.2%, 0.2%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Max RSS (memory usage)

This perf run didn't have relevant results for this metric.

Cycles

Results (primary -2.5%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-2.5% [-2.7%, -2.1%] 3
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) -2.5% [-2.7%, -2.1%] 3

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 486.808s -> 488.065s (0.26%)
Artifact size: 389.45 MiB -> 390.08 MiB (0.16%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 19, 2026
@Albab-Hasan

Copy link
Copy Markdown
Contributor Author

is this good or would more optimization be preffered?

@Urgau

Urgau commented Jul 19, 2026

Copy link
Copy Markdown
Member

Thanks, that's plenty optimized. I will take another look in the next days.

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

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

#[expect(redundant_lifetimes)] doesn't work when #[derive(Debug)] is present

5 participants