Skip to content

Commit 863b37c

Browse files
authored
Merge pull request #2383 from myrrlyn/lint_reasons
Lint Reasons RFC
2 parents 933b90a + db82e58 commit 863b37c

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed

text/2383-lint-reasons.md

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
- Feature Name: `lint_reasons`
2+
- Start Date: 2018-04-02
3+
- RFC PR: [rust-lang/rfcs#2383](https://github.com/rust-lang/rfcs/pull/2383)
4+
- Rust Issue: [rust-lang/rust#54503](https://github.com/rust-lang/rust/issues/54503)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
Rust has a number of code lints, both built into the compiler and provided
10+
through external tools, which provide guidelines for code style. The linter
11+
behavior can be customized by attaching attributes to regions of code to allow,
12+
warn, or forbid, certain lint checks.
13+
14+
The decision for allowing, warning on, or forbidding, specific lints is
15+
occasionally placed in a comment above the attribute or, more often, left
16+
unstated. This RFC proposes adding syntax to the lint attributes to encode the
17+
documented reason for a lint configuration choice.
18+
19+
# Motivation
20+
[motivation]: #motivation
21+
22+
The style guide for a project, team, or company can cover far more than just
23+
syntax layout. Rules for the semantic shape of a codebase are documented in
24+
natural language and often in automated checking programs, such as the Rust
25+
compiler and Clippy. Because the decisions about what rules to follow or ignore
26+
shape the codebase and its growth, the decisions are worth storing in the
27+
project directly with the settings they affect.
28+
29+
It is common wisdom that only the text the environment can read stays true; text
30+
it ignores will drift out of sync with the code over time, if it was even in
31+
sync to begin. Lint settings should have an explanation for their use to explain
32+
why they were chosen and where they are or are not applicable. As they are text
33+
that is read by some lint program, they have an opportunity to include an
34+
explanation similar to the way Rust documentation is a first-class attribute on
35+
code.
36+
37+
The RFC template asks three questions for motivation:
38+
39+
- Why are we doing this?
40+
41+
We are adding this behavior to give projects a mechanism for storing human
42+
design choices about code in a manner that the tools can track and use to
43+
empower human work. For example, the compiler could use the contents of the
44+
lint explanation when it emits lint messages, or the documenter could collect
45+
them into a set of code style information.
46+
47+
- What use cases does it support?
48+
49+
This supports the use cases of projects, teams, or organizations using specific
50+
sets of code style guidelines beyond the Rust defaults. This also enables the
51+
creation and maintenance of documented practices and preferences that can be
52+
standardized in a useful way. Furthermore, this provides a standardized means of
53+
explaining decisions when a style gude must be violated by attaching an
54+
overriding lint attribute to a specific item.
55+
56+
- What is the expected outcome?
57+
58+
The expected outcome of this RFC is that projects will have more information
59+
about the decisions and expectations of the project, and can have support from
60+
the tools to maintain and inform these decisions. Global and specific choices
61+
can have their information checked and maintained by the tools, and the Rust
62+
ecosystem can have a somewhat more uniform means of establishing code guidelines
63+
and practices.
64+
65+
I expect Clippy will be a significant benefactor of this RFC, as Clippy lints
66+
are far more specific and plentiful than the compiler lints, and from personal
67+
experience much more likely to want explanation for their use or disuse.
68+
69+
# Guide-level explanation
70+
[guide-level-explanation]: #guide-level-explanation
71+
72+
When a linting tool such as the compiler or Clippy encounter a code span that
73+
they determine breaks one of their rules, they emit a message indicating the
74+
problem and, often, how to fix it. These messages explain how to make the linter
75+
program happy, but carry very little information on why the code may be a
76+
problem from a human perspective.
77+
78+
These lints can be configured away from the default settings by the use of an
79+
attribute modifying the code span that triggers a lint, or by setting the linter
80+
behavior for a module or crate, with attributes like `#[allow(rule)]` and
81+
`#![deny(rule)]`.
82+
83+
It is generally good practice to include an explanation for why certain rules
84+
are set so that programmers working on a project can know what the project
85+
expects from their work. These explanations can be embedded directly in the lint
86+
attribute with the `reason = "Your reasoning here"` attribute.
87+
88+
For example, if you are implementing `Ord` on an enum where the discriminants
89+
are not the correct ordering, you might have code like this:
90+
91+
```rust
92+
enum Severity { Red, Blue, Green, Yellow }
93+
impl Ord for Severity {
94+
fn cmp(&self, other: &Self) -> Ordering {
95+
use Severity::*;
96+
use Ordering::*;
97+
match (*self, *other) {
98+
(Red, Red) |
99+
(Blue, Blue) |
100+
(Green, Green) |
101+
(Yellow, Yellow) => Equal,
102+
103+
(Blue, _) => Greater,
104+
(Red, _) => Less,
105+
106+
(Green, Blue) => Less,
107+
(Green, _) => Greater,
108+
109+
(Yellow, Red) => Greater,
110+
(Yellow, _) => Less,
111+
}
112+
}
113+
}
114+
```
115+
116+
The ordering of the left hand side of the match branches is significant, and
117+
allows a compact number of match arms. However, if you're using Clippy, this
118+
will cause the `match_same_arms` lint to trip! You can silence the lint in this
119+
spot, and provide an explanation that indicates you are doing so deliberately,
120+
by placing this attribute above the `match` line:
121+
122+
```rust
123+
#[allow(match_same_arms, reason = "The arms are order-dependent")]
124+
```
125+
126+
Now, when the lints run, no warning will be raised on this specific instance,
127+
and there is an explanation of why you disabled the lint, directly in the lint
128+
command.
129+
130+
Similarly, if you want to increase the strictness of a lint, you can explain why
131+
you think the lint is worth warning or forbidding directly in it:
132+
133+
```rust
134+
#![deny(float_arithmetic, reason = "This code runs in a context without floats")]
135+
```
136+
137+
With a warning or denial marker, when a linting tool encounters such a lint trap
138+
it will emit its builtin diagnostic, but also include the reason in its output.
139+
140+
For instance, using the above Clippy lint and some floating-point arithmetic
141+
code will result in the following lint output:
142+
143+
```text
144+
error: floating-point arithmetic detected
145+
reason: This code runs in a context without floats
146+
--> src/lib.rs:4:2
147+
|
148+
4 | a + b
149+
| ^^^^^
150+
|
151+
note: lint level defined here
152+
--> src/lib.rs:1:44
153+
|
154+
1 | #![cfg_attr(deny(float_arithmetic, reason = "..."))]
155+
| ^^^^^^^^^^^^^^^^
156+
= help: for further information visit ...
157+
```
158+
159+
## `expect` Lint Attribute
160+
161+
This RFC adds an `expect` lint attribute that functions identically to `allow`,
162+
but will cause a lint to be emitted when the code it decorates ***does not***
163+
raise a lint warning. This lint was inspired by Yehuda Katz:
164+
165+
> [@ManishEarth](https://twitter.com/ManishEarth) has anyone ever asked for
166+
> something like #[expect(lint)] which would basically be like #[allow(lint)]
167+
> but give you a lint warning if the problem ever went away?
168+
>
169+
> I basically want to mark things as ok while doing initial work, but I need to
170+
> know when safe to remove
171+
>
172+
> — Yehuda Katz ([@wycats](https://twitter.com/wycats))
173+
>
174+
> [March 30, 2018](https://twitter.com/wycats/status/979742693378019329)
175+
176+
When the lint passes run, the `expect` attribute suppresses a lint generated by
177+
the span to which it attached. It does not swallow any other lint raised, and
178+
when it does not receive a lint to suppress, it raises a lint warning itself.
179+
`expect` can take a `reason` field, which is printed when the lint is raised,
180+
just as with the `allow`/`warn`/`deny` markers.
181+
182+
This is used when prototyping and using code that will generate lints for now,
183+
but will eventually be refactored and stop generating lints and thus no longer
184+
need the permission.
185+
186+
```rust
187+
#[expect(unused_mut, reason = "Everything is mut until I get this figured out")]
188+
fn foo() -> usize {
189+
let mut a = Vec::new();
190+
a.len()
191+
}
192+
```
193+
194+
will remain quiet while you're not mutating `a`, but when you do write code that
195+
mutates it, or decide you don't need it mutable and strip the `mut`, the
196+
`expect` lint will fire and inform you that there is no unused mutation in the
197+
span.
198+
199+
```rust
200+
#[expect(unused_mut, reason = "...")]
201+
fn foo() {
202+
let a = Vec::new();
203+
a.len()
204+
}
205+
```
206+
207+
will emit
208+
209+
```text
210+
warning: expected lint `unused_mut` did not appear
211+
reason: Everything is mut until I get this figured out
212+
--> src/lib.rs:1:1
213+
|
214+
1 | #[expect(unused_mut, reason = "...")]
215+
| -------^^^^^^^^^^-----------------
216+
| |
217+
| help: remove this `#[expect(...)]`
218+
|
219+
= note: #[warn(expectation_missing)] on by default
220+
```
221+
222+
# Reference-level explanation
223+
[reference-level-explanation]: #reference-level-explanation
224+
225+
This RFC adds a `reason = STRING` element to the three lint attributes. The
226+
diagnostic emitter in the compiler and other lint tools such as Clippy will need
227+
to be made aware of this element so that they can emit it in diagnostic text.
228+
229+
This RFC also adds the `expect(lint_name, reason = STRING)` lint attribute. The
230+
`expect` attribute uses the same lint-suppression mechanism that `allow` does,
231+
but will raise a new lint, `expectation_missing` (name open to change), when the
232+
lint it expects does not arrive.
233+
234+
The `expectation_missing` lint is itself subject to
235+
`allow`/`expect`/`warn`/`deny` attributes in a higher scope, so it is possible
236+
to suppress expectation failures, lint when no expectation failures occur, or
237+
fail the build when one occurs. The default setting is
238+
`#![warn(expectation_missing)]`.
239+
240+
That’s pretty much it, for technical details.
241+
242+
## OPTIONAL — Yet Another Comment Syntax
243+
244+
A sugar for lint text MAY be the line comment `//#` or the block comment
245+
`/*# #*/` with `U+0023 # NUMBER SIGN` as the signifying character. These
246+
comments MUST be placed immediately above a lint attribute. They are collected
247+
into a single string and collapsed as the text content of the attribute they
248+
decorate using the same processing logic that documentation comments (`///` and
249+
`//!` and their block variants) currently use. Example:
250+
251+
```rust
252+
//# Floating Point Arithmetic Unsupported
253+
//#
254+
//# This crate is written to be run on an AVR processor which does not have
255+
//# floating-point capability in hardware. As such, all floating-point work is
256+
//# done in software routines that can take a significant amount of time and
257+
//# space to perform. Rather than pay this cost, floating-point work is
258+
//# statically disabled. All arithmetic is in fixed-point integer math, using
259+
//# the `FixedPoint` wrapper over integer primitives.
260+
#![deny(float_arithmetic)]
261+
```
262+
263+
The `#` character is chosen as the signifying character to provide room for
264+
possible future expansion – these comments MAY in the future be repurposed as
265+
sugar for writing the text of an attribute that declares a string parameter that
266+
can accept such comments.
267+
268+
This comment syntax already pushes the edge of the scope of this RFC, and
269+
extension of all attributes is certainly beyond it.
270+
271+
Implementing this comment syntax would require extending the existing transform
272+
pass that replaces documentation comments with documentation attributes.
273+
Specifically, the transform pass would ensure that all lint comments are
274+
directly attached to a lint attribute, and then use the strip-and-trim method
275+
that the documentation comments experience to remove the comment markers and
276+
collapse the comment text, across multiple consecutive comment spans, into a
277+
single string that is then inserted as `reason = STRING` into the attribute.
278+
279+
Given that this is a lot of work and a huge addition to the comment grammar, the
280+
author does not expect it to be included in the main RFC at all, and is writing
281+
it solely to be a published prior art in case of future desire for such a
282+
feature.
283+
284+
# Drawbacks
285+
[drawbacks]: #drawbacks
286+
287+
Why should we *not* do this?
288+
289+
Possibly low value add for the effort.
290+
291+
# Rationale and alternatives
292+
[alternatives]: #alternatives
293+
294+
- Why is this design the best in the space of possible designs?
295+
296+
Attributes taking descriptive text is a common pattern in Rust.
297+
298+
- What other designs have been considered and what is the rationale for not
299+
choosing them?
300+
301+
None.
302+
303+
- What is the impact of not doing this?
304+
305+
None.
306+
307+
# Prior art
308+
[prior-art]: #prior-art
309+
310+
The `stable` and `unstable` attributes both take descriptive text parameters
311+
that appear in diagnostic and documentation output.
312+
313+
# Unresolved questions
314+
[unresolved]: #unresolved-questions
315+
316+
- What parts of the design do you expect to resolve through the RFC process
317+
before this gets merged?
318+
319+
The name of the `reason` parameter.
320+
321+
- What parts of the design do you expect to resolve through the implementation
322+
of this feature before stabilization?
323+
324+
The use sites of the `reason` parameter.
325+
326+
- What related issues do you consider out of scope for this RFC that could be
327+
addressed in the future independently of the solution that comes out of this
328+
RFC?
329+
330+
The means of filling the `reason` parameter, or what tools like `rustdoc`
331+
would do with them.

0 commit comments

Comments
 (0)