|
| 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