From c55c8242971767715edb2a464cbc94167f8b9a95 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:46:11 +0200 Subject: [PATCH] Document the `rustc_on_unimplemented` attribute. --- compiler/rustc_attr_ir/src/attribute_docs.rs | 111 ++++++++++ src/doc/rustc-dev-guide/src/diagnostics.md | 205 ++---------------- .../doc_examples/rustc_on_unimplemented.rs | 16 ++ .../rustc_on_unimplemented.stderr | 18 ++ .../rustc_on_unimplemented_filter.rs | 18 ++ .../rustc_on_unimplemented_filter.stderr | 18 ++ .../rustc_on_unimplemented_format.rs | 15 ++ .../rustc_on_unimplemented_format.stderr | 19 ++ 8 files changed, 232 insertions(+), 188 deletions(-) create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented.rs create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented.stderr create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.rs create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.stderr create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.rs create mode 100644 tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.stderr diff --git a/compiler/rustc_attr_ir/src/attribute_docs.rs b/compiler/rustc_attr_ir/src/attribute_docs.rs index a2b00c90b3028..9c3d3a47590d9 100644 --- a/compiler/rustc_attr_ir/src/attribute_docs.rs +++ b/compiler/rustc_attr_ir/src/attribute_docs.rs @@ -283,3 +283,114 @@ const _: () = (); /// [#156920]:https://github.com/rust-lang/rust/issues/156920 "`dyn Allocator` together with `Allocator + Clone` requirements is unsound, leading to UB with `Arc`" /// [#160045]:https://github.com/rust-lang/rust/issues/160045 "`iter::Rev`'s `TrustedLen` impl is unsound with trait objects" const _: () = (); + +#[doc(attribute = "rustc_on_unimplemented")] +/// Customize the error message when a trait is not implemented. +/// +/// It must be used on the declaration of said trait. +/// +/// # Syntax +/// +/// ```grammar +/// RustcOnUnimplementedAttribute -> +/// rustc_on_unimplemented ( ( Directive ),+ ) +/// +/// Directive -> +/// on ( Filter, ( DirectiveOption ),+ ) +/// | ( DirectiveOption ),* +/// +/// DirectiveOption -> +/// message = STRING_LITERAL +/// | label = STRING_LITERAL +/// | note = STRING_LITERAL +/// +/// Filter -> +/// FilterAll +/// | FilterAny +/// | FilterNot +/// | FilterOption +/// +/// FilterAll -> +/// all ( ( Filter ),* ) +/// +/// FilterAny -> +/// any ( ( Filter ),* ) +/// +/// FilterNot -> +/// not ( Filter ) +/// +/// FilterOption -> +/// crate_local +/// | direct +/// | from_desugaring ( = STRING_LITERAL )? +/// | cause = STRING_LITERAL +/// | IDENTIFIER = STRING_LITERAL +/// +/// ``` +/// +/// The following keys have the given meaning. At least one must be specified. +/// - `on` - filters the application of the attribute. See [#Filters](#filters). +/// - `message` — The text for the top level error message. May only be specified at most once. +/// - `label` — The text for the label shown inline in the broken code in the error message. +/// May only be specified at most once. +/// - `note` — Provides additional note(s) +/// +/// `message`, `label`, and `note` are available with the [`diagnostic::on_unimplemented`] +/// attribute. If possible, use that instead. +/// +/// # Example +/// +#[doc = include_example!("rustc_on_unimplemented")] +/// +/// # Filters +/// +/// To allow more targeted error messages, it is possible to filter the +/// application of these keys with `on`. +/// +/// You can filter on the following boolean flags: +/// - `crate_local`: whether the code causing the trait bound to not be +/// fulfilled is part of the user's crate. +/// This is used to avoid suggesting code changes that would require modifying a dependency. +/// - `direct`: whether this is a user-specified rather than derived obligation. +/// - `from_desugaring`: whether we are in some kind of desugaring, like `?` +/// or a `try` block for example. +/// This flag can also be matched on, see below. +/// +/// You can match on the following names and values, using `name = "value"`: +/// - `cause`: Match against one variant of the `ObligationCauseCode` enum. +/// Only `"MainFunctionType"` is supported. +/// - `from_desugaring`: Match against a particular variant of the `DesugaringKind` enum. +/// The desugaring is identified by its variant name, for example +/// `"QuestionMark"` for `?` desugaring, or `"TryBlock"` for `try` blocks. +/// - `Self` and any generic arguments of the trait, like `Self = "alloc::string::String"` +/// or `Rhs="i32"`. +/// +/// The compiler provides several values to match on, for example: +/// - the self_ty, pretty printed with and without type arguments resolved. +/// - `"{integral}"`, if self_ty is an integral of which the type is known. +/// - `"[]"`, `"[{ty}]"`, `"[{ty}; _]"`, `"[{ty}; $N]"` when applicable. +/// - references to said slices and arrays. +/// - `"fn"`, `"unsafe fn"` or `"#[target_feature] fn"` when self is a function. +/// - `"{integer}"` and `"{float}"` if the type is a number but we haven't inferred it yet. +/// - `"{struct}"`, `"{enum}"` and `"{union}"` to match self as an ADT +/// - combinations of the above, like `"[{integral}; _]"`. +/// +#[doc = include_example!("rustc_on_unimplemented_filter")] +/// +/// # Formatting +/// +/// The string literals are format strings that accept parameters wrapped in braces - +/// positional and listed parameters are not accepted. +/// The following parameter names are valid: +/// - `Self` and all generic parameters of the trait. +/// - `This`: the name of the trait the attribute is on, without generics. +/// - `This:path`: the full path of the trait the attribute is on, with unresolved generics. +/// - `This:resolved`: the full path of the trait the attribute is on, with resolved generics. +/// Additionally, this will "sugar" the `Fn(...)` traits. +/// - `ItemContext`: the kind of `hir::Node` we're in, things like `"an async block"`, +/// `"a function"`, `"an async function"`, etc. +/// +#[doc = include_example!("rustc_on_unimplemented_format")] +/// +/// [`diagnostic::on_unimplemented`]: https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-diagnosticon_unimplemented-attribute +const _: () = (); diff --git a/src/doc/rustc-dev-guide/src/diagnostics.md b/src/doc/rustc-dev-guide/src/diagnostics.md index 661e07af7189c..bdb3f8c9f22ae 100644 --- a/src/doc/rustc-dev-guide/src/diagnostics.md +++ b/src/doc/rustc-dev-guide/src/diagnostics.md @@ -862,192 +862,21 @@ struct](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/json/struct Don't confuse this with [`errors::Diag`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.Diag.html)! -## `#[rustc_on_unimplemented]` - -This attribute allows trait definitions to modify error messages when an implementation was -expected but not found. -The string literals in the attribute are format strings and can be formatted with named parameters. -See the Formatting section below for what parameters are permitted. - -```rust,ignore -#[rustc_on_unimplemented(message = "an iterator over \ - elements of type `{A}` cannot be built from a \ - collection of type `{Self}`")] -trait MyIterator { - fn next(&mut self) -> A; -} - -fn iterate_chars>(i: I) { - // ... -} - -fn main() { - iterate_chars(&[1, 2, 3][..]); -} -``` - -When the user compiles this, they will see the following; - -```txt -error[E0277]: an iterator over elements of type `char` cannot be built from a collection of type `&[{integer}]` - --> src/main.rs:13:19 - | -13 | iterate_chars(&[1, 2, 3][..]); - | ------------- ^^^^^^^^^^^^^^ the trait `MyIterator` is not implemented for `&[{integer}]` - | | - | required by a bound introduced by this call - | -note: required by a bound in `iterate_chars` -``` - -You can modify the contents of: - - the main error message (`message`) - - the label (`label`) - - the note(s) (`note`) - -For example, the following attribute - -```rust,ignore -#[rustc_on_unimplemented(message = "message", label = "label", note = "note")] -trait MyIterator { - fn next(&mut self) -> A; -} -``` - -Would generate the following output: - -```text -error[E0277]: message - --> :10:19 - | -10 | iterate_chars(&[1, 2, 3][..]); - | ------------- ^^^^^^^^^^^^^^ label - | | - | required by a bound introduced by this call - | - = help: the trait `MyIterator` is not implemented for `&[{integer}]` - = note: note -note: required by a bound in `iterate_chars` -``` - -The functionality discussed so far is also available with -[`#[diagnostic::on_unimplemented]`](https://doc.rust-lang.org/nightly/reference/attributes/diagnostics.html#the-diagnosticon_unimplemented-attribute). -If you can, you should use that instead. - -### Filtering - -To allow more targeted error messages, -it is possible to filter the application of these fields with `on`. - -You can filter on the following boolean flags: - - `crate_local`: whether the code causing the trait bound to not be - fulfilled is part of the user's crate. - This is used to avoid suggesting code changes that would require modifying a dependency. - - `direct`: whether this is a user-specified rather than derived obligation. - - `from_desugaring`: whether we are in some kind of desugaring, - like `?` or a `try` block for example. - This flag can also be matched on, see below. - -You can match on the following names and values, using `name = "value"`: - - `cause`: Match against one variant of the `ObligationCauseCode` enum. - Only `"MainFunctionType"` is supported. - - `from_desugaring`: Match against a particular variant of the `DesugaringKind` enum. - The desugaring is identified by its variant name, for example - `"QuestionMark"` for `?` desugaring, or `"TryBlock"` for `try` blocks. - - `Self` and any generic arguments of the trait, - like `Self = "alloc::string::String"` or `Rhs="i32"`. - -The compiler can provide several values to match on, for example: - - the self_ty, pretty printed with and without type arguments resolved. - - `"{integral}"`, if self_ty is an integral of which the type is known. - - `"[]"`, `"[{ty}]"`, `"[{ty}; _]"`, `"[{ty}; $N]"` when applicable. - - references to said slices and arrays. - - `"fn"`, `"unsafe fn"` or `"#[target_feature] fn"` when self is a function. - - `"{integer}"` and `"{float}"` if the type is a number but we haven't inferred it yet. - - `"{struct}"`, `"{enum}"` and `"{union}"` to match self as an ADT - - combinations of the above, like `"[{integral}; _]"`. - -For example, the `Iterator` trait can be filtered in the following way: - -```rust,ignore -#[rustc_on_unimplemented( - on(Self = "&str", note = "call `.chars()` or `.as_bytes()` on `{Self}`"), - message = "`{Self}` is not an iterator", - label = "`{Self}` is not an iterator", - note = "maybe try calling `.iter()` or a similar method" -)] -pub trait Iterator {} -``` - -Which would produce the following outputs: - -```text -error[E0277]: `Foo` is not an iterator - --> src/main.rs:4:16 - | -4 | for foo in Foo {} - | ^^^ `Foo` is not an iterator - | - = note: maybe try calling `.iter()` or a similar method - = help: the trait `std::iter::Iterator` is not implemented for `Foo` - = note: required by `std::iter::IntoIterator::into_iter` - -error[E0277]: `&str` is not an iterator - --> src/main.rs:5:16 - | -5 | for foo in "" {} - | ^^ `&str` is not an iterator - | - = note: call `.chars()` or `.bytes() on `&str` - = help: the trait `std::iter::Iterator` is not implemented for `&str` - = note: required by `std::iter::IntoIterator::into_iter` -``` - -The `on` filter accepts `all`, `any` and `not` predicates similar to the `cfg` attribute: - -```rust,ignore -#[rustc_on_unimplemented(on( - all(Self = "&str", T = "alloc::string::String"), - note = "you can coerce a `{T}` into a `{Self}` by writing `&*variable`" -))] -pub trait From: Sized { - /* ... */ -} -``` - -### Formatting - -The string literals are format strings that accept parameters wrapped in braces -but positional and listed parameters are not accepted. -The following parameter names are valid: -- `Self` and all generic parameters of the trait. -- `This`: the name of the trait the attribute is on, without generics. -- `This:path`: the full path of the trait the attribute is on, with unresolved generics. -- `This:resolved`: the full path of the trait the attribute is on, with resolved generics. -Additionally, this will "sugar" the `Fn(...)` traits. -- `ItemContext`: the kind of `hir::Node` we're in, things like `"an async block"`, - `"a function"`, `"an async function"`, etc. - -Something like: - -```rust,ignore -#![feature(rustc_attrs)] - -#[rustc_on_unimplemented(message = "Self = `{Self}`, \ - T = `{T}`, this = `{This}`, trait = `{Trait}`, \ - context = `{ItemContext}`")] -pub trait From: Sized { - fn from(x: T) -> Self; -} - -fn main() { - let x: i8 = From::from(42_i32); -} -``` - -Will format the message into -```text -"Self = `i8`, T = `i32`, this = `From`, trait = `From`, context = `a function`" -``` - +## Diagnostic attributes + +There are many attributes that can be used to generate or improve error messages: +- `diagnostic::on_unimplemented` and [`rustc_on_unimplemented`]: customize unimplemented trait errors +- `diagnostic::on_move`: customize borrowcheck errors +- `diagnostic::on_unknown`: customize unresolved imports errors +- `diagnostic::on_unmatched_args`: customize macro matcher errors +- `diagnostic::opaque`: stop the internals of macros from showing up in error messages +- `diagnostic::on_const`: customize non-const trait impl errors +- `rustc_as_ptr`: used by the `dangling_pointers_from_temporaries` lint. +- `rustc_never_returns_null_ptr`: used by the `useless_ptr_null_checks` lint. +- `rustc_no_implicit_autorefs`: used for the `dangerous_implicit_autorefs` lint. + +If possible, consider improving or implementing such an attribute rather than adding custom error +reporting code. + +[`rustc_on_unimplemented`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_ir/attribute.rustc_on_unimplemented.html [diag]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/struct.Diag.html diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented.rs b/tests/ui/attributes/doc_examples/rustc_on_unimplemented.rs new file mode 100644 index 0000000000000..d61725a4f042a --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented.rs @@ -0,0 +1,16 @@ +//@ dont-require-annotations: ERROR +//@ compile-flags: --crate-type lib -Z ui-testing=no + +#![feature(rustc_attrs)] + +#[rustc_on_unimplemented( + message = "cannot add `{Rhs}` to `{Self}`", + label = "no implementation for `{Self} + {Rhs}`", +)] +pub trait MyAdd { + fn add(self, rhs: Rhs) -> Self; +} + +fn main() { + MyAdd::add(42_u8, 42.0); +} diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented.stderr b/tests/ui/attributes/doc_examples/rustc_on_unimplemented.stderr new file mode 100644 index 0000000000000..fb106e0e14572 --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented.stderr @@ -0,0 +1,18 @@ +error[E0277]: cannot add `_` to `u8` + --> $DIR/rustc_on_unimplemented.rs:15:16 + | +15 | MyAdd::add(42_u8, 42.0); + | ---------- ^^^^^ no implementation for `u8 + _` + | | + | required by a bound introduced by this call + | + = help: the trait `MyAdd<_>` is not implemented for `u8` +help: this trait has no implementations, consider adding one + --> $DIR/rustc_on_unimplemented.rs:10:1 + | +10 | pub trait MyAdd { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.rs b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.rs new file mode 100644 index 0000000000000..76eac2be8e098 --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.rs @@ -0,0 +1,18 @@ +//@ dont-require-annotations: ERROR +//@ compile-flags: --crate-type lib -Z ui-testing=no + +#![feature(rustc_attrs)] + +#[rustc_on_unimplemented( + on(all(Self = "{integer}", Rhs = "{float}"), message = "cannot add a float to an integer",), + on(all(Self = "{float}", Rhs = "{integer}"), message = "cannot add an integer to a float",), + message = "cannot add `{Rhs}` to `{Self}`", + label = "no implementation for `{Self} + {Rhs}`" +)] +pub trait MyAdd { + fn add(self, rhs: Rhs) -> Self; +} + +fn main() { + MyAdd::add(42_u8, 42.0); +} diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.stderr b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.stderr new file mode 100644 index 0000000000000..3518953c9051d --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_filter.stderr @@ -0,0 +1,18 @@ +error[E0277]: cannot add `_` to `u8` + --> $DIR/rustc_on_unimplemented_filter.rs:17:16 + | +17 | MyAdd::add(42_u8, 42.0); + | ---------- ^^^^^ no implementation for `u8 + _` + | | + | required by a bound introduced by this call + | + = help: the trait `MyAdd<_>` is not implemented for `u8` +help: this trait has no implementations, consider adding one + --> $DIR/rustc_on_unimplemented_filter.rs:12:1 + | +12 | pub trait MyAdd { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.rs b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.rs new file mode 100644 index 0000000000000..f67f54e279efd --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.rs @@ -0,0 +1,15 @@ +//@ dont-require-annotations: ERROR +//@ compile-flags: --crate-type lib -Z ui-testing=no + +#![feature(rustc_attrs)] + +#[rustc_on_unimplemented(message = "Self = `{Self}`, \n \ + T = `{T}`, this = `{This}`, path = `{This:path}`, \n \ + resolved = `{This:resolved}`, context = `{ItemContext}`")] +pub trait From: Sized { + fn from(x: T) -> Self; +} + +fn main() { + let x: i8 = From::from(42_i32); +} diff --git a/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.stderr b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.stderr new file mode 100644 index 0000000000000..023ffd76bc470 --- /dev/null +++ b/tests/ui/attributes/doc_examples/rustc_on_unimplemented_format.stderr @@ -0,0 +1,19 @@ +error[E0277]: Self = `i8`, + T = `i32`, this = `From`, path = `From`, + resolved = `From`, context = `a function` + --> $DIR/rustc_on_unimplemented_format.rs:14:28 + | +14 | let x: i8 = From::from(42_i32); + | ---------- ^^^^^^ the trait `From` is not implemented for `i8` + | | + | required by a bound introduced by this call + | +help: this trait has no implementations, consider adding one + --> $DIR/rustc_on_unimplemented_format.rs:9:1 + | + 9 | pub trait From: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.