Skip to content

Stabilize #[diagnostic::on_unknown] - #163636

Open
weiznich wants to merge 1 commit into
rust-lang:mainfrom
weiznich:stablilize_on_unknown
Open

weiznich wants to merge 1 commit into
rust-lang:mainfrom
weiznich:stablilize_on_unknown

Conversation

@weiznich

@weiznich weiznich commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Stabilization report

Summary

Remind us what this feature is and what value it provides. Tell the story of what led up to this stabilization

This PR seeks to stabilize the #[diagnostic::on_unknown] attribute in the #[diagnostic] namespace. This attribute let users change the error message emitted by rustc for the case that an referenced item is not found. This allows crate authors to provide custom error messages for specific use-cases to help their users to more easily understand how a certain feature works and why a specific code example doesn't compile.

Tracking:

Reference PRs:

  • Needs to be done, will be here as soon as the initial feedback on stabilization is positive

Not sure who to really ping on stabilization. The #[diagnostic] namespace RFC states:

Adding a new attribute or option to the #[diagnostic] namespace is for now a decision of the language team. The language team can delegate these decisions partially or completely to a different team without requiring a new RFC.

I'm not sure what's the current state of that delegation, I remember that it was planned to delegate it to the compiler team at some point.

What is stabilized

Describe each behavior being stabilized and give a short example of code that will now be accepted.

The #[diagnostic::on_unknown] attribute is an attribute in the #[diagnostic] tool namespace. It follows the general rules of that namespace, so it can only affect compiler diagnostics, not change actual behaviour. Any syntax error on any of the attributes parameters will be emitted as warning and otherwise ignored. There is no guarantee by the compiler that a certain output is emitted even with the attribute being accepted, so future changes (including ignoring it) to this attribute are possible.
The attribute should be
placed on use and module declarations as well as the crate root, though it is not an error to be located in other
positions.

Format parameters with the given named parameter will be replaced with the following text:

  • {Unresolved} — The SimplePathSegment of the import path that could not be resolved.
  • {This} — The name of the annotated item. On use statements this is identical to {Unresolved}.

The original error message will not be suppressed but is emitted as a note instead.

On use declarations

#[diagnostic::on_unknown(
    message = "`{Unresolved}` doesn't exist",
    label = "you did something silly here"
)]
use doesnt_exist;

This will result in the following error:

error[E0432]: `doesnt_exist` doesn't exist
 --> src/lib.rs:7:5
  |
7 | use doesnt_exist;
  |     ^^^^^^^^^^^^ you did something silly here
  |
  = note: unresolved import `doesnt_exist`

For more information about this error, try `rustc --explain E0432`.

On module declarations

#[diagnostic::on_unknown(
    message = "module `{This}` is empty, there is no `{Unresolved}` here",
    label = "can't import something from an empty module"
)]
mod empty {}

use empty::what;

This will result in the following error:

error[E0432]: module `empty` is empty, there is no `what` here
 --> src/lib.rs:9:5
  |
9 | use empty::what;
  |     ^^^^^^^----
  |            |
  |            can't import something from an empty module
  |
  = note: unresolved import `empty::what`

Example

Consider the following pair of macros, one which creates a hidden module with a constant and another that reads it.

#![feature(macro_metavar_expr, macro_attr)]

macro_rules! instrument {
    attr() { $vis:vis fn $fn_name:ident ($($arg_name:ident : $arg_ty:ty),*) $(-> $ret:ty)? $body:block } => {
        $vis fn $fn_name ($($arg_name:$arg_ty),*) $(-> $ret)? $body
        #[doc(hidden)]
        $vis mod $fn_name {
            pub const LEN: usize = ${ count($arg_name) };
        }
    }
}

macro_rules! count_args {
    ($name:ident) => {{
        $name::LEN
    }}
}

#[instrument]
fn add(a: u8, b: u8) -> u8 {
    a + b
}

fn main() {
    let n = count_args!(add);
    println!("`add` has {n} arguments");
}

If the #[instrument] macro is omitted it will emit this confusing error:

error[E0433]: cannot find module or crate `add` in this scope
  --> src/main.rs:25:25
   |
25 |     let n = count_args!(add);
   |                         ^^^ function `add` is not a crate or module

#[diagnostic::on_unknown] can be used to customize this error message:

macro_rules! count_args {
    ($name:ident) => {{
        #[diagnostic::on_unknown(
            message = "cannot count arguments of `{Unresolved}`",
            label = "`{Unresolved}` is not a function decorated \
                    with the `#[instrument]` macro"
        )]
        use $name::LEN as length;
        length
    }}
}

// #[instrument]
fn add(a: u8, b: u8) -> u8 {
  a + b
}

fn main() {
    let n = count_args!(add);
    println!("`add` has {n} arguments");
}

This produces:

error[E0432]: cannot count arguments of `add`
  --> src/main.rs:33:25
   |
33 |     let n = count_args!(add);
   |                         ^^^ `add` is not a function decorated with
   |                              the `#[instrument]` macro
   |
   = note: unresolved import `add`

Edition differences

