Skip to content

Commit b9bf6ad

Browse files
committed
The #[diagnostic] attribute namespace
1 parent 873890e commit b9bf6ad

File tree

1 file changed

+222
-0
lines changed

1 file changed

+222
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
- Feature Name: The `#[diagnostic]` attribute namespace
2+
- Start Date: 2023-01-06
3+
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
4+
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
5+
6+
7+
# Summary
8+
[summary]: #summary
9+
10+
This RFC proposed to add a stable `#[diagnostic]` attribute namespace, which contains attributes to influence error messages emitted by the compiler. In addition it proposed to add a `#[diagnostic::on_unimplemented]` attribute to influence error messages emitted by unsatisfied traits bounds.
11+
12+
# Motivation
13+
[motivation]: #motivation
14+
15+
Rust has the reputation to generate helpful error messages when something goes wrong. Nevertheless there are always cases of error messages that can be improved. One common example of such error messages in the rust ecosystem are those that are generated by crates using the type system to verify certain invariants at compile time. While these crates provide additional guarantees about these invariants, they sometimes generate large incomprehensible error messages when something goes wrong. These error messages do not always indicate clearly what went wrong. Well known examples of crates with such issues include bevy, axum or diesel. In some situations such a specific error message always indicates a certain problem. By providing authors of such crates tools to control the error messages emitted by the compiler they can improve the situation on their own.
16+
17+
# Guide-level explanation
18+
[guide-level-explanation]: #guide-level-explanation
19+
20+
This feature has two possible groups of users:
21+
22+
* Users that develop code and consume error messages from the compiler
23+
* Users that write crates involving complex type hierarchies
24+
25+
The first user group will interact with the proposed feature through the error messages emitted by the compiler. As of this I do not expect any major documentation requirements for this group of users. Although we might want to indicate that a certain error message was provided via the described feature set, rather than by the compiler itself to prevent users for filling issues about bad error messages in the compilers issue tracker.
26+
27+
The second user group interacts with the described feature through attributes. These attributes allow them to hint the compiler to emit specific error messages in certain cases. The `#[diagnostic]` attribute namespace provides a general framework for what can and can't be done by such an attribute. As of this users won't interact directly with the attribute namespace itself.
28+
29+
The `#[diagnostic::on_unimplemented]` attribute allows to hint the compiler to emit a specific error message if a certain trait is not implemented. This attribute should provide the following interface:
30+
31+
```rust
32+
#[diagnostic::on_unimplemented(
33+
message="message",
34+
label="label",
35+
note="note"
36+
)]
37+
trait MyIterator<A> {
38+
fn next(&mut self) -> A;
39+
}
40+
41+
42+
fn iterate_chars<I: MyIterator<char>>(i: I) {
43+
// ...
44+
}
45+
fn main() {
46+
iterate_chars(&[1, 2, 3][..]);
47+
}
48+
```
49+
50+
which might result in the compiler emitting the following error message:
51+
52+
```
53+
error[E0277]: message
54+
--> <anon>:14:5
55+
|
56+
14 | iterate_chars(&[1, 2, 3][..]);
57+
| ^^^^^^^^^^^^^ label
58+
|
59+
= note: note
60+
= help: the trait `MyIterator<char>` is not implemented for `&[{integer}]`
61+
= note: required by `iterate_chars`
62+
```
63+
64+
I expect the new attributes to be documented on the existing [Diagnostics](https://doc.rust-lang.org/reference/attributes/diagnostics.html) attributes page on the rust reference similar to existing attributes like for example `#[deprecated]`
65+
66+
67+
# Reference-level explanation
68+
[reference-level-explanation]: #reference-level-explanation
69+
70+
## The `#[diagnostic]` attribute namespace
71+
72+
This RFC proposes to introduce a new `#[diagnostic]` attribute namespace. This namespace is supposed to contain different attributes, which allow users to hint the compiler to emit specific error messages in certain cases like type mismatches, unsatisfied trait bounds or similar situations. By collecting such attributes in a common namespace it is easier for users to find useful attributes and it is easier for the language team to establish a set of common rules for these attributes.
73+
74+
Any attribute in this namespace may:
75+
76+
* Hint the compiler to emit a specific error message in a specific situation
77+
78+
Any attribute in this namespace is not allowed to:
79+
80+
* Change the result of the compilation, which means applying such an attribute should never cause an error as long as they are syntactically valid
81+
82+
The compiler is allowed to:
83+
84+
* Ignore the provided hints
85+
* Use only parts of the provided hints, for example for the proposed `#[diagnostic::on_unimplemented]` only use the `message` option
86+
* Change this behaviour at any time
87+
88+
The compiler must:
89+
90+
* Verify that the attribute is syntactically correct, which means:
91+
+ Its one of the accepted attributes
92+
+ It only contains the allowed options
93+
+ Any provided option follows the defined syntax
94+
* Follow the described semantics if the attribute is not ignored.
95+
96+
97+
## The `#[diagnostic::on_unimplemented]` attribute
98+
99+
This section describes the syntax of the `on_unimplemented` attribute and additionally how it is supposed to work. Implementing the later part is optional as outlined in the previous section. Any syntax not explicitly described in this section must be rejected as invalid according to the general rules of the `#[diagnostic]` attribute namespace.
100+
101+
```rust
102+
#[diagnostic::on_unimplemented(
103+
message="message",
104+
label="label",
105+
note="note",
106+
)]
107+
trait MyIterator<A> {
108+
fn next(&mut self) -> A;
109+
}
110+
```
111+
112+
113+
Each of the options `message`, `label` and `note` are optional. They are separated by comma. The tailing comma is optional. Specifying any of these options hints the compiler to replace the normally emitted part of the error message with the provided string. At least one of these options needs to exist. Each option can appear at most once. The error message can include type information for the `Self` type or any generic type by using `{Self}` or `{A}` (where `A` refers to the generic type name in the definition). These placeholders are replaced by the actual type name.
114+
115+
In addition the `on_unimplemented` attribute provides mechanisms to specify for which exact types a certain message should be emitted via an `if()` option. It accepts a set of filter options. A filter option consists on the generic parameter name from the trait definition and a type path against which the parameter should be checked. These type path could either be a fully qualified path or refer to any type in the current scope. As a special generic parameter name `Self` is added to refer to the `Self` type of the trait implementation. A filter option evaluates to `true` if the corresponding generic parameter in the trait definition matches the specified type. The provided `message`/`note`/`label` options are only emitted if the filter operation evaluates to `true`.
116+
117+
The `any` and `all` option allows to combine multiple filter options. The `any` option matches if one of the supplied filter options evaluates to `true`, the `all` option requires that all supplied filter options evaluate to true. `not` allows to negate a given filter option. It evaluates to `true` if the inner filter option evaluates to `false`. These options can be nested to construct complex filters.
118+
119+
The `on_unimplemented` attribute can be applied multiple times to the same trait definition. Multiple attributes are evaluated in order. The first matching instance for each of the `message`/`label`/`note` options is emitted.
120+
```rust
121+
#[diagnostic::on_unimplemented(
122+
if(Self = std::string::String),
123+
note = "That's only emitted if Self == std::string::String"
124+
)]
125+
#[diagnostic::on_unimplemented(
126+
if(A = String), // Refers to whatever `String` is in the current scope
127+
note = "That's only emitted if A == String",
128+
)]
129+
#[diagnostic::on_unimplemented(
130+
if(any(A = i32, Self = i32)),
131+
note = "That's emitted if A or Self is a i32",
132+
)]
133+
// this attribute will not have any affect as
134+
// the attribute above will always match before
135+
#[diagnostic::on_unimplemented(
136+
if(all(A = i32, Self = i32)),
137+
note = "That's emitted if A and Self is a i32"
138+
)]
139+
#[diagnostic::on_unimplemented(
140+
if(not(A = String)),
141+
// and implicitly `A` is not a `i32` as that case is
142+
// matched earlier
143+
note = "That's emitted if A is not a `String`"
144+
)]
145+
#[diagnostic::on_unimplemented(
146+
message="message",
147+
label="label",
148+
note="That's emitted if neither of the condition above are meet",
149+
)]
150+
trait MyIterator<A> {
151+
fn next(&mut self) -> A;
152+
}
153+
```
154+
155+
# Drawbacks
156+
[drawbacks]: #drawbacks
157+
158+
A possible drawback is that this feature adds additional complexity to the compiler implementation. The compiler needs to handle an additional attribute namespace with at least one additional attribute.
159+
160+
Another drawback is that crates might hint lower quality error messages than the compiler itself. Technically the compiler would be free to ignore such hints, practically I would assume that it is impossible to judge the quality of such error messages in an automated way.
161+
162+
# Rationale and alternatives
163+
[rationale-and-alternatives]: #rationale-and-alternatives
164+
165+
This proposal tries to improve error messages generated by rustc. It would give crate authors a tool to influence what error message is emitted in a certain situation, as they might sometimes want to provide specific details on certain error conditions. Not implementing this proposal would result in the current status quo. Currently the compiler always shows a "general" error message, even if it would be helpful to show additional details.
166+
167+
There are alternative designs for the proposed `on_unimplemented` attribute:
168+
169+
* The `on()` based filtering might be replaceable by placing the attribute on negative trait impls. This would turn a filter like
170+
```rust
171+
#[diagnostic::on_unimplemented(
172+
on(Self = `String`, message = "Strings do not implement `IntoIterator` directly")
173+
)]
174+
trait IntoIterator {}
175+
```
176+
177+
into the following negative trait impl:
178+
```rust
179+
#[diagnostic::on_unimplemented(message = "Strings do not implement `IntoIterator` directly")]
180+
impl !IntoIterator for String {}
181+
```
182+
183+
This would simplify the syntax of the proposed attribute, but in turn block the implementation of type based filtering on the stabilization of `negative_impls`. On the other hand it would likely simplify writing more complex filters, that match only a certain generic set of types and it would prevent "duplicating" the filter-logic as this reuses the exiting trait system. The large disadvantage of this approach is that it couples error messages to the crates public API. Removing a negative trait impl is a breaking change, removing a `#[on_unimplemented]` attribute is only a change in the emitted compiler error.
184+
185+
* Allow `#[diagnostic::on_unimplemented]` to be placed on types instead of traits. This would allow third party crates to customize the error messages emitted for unsatisfied trait bounds with out of crate traits. This feels more like an extension tho the proposed attribute.
186+
187+
# Prior art
188+
[prior-art]: #prior-art
189+
190+
* [rustc_on_unimplemented](https://rustc-dev-guide.rust-lang.org/diagnostics.html#rustc_on_unimplemented) already provides the described functionality as rustc internal attribute. It is used for improving error messages for various standard library API's. [This repo](https://github.com/weiznich/rust-foundation-community-grant/) contains several examples on how this attribute can be used in external crates to improve their error messages.
191+
* [GHC](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_errors.html) provides an Haskell language extension for specifying custom compile time errors
192+
193+
Notably all of the listed similar features are unofficial language extensions.
194+
195+
# Unresolved questions
196+
[unresolved-questions]: #unresolved-questions
197+
198+
Clarify the procedure of various potential changes prior stabilisation of the attribute namespace:
199+
200+
* Process of adding a new attribute to the `#[diagnostics]` attribute macro namespace
201+
+ Requires a RFC? (t-lang seems to prefer that?)
202+
+ Requires a language/compiler MCP?
203+
+ Just a PR to the compiler?
204+
* Process of extending an existing attribute by adding more options
205+
+ Requires a RFC?
206+
+ Requires a language/compiler MCP?
207+
+ Just a PR to the compiler?
208+
* Process of removing support for specific options or an entire attribute from rustc
209+
+ Requires a compiler MCP?
210+
+ Just a PR to the compiler?
211+
* Exact syntax of the `on_unimplemented` attribute
212+
+ Can `Self` be accepted in that position or do we need another name?
213+
+ Is `if()` a valid identifier in the proposed position?
214+
215+
# Future possibilities
216+
[future-possibilities]: #future-possibilities
217+
218+
* More attributes like `#[diagnostics::on_type_error]`
219+
* Extend the `#[diagnostics::on_unimplemented]` attribute to incorporate the semantics of `#[do_not_recommend]`
220+
* Un-RFC `#[do_not_recommend]`?
221+
* Apply `#[diagnostics::on_unimplemented]` to types as well
222+
* Extend the `if()` filter syntax to allow more complex filter expressions

0 commit comments

Comments
 (0)