-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a count parameter to the expect attribute #3400
Open
JarredAllen
wants to merge
2
commits into
rust-lang:master
Choose a base branch
from
JarredAllen:expect-attribute-count-parameter
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
- Feature Name: Add a count parameter to the expect attribute | ||
- Start Date: 2023-03-13 | ||
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add a new paramter to [the `#[expect]` | ||
attribute](https://github.com/rust-lang/rust/issues/54503) named `count` that asserts a | ||
precise count for the number of lints suppressed by the attribute. If the number | ||
of lints to suppress is given, then it will raise a warning unless that number | ||
is exactly matched. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
A major use-case of the `#[expect]` attribute is temporarily adding an exception | ||
to a lint, to be resolved later. By using `#[expect]` instead of `#[allow]`, you | ||
will immediately know when it is safe to remove the exception to the lint. | ||
|
||
However, adding a lint exception to any level of scope can often, in my | ||
experience, lead to additional exceptions that weren't originally intended | ||
accidentally slipping through, resulting in a process that, instead of gradually | ||
phasing out lint exceptions, causes more and more offending code to be added | ||
without anyone realizing. Especially with an exception on a wide scope, the | ||
author of the code may not even know the exception exists. | ||
|
||
By setting an explicit number of allowances, anything which produces another | ||
instance of the lint will cause the lint to still trigger, making it clear to | ||
the programmer that they introduced another instance of the lint and allowing | ||
them to decide whether the original exception should extend to it as well, or | ||
whether they should fix the instance immediately. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
(to be appended to the guide-level explanation on the [`#[expect]` lint | ||
attribute](https://github.com/rust-lang/rfcs/blob/master/text/2383-lint-reasons.md#expect-lint-attribute) | ||
|
||
Additionally, you may specify an expected amount of lint instances to suppress, | ||
in which case, it will suppress the lints only if there is exactly the expected | ||
number, and in all other cases it will log an additional warning (in addition to | ||
any lints inside the scope). | ||
|
||
This is useful when gradually applying a new lint to a codebase, to allow an | ||
exception for existing instances of the lint, without allowing any new | ||
exceptions to accidentally slip through. | ||
```rust | ||
#[expect(unused_mut, count = 1)] | ||
fn foo() -> usize { | ||
let mut a = Vec::new(); | ||
a.len() | ||
} | ||
``` | ||
will remain quiet and not produce any lints. However, if you add another | ||
instance of `unused_mut`, then it will fire and warn you. | ||
```rust | ||
#[expect(unused_mut, count = 1)] | ||
fn foo() -> usize { | ||
let mut a = Vec::new(); | ||
let mut b = Vec::new(); | ||
a.len() + b.len() | ||
} | ||
``` | ||
will emit the `unused_mut` lints for both `a` and `b` at a `warn` level, and | ||
will also emit: | ||
``` | ||
warning: expected lint `unused_mut` did not appear the expected number of times | ||
--> src/lib.rs:1:1 | ||
| | ||
1 | #[expect(unused_mut, count = 1)] | ||
| ---------------------------^- | ||
| | | ||
| help: replace `count` from `1` to `2` | ||
| | ||
= note: #[warn(expectation_missing)] on by default | ||
``` | ||
|
||
As previously, removing all instances of the lint will produce the same error | ||
message, so: | ||
```rust | ||
#[expect(unused_mut, count = 1)] | ||
fn foo() -> usize { | ||
let a = Vec::new(); | ||
a.len() | ||
} | ||
``` | ||
will emit: | ||
``` | ||
warning: expected lint `unused_mut` did not appear | ||
--> src/lib.rs:1:1 | ||
| | ||
1 | #[expect(unused_mut, count = 1)] | ||
| ---------------------------^- | ||
| | | ||
| help: remove this `#[expect(...)]` | ||
| | ||
= note: #[warn(expectation_missing)] on by default | ||
``` | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
This would reuse the existing `#[expect]` lint level, but, when a `count` is | ||
provided, it will act like a `#[warn]` instead of an `#[allow]` when the number | ||
of instances of the lint covered don't match. Additionally, when the count | ||
mismatches, it will emit the same lint as if a normal `#[expect]` attribute had | ||
not matched any lints (open to change). | ||
|
||
The `count` parameter is expected to be strictly positive, and it will fail to | ||
compile if a `count` which is either zero or negative is given. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
The only drawback to adding this feature is that doing so would increase the | ||
scope of the rust compiler to support it, and potentially extra work of figuring | ||
out how future features interact with this one. | ||
|
||
Nothing would be forced upon codebases, so there is no threat of increasing the | ||
complexity of learning the language to newcomers. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
## Add to `#[allow]` instead of `#[expect]` | ||
|
||
This could be made an option on the `#[allow]` attribute and entirely supplant the | ||
need for `#[expect]`. However, I think it makes more sense to include this on | ||
the `#[expect]` if such an attribute exists, and removing the `#[expect]` | ||
attribtue for just allowing a specific count would remove the case where you | ||
don't care how many violations exist. | ||
|
||
## Other ways of handling the count | ||
|
||
The `count` as proposed must match exactly the number of instances of the lint | ||
triggered inside. We could allow constructs like `count <= #`, either in | ||
addition to or instead of the currently-proposed exact match. | ||
|
||
In my view, this is worse for the case of gradually removing lints from an | ||
existing project. If an `expect` has 10 instances of the lint, I might slap an | ||
`#[expect(..., count <= 10)]` on it. Then, say I do some cleanup and reduce it | ||
down to 5 instances. Nothing prompts me to change the count, so I don't. Later, | ||
having forgotten about this, I do something else that adds back in up to 5 | ||
instances of the lint, and the "slack" from the previous 5 being removed cause | ||
me to not get any warnings about the new instances. | ||
|
||
## Other ways of handling the inner lint instances | ||
|
||
If the `count` doesn't match, then we could continue to suppress the lints | ||
inside, but output one mega-lint which tells the user about all of the instances | ||
inside. However, I think that would be worse from a UX perspective. | ||
|
||
We could also track the level that was specified for this lint outside of the | ||
`#[expect]` annotation and reapply that level. However, this seems like extra | ||
work for marginal benefit, and it would lead to imo worse UX if the outside | ||
scope was an `#[allow]`, as it would render the lint locations effectively | ||
invisible. | ||
|
||
We could also require the `count` attribute be paired with a `level` attribute | ||
to indicate the level to pass through lints on a mismatched count, but in my | ||
opinion that adds too much complexity and makes it needlessly difficult to use. | ||
|
||
## Do Nothing | ||
|
||
We could do nothing, which would miss out on the benefits I outlined in the | ||
motivation section. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
## Syntax in Rust | ||
|
||
Other attributes take such named parameters, such as the `stable` and `unstable` | ||
attributes, and the `reason` parameter proposed for all lint levels in the same | ||
RFC as the `#[expect]` lint level. | ||
|
||
## Behavior | ||
|
||
I am unaware of any languages which have a similar functionality to what I am | ||
describing here. | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
- Can we allow `#[expect(..., count = 0)]` with some useful behavior? What about | ||
negative `count`s? |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(lint, count = 0)
seems like it should be the same asdeny(lint)
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I could see
count = 0
being convenient for a tool that decreases the expected count when it is too high. Such a tool would then not have to deal with this edge case.In my mind
expect(lint, count = 0)
should compile with a warning (or clippy lint) suggesting usingdeny
instead.