In the 2015 edition, use paths are relative to the crate root. For example, use empty will be resolved relative to the crate root and resolution will not encounter the mod empty {} declaration.

#![feature(diagnostic_on_unknown)]

mod foo {
   #[diagnostic::on_unknown(message = "oh oh")]
   mod empty {}

   use empty::what;
}
error[E0432]: unresolved import `empty`
 --> src/main.rs:9:8
  |
9 |    use empty::what;
  |        ^^^^^
  |

What isn't stabilized

Describe any parts of the feature not being stabilized. Talk about what we might want to do later and what doors are being left open for that. If what we're not stabilizing might lead to surprises for users, talk about that in particular.

Any usage that also requires another unstable feature is not stabilized in so far that the other feature is required to write the code using the attribute in that location

Design

Reference

What updates are needed to the Reference? Link to each PR. If the Reference is missing content needed for describing this feature, discuss that.

The reference needs a description of the attribute, basically what's removed from the unstable book and in the stabilization report above.

RFC history

What RFCs have been accepted for this feature?

This feature is part of the #[diagnostic] tool attribute namespace, which was defined in https://rust-lang.github.io/rfcs/3368-diagnostic-attribute-namespace.html. The attribute itself was not specified by an RFC as the RFC explicitly allows adding new attributes to that namespace without RFC.

Answers to unresolved questions

There are no unresolved questions I'm aware of

Post-RFC changes

What other user-visible changes have occurred since the RFC was accepted? Describe both changes that the lang team accepted (and link to those decisions) as well as changes that are being presented to the team for the first time in this stabilization report.

Given that there was no RFC for this particular attribute, there are no changes here to discuss

Key points

What decisions have been most difficult and what behaviors to be stabilized have proved most contentious? Summarize the major arguments on all sides and link to earlier documents and discussions.

The name of the attribute got some discussion. It started as #[diagnostic::on_unknown_item] and was then renamed to the current name. Otherwise I think @petrochenkov raised the question if we really want to allow customizing all the compiler error messages like that. Other than that there were no major arguments that I'm aware of.

Nightly extensions

Are there extensions to this feature that remain unstable? How do we know that we are not accidentally committing to those?

There are none

Doors closed

What doors does this stabilization close for later changes to the language? E.g., does this stabilization make any other RFCs, lang experiments, or known in-flight proposals more difficult or impossible to do later?

Feedback

Call for testing

Has a "call for testing" been done? If so, what feedback was received?

There was no call for testing as this is a rather specialized and somewhat simple feature

Nightly use

Do any known nightly users use this feature? Counting instances of #![feature(FEATURE_NAME)] on GitHub with grep might be informative.

The compiler itself uses this feature in rustc_span:

  • #[diagnostic::on_unknown(
    label = "`{Unresolved}` is not a pre-interned symbol",
    note = "consider adding `{Unresolved}` to the `symbols!` invocation in compiler/rustc_span/src/symbol.rs"
    )]

I also did a small test implementation in Diesel, to demonstrate how this can be used to significantly improve error messages emitted by one of the derives diesel provides:

For the given code:

#[derive(AsChangeset)]
struct User {
    id: i32,
    name: String,
}

the following error message is emitted

error[E0432]: invalid table name `users` inferred
 --> tests/fail/derive/no_table.rs:9:8
  |
LL | struct User {
  |        ^^^^ cannot find the default table name `users` in scope
  |
  = note: unresolved import `users`
  = note: without a `#[diesel(table_name = _)]` attribute looks for a `users` item in scope. Add a import to your schema module like `use crate::schema::users; or use the `#[diesel(table_name = _)]` to explicitly specify a different table name
  = help: you might be missing a crate named `users`

the underlying problem here is that the proc-macro derive needs access to another user defined module. By default it assumes a name based on the struct name, users can customize that name via an attribute. In the past users often struggled with discovering that this attribute existed, so it is really helpful to be able to point to the attribute as part of the error message.

See the full change here:

weiznich/diesel@15d6442

Implementation

Major parts

Summarize the major parts of the implementation and provide links into the code and to relevant PRs.

See, e.g., this breakdown of the major parts of async closures:

This feature was implemented in the following PR's:

Coverage

Summarize the test coverage of this feature.

Consider what the "edges" of this feature are. We're particularly interested in seeing tests that assure us about exactly what nearby things we're not stabilizing. Tests should of course comprehensively demonstrate that the feature works. Think too about demonstrating the diagnostics seen when common mistakes are made and the feature is used incorrectly.

Within each test, include a comment at the top describing the purpose of the test and what set of invariants it intends to demonstrate. This is a great help to our review.

Describe any known or intentional gaps in test coverage.

Contextualize and link to test folders and individual tests.

There is a good test coverage in the UI test suite:

https://github.com/rust-lang/rust/tree/a5c10c39125788787370a1ab11c954b9dff37859/tests/ui/diagnostic_namespace/on_unknown

Outstanding bugs

None that I'm aware of

Outstanding FIXMEs

None that I'm aware of

Tool changes

What changes must be made to our other tools to support this feature. Has this work been done? Link to any relevant PRs and issues.

  • rustfmt
    • No changes required as far as I'm aware
  • rust-analyzer
    • Might need to add the new attribute to their built-in completion list
  • rustdoc (both JSON and HTML)
    • No changes required as far as I'm aware
  • cargo
    • No changes required as far as I'm aware
  • clippy
    • No changes required as far as I'm aware
  • rustup
    • No changes required as far as I'm aware
  • docs.rs
    • No changes required as far as I'm aware

Breaking changes

If this stabilization represents a known breaking change, link to the crater report, the analysis of the crater report, and to all PRs we've made to ecosystem projects affected by this breakage. Discuss any limitations of what we're able to know about or to fix.

No breaking change required

Type system, opsem

Compile-time checks

What compilation-time checks are done that are needed to prevent undefined behavior?

Link to tests demonstrating that these checks are being done.

It's not possible to use this to trigger undefined behaviour, so no compile time checks preventing that exit

Type system rules

What type system rules are enforced for this feature and what is the purpose of each?

None, as this feature doesn't affect the type system

Sound by default?

Does the feature's implementation need specific checks to prevent UB, or is it sound by default and need specific opt-in to perform the dangerous/unsafe operations? If it is not sound by default, what is the rationale?

No, it cannot be used to introduce undefined behaviour, it only changes compiler diagnostics

Breaks the AM?

Can users use this feature to introduce undefined behavior, or use this feature to break the abstraction of Rust and expose the underlying assembly-level implementation? Describe this if so.

No, it cannot be used to introduce undefined behaviour, it only changes compiler diagnostics

Common interactions

Temporaries

Does this feature introduce new expressions that can produce temporaries? What are the scopes of those temporaries?

This feature doesn't introduce new expressions

Drop order

Does this feature raise questions about the order in which we should drop values? Talk about the decisions made here and how they're consistent with our earlier decisions.

This feature doesn't change the generated code, so it cannot change the drop order

Pre-expansion / post-expansion

Does this feature raise questions about what should be accepted pre-expansion (e.g. in code covered by #[cfg(false)]) versus what should be accepted post-expansion? What decisions were made about this?

This feature doesn't change any expansion results, it just changes diagnostics.

Edition hygiene

If this feature is gated on an edition, how do we decide, in the context of the edition hygiene of tokens, whether to accept or reject code. E.g., what token do we use to decide?

It's not gated on an edition

SemVer implications

Does this feature create any new ways in which library authors must take care to prevent breaking downstreams when making minor-version releases? Describe these. Are these new hazards "major" or "minor" according to RFC 1105?

No, the #[diagnostic] namespace even allows to use that feature without bumping the minimal supported rust version as it ignores (with a warning) unknown diagnostic attributes by default

Exposing other features

Are there any other unstable features whose behavior may be exposed by this feature in any way? What features present the highest risk of that?

None that I'm aware of

History

List issues and PRs that are important for understanding how we got here.

Acknowledgments

Summarize contributors to the feature by name for recognition and so that those people are notified about the stabilization. Does anyone who worked on this not think it should be stabilized right now? We'd like to hear about that if so.

@mejrs helped a lot to implement various features here and to guide the initial implementation. @estebank motivated the original work

Open items

List any known items that have not yet been completed and that should be before this is stabilized.

None that I'm aware of

This commit just removes the feature gate and moves the attribute to be
stable. There haven't been any problems so far, it seems to work and the
`#[diagnostic]` namespace guarantees that changes to this can happen at
later points as well so there is no risk of stabilizing it.
@rustbot

rustbot commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in compiler/rustc_attr_parsing

cc @jdonszelmann, @JonathanBrouwer

Some changes occurred to diagnostic attributes.

cc @mejrs

@rustbot rustbot added A-attributes Area: Attributes (`#[…]`, `#![…]`) 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 Oct 2, 2026
@rustbot

rustbot commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

r? @chenyukang

rustbot has assigned @chenyukang.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 77 candidates
  • Random selection from 20 candidates

@mejrs mejrs left a comment •

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.

r? me

There are some outdated comments wrt the feature gating of this attribute, which need deleting

/// This is `None` if the feature flag for `diagnostic::on_unknown` is disabled.

// We don't need to check feature gates here; that happens on initialization of the
// `on_unknown_attr` fields.

What isn't stabilized

Describe any parts of the feature not being stabilized. Talk about what we might want to do later and what doors are being left open for that. If what we're not stabilizing might lead to surprises for users, talk about that in particular.

Any usage that also requires another unstable feature is not stabilized in so far that the other feature is required to write the code using the attribute in that location

Can you clarify that this applies to the crate root, where the attribute has meaning but it remains gated by custom_inner_attributes?

View changes since this review

@rustbot rustbot assigned mejrs and unassigned chenyukang Oct 4, 2026
@mejrs mejrs added I-lang-nominated Nominated for discussion during a lang team meeting. needs-reference-pr This language change needs an approved Reference PR to proceed. labels Oct 4, 2026

This branch has not been deployed

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

Labels

A-attributes Area: Attributes (`#[…]`, `#![…]`) I-lang-nominated Nominated for discussion during a lang team meeting. needs-reference-pr This language change needs an approved Reference PR to proceed. 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.

4 participants