Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2022-12-01 12:39:42 +01:00
commit 11434f270f
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
249 changed files with 4286 additions and 1957 deletions

View File

@ -4188,6 +4188,7 @@ Released 2018-09-13
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
[`mismatching_type_param_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatching_type_param_order
[`misnamed_getters`]: https://rust-lang.github.io/rust-clippy/master/index.html#misnamed_getters
[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
@ -4450,6 +4451,7 @@ Released 2018-09-13
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by

View File

@ -42,6 +42,7 @@ filetime = "0.2"
rustc-workspace-hack = "1.0"
# UI test dependencies
clap = { version = "3.1", features = ["derive"] }
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"

View File

@ -197,8 +197,8 @@ disallowed-names = ["toto", "tata", "titi"]
cognitive-complexity-threshold = 30
```
See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which
lints can be configured and the meaning of the variables.
See the [list of configurable lints](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration),
the lint descriptions contain the names and meanings of these configuration variables.
> **Note**
>
@ -224,7 +224,7 @@ in the `Cargo.toml` can be used.
rust-version = "1.30"
```
The MSRV can also be specified as an inner attribute, like below.
The MSRV can also be specified as an attribute, like below.
```rust
#![feature(custom_inner_attributes)]

View File

@ -21,3 +21,4 @@
- [The Clippy Book](development/infrastructure/book.md)
- [Proposals](development/proposals/README.md)
- [Roadmap 2021](development/proposals/roadmap-2021.md)
- [Syntax Tree Patterns](development/proposals/syntax-tree-patterns.md)

View File

@ -11,8 +11,8 @@ disallowed-names = ["toto", "tata", "titi"]
cognitive-complexity-threshold = 30
```
See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which
lints can be configured and the meaning of the variables.
See the [list of configurable lints](https://rust-lang.github.io/rust-clippy/master/index.html#Configuration),
the lint descriptions contain the names and meanings of these configuration variables.
To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS`
environment variable.
@ -72,7 +72,7 @@ minimum supported Rust version (MSRV) in the clippy configuration file.
msrv = "1.30.0"
```
The MSRV can also be specified as an inner attribute, like below.
The MSRV can also be specified as an attribute, like below.
```rust
#![feature(custom_inner_attributes)]

View File

@ -443,27 +443,27 @@ value is passed to the constructor in `clippy_lints/lib.rs`.
```rust
pub struct ManualStrip {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualStrip {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
```
The project's MSRV can then be matched against the feature MSRV in the LintPass
using the `meets_msrv` utility function.
using the `Msrv::meets` method.
``` rust
if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) {
if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
return;
}
```
The project's MSRV can also be specified as an inner attribute, which overrides
The project's MSRV can also be specified as an attribute, which overrides
the value from `clippy.toml`. This can be accounted for using the
`extract_msrv_attr!(LintContext)` macro and passing
`LateContext`/`EarlyContext`.
@ -483,19 +483,15 @@ have a case for the version below the MSRV and one with the same contents but
for the MSRV version itself.
```rust
#![feature(custom_inner_attributes)]
...
#[clippy::msrv = "1.44"]
fn msrv_1_44() {
#![clippy::msrv = "1.44"]
/* something that would trigger the lint */
}
#[clippy::msrv = "1.45"]
fn msrv_1_45() {
#![clippy::msrv = "1.45"]
/* something that would trigger the lint */
}
```

View File

@ -0,0 +1,986 @@
- Feature Name: syntax-tree-patterns
- Start Date: 2019-03-12
- RFC PR: [#3875](https://github.com/rust-lang/rust-clippy/pull/3875)
# Summary
Introduce a domain-specific language (similar to regular expressions) that
allows to describe lints using *syntax tree patterns*.
# Motivation
Finding parts of a syntax tree (AST, HIR, ...) that have certain properties
(e.g. "*an if that has a block as its condition*") is a major task when writing
lints. For non-trivial lints, it often requires nested pattern matching of AST /
HIR nodes. For example, testing that an expression is a boolean literal requires
the following checks:
```rust
if let ast::ExprKind::Lit(lit) = &expr.node {
if let ast::LitKind::Bool(_) = &lit.node {
...
}
}
```
Writing this kind of matching code quickly becomes a complex task and the
resulting code is often hard to comprehend. The code below shows a simplified
version of the pattern matching required by the `collapsible_if` lint:
```rust
// simplified version of the collapsible_if lint
if let ast::ExprKind::If(check, then, None) = &expr.node {
if then.stmts.len() == 1 {
if let ast::StmtKind::Expr(inner) | ast::StmtKind::Semi(inner) = &then.stmts[0].node {
if let ast::ExprKind::If(check_inner, content, None) = &inner.node {
...
}
}
}
}
```
The `if_chain` macro can improve readability by flattening the nested if
statements, but the resulting code is still quite hard to read:
```rust
// simplified version of the collapsible_if lint
if_chain! {
if let ast::ExprKind::If(check, then, None) = &expr.node;
if then.stmts.len() == 1;
if let ast::StmtKind::Expr(inner) | ast::StmtKind::Semi(inner) = &then.stmts[0].node;
if let ast::ExprKind::If(check_inner, content, None) = &inner.node;
then {
...
}
}
```
The code above matches if expressions that contain only another if expression
(where both ifs don't have an else branch). While it's easy to explain what the
lint does, it's hard to see that from looking at the code samples above.
Following the motivation above, the first goal this RFC is to **simplify writing
and reading lints**.
The second part of the motivation is clippy's dependence on unstable
compiler-internal data structures. Clippy lints are currently written against
the compiler's AST / HIR which means that even small changes in these data
structures might break a lot of lints. The second goal of this RFC is to **make
lints independant of the compiler's AST / HIR data structures**.
# Approach
A lot of complexity in writing lints currently seems to come from having to
manually implement the matching logic (see code samples above). It's an
imparative style that describes *how* to match a syntax tree node instead of
specifying *what* should be matched against declaratively. In other areas, it's
common to use declarative patterns to describe desired information and let the
implementation do the actual matching. A well-known example of this approach are
[regular expressions](https://en.wikipedia.org/wiki/Regular_expression). Instead
of writing code that detects certain character sequences, one can describe a
search pattern using a domain-specific language and search for matches using
that pattern. The advantage of using a declarative domain-specific language is
that its limited domain (e.g. matching character sequences in the case of
regular expressions) allows to express entities in that domain in a very natural
and expressive way.
While regular expressions are very useful when searching for patterns in flat
character sequences, they cannot easily be applied to hierarchical data
structures like syntax trees. This RFC therefore proposes a pattern matching
system that is inspired by regular expressions and designed for hierarchical
syntax trees.
# Guide-level explanation
This proposal adds a `pattern!` macro that can be used to specify a syntax tree
pattern to search for. A simple pattern is shown below:
```rust
pattern!{
my_pattern: Expr =
Lit(Bool(false))
}
```
This macro call defines a pattern named `my_pattern` that can be matched against
an `Expr` syntax tree node. The actual pattern (`Lit(Bool(false))` in this case)
defines which syntax trees should match the pattern. This pattern matches
expressions that are boolean literals with value `false`.
The pattern can then be used to implement lints in the following way:
```rust
...
impl EarlyLintPass for MyAwesomeLint {
fn check_expr(&mut self, cx: &EarlyContext, expr: &syntax::ast::Expr) {
if my_pattern(expr).is_some() {
cx.span_lint(
MY_AWESOME_LINT,
expr.span,
"This is a match for a simple pattern. Well done!",
);
}
}
}
```
The `pattern!` macro call expands to a function `my_pattern` that expects a
syntax tree expression as its argument and returns an `Option` that indicates
whether the pattern matched.
> Note: The result type is explained in more detail in [a later
> section](#the-result-type). For now, it's enough to know that the result is
> `Some` if the pattern matched and `None` otherwise.
## Pattern syntax
The following examples demonstate the pattern syntax:
#### Any (`_`)
The simplest pattern is the any pattern. It matches anything and is therefore
similar to regex's `*`.
```rust
pattern!{
// matches any expression
my_pattern: Expr =
_
}
```
#### Node (`<node-name>(<args>)`)
Nodes are used to match a specific variant of an AST node. A node has a name and
a number of arguments that depends on the node type. For example, the `Lit` node
has a single argument that describes the type of the literal. As another
example, the `If` node has three arguments describing the if's condition, then
block and else block.
```rust
pattern!{
// matches any expression that is a literal
my_pattern: Expr =
Lit(_)
}
pattern!{
// matches any expression that is a boolean literal
my_pattern: Expr =
Lit(Bool(_))
}
pattern!{
// matches if expressions that have a boolean literal in their condition
// Note: The `_?` syntax here means that the else branch is optional and can be anything.
// This is discussed in more detail in the section `Repetition`.
my_pattern: Expr =
If( Lit(Bool(_)) , _, _?)
}
```
#### Literal (`<lit>`)
A pattern can also contain Rust literals. These literals match themselves.
```rust
pattern!{
// matches the boolean literal false
my_pattern: Expr =
Lit(Bool(false))
}
pattern!{
// matches the character literal 'x'
my_pattern: Expr =
Lit(Char('x'))
}
```
#### Alternations (`a | b`)
```rust
pattern!{
// matches if the literal is a boolean or integer literal
my_pattern: Lit =
Bool(_) | Int(_)
}
pattern!{
// matches if the expression is a char literal with value 'x' or 'y'
my_pattern: Expr =
Lit( Char('x' | 'y') )
}
```
#### Empty (`()`)
The empty pattern represents an empty sequence or the `None` variant of an
optional.
```rust
pattern!{
// matches if the expression is an empty array
my_pattern: Expr =
Array( () )
}
pattern!{
// matches if expressions that don't have an else clause
my_pattern: Expr =
If(_, _, ())
}
```
#### Sequence (`<a> <b>`)
```rust
pattern!{
// matches the array [true, false]
my_pattern: Expr =
Array( Lit(Bool(true)) Lit(Bool(false)) )
}
```
#### Repetition (`<a>*`, `<a>+`, `<a>?`, `<a>{n}`, `<a>{n,m}`, `<a>{n,}`)
Elements may be repeated. The syntax for specifying repetitions is identical to
[regex's syntax](https://docs.rs/regex/1.1.2/regex/#repetitions).
```rust
pattern!{
// matches arrays that contain 2 'x's as their last or second-last elements
// Examples:
// ['x', 'x'] match
// ['x', 'x', 'y'] match
// ['a', 'b', 'c', 'x', 'x', 'y'] match
// ['x', 'x', 'y', 'z'] no match
my_pattern: Expr =
Array( _* Lit(Char('x')){2} _? )
}
pattern!{
// matches if expressions that **may or may not** have an else block
// Attn: `If(_, _, _)` matches only ifs that **have** an else block
//
// | if with else block | if witout else block
// If(_, _, _) | match | no match
// If(_, _, _?) | match | match
// If(_, _, ()) | no match | match
my_pattern: Expr =
If(_, _, _?)
}
```
#### Named submatch (`<a>#<name>`)
```rust
pattern!{
// matches character literals and gives the literal the name foo
my_pattern: Expr =
Lit(Char(_)#foo)
}
pattern!{
// matches character literals and gives the char the name bar
my_pattern: Expr =
Lit(Char(_#bar))
}
pattern!{
// matches character literals and gives the expression the name baz
my_pattern: Expr =
Lit(Char(_))#baz
}
```
The reason for using named submatches is described in the section [The result
type](#the-result-type).
### Summary
The following table gives an summary of the pattern syntax:
| Syntax | Concept | Examples |
|-------------------------|------------------|--------------------------------------------|
|`_` | Any | `_` |
|`<node-name>(<args>)` | Node | `Lit(Bool(true))`, `If(_, _, _)` |
|`<lit>` | Literal | `'x'`, `false`, `101` |
|`<a> \| <b>` | Alternation | `Char(_) \| Bool(_)` |
|`()` | Empty | `Array( () )` |
|`<a> <b>` | Sequence | `Tuple( Lit(Bool(_)) Lit(Int(_)) Lit(_) )` |
|`<a>*` <br> `<a>+` <br> `<a>?` <br> `<a>{n}` <br> `<a>{n,m}` <br> `<a>{n,}` | Repetition <br> <br> <br> <br> <br><br> | `Array( _* )`, <br> `Block( Semi(_)+ )`, <br> `If(_, _, Block(_)?)`, <br> `Array( Lit(_){10} )`, <br> `Lit(_){5,10}`, <br> `Lit(Bool(_)){10,}` |
|`<a>#<name>` | Named submatch | `Lit(Int(_))#foo` `Lit(Int(_#bar))` |
## The result type
A lot of lints require checks that go beyond what the pattern syntax described
above can express. For example, a lint might want to check whether a node was
created as part of a macro expansion or whether there's no comment above a node.
Another example would be a lint that wants to match two nodes that have the same
value (as needed by lints like `almost_swapped`). Instead of allowing users to
write these checks into the pattern directly (which might make patterns hard to
read), the proposed solution allows users to assign names to parts of a pattern
expression. When matching a pattern against a syntax tree node, the return value
will contain references to all nodes that were matched by these named
subpatterns. This is similar to capture groups in regular expressions.
For example, given the following pattern
```rust
pattern!{
// matches character literals
my_pattern: Expr =
Lit(Char(_#val_inner)#val)#val_outer
}
```
one could get references to the nodes that matched the subpatterns in the
following way:
```rust
...
fn check_expr(expr: &syntax::ast::Expr) {
if let Some(result) = my_pattern(expr) {
result.val_inner // type: &char
result.val // type: &syntax::ast::Lit
result.val_outer // type: &syntax::ast::Expr
}
}
```
The types in the `result` struct depend on the pattern. For example, the
following pattern
```rust
pattern!{
// matches arrays of character literals
my_pattern_seq: Expr =
Array( Lit(_)*#foo )
}
```
matches arrays that consist of any number of literal expressions. Because those
expressions are named `foo`, the result struct contains a `foo` attribute which
is a vector of expressions:
```rust
...
if let Some(result) = my_pattern_seq(expr) {
result.foo // type: Vec<&syntax::ast::Expr>
}
```
Another result type occurs when a name is only defined in one branch of an
alternation:
```rust
pattern!{
// matches if expression is a boolean or integer literal
my_pattern_alt: Expr =
Lit( Bool(_#bar) | Int(_) )
}
```
In the pattern above, the `bar` name is only defined if the pattern matches a
boolean literal. If it matches an integer literal, the name isn't set. To
account for this, the result struct's `bar` attribute is an option type:
```rust
...
if let Some(result) = my_pattern_alt(expr) {
result.bar // type: Option<&bool>
}
```
It's also possible to use a name in multiple alternation branches if they have
compatible types:
```rust
pattern!{
// matches if expression is a boolean or integer literal
my_pattern_mult: Expr =
Lit(_#baz) | Array( Lit(_#baz) )
}
...
if let Some(result) = my_pattern_mult(expr) {
result.baz // type: &syntax::ast::Lit
}
```
Named submatches are a **flat** namespace and this is intended. In the example
above, two different sub-structures are assigned to a flat name. I expect that
for most lints, a flat namespace is sufficient and easier to work with than a
hierarchical one.
#### Two stages
Using named subpatterns, users can write lints in two stages. First, a coarse
selection of possible matches is produced by the pattern syntax. In the second
stage, the named subpattern references can be used to do additional tests like
asserting that a node hasn't been created as part of a macro expansion.
## Implementing clippy lints using patterns
As a "real-world" example, I re-implemented the `collapsible_if` lint using
patterns. The code can be found
[here](https://github.com/fkohlgrueber/rust-clippy-pattern/blob/039b07ecccaf96d6aa7504f5126720d2c9cceddd/clippy_lints/src/collapsible_if.rs#L88-L163).
The pattern-based version passes all test cases that were written for
`collapsible_if`.
# Reference-level explanation
## Overview
The following diagram shows the dependencies between the main parts of the
proposed solution:
```
Pattern syntax
|
| parsing / lowering
v
PatternTree
^
|
|
IsMatch trait
|
|
+---------------+-----------+---------+
| | | |
v v v v
syntax::ast rustc::hir syn ...
```
The pattern syntax described in the previous section is parsed / lowered into
the so-called *PatternTree* data structure that represents a valid syntax tree
pattern. Matching a *PatternTree* against an actual syntax tree (e.g. rust ast /
hir or the syn ast, ...) is done using the *IsMatch* trait.
The *PatternTree* and the *IsMatch* trait are introduced in more detail in the
following sections.
## PatternTree
The core data structure of this RFC is the **PatternTree**.
It's a data structure similar to rust's AST / HIR, but with the following
differences:
- The PatternTree doesn't contain parsing information like `Span`s
- The PatternTree can represent alternatives, sequences and optionals
The code below shows a simplified version of the current PatternTree:
> Note: The current implementation can be found
> [here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/pattern_tree.rs#L50-L96).
```rust
pub enum Expr {
Lit(Alt<Lit>),
Array(Seq<Expr>),
Block_(Alt<BlockType>),
If(Alt<Expr>, Alt<BlockType>, Opt<Expr>),
IfLet(
Alt<BlockType>,
Opt<Expr>,
),
}
pub enum Lit {
Char(Alt<char>),
Bool(Alt<bool>),
Int(Alt<u128>),
}
pub enum Stmt {
Expr(Alt<Expr>),
Semi(Alt<Expr>),
}
pub enum BlockType {
Block(Seq<Stmt>),
}
```
The `Alt`, `Seq` and `Opt` structs look like these:
> Note: The current implementation can be found
> [here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/matchers.rs#L35-L60).
```rust
pub enum Alt<T> {
Any,
Elmt(Box<T>),
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}
pub enum Opt<T> {
Any, // anything, but not None
Elmt(Box<T>),
None,
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}
pub enum Seq<T> {
Any,
Empty,
Elmt(Box<T>),
Repeat(Box<Self>, RepeatRange),
Seq(Box<Self>, Box<Self>),
Alt(Box<Self>, Box<Self>),
Named(Box<Self>, ...)
}
pub struct RepeatRange {
pub start: usize,
pub end: Option<usize> // exclusive
}
```
## Parsing / Lowering
The input of a `pattern!` macro call is parsed into a `ParseTree` first and then
lowered to a `PatternTree`.
Valid patterns depend on the *PatternTree* definitions. For example, the pattern
`Lit(Bool(_)*)` isn't valid because the parameter type of the `Lit` variant of
the `Expr` enum is `Any<Lit>` and therefore doesn't support repetition (`*`). As
another example, `Array( Lit(_)* )` is a valid pattern because the parameter of
`Array` is of type `Seq<Expr>` which allows sequences and repetitions.
> Note: names in the pattern syntax correspond to *PatternTree* enum
> **variants**. For example, the `Lit` in the pattern above refers to the `Lit`
> variant of the `Expr` enum (`Expr::Lit`), not the `Lit` enum.
## The IsMatch Trait
The pattern syntax and the *PatternTree* are independant of specific syntax tree
implementations (rust ast / hir, syn, ...). When looking at the different
pattern examples in the previous sections, it can be seen that the patterns
don't contain any information specific to a certain syntax tree implementation.
In contrast, clippy lints currently match against ast / hir syntax tree nodes
and therefore directly depend on their implementation.
The connection between the *PatternTree* and specific syntax tree
implementations is the `IsMatch` trait. It defines how to match *PatternTree*
nodes against specific syntax tree nodes. A simplified implementation of the
`IsMatch` trait is shown below:
```rust
pub trait IsMatch<O> {
fn is_match(&self, other: &'o O) -> bool;
}
```
This trait needs to be implemented on each enum of the *PatternTree* (for the
corresponding syntax tree types). For example, the `IsMatch` implementation for
matching `ast::LitKind` against the *PatternTree's* `Lit` enum might look like
this:
```rust
impl IsMatch<ast::LitKind> for Lit {
fn is_match(&self, other: &ast::LitKind) -> bool {
match (self, other) {
(Lit::Char(i), ast::LitKind::Char(j)) => i.is_match(j),
(Lit::Bool(i), ast::LitKind::Bool(j)) => i.is_match(j),
(Lit::Int(i), ast::LitKind::Int(j, _)) => i.is_match(j),
_ => false,
}
}
}
```
All `IsMatch` implementations for matching the current *PatternTree* against
`syntax::ast` can be found
[here](https://github.com/fkohlgrueber/pattern-matching/blob/dfb3bc9fbab69cec7c91e72564a63ebaa2ede638/pattern-match/src/ast_match.rs).
# Drawbacks
#### Performance
The pattern matching code is currently not optimized for performance, so it
might be slower than hand-written matching code. Additionally, the two-stage
approach (matching against the coarse pattern first and checking for additional
properties later) might be slower than the current practice of checking for
structure and additional properties in one pass. For example, the following lint
```rust
pattern!{
pat_if_without_else: Expr =
If(
_,
Block(
Expr( If(_, _, ())#inner )
| Semi( If(_, _, ())#inner )
)#then,
()
)
}
...
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if let Some(result) = pat_if_without_else(expr) {
if !block_starts_with_comment(cx, result.then) {
...
}
}
```
first matches against the pattern and then checks that the `then` block doesn't
start with a comment. Using clippy's current approach, it's possible to check
for these conditions earlier:
```rust
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) {
if_chain! {
if let ast::ExprKind::If(ref check, ref then, None) = expr.node;
if !block_starts_with_comment(cx, then);
if let Some(inner) = expr_block(then);
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.node;
then {
...
}
}
}
```
Whether or not this causes performance regressions depends on actual patterns.
If it turns out to be a problem, the pattern matching algorithms could be
extended to allow "early filtering" (see the [Early Filtering](#early-filtering)
section in Future Possibilities).
That being said, I don't see any conceptual limitations regarding pattern
matching performance.
#### Applicability
Even though I'd expect that a lot of lints can be written using the proposed
pattern syntax, it's unlikely that all lints can be expressed using patterns. I
suspect that there will still be lints that need to be implemented by writing
custom pattern matching code. This would lead to mix within clippy's codebase
where some lints are implemented using patterns and others aren't. This
inconsistency might be considered a drawback.
# Rationale and alternatives
Specifying lints using syntax tree patterns has a couple of advantages compared
to the current approach of manually writing matching code. First, syntax tree
patterns allow users to describe patterns in a simple and expressive way. This
makes it easier to write new lints for both novices and experts and also makes
reading / modifying existing lints simpler.
Another advantage is that lints are independent of specific syntax tree
implementations (e.g. AST / HIR, ...). When these syntax tree implementations
change, only the `IsMatch` trait implementations need to be adapted and existing
lints can remain unchanged. This also means that if the `IsMatch` trait
implementations were integrated into the compiler, updating the `IsMatch`
implementations would be required for the compiler to compile successfully. This
could reduce the number of times clippy breaks because of changes in the
compiler. Another advantage of the pattern's independence is that converting an
`EarlyLintPass` lint into a `LatePassLint` wouldn't require rewriting the whole
pattern matching code. In fact, the pattern might work just fine without any
adaptions.
## Alternatives
### Rust-like pattern syntax
The proposed pattern syntax requires users to know the structure of the
`PatternTree` (which is very similar to the AST's / HIR's structure) and also
the pattern syntax. An alternative would be to introduce a pattern syntax that
is similar to actual Rust syntax (probably like the `quote!` macro). For
example, a pattern that matches `if` expressions that have `false` in their
condition could look like this:
```rust
if false {
#[*]
}
```
#### Problems
Extending Rust syntax (which is quite complex by itself) with additional syntax
needed for specifying patterns (alternations, sequences, repetisions, named
submatches, ...) might become difficult to read and really hard to parse
properly.
For example, a pattern that matches a binary operation that has `0` on both
sides might look like this:
```
0 #[*:BinOpKind] 0
```
Now consider this slightly more complex example:
```
1 + 0 #[*:BinOpKind] 0
```
The parser would need to know the precedence of `#[*:BinOpKind]` because it
affects the structure of the resulting AST. `1 + 0 + 0` is parsed as `(1 + 0) +
0` while `1 + 0 * 0` is parsed as `1 + (0 * 0)`. Since the pattern could be any
`BinOpKind`, the precedence cannot be known in advance.
Another example of a problem would be named submatches. Take a look at this
pattern:
```rust
fn test() {
1 #foo
}
```
Which node is `#foo` referring to? `int`, `ast::Lit`, `ast::Expr`, `ast::Stmt`?
Naming subpatterns in a rust-like syntax is difficult because a lot of AST nodes
don't have a syntactic element that can be used to put the name tag on. In these
situations, the only sensible option would be to assign the name tag to the
outermost node (`ast::Stmt` in the example above), because the information of
all child nodes can be retrieved through the outermost node. The problem with
this then would be that accessing inner nodes (like `ast::Lit`) would again
require manual pattern matching.
In general, Rust syntax contains a lot of code structure implicitly. This
structure is reconstructed during parsing (e.g. binary operations are
reconstructed using operator precedence and left-to-right) and is one of the
reasons why parsing is a complex task. The advantage of this approach is that
writing code is simpler for users.
When writing *syntax tree patterns*, each element of the hierarchy might have
alternatives, repetitions, etc.. Respecting that while still allowing
human-friendly syntax that contains structure implicitly seems to be really
complex, if not impossible.
Developing such a syntax would also require to maintain a custom parser that is
at least as complex as the Rust parser itself. Additionally, future changes in
the Rust syntax might be incompatible with such a syntax.
In summary, I think that developing such a syntax would introduce a lot of
complexity to solve a relatively minor problem.
The issue of users not knowing about the *PatternTree* structure could be solved
by a tool that, given a rust program, generates a pattern that matches only this
program (similar to the clippy author lint).
For some simple cases (like the first example above), it might be possible to
successfully mix Rust and pattern syntax. This space could be further explored
in a future extension.
# Prior art
The pattern syntax is heavily inspired by regular expressions (repetitions,
alternatives, sequences, ...).
From what I've seen until now, other linters also implement lints that directly
work on syntax tree data structures, just like clippy does currently. I would
therefore consider the pattern syntax to be *new*, but please correct me if I'm
wrong.
# Unresolved questions
#### How to handle multiple matches?
When matching a syntax tree node against a pattern, there are possibly multiple
ways in which the pattern can be matched. A simple example of this would be the
following pattern:
```rust
pattern!{
my_pattern: Expr =
Array( _* Lit(_)+#literals)
}
```
This pattern matches arrays that end with at least one literal. Now given the
array `[x, 1, 2]`, should `1` be matched as part of the `_*` or the `Lit(_)+`
part of the pattern? The difference is important because the named submatch
`#literals` would contain 1 or 2 elements depending how the pattern is matched.
In regular expressions, this problem is solved by matching "greedy" by default
and "non-greedy" optionally.
I haven't looked much into this yet because I don't know how relevant it is for
most lints. The current implementation simply returns the first match it finds.
# Future possibilities
#### Implement rest of Rust Syntax
The current project only implements a small part of the Rust syntax. In the
future, this should incrementally be extended to more syntax to allow
implementing more lints. Implementing more of the Rust syntax requires extending
the `PatternTree` and `IsMatch` implementations, but should be relatively
straight-forward.
#### Early filtering
As described in the *Drawbacks/Performance* section, allowing additional checks
during the pattern matching might be beneficial.
The pattern below shows how this could look like:
```rust
pattern!{
pat_if_without_else: Expr =
If(
_,
Block(
Expr( If(_, _, ())#inner )
| Semi( If(_, _, ())#inner )
)#then,
()
)
where
!in_macro(#then.span);
}
```
The difference compared to the currently proposed two-stage filtering is that
using early filtering, the condition (`!in_macro(#then.span)` in this case)
would be evaluated as soon as the `Block(_)#then` was matched.
Another idea in this area would be to introduce a syntax for backreferences.
They could be used to require that multiple parts of a pattern should match the
same value. For example, the `assign_op_pattern` lint that searches for `a = a
op b` and recommends changing it to `a op= b` requires that both occurrances of
`a` are the same. Using `=#...` as syntax for backreferences, the lint could be
implemented like this:
```rust
pattern!{
assign_op_pattern: Expr =
Assign(_#target, Binary(_, =#target, _)
}
```
#### Match descendant
A lot of lints currently implement custom visitors that check whether any
subtree (which might not be a direct descendant) of the current node matches
some properties. This cannot be expressed with the proposed pattern syntax.
Extending the pattern syntax to allow patterns like "a function that contains at
least two return statements" could be a practical addition.
#### Negation operator for alternatives
For patterns like "a literal that is not a boolean literal" one currently needs
to list all alternatives except the boolean case. Introducing a negation
operator that allows to write `Lit(!Bool(_))` might be a good idea. This pattern
would be eqivalent to `Lit( Char(_) | Int(_) )` (given that currently only three
literal types are implemented).
#### Functional composition
Patterns currently don't have any concept of composition. This leads to
repetitions within patterns. For example, one of the collapsible-if patterns
currently has to be written like this:
```rust
pattern!{
pat_if_else: Expr =
If(
_,
_,
Block_(
Block(
Expr((If(_, _, _?) | IfLet(_, _?))#else_) |
Semi((If(_, _, _?) | IfLet(_, _?))#else_)
)#block_inner
)#block
) |
IfLet(
_,
Block_(
Block(
Expr((If(_, _, _?) | IfLet(_, _?))#else_) |
Semi((If(_, _, _?) | IfLet(_, _?))#else_)
)#block_inner
)#block
)
}
```
If patterns supported defining functions of subpatterns, the code could be
simplified as follows:
```rust
pattern!{
fn expr_or_semi(expr: Expr) -> Stmt {
Expr(expr) | Semi(expr)
}
fn if_or_if_let(then: Block, else: Opt<Expr>) -> Expr {
If(_, then, else) | IfLet(then, else)
}
pat_if_else: Expr =
if_or_if_let(
_,
Block_(
Block(
expr_or_semi( if_or_if_let(_, _?)#else_ )
)#block_inner
)#block
)
}
```
Additionally, common patterns like `expr_or_semi` could be shared between
different lints.
#### Clippy Pattern Author
Another improvement could be to create a tool that, given some valid Rust
syntax, generates a pattern that matches this syntax exactly. This would make
starting to write a pattern easier. A user could take a look at the patterns
generated for a couple of Rust code examples and use that information to write a
pattern that matches all of them.
This is similar to clippy's author lint.
#### Supporting other syntaxes
Most of the proposed system is language-agnostic. For example, the pattern
syntax could also be used to describe patterns for other programming languages.
In order to support other languages' syntaxes, one would need to implement
another `PatternTree` that sufficiently describes the languages' AST and
implement `IsMatch` for this `PatternTree` and the languages' AST.
One aspect of this is that it would even be possible to write lints that work on
the pattern syntax itself. For example, when writing the following pattern
```rust
pattern!{
my_pattern: Expr =
Array( Lit(Bool(false)) Lit(Bool(false)) )
}
```
a lint that works on the pattern syntax's AST could suggest using this pattern
instead:
```rust
pattern!{
my_pattern: Expr =
Array( Lit(Bool(false)){2} )
}
```
In the future, clippy could use this system to also provide lints for custom
syntaxes like those found in macros.

View File

@ -120,7 +120,7 @@ fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> {
let new_lint = if enable_msrv {
format!(
"store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(msrv)));\n ",
"store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(msrv())));\n ",
lint_pass = lint.pass,
ctor_arg = if lint.pass == "late" { "_" } else { "" },
module_name = lint.name,
@ -238,10 +238,9 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
result.push_str(&if enable_msrv {
formatdoc!(
r#"
use clippy_utils::msrvs;
use clippy_utils::msrvs::{{self, Msrv}};
{pass_import}
use rustc_lint::{{{context_import}, {pass_type}, LintContext}};
use rustc_semver::RustcVersion;
use rustc_session::{{declare_tool_lint, impl_lint_pass}};
"#
@ -263,12 +262,12 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String {
formatdoc!(
r#"
pub struct {name_camel} {{
msrv: Option<RustcVersion>,
msrv: Msrv,
}}
impl {name_camel} {{
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {{
pub fn new(msrv: Msrv) -> Self {{
Self {{ msrv }}
}}
}}
@ -357,15 +356,14 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R
let _ = writedoc!(
lint_file_contents,
r#"
use clippy_utils::{{meets_msrv, msrvs}};
use clippy_utils::msrvs::{{self, Msrv}};
use rustc_lint::{{{context_import}, LintContext}};
use rustc_semver::RustcVersion;
use super::{name_upper};
// TODO: Adjust the parameters as necessary
pub(super) fn check(cx: &{context_import}, msrv: Option<RustcVersion>) {{
if !meets_msrv(msrv, todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
pub(super) fn check(cx: &{context_import}, msrv: &Msrv) {{
if !msrv.meets(todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{
return;
}}
todo!();

View File

@ -19,7 +19,7 @@ quine-mc_cluskey = "0.2"
regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
tempfile = { version = "3.2", optional = true }
tempfile = { version = "3.3.0", optional = true }
toml = "0.5"
unicode-normalization = "0.1"
unicode-script = { version = "0.5", default-features = false }

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{trim_span, walk_span_to_context};
use clippy_utils::{meets_msrv, msrvs};
use rustc_ast::ast::{Expr, ExprKind, LitKind, Pat, PatKind, RangeEnd, RangeLimits};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
@ -33,10 +32,10 @@ declare_clippy_lint! {
impl_lint_pass!(AlmostCompleteLetterRange => [ALMOST_COMPLETE_LETTER_RANGE]);
pub struct AlmostCompleteLetterRange {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl AlmostCompleteLetterRange {
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -46,7 +45,7 @@ impl EarlyLintPass for AlmostCompleteLetterRange {
let ctxt = e.span.ctxt();
let sugg = if let Some(start) = walk_span_to_context(start.span, ctxt)
&& let Some(end) = walk_span_to_context(end.span, ctxt)
&& meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE)
&& self.msrv.meets(msrvs::RANGE_INCLUSIVE)
{
Some((trim_span(cx.sess().source_map(), start.between(end)), "..="))
} else {
@ -60,7 +59,7 @@ impl EarlyLintPass for AlmostCompleteLetterRange {
if let PatKind::Range(Some(start), Some(end), kind) = &p.kind
&& matches!(kind.node, RangeEnd::Excluded)
{
let sugg = if meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE) {
let sugg = if self.msrv.meets(msrvs::RANGE_INCLUSIVE) {
"..="
} else {
"..."

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::msrvs::{self, Msrv};
use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
@ -63,12 +63,12 @@ const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
];
pub struct ApproxConstant {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ApproxConstant {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
@ -87,7 +87,7 @@ impl ApproxConstant {
let s = s.as_str();
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits, msrv) in &KNOWN_CONSTS {
if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| meets_msrv(self.msrv, msrv)) {
if is_approx_const(constant, s, min_digits) && msrv.map_or(true, |msrv| self.msrv.meets(msrv)) {
span_lint_and_help(
cx,
APPROX_CONSTANT,

View File

@ -2,9 +2,8 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::{is_panic, macro_backtrace};
use clippy_utils::msrvs;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
use clippy_utils::{extract_msrv_attr, meets_msrv};
use if_chain::if_chain;
use rustc_ast::{AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem};
use rustc_errors::Applicability;
@ -14,7 +13,6 @@ use rustc_hir::{
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use rustc_span::symbol::Symbol;
@ -599,7 +597,7 @@ fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
}
pub struct EarlyAttributes {
pub msrv: Option<RustcVersion>,
pub msrv: Msrv,
}
impl_lint_pass!(EarlyAttributes => [
@ -614,7 +612,7 @@ impl EarlyLintPass for EarlyAttributes {
}
fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
check_deprecated_cfg_attr(cx, attr, self.msrv);
check_deprecated_cfg_attr(cx, attr, &self.msrv);
check_mismatched_target_os(cx, attr);
}
@ -654,9 +652,9 @@ fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::It
}
}
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Option<RustcVersion>) {
fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
if_chain! {
if meets_msrv(msrv, msrvs::TOOL_ATTRIBUTES);
if msrv.meets(msrvs::TOOL_ATTRIBUTES);
// check cfg_attr
if attr.has_name(sym::cfg_attr);
if let Some(items) = attr.meta_item_list();

View File

@ -85,8 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
);
}
} else {
let span =
block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
if span.from_expansion() || expr.span.from_expansion() {
return;
}

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::{meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_semver::RustcVersion;
use super::CAST_ABS_TO_UNSIGNED;
@ -15,9 +14,9 @@ pub(super) fn check(
cast_expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if meets_msrv(msrv, msrvs::UNSIGNED_ABS)
if msrv.meets(msrvs::UNSIGNED_ABS)
&& let ty::Int(from) = cast_from.kind()
&& let ty::Uint(to) = cast_to.kind()
&& let ExprKind::MethodCall(method_path, receiver, ..) = cast_expr.kind

View File

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_constant;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_isize_or_usize;
use clippy_utils::{in_constant, meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, FloatTy, Ty};
use rustc_semver::RustcVersion;
use super::{utils, CAST_LOSSLESS};
@ -16,7 +16,7 @@ pub(super) fn check(
cast_op: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if !should_lint(cx, expr, cast_from, cast_to, msrv) {
return;
@ -57,13 +57,7 @@ pub(super) fn check(
);
}
fn should_lint(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to: Ty<'_>,
msrv: Option<RustcVersion>,
) -> bool {
fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: &Msrv) -> bool {
// Do not suggest using From in consts/statics until it is valid to do so (see #2267).
if in_constant(cx, expr.hir_id) {
return false;
@ -89,7 +83,7 @@ fn should_lint(
};
!is_isize_or_usize(cast_from) && from_nbits < to_nbits
},
(false, true) if matches!(cast_from.kind(), ty::Bool) && meets_msrv(msrv, msrvs::FROM_BOOL) => true,
(false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(msrvs::FROM_BOOL) => true,
(_, _) => {
matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64))
},

View File

@ -118,12 +118,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
};
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
matches!(
ty,
IntegerType::Pointer(_),
)
});
let cast_from_ptr_size = def.repr().int.map_or(true, |ty| matches!(ty, IntegerType::Pointer(_),));
let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) {
(false, false) if from_nbits > to_nbits => "",
(true, false) if from_nbits > to_nbits => "",

View File

@ -1,16 +1,16 @@
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{diagnostics::span_lint_and_then, source};
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
use rustc_semver::RustcVersion;
use super::CAST_SLICE_DIFFERENT_SIZES;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Option<RustcVersion>) {
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: &Msrv) {
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
if !meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS) {
if !msrv.meets(msrvs::PTR_SLICE_RAW_PARTS) {
return;
}

View File

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_def_path, meets_msrv, msrvs, paths};
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{def_id::DefId, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_semver::RustcVersion;
use super::CAST_SLICE_FROM_RAW_PARTS;
@ -25,15 +25,9 @@ fn raw_parts_kind(cx: &LateContext<'_>, did: DefId) -> Option<RawPartsKind> {
}
}
pub(super) fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
cast_expr: &Expr<'_>,
cast_to: Ty<'_>,
msrv: Option<RustcVersion>,
) {
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to: Ty<'_>, msrv: &Msrv) {
if_chain! {
if meets_msrv(msrv, msrvs::PTR_SLICE_RAW_PARTS);
if msrv.meets(msrvs::PTR_SLICE_RAW_PARTS);
if let ty::RawPtr(ptrty) = cast_to.kind();
if let ty::Slice(_) = ptrty.ty.kind();
if let ExprKind::Call(fun, [ptr_arg, len_arg]) = cast_expr.peel_blocks().kind;

View File

@ -21,11 +21,11 @@ mod ptr_as_ptr;
mod unnecessary_cast;
mod utils;
use clippy_utils::{is_hir_ty_cfg_dependant, meets_msrv, msrvs};
use clippy_utils::is_hir_ty_cfg_dependant;
use clippy_utils::msrvs::{self, Msrv};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -648,12 +648,12 @@ declare_clippy_lint! {
}
pub struct Casts {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl Casts {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -686,7 +686,7 @@ impl_lint_pass!(Casts => [
impl<'tcx> LateLintPass<'tcx> for Casts {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !in_external_macro(cx.sess(), expr.span) {
ptr_as_ptr::check(cx, expr, self.msrv);
ptr_as_ptr::check(cx, expr, &self.msrv);
}
if expr.span.from_expansion() {
@ -705,7 +705,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) {
return;
}
cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, self.msrv);
cast_slice_from_raw_parts::check(cx, expr, cast_expr, cast_to, &self.msrv);
as_ptr_cast_mut::check(cx, expr, cast_expr, cast_to);
fn_to_numeric_cast_any::check(cx, expr, cast_expr, cast_from, cast_to);
fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to);
@ -717,16 +717,16 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_possible_wrap::check(cx, expr, cast_from, cast_to);
cast_precision_loss::check(cx, expr, cast_from, cast_to);
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
cast_abs_to_unsigned::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_nan_to_int::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, self.msrv);
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
}
as_underscore::check(cx, expr, cast_to_hir);
if meets_msrv(self.msrv, msrvs::BORROW_AS_PTR) {
if self.msrv.meets(msrvs::BORROW_AS_PTR) {
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
}
}
@ -734,8 +734,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_ref_to_mut::check(cx, expr);
cast_ptr_alignment::check(cx, expr);
char_lit_as_u8::check(cx, expr);
ptr_as_ptr::check(cx, expr, self.msrv);
cast_slice_different_sizes::check(cx, expr, self.msrv);
ptr_as_ptr::check(cx, expr, &self.msrv);
cast_slice_different_sizes::check(cx, expr, &self.msrv);
}
extract_msrv_attr!(LateContext);

View File

@ -1,19 +1,18 @@
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::{meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, TypeAndMut};
use rustc_semver::RustcVersion;
use super::PTR_AS_PTR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Option<RustcVersion>) {
if !meets_msrv(msrv, msrvs::POINTER_CAST) {
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) {
if !msrv.meets(msrvs::POINTER_CAST) {
return;
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_opt;
use clippy_utils::{get_parent_expr, path_to_local};
use if_chain::if_chain;
use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_errors::Applicability;
@ -75,13 +75,26 @@ pub(super) fn check<'tcx>(
}
if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) {
if let Some(id) = path_to_local(cast_expr)
&& let Some(span) = cx.tcx.hir().opt_span(id)
&& span.ctxt() != cast_expr.span.ctxt()
{
// Binding context is different than the identifiers context.
// Weird macro wizardry could be involved here.
return false;
}
span_lint_and_sugg(
cx,
UNNECESSARY_CAST,
expr.span,
&format!("casting to the same type is unnecessary (`{cast_from}` -> `{cast_to}`)"),
"try",
cast_str,
if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::AddrOf(..))) {
format!("{{ {cast_str} }}")
} else {
cast_str
},
Applicability::MachineApplicable,
);
return true;

View File

@ -1,14 +1,14 @@
//! lint on manually implemented checked conversions that could be transformed into `try_from`
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, is_integer_literal, meets_msrv, msrvs, SpanlessEq};
use clippy_utils::{in_constant, is_integer_literal, SpanlessEq};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -37,12 +37,12 @@ declare_clippy_lint! {
}
pub struct CheckedConversions {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl CheckedConversions {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -51,7 +51,7 @@ impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]);
impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::TRY_FROM) {
if !self.msrv.meets(msrvs::TRY_FROM) {
return;
}

View File

@ -160,11 +160,13 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
if let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind;
// Prevent triggering on `if c { if let a = b { .. } }`.
if !matches!(check_inner.kind, ast::ExprKind::Let(..));
if expr.span.ctxt() == inner.span.ctxt();
let ctxt = expr.span.ctxt();
if inner.span.ctxt() == ctxt;
then {
span_lint_and_then(cx, COLLAPSIBLE_IF, expr.span, "this `if` statement can be collapsed", |diag| {
let lhs = Sugg::ast(cx, check, "..");
let rhs = Sugg::ast(cx, check_inner, "..");
let mut app = Applicability::MachineApplicable;
let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app);
let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app);
diag.span_suggestion(
expr.span,
"collapse nested if block",
@ -173,7 +175,7 @@ fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &
lhs.and(&rhs),
snippet_block(cx, content.span, "..", Some(expr.span)),
),
Applicability::MachineApplicable, // snippet
app, // snippet
);
});
}

View File

@ -177,6 +177,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::from_raw_with_void_ptr::FROM_RAW_WITH_VOID_PTR_INFO,
crate::from_str_radix_10::FROM_STR_RADIX_10_INFO,
crate::functions::DOUBLE_MUST_USE_INFO,
crate::functions::MISNAMED_GETTERS_INFO,
crate::functions::MUST_USE_CANDIDATE_INFO,
crate::functions::MUST_USE_UNIT_INFO,
crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO,
@ -583,6 +584,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::types::TYPE_COMPLEXITY_INFO,
crate::types::VEC_BOX_INFO,
crate::undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS_INFO,
crate::undocumented_unsafe_blocks::UNNECESSARY_SAFETY_COMMENT_INFO,
crate::unicode::INVISIBLE_CHARACTERS_INFO,
crate::unicode::NON_ASCII_LITERAL_INFO,
crate::unicode::UNICODE_NOT_NFC_INFO,

View File

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
use clippy_utils::{
fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, meets_msrv, msrvs, path_to_local,
walk_to_expr_usage,
fn_def_id, get_parent_expr, get_parent_expr_for_hir, is_lint_allowed, path_to_local, walk_to_expr_usage,
};
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::graph::iterate::{CycleDetector, TriColorDepthFirstSearch};
@ -28,7 +29,6 @@ use rustc_middle::ty::{
self, Binder, BoundVariableKind, Clause, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults,
};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span, Symbol};
use rustc_trait_selection::infer::InferCtxtExt as _;
@ -181,12 +181,12 @@ pub struct Dereferencing<'tcx> {
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
// `IntoIterator` for arrays requires Rust 1.53.
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl<'tcx> Dereferencing<'tcx> {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
..Dereferencing::default()
@ -286,26 +286,27 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
match (self.state.take(), kind) {
(None, kind) => {
let expr_ty = typeck.expr_ty(expr);
let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv);
let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, &self.msrv);
match kind {
RefOp::Deref => {
let sub_ty = typeck.expr_ty(sub_expr);
if let Position::FieldAccess {
name,
of_union: false,
} = position
&& !ty_contains_field(typeck.expr_ty(sub_expr), name)
&& !ty_contains_field(sub_ty, name)
{
self.state = Some((
State::ExplicitDerefField { name },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
} else if position.is_deref_stable() {
} else if position.is_deref_stable() && sub_ty.is_ref() {
self.state = Some((
State::ExplicitDeref { mutability: None },
StateData { span: expr.span, hir_id: expr.hir_id, position },
));
}
}
},
RefOp::Method(target_mut)
if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id)
&& position.lint_explicit_deref() =>
@ -320,7 +321,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
StateData {
span: expr.span,
hir_id: expr.hir_id,
position
position,
},
));
},
@ -394,7 +395,11 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
msg,
snip_expr,
}),
StateData { span: expr.span, hir_id: expr.hir_id, position },
StateData {
span: expr.span,
hir_id: expr.hir_id,
position,
},
));
} else if position.is_deref_stable()
// Auto-deref doesn't combine with other adjustments
@ -406,7 +411,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
StateData {
span: expr.span,
hir_id: expr.hir_id,
position
position,
},
));
}
@ -698,7 +703,7 @@ fn walk_parents<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
e: &'tcx Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> (Position, &'tcx [Adjustment<'tcx>]) {
let mut adjustments = [].as_slice();
let mut precedence = 0i8;
@ -862,7 +867,11 @@ fn walk_parents<'tcx>(
} && impl_ty.is_ref()
&& let infcx = cx.tcx.infer_ctxt().build()
&& infcx
.type_implements_trait(trait_id, [impl_ty.into()].into_iter().chain(subs.iter().copied()), cx.param_env)
.type_implements_trait(
trait_id,
[impl_ty.into()].into_iter().chain(subs.iter().copied()),
cx.param_env,
)
.must_apply_modulo_regions()
{
return Some(Position::MethodReceiverRefImpl)
@ -1078,7 +1087,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
param_ty: ParamTy,
mut expr: &Expr<'tcx>,
precedence: i8,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> Position {
let destruct_trait_def_id = cx.tcx.lang_items().destruct_trait();
let sized_trait_def_id = cx.tcx.lang_items().sized_trait();
@ -1178,7 +1187,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
&& let ty::Param(param_ty) = trait_predicate.self_ty().kind()
&& let GenericArgKind::Type(ty) = substs_with_referent_ty[param_ty.index as usize].unpack()
&& ty.is_array()
&& !meets_msrv(msrv, msrvs::ARRAY_INTO_ITERATOR)
&& !msrv.meets(msrvs::ARRAY_INTO_ITERATOR)
{
return false;
}

View File

@ -14,8 +14,8 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::traits::Reveal;
use rustc_middle::ty::{
self, Binder, BoundConstness, Clause, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef,
Ty, TyCtxt,
self, Binder, BoundConstness, Clause, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate,
TraitRef, Ty, TyCtxt,
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;

View File

@ -253,7 +253,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.66.0"]
pub UNNECESSARY_SAFETY_DOC,
style,
restriction,
"`pub fn` or `pub trait` with `# Safety` docs"
}

View File

@ -11,7 +11,7 @@ use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
use rustc_middle::ty::{self, Ty, TypeVisitable};
use rustc_middle::ty::{self, EarlyBinder, SubstsRef, Ty, TypeVisitable};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
@ -125,7 +125,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait()
&& let args = cx.tcx.erase_late_bound_regions(substs.as_closure().sig()).inputs()
&& implements_trait(cx, callee_ty.peel_refs(), fn_mut_id, &args.iter().copied().map(Into::into).collect::<Vec<_>>())
&& implements_trait(
cx,
callee_ty.peel_refs(),
fn_mut_id,
&args.iter().copied().map(Into::into).collect::<Vec<_>>(),
)
&& path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr))
{
// Mutable closure is used after current expr; we cannot consume it.
@ -152,7 +157,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
if check_sig(cx, closure_ty, call_ty);
then {
span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
let name = get_ufcs_type_name(cx, method_def_id);
let name = get_ufcs_type_name(cx, method_def_id, substs);
diag.span_suggestion(
expr.span,
"replace the closure with the method itself",
@ -222,7 +227,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tc
cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)
}
fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, substs: SubstsRef<'tcx>) -> String {
let assoc_item = cx.tcx.associated_item(method_def_id);
let def_id = assoc_item.container_id(cx.tcx);
match assoc_item.container {
@ -231,6 +236,15 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {
let ty = cx.tcx.type_of(def_id);
match ty.kind() {
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
ty::Array(..)
| ty::Dynamic(..)
| ty::Never
| ty::RawPtr(_)
| ty::Ref(..)
| ty::Slice(_)
| ty::Tuple(_) => {
format!("<{}>", EarlyBinder(ty).subst(cx.tcx, substs))
},
_ => ty.to_string(),
}
},

View File

@ -7,21 +7,34 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// `exit()` terminates the program and doesn't provide a
/// stack trace.
/// Detects calls to the `exit()` function which terminates the program.
///
/// ### Why is this bad?
/// Ideally a program is terminated by finishing
/// Exit terminates the program at the location it is called. For unrecoverable
/// errors `panics` should be used to provide a stacktrace and potentualy other
/// information. A normal termination or one with an error code should happen in
/// the main function.
///
/// ### Example
/// ```ignore
/// ```
/// std::process::exit(0)
/// ```
///
/// Use instead:
///
/// ```ignore
/// // To provide a stacktrace and additional information
/// panic!("message");
///
/// // or a main method with a return
/// fn main() -> Result<(), i32> {
/// Ok(())
/// }
/// ```
#[clippy::version = "1.41.0"]
pub EXIT,
restriction,
"`std::process::exit` is called, terminating the program"
"detects `std::process::exit` calls"
}
declare_lint_pass!(Exit => [EXIT]);

View File

@ -1,19 +1,22 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::macros::FormatParamKind::{Implicit, Named, Numbered, Starred};
use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred};
use clippy_utils::macros::{
is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam, FormatParamUsage,
};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs};
use if_chain::if_chain;
use itertools::Itertools;
use rustc_errors::Applicability;
use rustc_errors::{
Applicability,
SuggestionStyle::{CompletelyHidden, ShowCode},
};
use rustc_hir::{Expr, ExprKind, HirId, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021;
@ -103,19 +106,25 @@ declare_clippy_lint! {
/// format!("{var:.prec$}");
/// ```
///
/// ### Known Problems
///
/// There may be a false positive if the format string is expanded from certain proc macros:
///
/// ```ignore
/// println!(indoc!("{}"), var);
/// If allow-mixed-uninlined-format-args is set to false in clippy.toml,
/// the following code will also trigger the lint:
/// ```rust
/// # let var = 42;
/// format!("{} {}", var, 1+2);
/// ```
/// Use instead:
/// ```rust
/// # let var = 42;
/// format!("{var} {}", 1+2);
/// ```
///
/// ### Known Problems
///
/// If a format string contains a numbered argument that cannot be inlined
/// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`.
#[clippy::version = "1.65.0"]
pub UNINLINED_FORMAT_ARGS,
pedantic,
style,
"using non-inlined variables in `format!` calls"
}
@ -158,13 +167,17 @@ impl_lint_pass!(FormatArgs => [
]);
pub struct FormatArgs {
msrv: Option<RustcVersion>,
msrv: Msrv,
ignore_mixed: bool,
}
impl FormatArgs {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
Self {
msrv,
ignore_mixed: allow_mixed_uninlined_format_args,
}
}
}
@ -188,8 +201,8 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
check_to_string_in_format_args(cx, name, arg.param.value);
}
if meets_msrv(self.msrv, msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id);
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed);
}
}
}
@ -267,7 +280,13 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
}
}
fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_site: Span, def_id: DefId) {
fn check_uninlined_args(
cx: &LateContext<'_>,
args: &FormatArgsExpn<'_>,
call_site: Span,
def_id: DefId,
ignore_mixed: bool,
) {
if args.format_string.span.from_expansion() {
return;
}
@ -282,14 +301,13 @@ fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_si
// we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
if !args.params().all(|p| check_one_arg(args, &p, &mut fixes)) || fixes.is_empty() {
if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() {
return;
}
// Temporarily ignore multiline spans: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
if fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span)) {
return;
}
// multiline span display suggestion is sometimes broken: https://github.com/rust-lang/rust/pull/102729#discussion_r988704308
// in those cases, make the code suggestion hidden
let multiline_fix = fixes.iter().any(|(span, _)| cx.sess().source_map().is_multiline(*span));
span_lint_and_then(
cx,
@ -297,12 +315,22 @@ fn check_uninlined_args(cx: &LateContext<'_>, args: &FormatArgsExpn<'_>, call_si
call_site,
"variables can be used directly in the `format!` string",
|diag| {
diag.multipart_suggestion("change this to", fixes, Applicability::MachineApplicable);
diag.multipart_suggestion_with_style(
"change this to",
fixes,
Applicability::MachineApplicable,
if multiline_fix { CompletelyHidden } else { ShowCode },
);
},
);
}
fn check_one_arg(args: &FormatArgsExpn<'_>, param: &FormatParam<'_>, fixes: &mut Vec<(Span, String)>) -> bool {
fn check_one_arg(
args: &FormatArgsExpn<'_>,
param: &FormatParam<'_>,
fixes: &mut Vec<(Span, String)>,
ignore_mixed: bool,
) -> bool {
if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
&& let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
&& let [segment] = path.segments
@ -317,8 +345,10 @@ fn check_one_arg(args: &FormatArgsExpn<'_>, param: &FormatParam<'_>, fixes: &mut
fixes.push((arg_span, String::new()));
true // successful inlining, continue checking
} else {
// if we can't inline a numbered argument, we can't continue
param.kind != Numbered
// Do not continue inlining (return false) in case
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_)))
}
}
@ -330,12 +360,7 @@ fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
}
}
fn check_format_in_format_args(
cx: &LateContext<'_>,
call_site: Span,
name: Symbol,
arg: &Expr<'_>,
) {
fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() {
return;
@ -408,7 +433,10 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
/// Returns true if `hir_id` is referred to by multiple format params
fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
args.params().filter(|param| param.value.hir_id == hir_id).at_most_one().is_err()
args.params()
.filter(|param| param.value.hir_id == hir_id)
.at_most_one()
.is_err()
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
@ -418,7 +446,11 @@ where
let mut n_total = 0;
let mut n_needed = 0;
loop {
if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
if let Some(Adjustment {
kind: Adjust::Deref(overloaded_deref),
target,
}) = iter.next()
{
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;

View File

@ -1,7 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::span_is_local;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::path_def_id;
use clippy_utils::source::snippet_opt;
use clippy_utils::{meets_msrv, msrvs, path_def_id};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_path, Visitor};
use rustc_hir::{
@ -10,7 +11,6 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::{kw, sym};
use rustc_span::{Span, Symbol};
@ -49,12 +49,12 @@ declare_clippy_lint! {
}
pub struct FromOverInto {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl FromOverInto {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
FromOverInto { msrv }
}
}
@ -63,7 +63,7 @@ impl_lint_pass!(FromOverInto => [FROM_OVER_INTO]);
impl<'tcx> LateLintPass<'tcx> for FromOverInto {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if !meets_msrv(self.msrv, msrvs::RE_REBALANCING_COHERENCE) || !span_is_local(item.span) {
if !self.msrv.meets(msrvs::RE_REBALANCING_COHERENCE) || !span_is_local(item.span) {
return;
}

View File

@ -0,0 +1,125 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::{intravisit::FnKind, Body, ExprKind, FnDecl, HirId, ImplicitSelfKind, Unsafety};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
use std::iter;
use super::MISNAMED_GETTERS;
pub fn check_fn(
cx: &LateContext<'_>,
kind: FnKind<'_>,
decl: &FnDecl<'_>,
body: &Body<'_>,
span: Span,
_hir_id: HirId,
) {
let FnKind::Method(ref ident, sig) = kind else {
return;
};
// Takes only &(mut) self
if decl.inputs.len() != 1 {
return;
}
let name = ident.name.as_str();
let name = match decl.implicit_self {
ImplicitSelfKind::MutRef => {
let Some(name) = name.strip_suffix("_mut") else {
return;
};
name
},
ImplicitSelfKind::Imm | ImplicitSelfKind::Mut | ImplicitSelfKind::ImmRef => name,
ImplicitSelfKind::None => return,
};
let name = if sig.header.unsafety == Unsafety::Unsafe {
name.strip_suffix("_unchecked").unwrap_or(name)
} else {
name
};
// Body must be &(mut) <self_data>.name
// self_data is not neccessarilly self, to also lint sub-getters, etc…
let block_expr = if_chain! {
if let ExprKind::Block(block,_) = body.value.kind;
if block.stmts.is_empty();
if let Some(block_expr) = block.expr;
then {
block_expr
} else {
return;
}
};
let expr_span = block_expr.span;
// Accept &<expr>, &mut <expr> and <expr>
let expr = if let ExprKind::AddrOf(_, _, tmp) = block_expr.kind {
tmp
} else {
block_expr
};
let (self_data, used_ident) = if_chain! {
if let ExprKind::Field(self_data, ident) = expr.kind;
if ident.name.as_str() != name;
then {
(self_data, ident)
} else {
return;
}
};
let mut used_field = None;
let mut correct_field = None;
let typeck_results = cx.typeck_results();
for adjusted_type in iter::once(typeck_results.expr_ty(self_data))
.chain(typeck_results.expr_adjustments(self_data).iter().map(|adj| adj.target))
{
let ty::Adt(def,_) = adjusted_type.kind() else {
continue;
};
for f in def.all_fields() {
if f.name.as_str() == name {
correct_field = Some(f);
}
if f.name == used_ident.name {
used_field = Some(f);
}
}
}
let Some(used_field) = used_field else {
// Can happen if the field access is a tuple. We don't lint those because the getter name could not start with a number.
return;
};
let Some(correct_field) = correct_field else {
// There is no field corresponding to the getter name.
// FIXME: This can be a false positive if the correct field is reachable trought deeper autodereferences than used_field is
return;
};
if cx.tcx.type_of(used_field.did) == cx.tcx.type_of(correct_field.did) {
let left_span = block_expr.span.until(used_ident.span);
let snippet = snippet(cx, left_span, "..");
let sugg = format!("{snippet}{name}");
span_lint_and_then(
cx,
MISNAMED_GETTERS,
span,
"getter function appears to return the wrong field",
|diag| {
diag.span_suggestion(expr_span, "consider using", sugg, Applicability::MaybeIncorrect);
},
);
}
}

View File

@ -1,3 +1,4 @@
mod misnamed_getters;
mod must_use;
mod not_unsafe_ptr_arg_deref;
mod result;
@ -260,6 +261,48 @@ declare_clippy_lint! {
"function returning `Result` with large `Err` type"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for getter methods that return a field that doesn't correspond
/// to the name of the method, when there is a field's whose name matches that of the method.
///
/// ### Why is this bad?
/// It is most likely that such a method is a bug caused by a typo or by copy-pasting.
///
/// ### Example
/// ```rust
/// struct A {
/// a: String,
/// b: String,
/// }
///
/// impl A {
/// fn a(&self) -> &str{
/// &self.b
/// }
/// }
/// ```
/// Use instead:
/// ```rust
/// struct A {
/// a: String,
/// b: String,
/// }
///
/// impl A {
/// fn a(&self) -> &str{
/// &self.a
/// }
/// }
/// ```
#[clippy::version = "1.67.0"]
pub MISNAMED_GETTERS,
suspicious,
"getter method returning the wrong field"
}
#[derive(Copy, Clone)]
pub struct Functions {
too_many_arguments_threshold: u64,
@ -286,6 +329,7 @@ impl_lint_pass!(Functions => [
MUST_USE_CANDIDATE,
RESULT_UNIT_ERR,
RESULT_LARGE_ERR,
MISNAMED_GETTERS,
]);
impl<'tcx> LateLintPass<'tcx> for Functions {
@ -301,6 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
too_many_lines::check_fn(cx, kind, span, body, self.too_many_lines_threshold);
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
misnamed_getters::check_fn(cx, kind, decl, body, span, hir_id);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {

View File

@ -94,7 +94,9 @@ fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty
if let hir::ItemKind::Enum(ref def, _) = item.kind;
then {
let variants_size = AdtVariantInfo::new(cx, *adt, subst);
if variants_size[0].size >= large_err_threshold {
if let Some((first_variant, variants)) = variants_size.split_first()
&& first_variant.size >= large_err_threshold
{
span_lint_and_then(
cx,
RESULT_LARGE_ERR,
@ -102,11 +104,11 @@ fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty
"the `Err`-variant returned from this function is very large",
|diag| {
diag.span_label(
def.variants[variants_size[0].ind].span,
def.variants[first_variant.ind].span,
format!("the largest variant contains at least {} bytes", variants_size[0].size),
);
for variant in &variants_size[1..] {
for variant in variants {
if variant.size >= large_err_threshold {
let variant_def = &def.variants[variant.ind];
diag.span_label(

View File

@ -91,7 +91,9 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
infcx
.err_ctxt()
.maybe_note_obligation_cause_for_async_await(db, &obligation);
if let PredicateKind::Clause(Clause::Trait(trait_pred)) = obligation.predicate.kind().skip_binder() {
if let PredicateKind::Clause(Clause::Trait(trait_pred)) =
obligation.predicate.kind().skip_binder()
{
db.note(&format!(
"`{}` doesn't implement `{}`",
trait_pred.self_ty(),

View File

@ -1,14 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::{
contains_return, higher, is_else_clause, is_res_lang_ctor, meets_msrv, msrvs, path_res, peel_blocks,
};
use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -47,12 +45,12 @@ declare_clippy_lint! {
}
pub struct IfThenSomeElseNone {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl IfThenSomeElseNone {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -61,7 +59,7 @@ impl_lint_pass!(IfThenSomeElseNone => [IF_THEN_SOME_ELSE_NONE]);
impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !meets_msrv(self.msrv, msrvs::BOOL_THEN) {
if !self.msrv.meets(msrvs::BOOL_THEN) {
return;
}
@ -94,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
} else {
format!("{{ /* snippet */ {arg_snip} }}")
};
let method_name = if switch_to_eager_eval(cx, expr) && meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) {
"then_some"
} else {
method_body.insert_str(0, "|| ");

View File

@ -1,8 +1,9 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLet;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local};
use clippy_utils::{is_expn_of, is_lint_allowed, path_to_local};
use if_chain::if_chain;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability;
@ -11,7 +12,6 @@ use rustc_hir::intravisit::{self, Visitor};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::Ident, Span};
@ -47,18 +47,17 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.59.0"]
pub INDEX_REFUTABLE_SLICE,
nursery,
pedantic,
"avoid indexing on slices which could be destructed"
}
#[derive(Copy, Clone)]
pub struct IndexRefutableSlice {
max_suggested_slice: u64,
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl IndexRefutableSlice {
pub fn new(max_suggested_slice_pattern_length: u64, msrv: Option<RustcVersion>) -> Self {
pub fn new(max_suggested_slice_pattern_length: u64, msrv: Msrv) -> Self {
Self {
max_suggested_slice: max_suggested_slice_pattern_length,
msrv,
@ -74,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice {
if !expr.span.from_expansion() || is_expn_of(expr.span, "if_chain").is_some();
if let Some(IfLet {let_pat, if_then, ..}) = IfLet::hir(cx, expr);
if !is_lint_allowed(cx, INDEX_REFUTABLE_SLICE, expr.hir_id);
if meets_msrv(self.msrv, msrvs::SLICE_PATTERNS);
if self.msrv.meets(msrvs::SLICE_PATTERNS);
let found_slices = find_slice_values(cx, let_pat);
if !found_slices.is_empty();

View File

@ -1,13 +1,11 @@
use clippy_utils::{
diagnostics::{self, span_lint_and_sugg},
meets_msrv, msrvs, source,
sugg::Sugg,
ty,
};
use clippy_utils::diagnostics::{self, span_lint_and_sugg};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{source_map::Spanned, sym};
@ -68,12 +66,12 @@ declare_clippy_lint! {
}
pub struct InstantSubtraction {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl InstantSubtraction {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -101,7 +99,7 @@ impl LateLintPass<'_> for InstantSubtraction {
} else {
if_chain! {
if !expr.span.from_expansion();
if meets_msrv(self.msrv, msrvs::TRY_FROM);
if self.msrv.meets(msrvs::TRY_FROM);
if is_an_instant(cx, lhs);
if is_a_duration(cx, rhs);

View File

@ -52,10 +52,9 @@ extern crate declare_clippy_lint;
use std::io;
use std::path::PathBuf;
use clippy_utils::parse_msrv;
use clippy_utils::msrvs::Msrv;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use rustc_semver::RustcVersion;
use rustc_session::Session;
#[cfg(feature = "internal")]
@ -322,48 +321,10 @@ pub use crate::utils::conf::{lookup_conf_file, Conf};
/// Used in `./src/driver.rs`.
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) {
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
let msrv = Msrv::read(&conf.msrv, sess);
let msrv = move || msrv.clone();
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
})
});
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv }));
}
fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
.ok()
.and_then(|v| parse_msrv(&v, None, None));
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
})
});
if let Some(cargo_msrv) = cargo_msrv {
if let Some(clippy_msrv) = clippy_msrv {
// if both files have an msrv, let's compare them and emit a warning if they differ
if clippy_msrv != cargo_msrv {
sess.warn(format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
));
}
Some(clippy_msrv)
} else {
Some(cargo_msrv)
}
} else {
clippy_msrv
}
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv: msrv() }));
}
#[doc(hidden)]
@ -595,43 +556,44 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
let msrv = read_msrv(conf, sess);
let msrv = Msrv::read(&conf.msrv, sess);
let msrv = move || msrv.clone();
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
let allow_expect_in_tests = conf.allow_expect_in_tests;
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv)));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
store.register_late_pass(move |_| {
Box::new(methods::Methods::new(
avoid_breaking_exported_api,
msrv,
msrv(),
allow_expect_in_tests,
allow_unwrap_in_tests,
))
});
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv)));
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
let matches_for_let_else = conf.matches_for_let_else;
store.register_late_pass(move |_| Box::new(manual_let_else::ManualLetElse::new(msrv, matches_for_let_else)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(msrv)));
store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv)));
store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv)));
store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(msrv)));
store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(msrv)));
store.register_late_pass(move |_| Box::new(ranges::Ranges::new(msrv)));
store.register_late_pass(move |_| Box::new(from_over_into::FromOverInto::new(msrv)));
store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(msrv)));
store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_let_else::ManualLetElse::new(msrv(), matches_for_let_else)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(msrv())));
store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(msrv())));
store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(msrv())));
store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(msrv())));
store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(msrv())));
store.register_late_pass(move |_| Box::new(ranges::Ranges::new(msrv())));
store.register_late_pass(move |_| Box::new(from_over_into::FromOverInto::new(msrv())));
store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(msrv())));
store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(msrv())));
store.register_late_pass(move |_| Box::new(needless_question_mark::NeedlessQuestionMark));
store.register_late_pass(move |_| Box::new(casts::Casts::new(msrv)));
store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv)));
store.register_late_pass(move |_| Box::new(casts::Casts::new(msrv())));
store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv())));
store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount));
store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod));
let max_suggested_slice_pattern_length = conf.max_suggested_slice_pattern_length;
store.register_late_pass(move |_| {
Box::new(index_refutable_slice::IndexRefutableSlice::new(
max_suggested_slice_pattern_length,
msrv,
msrv(),
))
});
store.register_late_pass(|_| Box::<shadow::Shadow>::default());
@ -648,7 +610,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef));
store.register_late_pass(|_| Box::new(no_effect::NoEffect));
store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment));
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv)));
store.register_late_pass(move |_| Box::new(transmute::Transmute::new(msrv())));
let cognitive_complexity_threshold = conf.cognitive_complexity_threshold;
store.register_late_pass(move |_| {
Box::new(cognitive_complexity::CognitiveComplexity::new(
@ -806,7 +768,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports)));
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv)));
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
@ -840,7 +802,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::<vec_init_then_push::VecInitThenPush>::default());
store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing));
store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10));
store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv())));
store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle));
store.register_late_pass(|_| Box::new(unused_async::UnusedAsync));
@ -865,14 +827,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv)));
let allow_mixed_uninlined = conf.allow_mixed_uninlined_format_args;
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined)));
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
store.register_late_pass(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse));
store.register_late_pass(|_| Box::new(init_numbered_fields::NumberedFields));
store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames));
store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(msrv())));
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
let allow_dbg_in_tests = conf.allow_dbg_in_tests;
@ -896,20 +859,20 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
store.register_early_pass(|| Box::<duplicate_mod::DuplicateMod>::default());
store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding));
store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv)));
store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv())));
store.register_late_pass(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef));
store.register_late_pass(|_| Box::new(mismatching_type_param_order::TypeParamMismatch));
store.register_late_pass(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec));
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold;
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold)));
store.register_late_pass(|_| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked));
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv)));
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(msrv())));
store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew));
store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable));
store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments));
@ -920,7 +883,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods));
store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr));
store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow));
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv)));
store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(msrv())));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -3,7 +3,7 @@ use clippy_utils::trait_ref_of_method;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter};
use rustc_hir::intravisit::{
walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
walk_fn_decl, walk_generic_arg, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound,
walk_poly_trait_ref, walk_trait_ref, walk_ty, Visitor,
};
use rustc_hir::lang_items;
@ -481,7 +481,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
sub_visitor.visit_fn_decl(decl);
self.nested_elision_site_lts.append(&mut sub_visitor.all_lts());
},
TyKind::TraitObject(bounds, ref lt, _) => {
TyKind::TraitObject(bounds, lt, _) => {
if !lt.is_elided() {
self.unelided_trait_object_lifetime = true;
}
@ -497,14 +497,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
if let GenericArg::Lifetime(l) = generic_arg && let LifetimeName::Param(def_id) = l.res {
self.lifetime_generic_arg_spans.entry(def_id).or_insert(l.ident.span);
}
// Replace with `walk_generic_arg` if/when https://github.com/rust-lang/rust/pull/103692 lands.
// walk_generic_arg(self, generic_arg);
match generic_arg {
GenericArg::Lifetime(lt) => self.visit_lifetime(lt),
GenericArg::Type(ty) => self.visit_ty(ty),
GenericArg::Const(ct) => self.visit_anon_const(&ct.value),
GenericArg::Infer(inf) => self.visit_infer(inf),
}
walk_generic_arg(self, generic_arg);
}
}

View File

@ -35,7 +35,8 @@ struct PathAndSpan {
span: Span,
}
/// `MacroRefData` includes the name of the macro.
/// `MacroRefData` includes the name of the macro
/// and the path from `SourceMap::span_to_filename`.
#[derive(Debug, Clone)]
pub struct MacroRefData {
name: String,

View File

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_parent_expr, meets_msrv, msrvs};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
@ -34,12 +34,12 @@ declare_clippy_lint! {
#[derive(Clone)]
pub struct ManualBits {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualBits {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -48,7 +48,7 @@ impl_lint_pass!(ManualBits => [MANUAL_BITS]);
impl<'tcx> LateLintPass<'tcx> for ManualBits {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::MANUAL_BITS) {
if !self.msrv.meets(msrvs::MANUAL_BITS) {
return;
}

View File

@ -1,28 +1,25 @@
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::higher::If;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::MaybePath;
use clippy_utils::{
eq_expr_value, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
};
use itertools::Itertools;
use rustc_errors::Applicability;
use rustc_errors::Diagnostic;
use rustc_hir::{
def::Res, Arm, BinOpKind, Block, Expr, ExprKind, Guard, HirId, PatKind, PathSegment, PrimTy, QPath, StmtKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{symbol::sym, Span};
use std::ops::Deref;
use clippy_utils::{
diagnostics::{span_lint_and_then, span_lint_hir_and_then},
eq_expr_value,
higher::If,
is_diag_trait_item, is_trait_method, meets_msrv, msrvs, path_res, path_to_local_id, peel_blocks,
peel_blocks_with_stmt,
sugg::Sugg,
ty::implements_trait,
visitors::is_const_evaluatable,
MaybePath,
};
use rustc_errors::Applicability;
declare_clippy_lint! {
/// ### What it does
/// Identifies good opportunities for a clamp function from std or core, and suggests using it.
@ -87,11 +84,11 @@ declare_clippy_lint! {
impl_lint_pass!(ManualClamp => [MANUAL_CLAMP]);
pub struct ManualClamp {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualClamp {
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -114,7 +111,7 @@ struct InputMinMax<'tcx> {
impl<'tcx> LateLintPass<'tcx> for ManualClamp {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !meets_msrv(self.msrv, msrvs::CLAMP) {
if !self.msrv.meets(msrvs::CLAMP) {
return;
}
if !expr.span.from_expansion() {
@ -130,7 +127,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp {
}
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if !meets_msrv(self.msrv, msrvs::CLAMP) {
if !self.msrv.meets(msrvs::CLAMP) {
return;
}
for suggestion in is_two_if_pattern(cx, block) {

View File

@ -1,15 +1,12 @@
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{diagnostics::span_lint_and_sugg, in_constant, macros::root_macro_call, source::snippet};
use rustc_ast::LitKind::{Byte, Char};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, PatKind, RangeEnd};
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{def_id::DefId, sym};
use clippy_utils::{
diagnostics::span_lint_and_sugg, in_constant, macros::root_macro_call, meets_msrv, msrvs, source::snippet,
};
declare_clippy_lint! {
/// ### What it does
/// Suggests to use dedicated built-in methods,
@ -45,12 +42,12 @@ declare_clippy_lint! {
impl_lint_pass!(ManualIsAsciiCheck => [MANUAL_IS_ASCII_CHECK]);
pub struct ManualIsAsciiCheck {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualIsAsciiCheck {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -70,11 +67,11 @@ enum CharRange {
impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::IS_ASCII_DIGIT) {
if !self.msrv.meets(msrvs::IS_ASCII_DIGIT) {
return;
}
if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::IS_ASCII_DIGIT_CONST) {
if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::IS_ASCII_DIGIT_CONST) {
return;
}

View File

@ -1,16 +1,16 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::snippet_opt;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::peel_blocks;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{for_each_expr, Descend};
use clippy_utils::{meets_msrv, msrvs, peel_blocks};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
use rustc_span::Span;
@ -50,13 +50,13 @@ declare_clippy_lint! {
}
pub struct ManualLetElse {
msrv: Option<RustcVersion>,
msrv: Msrv,
matches_behaviour: MatchLintBehaviour,
}
impl ManualLetElse {
#[must_use]
pub fn new(msrv: Option<RustcVersion>, matches_behaviour: MatchLintBehaviour) -> Self {
pub fn new(msrv: Msrv, matches_behaviour: MatchLintBehaviour) -> Self {
Self {
msrv,
matches_behaviour,
@ -69,7 +69,7 @@ impl_lint_pass!(ManualLetElse => [MANUAL_LET_ELSE]);
impl<'tcx> LateLintPass<'tcx> for ManualLetElse {
fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &'tcx Stmt<'tcx>) {
let if_let_or_match = if_chain! {
if meets_msrv(self.msrv, msrvs::LET_ELSE);
if self.msrv.meets(msrvs::LET_ELSE);
if !in_external_macro(cx.sess(), stmt.span);
if let StmtKind::Local(local) = stmt.kind;
if let Some(init) = local.init;
@ -141,20 +141,18 @@ fn emit_manual_let_else(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, pat:
// * unused binding collision detection with existing ones
// * putting patterns with at the top level | inside ()
// for this to be machine applicable.
let app = Applicability::HasPlaceholders;
let mut app = Applicability::HasPlaceholders;
let (sn_pat, _) = snippet_with_context(cx, pat.span, span.ctxt(), "", &mut app);
let (sn_expr, _) = snippet_with_context(cx, expr.span, span.ctxt(), "", &mut app);
let (sn_else, _) = snippet_with_context(cx, else_body.span, span.ctxt(), "", &mut app);
if let Some(sn_pat) = snippet_opt(cx, pat.span) &&
let Some(sn_expr) = snippet_opt(cx, expr.span) &&
let Some(sn_else) = snippet_opt(cx, else_body.span)
{
let else_bl = if matches!(else_body.kind, ExprKind::Block(..)) {
sn_else
} else {
format!("{{ {sn_else} }}")
};
let sugg = format!("let {sn_pat} = {sn_expr} else {else_bl};");
diag.span_suggestion(span, "consider writing", sugg, app);
}
let else_bl = if matches!(else_body.kind, ExprKind::Block(..)) {
sn_else.into_owned()
} else {
format!("{{ {sn_else} }}")
};
let sugg = format!("let {sn_pat} = {sn_expr} else {else_bl};");
diag.span_suggestion(span, "consider writing", sugg, app);
},
);
}

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
use clippy_utils::is_doc_hidden;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::{is_doc_hidden, meets_msrv, msrvs};
use rustc_ast::ast::{self, VisibilityKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@ -8,7 +9,6 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::ty::DefIdTree;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::{sym, Span};
@ -63,12 +63,12 @@ declare_clippy_lint! {
#[expect(clippy::module_name_repetitions)]
pub struct ManualNonExhaustiveStruct {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualNonExhaustiveStruct {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -77,14 +77,14 @@ impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
#[expect(clippy::module_name_repetitions)]
pub struct ManualNonExhaustiveEnum {
msrv: Option<RustcVersion>,
msrv: Msrv,
constructed_enum_variants: FxHashSet<(DefId, DefId)>,
potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
}
impl ManualNonExhaustiveEnum {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
constructed_enum_variants: FxHashSet::default(),
@ -97,7 +97,7 @@ impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
impl EarlyLintPass for ManualNonExhaustiveStruct {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) {
return;
}
@ -149,7 +149,7 @@ impl EarlyLintPass for ManualNonExhaustiveStruct {
impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if !meets_msrv(self.msrv, msrvs::NON_EXHAUSTIVE) {
if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) {
return;
}

View File

@ -1,12 +1,12 @@
use clippy_utils::consts::{constant_full_int, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{in_constant, meets_msrv, msrvs, path_to_local};
use clippy_utils::{in_constant, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -34,12 +34,12 @@ declare_clippy_lint! {
}
pub struct ManualRemEuclid {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualRemEuclid {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -48,11 +48,11 @@ impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]);
impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::REM_EUCLID) {
if !self.msrv.meets(msrvs::REM_EUCLID) {
return;
}
if in_constant(cx, expr.hir_id) && !meets_msrv(self.msrv, msrvs::REM_EUCLID_CONST) {
if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::REM_EUCLID_CONST) {
return;
}

View File

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{get_parent_expr, match_def_path, paths, SpanlessEq};
use clippy_utils::{meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
@ -50,12 +50,12 @@ declare_clippy_lint! {
}
pub struct ManualRetain {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualRetain {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -71,9 +71,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
&& let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& match_def_path(cx, collect_def_id, &paths::CORE_ITER_COLLECT) {
check_into_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
check_iter(cx, parent_expr, left_expr, target_expr, self.msrv);
check_to_owned(cx, parent_expr, left_expr, target_expr, self.msrv);
check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv);
check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv);
}
}
@ -85,7 +85,7 @@ fn check_into_iter(
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
&& let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
@ -104,7 +104,7 @@ fn check_iter(
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
&& let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
@ -127,9 +127,9 @@ fn check_to_owned(
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if meets_msrv(msrv, msrvs::STRING_RETAIN)
if msrv.meets(msrvs::STRING_RETAIN)
&& let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
&& let Some(to_owned_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id)
&& match_def_path(cx, to_owned_def_id, &paths::TO_OWNED_METHOD)
@ -215,10 +215,10 @@ fn match_acceptable_def_path(cx: &LateContext<'_>, collect_def_id: DefId) -> boo
.any(|&method| match_def_path(cx, collect_def_id, method))
}
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Option<RustcVersion>) -> bool {
fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
ACCEPTABLE_TYPES.iter().any(|(ty, acceptable_msrv)| {
is_type_diagnostic_item(cx, expr_ty, *ty)
&& acceptable_msrv.map_or(true, |acceptable_msrv| meets_msrv(msrv, acceptable_msrv))
&& acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv))
})
}

View File

@ -1,8 +1,9 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{eq_expr_value, higher, match_def_path, meets_msrv, msrvs, paths};
use clippy_utils::{eq_expr_value, higher, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_hir::def::Res;
@ -11,7 +12,6 @@ use rustc_hir::BinOpKind;
use rustc_hir::{BorrowKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Spanned;
use rustc_span::Span;
@ -48,12 +48,12 @@ declare_clippy_lint! {
}
pub struct ManualStrip {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl ManualStrip {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -68,7 +68,7 @@ enum StripKind {
impl<'tcx> LateLintPass<'tcx> for ManualStrip {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) {
if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) {
return;
}

View File

@ -23,13 +23,13 @@ mod single_match;
mod try_err;
mod wild_in_or_pats;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet_opt, walk_span_to_context};
use clippy_utils::{higher, in_constant, is_span_match, meets_msrv, msrvs};
use clippy_utils::{higher, in_constant, is_span_match};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, SpanData, SyntaxContext};
@ -930,13 +930,13 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
msrv: Msrv,
infallible_destructuring_match_linted: bool,
}
impl Matches {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
..Matches::default()
@ -1000,9 +1000,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) {
if source == MatchSource::Normal {
if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
&& match_like_matches::check_match(cx, expr, ex, arms))
{
if !(self.msrv.meets(msrvs::MATCHES_MACRO) && match_like_matches::check_match(cx, expr, ex, arms)) {
match_same_arms::check(cx, arms);
}
@ -1034,7 +1032,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else);
if !from_expansion {
if let Some(else_expr) = if_let.if_else {
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
if self.msrv.meets(msrvs::MATCHES_MACRO) {
match_like_matches::check_if_let(
cx,
expr,

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, is_res_lang_ctor, match_def_path, path_res, paths};
use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
@ -107,7 +107,7 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t
fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if match_def_path(cx, def.did(), &paths::POLL);
if cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did());
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
@ -124,7 +124,7 @@ fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<
fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if match_def_path(cx, def.did(), &paths::POLL);
if cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did());
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();

View File

@ -1,14 +1,14 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, meets_msrv, msrvs, path_res};
use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use rustc_span::symbol::sym;
@ -227,12 +227,12 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
}
pub struct MemReplace {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl MemReplace {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -248,7 +248,7 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
then {
check_replace_option_with_none(cx, src, dest, expr.span);
check_replace_with_uninit(cx, src, dest, expr.span);
if meets_msrv(self.msrv, msrvs::MEM_TAKE) {
if self.msrv.meets(msrvs::MEM_TAKE) {
check_replace_with_default(cx, src, dest, expr.span);
}
}

View File

@ -1,25 +1,25 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::{get_iterator_item_ty, is_copy};
use clippy_utils::{is_trait_method, meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_span::{sym, Span};
use super::CLONED_INSTEAD_OF_COPIED;
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: Option<RustcVersion>) {
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, msrv: &Msrv) {
let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
let inner_ty = match recv_ty.kind() {
// `Option<T>` -> `T`
ty::Adt(adt, subst)
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, msrvs::OPTION_COPIED) =>
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && msrv.meets(msrvs::OPTION_COPIED) =>
{
subst.type_at(0)
},
_ if is_trait_method(cx, expr, sym::Iterator) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) => {
_ if is_trait_method(cx, expr, sym::Iterator) && msrv.meets(msrvs::ITERATOR_COPIED) => {
match get_iterator_item_ty(cx, recv_ty) {
// <T as Iterator>::Item
Some(ty) => ty,

View File

@ -1,27 +1,27 @@
use super::ERR_EXPECT;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::has_debug_impl;
use clippy_utils::{meets_msrv, msrvs, ty::is_type_diagnostic_item};
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_middle::ty::Ty;
use rustc_semver::RustcVersion;
use rustc_span::{sym, Span};
pub(super) fn check(
cx: &LateContext<'_>,
_expr: &rustc_hir::Expr<'_>,
recv: &rustc_hir::Expr<'_>,
msrv: Option<RustcVersion>,
expect_span: Span,
err_span: Span,
msrv: &Msrv,
) {
if_chain! {
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
// Test the version to make sure the lint can be showed (expect_err has been
// introduced in rust 1.17.0 : https://github.com/rust-lang/rust/pull/38982)
if meets_msrv(msrv, msrvs::EXPECT_ERR);
if msrv.meets(msrvs::EXPECT_ERR);
// Grabs the `Result<T, E>` type
let result_type = cx.typeck_results().expr_ty(recv);

View File

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::is_trait_method;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::{is_trait_method, meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_semver::RustcVersion;
use rustc_span::sym;
use super::FILTER_MAP_NEXT;
@ -14,10 +14,10 @@ pub(super) fn check<'tcx>(
expr: &'tcx hir::Expr<'_>,
recv: &'tcx hir::Expr<'_>,
arg: &'tcx hir::Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if is_trait_method(cx, expr, sym::Iterator) {
if !meets_msrv(msrv, msrvs::ITERATOR_FIND_MAP) {
if !msrv.meets(msrvs::ITERATOR_FIND_MAP) {
return;
}

View File

@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::symbol::{Symbol, sym};
use rustc_span::symbol::{sym, Symbol};
use super::INEFFICIENT_TO_STRING;

View File

@ -1,23 +1,22 @@
//! Lint for `c.is_digit(10)`
use super::IS_DIGIT_ASCII_RADIX;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{
consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, meets_msrv, msrvs,
source::snippet_with_applicability,
consts::constant_full_int, consts::FullInt, diagnostics::span_lint_and_sugg, source::snippet_with_applicability,
};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_semver::RustcVersion;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
self_arg: &'tcx Expr<'_>,
radix: &'tcx Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if !meets_msrv(msrv, msrvs::IS_ASCII_DIGIT) {
if !msrv.meets(msrvs::IS_ASCII_DIGIT) {
return;
}

View File

@ -1,7 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
use clippy_utils::{is_diag_trait_item, meets_msrv, msrvs, peel_blocks};
use clippy_utils::{is_diag_trait_item, peel_blocks};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -9,19 +10,12 @@ use rustc_lint::LateContext;
use rustc_middle::mir::Mutability;
use rustc_middle::ty;
use rustc_middle::ty::adjustment::Adjust;
use rustc_semver::RustcVersion;
use rustc_span::symbol::Ident;
use rustc_span::{sym, Span};
use super::MAP_CLONE;
pub(super) fn check(
cx: &LateContext<'_>,
e: &hir::Expr<'_>,
recv: &hir::Expr<'_>,
arg: &hir::Expr<'_>,
msrv: Option<RustcVersion>,
) {
pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
if_chain! {
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id);
if cx.tcx.impl_of_method(method_id)
@ -97,10 +91,10 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) {
);
}
fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: Option<RustcVersion>) {
fn lint_explicit_closure(cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool, msrv: &Msrv) {
let mut applicability = Applicability::MachineApplicable;
let (message, sugg_method) = if is_copy && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
let (message, sugg_method) = if is_copy && msrv.meets(msrvs::ITERATOR_COPIED) {
("you are using an explicit closure for copying elements", "copied")
} else {
("you are using an explicit closure for cloning elements", "cloned")

View File

@ -1,12 +1,11 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{meets_msrv, msrvs};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_semver::RustcVersion;
use rustc_span::symbol::sym;
use super::MAP_UNWRAP_OR;
@ -19,13 +18,13 @@ pub(super) fn check<'tcx>(
recv: &'tcx hir::Expr<'_>,
map_arg: &'tcx hir::Expr<'_>,
unwrap_arg: &'tcx hir::Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> bool {
// lint if the caller of `map()` is an `Option`
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
if is_result && !meets_msrv(msrv, msrvs::RESULT_MAP_OR_ELSE) {
if is_result && !msrv.meets(msrvs::RESULT_MAP_OR_ELSE) {
return false;
}

View File

@ -104,8 +104,9 @@ mod zst_offset;
use bind_instead_of_map::BindInsteadOfMap;
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, meets_msrv, msrvs, return_ty};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
@ -113,7 +114,6 @@ use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
@ -3163,7 +3163,7 @@ declare_clippy_lint! {
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
}
@ -3172,7 +3172,7 @@ impl Methods {
#[must_use]
pub fn new(
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
) -> Self {
@ -3325,7 +3325,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
single_char_add_str::check(cx, expr, receiver, args);
into_iter_on_ref::check(cx, expr, method_span, method_call.ident.name, receiver);
single_char_pattern::check(cx, expr, method_call.ident.name, receiver, args);
unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, self.msrv);
unnecessary_to_owned::check(cx, expr, method_call.ident.name, receiver, args, &self.msrv);
},
hir::ExprKind::Binary(op, lhs, rhs) if op.node == hir::BinOpKind::Eq || op.node == hir::BinOpKind::Ne => {
let mut info = BinaryExprInfo {
@ -3501,7 +3501,7 @@ impl Methods {
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, self.msrv),
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv),
("collect", []) if is_trait_method(cx, expr, sym::Iterator) => {
needless_collect::check(cx, span, expr, recv, call_span);
match method_call(recv) {
@ -3512,7 +3512,7 @@ impl Methods {
map_collect_result_unit::check(cx, expr, m_recv, m_arg);
},
Some(("take", take_self_arg, [take_arg], _, _)) => {
if meets_msrv(self.msrv, msrvs::STR_REPEAT) {
if self.msrv.meets(msrvs::STR_REPEAT) {
manual_str_repeat::check(cx, expr, recv, take_self_arg, take_arg);
}
},
@ -3539,7 +3539,7 @@ impl Methods {
},
("expect", [_]) => match method_call(recv) {
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, self.msrv, span, err_span),
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
},
("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests),
@ -3578,7 +3578,7 @@ impl Methods {
unit_hash::check(cx, expr, recv, arg);
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
("iter" | "iter_mut" | "into_iter", []) => {
@ -3601,7 +3601,7 @@ impl Methods {
},
(name @ ("map" | "map_err"), [m_arg]) => {
if name == "map" {
map_clone::check(cx, expr, recv, m_arg, self.msrv);
map_clone::check(cx, expr, recv, m_arg, &self.msrv);
if let Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) = method_call(recv) {
iter_kv_map::check(cx, map_name, expr, recv2, m_arg);
}
@ -3610,8 +3610,8 @@ impl Methods {
}
if let Some((name, recv2, args, span2,_)) = method_call(recv) {
match (name, args) {
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, self.msrv),
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, self.msrv),
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, &self.msrv),
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, &self.msrv),
("filter", [f_arg]) => {
filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
},
@ -3632,7 +3632,7 @@ impl Methods {
match (name2, args2) {
("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
("filter", [arg]) => filter_next::check(cx, expr, recv2, arg),
("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv),
("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, &self.msrv),
("iter", []) => iter_next_slice::check(cx, expr, recv2),
("skip", [arg]) => iter_skip_next::check(cx, expr, recv2, arg),
("skip_while", [_]) => skip_while_next::check(cx, expr),
@ -3680,10 +3680,10 @@ impl Methods {
vec_resize_to_zero::check(cx, expr, count_arg, default_arg, span);
},
("seek", [arg]) => {
if meets_msrv(self.msrv, msrvs::SEEK_FROM_CURRENT) {
if self.msrv.meets(msrvs::SEEK_FROM_CURRENT) {
seek_from_current::check(cx, expr, recv, arg);
}
if meets_msrv(self.msrv, msrvs::SEEK_REWIND) {
if self.msrv.meets(msrvs::SEEK_REWIND) {
seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
}
},
@ -3699,7 +3699,7 @@ impl Methods {
("splitn" | "rsplitn", [count_arg, pat_arg]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
str_splitn::check(cx, name, expr, recv, pat_arg, count, self.msrv);
str_splitn::check(cx, name, expr, recv, pat_arg, count, &self.msrv);
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
@ -3717,7 +3717,7 @@ impl Methods {
},
("take", []) => needless_option_take::check(cx, expr, recv),
("then", [arg]) => {
if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) {
if !self.msrv.meets(msrvs::BOOL_THEN_SOME) {
return;
}
unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some");
@ -3760,7 +3760,7 @@ impl Methods {
},
("unwrap_or_else", [u_arg]) => match method_call(recv) {
Some(("map", recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
_ => {
unwrap_or_else_default::check(cx, expr, recv, u_arg);
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");

View File

@ -1,13 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_def_path, meets_msrv, msrvs, path_to_local_id, paths, peel_blocks};
use clippy_utils::{match_def_path, path_to_local_id, paths, peel_blocks};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_span::sym;
use super::OPTION_AS_REF_DEREF;
@ -19,9 +19,9 @@ pub(super) fn check(
as_ref_recv: &hir::Expr<'_>,
map_arg: &hir::Expr<'_>,
is_mut: bool,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if !meets_msrv(msrv, msrvs::OPTION_AS_DEREF) {
if !msrv.meets(msrvs::OPTION_AS_DEREF) {
return;
}

View File

@ -1,9 +1,10 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_context;
use clippy_utils::usage::local_used_after_expr;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{is_diag_item_method, match_def_path, meets_msrv, msrvs, path_to_local_id, paths};
use clippy_utils::{is_diag_item_method, match_def_path, path_to_local_id, paths};
use core::ops::ControlFlow;
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -12,7 +13,6 @@ use rustc_hir::{
};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_span::{sym, Span, Symbol, SyntaxContext};
use super::{MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN};
@ -24,7 +24,7 @@ pub(super) fn check(
self_arg: &Expr<'_>,
pat_arg: &Expr<'_>,
count: u128,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if count < 2 || !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
@ -34,7 +34,7 @@ pub(super) fn check(
IterUsageKind::Nth(n) => count > n + 1,
IterUsageKind::NextTuple => count > 2,
};
let manual = count == 2 && meets_msrv(msrv, msrvs::STR_SPLIT_ONCE);
let manual = count == 2 && msrv.meets(msrvs::STR_SPLIT_ONCE);
match parse_iter_usage(cx, expr.span.ctxt(), cx.tcx.hir().parent_iter(expr.hir_id)) {
Some(usage) if needless(usage.kind) => lint_needless(cx, method_name, expr, self_arg, pat_arg),

View File

@ -1,13 +1,11 @@
use super::implicit_clone::is_clone_like;
use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
use clippy_utils::visitors::find_all_ret_expressions;
use clippy_utils::{
fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty,
};
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty};
use rustc_errors::Applicability;
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, ItemKind, Node};
use rustc_hir_typeck::{FnCtxt, Inherited};
@ -16,14 +14,9 @@ use rustc_lint::LateContext;
use rustc_middle::mir::Mutability;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, OverloadedDeref};
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, SubstsRef};
use rustc_middle::ty::EarlyBinder;
use rustc_middle::ty::{self, Clause, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
use rustc_semver::RustcVersion;
use rustc_middle::ty::{self, Clause, EarlyBinder, ParamTy, PredicateKind, ProjectionPredicate, TraitPredicate, Ty};
use rustc_span::{sym, Symbol};
use rustc_trait_selection::traits::{
query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause,
};
use std::cmp::max;
use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
use super::UNNECESSARY_TO_OWNED;
@ -33,7 +26,7 @@ pub fn check<'tcx>(
method_name: Symbol,
receiver: &'tcx Expr<'_>,
args: &'tcx [Expr<'_>],
msrv: Option<RustcVersion>,
msrv: &Msrv,
) {
if_chain! {
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
@ -204,7 +197,7 @@ fn check_into_iter_call_arg(
expr: &Expr<'_>,
method_name: Symbol,
receiver: &Expr<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> bool {
if_chain! {
if let Some(parent) = get_parent_expr(cx, expr);
@ -219,7 +212,7 @@ fn check_into_iter_call_arg(
if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
return true;
}
let cloned_or_copied = if is_copy(cx, item_ty) && meets_msrv(msrv, msrvs::ITERATOR_COPIED) {
let cloned_or_copied = if is_copy(cx, item_ty) && msrv.meets(msrvs::ITERATOR_COPIED) {
"copied"
} else {
"cloned"
@ -267,11 +260,22 @@ fn check_other_call_arg<'tcx>(
if let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef);
if trait_predicate.def_id() == deref_trait_id || trait_predicate.def_id() == as_ref_trait_id;
let receiver_ty = cx.typeck_results().expr_ty(receiver);
if can_change_type(cx, maybe_arg, receiver_ty);
// We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
// `Target = T`.
if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
let n_refs = max(n_refs, usize::from(!is_copy(cx, receiver_ty)));
if let Some((n_refs, receiver_ty)) = if n_refs > 0 || is_copy(cx, receiver_ty) {
Some((n_refs, receiver_ty))
} else if trait_predicate.def_id() != deref_trait_id {
Some((1, cx.tcx.mk_ref(
cx.tcx.lifetimes.re_erased,
ty::TypeAndMut {
ty: receiver_ty,
mutbl: Mutability::Not,
},
)))
} else {
None
};
if can_change_type(cx, maybe_arg, receiver_ty);
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
span_lint_and_sugg(
@ -345,13 +349,13 @@ fn get_input_traits_and_projections<'tcx>(
if trait_predicate.trait_ref.self_ty() == input {
trait_predicates.push(trait_predicate);
}
}
},
PredicateKind::Clause(Clause::Projection(projection_predicate)) => {
if projection_predicate.projection_ty.self_ty() == input {
projection_predicates.push(projection_predicate);
}
}
_ => {}
},
_ => {},
}
}
(trait_predicates, projection_predicates)
@ -403,10 +407,12 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
let mut trait_predicates = cx.tcx.param_env(callee_def_id)
.caller_bounds().iter().filter(|predicate| {
if let PredicateKind::Clause(Clause::Trait(trait_predicate)) = predicate.kind().skip_binder()
&& trait_predicate.trait_ref.self_ty() == *param_ty {
true
} else {
if let PredicateKind::Clause(Clause::Trait(trait_predicate))
= predicate.kind().skip_binder()
&& trait_predicate.trait_ref.self_ty() == *param_ty
{
true
} else {
false
}
});
@ -466,12 +472,7 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id:
/// Returns true if the named method can be used to convert the receiver to its "owned"
/// representation.
fn is_to_owned_like<'a>(
cx: &LateContext<'a>,
call_expr: &Expr<'a>,
method_name: Symbol,
method_def_id: DefId,
) -> bool {
fn is_to_owned_like<'a>(cx: &LateContext<'a>, call_expr: &Expr<'a>, method_name: Symbol, method_def_id: DefId) -> bool {
is_clone_like(cx, method_name.as_str(), method_def_id)
|| is_cow_into_owned(cx, method_name, method_def_id)
|| is_to_string_on_string_like(cx, call_expr, method_name, method_def_id)

View File

@ -1,9 +1,8 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
use clippy_utils::ty::has_drop;
use clippy_utils::{
fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, meets_msrv, msrvs, trait_ref_of_method,
};
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method};
use rustc_hir as hir;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind;
@ -11,7 +10,6 @@ use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
@ -75,12 +73,12 @@ declare_clippy_lint! {
impl_lint_pass!(MissingConstForFn => [MISSING_CONST_FOR_FN]);
pub struct MissingConstForFn {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl MissingConstForFn {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -95,7 +93,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
span: Span,
hir_id: HirId,
) {
if !meets_msrv(self.msrv, msrvs::CONST_IF_MATCH) {
if !self.msrv.meets(msrvs::CONST_IF_MATCH) {
return;
}
@ -152,7 +150,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv) {
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, &self.msrv) {
if cx.tcx.is_const_fn_raw(def_id.to_def_id()) {
cx.tcx.sess.span_err(span, err.as_ref());
}

View File

@ -8,6 +8,7 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_from_proc_macro;
use if_chain::if_chain;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext};

View File

@ -92,10 +92,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View File

@ -1,7 +1,9 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::ptr::get_spans;
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy, is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::ty::{
implements_trait, implements_trait_with_env, is_copy, is_type_diagnostic_item, is_type_lang_item,
};
use clippy_utils::{get_trait_def_id, is_self, paths};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
@ -124,7 +126,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
.filter_map(|obligation| {
// Note that we do not want to deal with qualified predicates here.
match obligation.predicate.kind().no_bound_vars() {
Some(ty::PredicateKind::Clause(ty::Clause::Trait(pred))) if pred.def_id() != sized_trait => Some(pred),
Some(ty::PredicateKind::Clause(ty::Clause::Trait(pred))) if pred.def_id() != sized_trait => {
Some(pred)
},
_ => None,
}
})

View File

@ -6,7 +6,8 @@ use clippy_utils::ty::has_drop;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{is_range_literal, BinOpKind, BlockCheckMode, Expr, ExprKind, PatKind, Stmt, StmtKind, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::ops::Deref;
@ -159,8 +160,11 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
if_chain! {
if let StmtKind::Semi(expr) = stmt.kind;
let ctxt = stmt.span.ctxt();
if expr.span.ctxt() == ctxt;
if let Some(reduced) = reduce_expression(cx, expr);
if !&reduced.iter().any(|e| e.span.from_expansion());
if !in_external_macro(cx.sess(), stmt.span);
if reduced.iter().all(|e| e.span.ctxt() == ctxt);
then {
if let ExprKind::Index(..) = &expr.kind {
let snippet = if let (Some(arr), Some(func)) =

View File

@ -490,7 +490,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
ty_name: name.ident.name,
method_renames,
ref_prefix: RefPrefix {
lt: lt.clone(),
lt: *lt,
mutability,
},
deref_ty,
@ -693,9 +693,10 @@ fn matches_preds<'tcx>(
cx.tcx,
ObligationCause::dummy(),
cx.param_env,
cx.tcx.mk_predicate(Binder::dummy(
PredicateKind::Clause(Clause::Projection(p.with_self_ty(cx.tcx, ty))),
)),
cx.tcx
.mk_predicate(Binder::dummy(PredicateKind::Clause(Clause::Projection(
p.with_self_ty(cx.tcx, ty),
)))),
)),
ExistentialPredicate::AutoTrait(p) => infcx
.type_implements_trait(p, [ty], cx.param_env)

View File

@ -1,16 +1,16 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local};
use clippy_utils::{get_parent_expr, in_constant, is_integer_const, path_to_local};
use if_chain::if_chain;
use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::{Span, Spanned};
use std::cmp::Ordering;
@ -161,12 +161,12 @@ declare_clippy_lint! {
}
pub struct Ranges {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl Ranges {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -181,7 +181,7 @@ impl_lint_pass!(Ranges => [
impl<'tcx> LateLintPass<'tcx> for Ranges {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref op, l, r) = expr.kind {
if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
if self.msrv.meets(msrvs::RANGE_CONTAINS) {
check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
}
}

View File

@ -81,8 +81,8 @@ impl EarlyLintPass for RedundantClosureCall {
"try not to call a closure in the expression where it is declared",
|diag| {
if fn_decl.inputs.is_empty() {
let app = Applicability::MachineApplicable;
let mut hint = Sugg::ast(cx, body, "..");
let mut app = Applicability::MachineApplicable;
let mut hint = Sugg::ast(cx, body, "..", closure.span.ctxt(), &mut app);
if asyncness.is_async() {
// `async x` is a syntax error, so it becomes `async { x }`

View File

@ -1,10 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::msrvs::{self, Msrv};
use rustc_ast::ast::{Expr, ExprKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -37,12 +36,12 @@ declare_clippy_lint! {
}
pub struct RedundantFieldNames {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl RedundantFieldNames {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -51,7 +50,7 @@ impl_lint_pass!(RedundantFieldNames => [REDUNDANT_FIELD_NAMES]);
impl EarlyLintPass for RedundantFieldNames {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if !meets_msrv(self.msrv, msrvs::FIELD_INIT_SHORTHAND) {
if !self.msrv.meets(msrvs::FIELD_INIT_SHORTHAND) {
return;
}

View File

@ -1,10 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
use clippy_utils::{meets_msrv, msrvs};
use rustc_ast::ast::{Item, ItemKind, Ty, TyKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
@ -34,12 +33,12 @@ declare_clippy_lint! {
}
pub struct RedundantStaticLifetimes {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl RedundantStaticLifetimes {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -96,7 +95,7 @@ impl RedundantStaticLifetimes {
impl EarlyLintPass for RedundantStaticLifetimes {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if !meets_msrv(self.msrv, msrvs::STATIC_IN_CONST) {
if !self.msrv.meets(msrvs::STATIC_IN_CONST) {
return;
}

View File

@ -12,6 +12,7 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::subst::GenericArgKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::{BytePos, Pos};
declare_clippy_lint! {
/// ### What it does
@ -209,13 +210,14 @@ fn check_final_expr<'tcx>(
if cx.tcx.hir().attrs(expr.hir_id).is_empty() {
let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner));
if !borrows {
emit_return_lint(
cx,
peeled_drop_expr.span,
semi_spans,
inner.as_ref().map(|i| i.span),
replacement,
);
// check if expr return nothing
let ret_span = if inner.is_none() && replacement == RetReplacement::Empty {
extend_span_to_previous_non_ws(cx, peeled_drop_expr.span)
} else {
peeled_drop_expr.span
};
emit_return_lint(cx, ret_span, semi_spans, inner.as_ref().map(|i| i.span), replacement);
}
}
},
@ -289,3 +291,16 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
})
.is_some()
}
// Go backwards while encountering whitespace and extend the given Span to that point.
fn extend_span_to_previous_non_ws(cx: &LateContext<'_>, sp: Span) -> Span {
if let Ok(prev_source) = cx.sess().source_map().span_to_prev_source(sp) {
let ws = [' ', '\t', '\n'];
if let Some(non_ws_pos) = prev_source.rfind(|c| !ws.contains(&c)) {
let len = prev_source.len() - non_ws_pos - 1;
return sp.with_lo(sp.lo() - BytePos::from_usize(len));
}
}
sp
}

View File

@ -55,11 +55,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
if matches!(cx.tcx.def_kind(id.owner_id), DefKind::Impl)
&& let item = cx.tcx.hir().item(id)
&& let ItemKind::Impl(Impl {
items,
of_trait,
self_ty,
..
}) = &item.kind
items,
of_trait,
self_ty,
..
}) = &item.kind
&& let TyKind::Path(QPath::Resolved(_, Path { res, .. })) = self_ty.kind
{
if !map.contains_key(res) {

View File

@ -16,10 +16,10 @@ mod utils;
mod wrong_transmute;
use clippy_utils::in_constant;
use clippy_utils::msrvs::Msrv;
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::sym;
@ -410,7 +410,7 @@ declare_clippy_lint! {
}
pub struct Transmute {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl_lint_pass!(Transmute => [
CROSSPOINTER_TRANSMUTE,
@ -431,7 +431,7 @@ impl_lint_pass!(Transmute => [
]);
impl Transmute {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -461,7 +461,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
| transmuting_null::check(cx, e, arg, to_ty)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv)
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, &self.msrv)
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)

View File

@ -1,12 +1,12 @@
use super::TRANSMUTE_PTR_TO_REF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{meets_msrv, msrvs, sugg};
use clippy_utils::sugg;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, GenericArg, Mutability, Path, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, TypeVisitable};
use rustc_semver::RustcVersion;
/// Checks for `transmute_ptr_to_ref` lint.
/// Returns `true` if it's triggered, otherwise returns `false`.
@ -17,7 +17,7 @@ pub(super) fn check<'tcx>(
to_ty: Ty<'tcx>,
arg: &'tcx Expr<'_>,
path: &'tcx Path<'_>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => {
@ -37,7 +37,7 @@ pub(super) fn check<'tcx>(
let sugg = if let Some(ty) = get_explicit_type(path) {
let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app);
if meets_msrv(msrv, msrvs::POINTER_CAST) {
if msrv.meets(msrvs::POINTER_CAST) {
format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_par())
} else if from_ptr_ty.has_erased_regions() {
sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string()
@ -46,7 +46,7 @@ pub(super) fn check<'tcx>(
}
} else if from_ptr_ty.ty == *to_ref_ty {
if from_ptr_ty.has_erased_regions() {
if meets_msrv(msrv, msrvs::POINTER_CAST) {
if msrv.meets(msrvs::POINTER_CAST) {
format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_par())
} else {
sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}")))

View File

@ -1,6 +1,10 @@
use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::walk_span_to_context;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{get_parent_node, is_lint_allowed};
use hir::HirId;
use rustc_data_structures::sync::Lrc;
use rustc_hir as hir;
use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
@ -59,11 +63,39 @@ declare_clippy_lint! {
restriction,
"creating an unsafe block without explaining why it is safe"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `// SAFETY: ` comments on safe code.
///
/// ### Why is this bad?
/// Safe code has no safety requirements, so there is no need to
/// describe safety invariants.
///
/// ### Example
/// ```rust
/// use std::ptr::NonNull;
/// let a = &mut 42;
///
/// // SAFETY: references are guaranteed to be non-null.
/// let ptr = NonNull::new(a).unwrap();
/// ```
/// Use instead:
/// ```rust
/// use std::ptr::NonNull;
/// let a = &mut 42;
///
/// let ptr = NonNull::new(a).unwrap();
/// ```
#[clippy::version = "1.67.0"]
pub UNNECESSARY_SAFETY_COMMENT,
restriction,
"annotating safe code with a safety comment"
}
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
declare_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS, UNNECESSARY_SAFETY_COMMENT]);
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
&& !in_external_macro(cx.tcx.sess, block.span)
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
@ -87,35 +119,175 @@ impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
"consider adding a safety comment on the preceding line",
);
}
if let Some(tail) = block.expr
&& !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
&& !in_external_macro(cx.tcx.sess, tail.span)
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id)
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
{
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
tail.span,
"expression has unnecessary safety comment",
Some(help_span),
"consider removing the safety comment",
);
}
}
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &hir::Stmt<'tcx>) {
let (
hir::StmtKind::Local(&hir::Local { init: Some(expr), .. })
| hir::StmtKind::Expr(expr)
| hir::StmtKind::Semi(expr)
) = stmt.kind else { return };
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
&& !in_external_macro(cx.tcx.sess, stmt.span)
&& let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id)
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
{
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
stmt.span,
"statement has unnecessary safety comment",
Some(help_span),
"consider removing the safety comment",
);
}
}
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if let hir::ItemKind::Impl(imple) = item.kind
&& imple.unsafety == hir::Unsafety::Unsafe
&& !in_external_macro(cx.tcx.sess, item.span)
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
&& !is_unsafe_from_proc_macro(cx, item.span)
&& !item_has_safety_comment(cx, item)
{
if in_external_macro(cx.tcx.sess, item.span) {
return;
}
let mk_spans = |pos: BytePos| {
let source_map = cx.tcx.sess.source_map();
let span = Span::new(pos, pos, SyntaxContext::root(), None);
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
let span = if source_map.is_multiline(item.span) {
source_map.span_until_char(item.span, '\n')
} else {
item.span
};
(span, help_span)
};
span_lint_and_help(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe impl missing a safety comment",
None,
"consider adding a safety comment on the preceding line",
);
let item_has_safety_comment = item_has_safety_comment(cx, item);
match (&item.kind, item_has_safety_comment) {
// lint unsafe impl without safety comment
(hir::ItemKind::Impl(impl_), HasSafetyComment::No) if impl_.unsafety == hir::Unsafety::Unsafe => {
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
&& !is_unsafe_from_proc_macro(cx, item.span)
{
let source_map = cx.tcx.sess.source_map();
let span = if source_map.is_multiline(item.span) {
source_map.span_until_char(item.span, '\n')
} else {
item.span
};
span_lint_and_help(
cx,
UNDOCUMENTED_UNSAFE_BLOCKS,
span,
"unsafe impl missing a safety comment",
None,
"consider adding a safety comment on the preceding line",
);
}
},
// lint safe impl with unnecessary safety comment
(hir::ItemKind::Impl(impl_), HasSafetyComment::Yes(pos)) if impl_.unsafety == hir::Unsafety::Normal => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
let (span, help_span) = mk_spans(pos);
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
"impl has unnecessary safety comment",
Some(help_span),
"consider removing the safety comment",
);
}
},
(hir::ItemKind::Impl(_), _) => {},
// const and static items only need a safety comment if their body is an unsafe block, lint otherwise
(&hir::ItemKind::Const(.., body) | &hir::ItemKind::Static(.., body), HasSafetyComment::Yes(pos)) => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
let body = cx.tcx.hir().body(body);
if !matches!(
body.value.kind, hir::ExprKind::Block(block, _)
if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
) {
let (span, help_span) = mk_spans(pos);
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
&format!("{} has unnecessary safety comment", item.kind.descr()),
Some(help_span),
"consider removing the safety comment",
);
}
}
},
// Aside from unsafe impls and consts/statics with an unsafe block, items in general
// do not have safety invariants that need to be documented, so lint those.
(_, HasSafetyComment::Yes(pos)) => {
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
let (span, help_span) = mk_spans(pos);
span_lint_and_help(
cx,
UNNECESSARY_SAFETY_COMMENT,
span,
&format!("{} has unnecessary safety comment", item.kind.descr()),
Some(help_span),
"consider removing the safety comment",
);
}
},
_ => (),
}
}
}
fn expr_has_unnecessary_safety_comment<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
comment_pos: BytePos,
) -> Option<Span> {
// this should roughly be the reverse of `block_parents_have_safety_comment`
if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
hir::ExprKind::Block(
Block {
rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
..
},
_,
) => ControlFlow::Break(()),
// statements will be handled by check_stmt itself again
hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
_ => ControlFlow::Continue(Descend::Yes),
})
.is_some()
{
return None;
}
let source_map = cx.tcx.sess.source_map();
let span = Span::new(comment_pos, comment_pos, SyntaxContext::root(), None);
let help_span = source_map.span_extend_to_next_char(span, '\n', true);
Some(help_span)
}
fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
let file_pos = source_map.lookup_byte_offset(span.lo());
@ -170,85 +342,134 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
// won't work. This is to avoid dealing with where such a comment should be place relative to
// attributes and doc comments.
span_from_macro_expansion_has_safety_comment(cx, span) || span_in_body_has_safety_comment(cx, span)
matches!(
span_from_macro_expansion_has_safety_comment(cx, span),
HasSafetyComment::Yes(_)
) || span_in_body_has_safety_comment(cx, span)
}
enum HasSafetyComment {
Yes(BytePos),
No,
Maybe,
}
/// Checks if the lines immediately preceding the item contain a safety comment.
#[allow(clippy::collapsible_match)]
fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> bool {
if span_from_macro_expansion_has_safety_comment(cx, item.span) {
return true;
fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSafetyComment {
match span_from_macro_expansion_has_safety_comment(cx, item.span) {
HasSafetyComment::Maybe => (),
has_safety_comment => return has_safety_comment,
}
if item.span.ctxt() == SyntaxContext::root() {
if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
let comment_start = match parent_node {
Node::Crate(parent_mod) => {
comment_start_before_impl_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
},
Node::Item(parent_item) => {
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
comment_start_before_impl_in_mod(cx, parent_mod, parent_item.span, item)
} else {
// Doesn't support impls in this position. Pretend a comment was found.
return true;
}
},
Node::Stmt(stmt) => {
if let Some(stmt_parent) = get_parent_node(cx.tcx, stmt.hir_id) {
match stmt_parent {
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
_ => {
// Doesn't support impls in this position. Pretend a comment was found.
return true;
},
}
} else {
// Problem getting the parent node. Pretend a comment was found.
return true;
}
},
_ => {
if item.span.ctxt() != SyntaxContext::root() {
return HasSafetyComment::No;
}
if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
let comment_start = match parent_node {
Node::Crate(parent_mod) => {
comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
},
Node::Item(parent_item) => {
if let ItemKind::Mod(parent_mod) = &parent_item.kind {
comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
} else {
// Doesn't support impls in this position. Pretend a comment was found.
return true;
},
};
return HasSafetyComment::Maybe;
}
},
Node::Stmt(stmt) => {
if let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) {
walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
} else {
// Problem getting the parent node. Pretend a comment was found.
return HasSafetyComment::Maybe;
}
},
_ => {
// Doesn't support impls in this position. Pretend a comment was found.
return HasSafetyComment::Maybe;
},
};
let source_map = cx.sess().source_map();
if let Some(comment_start) = comment_start
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
unsafe_line.sf.lines(|lines| {
comment_start_line.line < unsafe_line.line && text_has_safety_comment(
let source_map = cx.sess().source_map();
if let Some(comment_start) = comment_start
&& let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
return unsafe_line.sf.lines(|lines| {
if comment_start_line.line >= unsafe_line.line {
HasSafetyComment::No
} else {
match text_has_safety_comment(
src,
&lines[comment_start_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
)
})
} else {
// Problem getting source text. Pretend a comment was found.
true
}
} else {
// No parent node. Pretend a comment was found.
true
) {
Some(b) => HasSafetyComment::Yes(b),
None => HasSafetyComment::No,
}
}
});
}
} else {
false
}
HasSafetyComment::Maybe
}
fn comment_start_before_impl_in_mod(
/// Checks if the lines immediately preceding the item contain a safety comment.
#[allow(clippy::collapsible_match)]
fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
match span_from_macro_expansion_has_safety_comment(cx, span) {
HasSafetyComment::Maybe => (),
has_safety_comment => return has_safety_comment,
}
if span.ctxt() != SyntaxContext::root() {
return HasSafetyComment::No;
}
if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
let comment_start = match parent_node {
Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
_ => return HasSafetyComment::Maybe,
};
let source_map = cx.sess().source_map();
if let Some(comment_start) = comment_start
&& let Ok(unsafe_line) = source_map.lookup_line(span.lo())
&& let Ok(comment_start_line) = source_map.lookup_line(comment_start)
&& Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
return unsafe_line.sf.lines(|lines| {
if comment_start_line.line >= unsafe_line.line {
HasSafetyComment::No
} else {
match text_has_safety_comment(
src,
&lines[comment_start_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
) {
Some(b) => HasSafetyComment::Yes(b),
None => HasSafetyComment::No,
}
}
});
}
}
HasSafetyComment::Maybe
}
fn comment_start_before_item_in_mod(
cx: &LateContext<'_>,
parent_mod: &hir::Mod<'_>,
parent_mod_span: Span,
imple: &hir::Item<'_>,
item: &hir::Item<'_>,
) -> Option<BytePos> {
parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
if *item_id == imple.item_id() {
if *item_id == item.item_id() {
if idx == 0 {
// mod A { /* comment */ unsafe impl T {} ... }
// ^------------------------------------------^ returns the start of this span
@ -270,11 +491,11 @@ fn comment_start_before_impl_in_mod(
})
}
fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> HasSafetyComment {
let source_map = cx.sess().source_map();
let ctxt = span.ctxt();
if ctxt == SyntaxContext::root() {
false
HasSafetyComment::Maybe
} else {
// From a macro expansion. Get the text from the start of the macro declaration to start of the
// unsafe block.
@ -286,15 +507,22 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span
&& let Some(src) = unsafe_line.sf.src.as_deref()
{
unsafe_line.sf.lines(|lines| {
macro_line.line < unsafe_line.line && text_has_safety_comment(
src,
&lines[macro_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
)
if macro_line.line < unsafe_line.line {
match text_has_safety_comment(
src,
&lines[macro_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
) {
Some(b) => HasSafetyComment::Yes(b),
None => HasSafetyComment::No,
}
} else {
HasSafetyComment::No
}
})
} else {
// Problem getting source text. Pretend a comment was found.
true
HasSafetyComment::Maybe
}
}
}
@ -333,7 +561,7 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
src,
&lines[body_line.line + 1..=unsafe_line.line],
unsafe_line.sf.start_pos.to_usize(),
)
).is_some()
})
} else {
// Problem getting source text. Pretend a comment was found.
@ -345,30 +573,34 @@ fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
}
/// Checks if the given text has a safety comment for the immediately proceeding line.
fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> bool {
fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> Option<BytePos> {
let mut lines = line_starts
.array_windows::<2>()
.rev()
.map_while(|[start, end]| {
let start = start.to_usize() - offset;
let end = end.to_usize() - offset;
src.get(start..end).map(|text| (start, text.trim_start()))
let text = src.get(start..end)?;
let trimmed = text.trim_start();
Some((start + (text.len() - trimmed.len()), trimmed))
})
.filter(|(_, text)| !text.is_empty());
let Some((line_start, line)) = lines.next() else {
return false;
return None;
};
// Check for a sequence of line comments.
if line.starts_with("//") {
let mut line = line;
let (mut line, mut line_start) = (line, line_start);
loop {
if line.to_ascii_uppercase().contains("SAFETY:") {
return true;
return Some(BytePos(
u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
));
}
match lines.next() {
Some((_, x)) if x.starts_with("//") => line = x,
_ => return false,
Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s),
_ => return None,
}
}
}
@ -377,16 +609,19 @@ fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) ->
let (mut line_start, mut line) = (line_start, line);
loop {
if line.starts_with("/*") {
let src = src[line_start..line_starts.last().unwrap().to_usize() - offset].trim_start();
let src = &src[line_start..line_starts.last().unwrap().to_usize() - offset];
let mut tokens = tokenize(src);
return src[..tokens.next().unwrap().len as usize]
return (src[..tokens.next().unwrap().len as usize]
.to_ascii_uppercase()
.contains("SAFETY:")
&& tokens.all(|t| t.kind == TokenKind::Whitespace);
&& tokens.all(|t| t.kind == TokenKind::Whitespace))
.then_some(BytePos(
u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
));
}
match lines.next() {
Some(x) => (line_start, line) = x,
None => return false,
None => return None,
}
}
}

View File

@ -2,14 +2,14 @@
use clippy_utils::ast_utils::{eq_field_pat, eq_id, eq_maybe_qself, eq_pat, eq_path};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{meets_msrv, msrvs, over};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::over;
use rustc_ast::mut_visit::*;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, Mutability, Pat, PatKind, PatKind::*, DUMMY_NODE_ID};
use rustc_ast_pretty::pprust;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::DUMMY_SP;
@ -45,14 +45,13 @@ declare_clippy_lint! {
"unnested or-patterns, e.g., `Foo(Bar) | Foo(Baz) instead of `Foo(Bar | Baz)`"
}
#[derive(Clone, Copy)]
pub struct UnnestedOrPatterns {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl UnnestedOrPatterns {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}
@ -61,13 +60,13 @@ impl_lint_pass!(UnnestedOrPatterns => [UNNESTED_OR_PATTERNS]);
impl EarlyLintPass for UnnestedOrPatterns {
fn check_arm(&mut self, cx: &EarlyContext<'_>, a: &ast::Arm) {
if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
if self.msrv.meets(msrvs::OR_PATTERNS) {
lint_unnested_or_patterns(cx, &a.pat);
}
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
if self.msrv.meets(msrvs::OR_PATTERNS) {
if let ast::ExprKind::Let(pat, _, _) = &e.kind {
lint_unnested_or_patterns(cx, pat);
}
@ -75,13 +74,13 @@ impl EarlyLintPass for UnnestedOrPatterns {
}
fn check_param(&mut self, cx: &EarlyContext<'_>, p: &ast::Param) {
if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
if self.msrv.meets(msrvs::OR_PATTERNS) {
lint_unnested_or_patterns(cx, &p.pat);
}
}
fn check_local(&mut self, cx: &EarlyContext<'_>, l: &ast::Local) {
if meets_msrv(self.msrv, msrvs::OR_PATTERNS) {
if self.msrv.meets(msrvs::OR_PATTERNS) {
lint_unnested_or_patterns(cx, &l.pat);
}
}

View File

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use rustc_ast::ast::{Expr, ExprKind, MethodCall};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
@ -29,22 +30,16 @@ declare_clippy_lint! {
}
declare_lint_pass!(UnusedRounding => [UNUSED_ROUNDING]);
fn is_useless_rounding(expr: &Expr) -> Option<(&str, String)> {
fn is_useless_rounding<'a>(cx: &EarlyContext<'_>, expr: &'a Expr) -> Option<(&'a str, String)> {
if let ExprKind::MethodCall(box MethodCall { seg:name_ident, receiver, .. }) = &expr.kind
&& let method_name = name_ident.ident.name.as_str()
&& (method_name == "ceil" || method_name == "round" || method_name == "floor")
&& let ExprKind::Lit(token_lit) = &receiver.kind
&& token_lit.is_semantic_float() {
let mut f_str = token_lit.symbol.to_string();
let f = f_str.trim_end_matches('_').parse::<f64>().unwrap();
if let Some(suffix) = token_lit.suffix {
f_str.push_str(suffix.as_str());
}
if f.fract() == 0.0 {
Some((method_name, f_str))
} else {
None
}
&& token_lit.is_semantic_float()
&& let Ok(f) = token_lit.symbol.as_str().replace('_', "").parse::<f64>() {
(f.fract() == 0.0).then(||
(method_name, snippet(cx, receiver.span, "..").to_string())
)
} else {
None
}
@ -52,7 +47,7 @@ fn is_useless_rounding(expr: &Expr) -> Option<(&str, String)> {
impl EarlyLintPass for UnusedRounding {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let Some((method_name, float)) = is_useless_rounding(expr) {
if let Some((method_name, float)) = is_useless_rounding(cx, expr) {
span_lint_and_sugg(
cx,
UNUSED_ROUNDING,

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::ty::same_type_and_consts;
use clippy_utils::{is_from_proc_macro, meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
@ -14,7 +15,6 @@ use rustc_hir::{
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
@ -57,13 +57,13 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct UseSelf {
msrv: Option<RustcVersion>,
msrv: Msrv,
stack: Vec<StackItem>,
}
impl UseSelf {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
..Self::default()
@ -199,7 +199,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) {
if_chain! {
if !hir_ty.span.from_expansion();
if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if let Some(&StackItem::Check {
impl_id,
in_body,
@ -228,7 +228,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if !expr.span.from_expansion();
if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
if cx.typeck_results().expr_ty(expr) == cx.tcx.type_of(impl_id);
then {} else { return; }
@ -248,7 +248,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
if_chain! {
if !pat.span.from_expansion();
if meets_msrv(self.msrv, msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
// get the path from the pattern
if let PatKind::Path(QPath::Resolved(_, path))

View File

@ -402,6 +402,10 @@ define_Conf! {
/// A list of paths to types that should be treated like `Arc`, i.e. ignored but
/// for the generic parameters for determining interior mutability
(ignore_interior_mutability: Vec<String> = Vec::from(["bytes::Bytes".into()])),
/// Lint: UNINLINED_FORMAT_ARGS.
///
/// Whether to allow mixed uninlined format args, e.g. `format!("{} {}", a, foo.bar)`
(allow_mixed_uninlined_format_args: bool = true),
}
/// Search for the configuration file.

View File

@ -41,7 +41,7 @@ impl LateLintPass<'_> for MsrvAttrImpl {
.type_of(f.did)
.walk()
.filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
.any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
.any(|t| match_type(cx, t.expect_ty(), &paths::MSRV))
});
if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
then {

View File

@ -125,19 +125,19 @@ fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'
}
}
pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
let mut unique_attr = None;
pub fn get_unique_attr<'a>(
sess: &'a Session,
attrs: &'a [ast::Attribute],
name: &'static str,
) -> Option<&'a ast::Attribute> {
let mut unique_attr: Option<&ast::Attribute> = None;
for attr in get_attr(sess, attrs, name) {
match attr.style {
ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
ast::AttrStyle::Inner => {
sess.struct_span_err(attr.span, &format!("`{name}` is defined multiple times"))
.span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
.emit();
},
ast::AttrStyle::Outer => {
sess.span_err(attr.span, format!("`{name}` cannot be an outer attribute"));
},
if let Some(duplicate) = unique_attr {
sess.struct_span_err(attr.span, &format!("`{name}` is defined multiple times"))
.span_note(duplicate.span, "first definition found here")
.emit();
} else {
unique_attr = Some(attr);
}
}
unique_attr

View File

@ -91,6 +91,16 @@ fn fn_eagerness(cx: &LateContext<'_>, fn_id: DefId, name: Symbol, have_one_arg:
}
}
fn res_has_significant_drop(res: Res, cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
if let Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) = res {
cx.typeck_results()
.expr_ty(e)
.has_significant_drop(cx.tcx, cx.param_env)
} else {
false
}
}
#[expect(clippy::too_many_lines)]
fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessSuggestion {
struct V<'cx, 'tcx> {
@ -113,13 +123,8 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
},
args,
) => match self.cx.qpath_res(path, hir_id) {
Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_) => {
if self
.cx
.typeck_results()
.expr_ty(e)
.has_significant_drop(self.cx.tcx, self.cx.param_env)
{
res @ (Res::Def(DefKind::Ctor(..) | DefKind::Variant, _) | Res::SelfCtor(_)) => {
if res_has_significant_drop(res, self.cx, e) {
self.eagerness = ForceNoChange;
return;
}
@ -147,6 +152,12 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
self.eagerness |= NoChange;
return;
},
ExprKind::Path(ref path) => {
if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) {
self.eagerness = ForceNoChange;
return;
}
},
ExprKind::MethodCall(name, ..) => {
self.eagerness |= self
.cx
@ -206,7 +217,6 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
| ExprKind::Match(..)
| ExprKind::Closure { .. }
| ExprKind::Field(..)
| ExprKind::Path(_)
| ExprKind::AddrOf(..)
| ExprKind::Struct(..)
| ExprKind::Repeat(..)

View File

@ -105,8 +105,6 @@ use rustc_middle::ty::{
layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture,
};
use rustc_middle::ty::{FloatTy, IntTy, UintTy};
use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap;
use rustc_span::sym;
@ -118,36 +116,17 @@ use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param};
use crate::visitors::for_each_expr;
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
return Some(version);
} else if let Some(sess) = sess {
if let Some(span) = span {
sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
}
}
None
}
pub fn meets_msrv(msrv: Option<RustcVersion>, lint_msrv: RustcVersion) -> bool {
msrv.map_or(true, |msrv| msrv.meets(lint_msrv))
}
#[macro_export]
macro_rules! extract_msrv_attr {
($context:ident) => {
fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
let sess = rustc_lint::LintContext::sess(cx);
match $crate::get_unique_inner_attr(sess, attrs, "msrv") {
Some(msrv_attr) => {
if let Some(msrv) = msrv_attr.value_str() {
self.msrv = $crate::parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
} else {
sess.span_err(msrv_attr.span, "bad clippy attribute");
}
},
_ => (),
}
self.msrv.enter_lint_attrs(sess, attrs);
}
fn exit_lint_attrs(&mut self, cx: &rustc_lint::$context<'_>, attrs: &[rustc_ast::ast::Attribute]) {
let sess = rustc_lint::LintContext::sess(cx);
self.msrv.exit_lint_attrs(sess, attrs);
}
};
}

View File

@ -1,4 +1,11 @@
use std::sync::OnceLock;
use rustc_ast::Attribute;
use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::Span;
use crate::attrs::get_unique_attr;
macro_rules! msrv_aliases {
($($major:literal,$minor:literal,$patch:literal {
@ -40,3 +47,97 @@ msrv_aliases! {
1,16,0 { STR_REPEAT }
1,55,0 { SEEK_REWIND }
}
fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
return Some(version);
} else if let Some(sess) = sess {
if let Some(span) = span {
sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
}
}
None
}
/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
#[derive(Debug, Clone, Default)]
pub struct Msrv {
stack: Vec<RustcVersion>,
}
impl Msrv {
fn new(initial: Option<RustcVersion>) -> Self {
Self {
stack: Vec::from_iter(initial),
}
}
fn read_inner(conf_msrv: &Option<String>, sess: &Session) -> Self {
let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
.ok()
.and_then(|v| parse_msrv(&v, None, None));
let clippy_msrv = conf_msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
})
});
// if both files have an msrv, let's compare them and emit a warning if they differ
if let Some(cargo_msrv) = cargo_msrv
&& let Some(clippy_msrv) = clippy_msrv
&& clippy_msrv != cargo_msrv
{
sess.warn(format!(
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
));
}
Self::new(clippy_msrv.or(cargo_msrv))
}
/// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version`
/// field in `Cargo.toml`
///
/// Returns a `&'static Msrv` as `Copy` types are more easily passed to the
/// `register_{late,early}_pass` callbacks
pub fn read(conf_msrv: &Option<String>, sess: &Session) -> &'static Self {
static PARSED: OnceLock<Msrv> = OnceLock::new();
PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
}
pub fn current(&self) -> Option<RustcVersion> {
self.stack.last().copied()
}
pub fn meets(&self, required: RustcVersion) -> bool {
self.current().map_or(true, |version| version.meets(required))
}
fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") {
if let Some(msrv) = msrv_attr.value_str() {
return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span));
}
sess.span_err(msrv_attr.span, "bad clippy attribute");
}
None
}
pub fn enter_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
if let Some(version) = Self::parse_attr(sess, attrs) {
self.stack.push(version);
}
}
pub fn exit_lint_attrs(&mut self, sess: &Session, attrs: &[Attribute]) {
if Self::parse_attr(sess, attrs).is_some() {
self.stack.pop();
}
}
}

View File

@ -60,6 +60,8 @@ pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
#[cfg(feature = "internal")]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MEM_SWAP: [&str; 3] = ["core", "mem", "swap"];
#[cfg(feature = "internal")]
pub const MSRV: [&str; 3] = ["clippy_utils", "msrvs", "Msrv"];
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
@ -72,7 +74,6 @@ pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekab
pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
#[cfg_attr(not(unix), allow(clippy::invalid_paths))]
pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "PermissionsExt", "from_mode"];
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"];
pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
@ -101,8 +102,6 @@ pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
#[cfg(feature = "internal")]
pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];

View File

@ -3,6 +3,7 @@
// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
// differ from the time of `rustc` even if the name stays the same.
use crate::msrvs::Msrv;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::{
@ -18,20 +19,22 @@ use std::borrow::Cow;
type McfResult = Result<(), (Span, Cow<'static, str>)>;
pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: Option<RustcVersion>) -> McfResult {
pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) -> McfResult {
let def_id = body.source.def_id();
let mut current = def_id;
loop {
let predicates = tcx.predicates_of(current);
for (predicate, _) in predicates.predicates {
match predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::Clause::RegionOutlives(_))
| ty::PredicateKind::Clause(ty::Clause::TypeOutlives(_))
ty::PredicateKind::Clause(
ty::Clause::RegionOutlives(_)
| ty::Clause::TypeOutlives(_)
| ty::Clause::Projection(_)
| ty::Clause::Trait(..),
)
| ty::PredicateKind::WellFormed(_)
| ty::PredicateKind::Clause(ty::Clause::Projection(_))
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
| ty::PredicateKind::Clause(ty::Clause::Trait(..))
| ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {predicate:#?}"),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {predicate:#?}"),
@ -281,7 +284,7 @@ fn check_terminator<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
terminator: &Terminator<'tcx>,
msrv: Option<RustcVersion>,
msrv: &Msrv,
) -> McfResult {
let span = terminator.source_info.span;
match &terminator.kind {
@ -365,7 +368,7 @@ fn check_terminator<'tcx>(
}
}
fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bool {
fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
tcx.is_const_fn(def_id)
&& tcx.lookup_const_stability(def_id).map_or(true, |const_stab| {
if let rustc_attr::StabilityLevel::Stable { since, .. } = const_stab.level {
@ -384,15 +387,12 @@ fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<RustcVersion>) -> bo
let since = rustc_span::Symbol::intern(short_version);
crate::meets_msrv(
msrv,
RustcVersion::parse(since.as_str()).unwrap_or_else(|err| {
panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{since}`, {err:?}")
}),
)
msrv.meets(RustcVersion::parse(since.as_str()).unwrap_or_else(|err| {
panic!("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted: `{since}`, {err:?}")
}))
} else {
// Unstable const fn with the feature enabled.
msrv.is_none()
msrv.current().is_none()
}
})
}

View File

@ -5,6 +5,7 @@
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LintContext};
use rustc_session::Session;
use rustc_span::hygiene;
use rustc_span::source_map::{original_sp, SourceMap};
use rustc_span::{BytePos, Pos, Span, SpanData, SyntaxContext, DUMMY_SP};
@ -204,11 +205,20 @@ pub fn snippet_with_applicability<'a, T: LintContext>(
span: Span,
default: &'a str,
applicability: &mut Applicability,
) -> Cow<'a, str> {
snippet_with_applicability_sess(cx.sess(), span, default, applicability)
}
fn snippet_with_applicability_sess<'a>(
sess: &Session,
span: Span,
default: &'a str,
applicability: &mut Applicability,
) -> Cow<'a, str> {
if *applicability != Applicability::Unspecified && span.from_expansion() {
*applicability = Applicability::MaybeIncorrect;
}
snippet_opt(cx, span).map_or_else(
snippet_opt_sess(sess, span).map_or_else(
|| {
if *applicability == Applicability::MachineApplicable {
*applicability = Applicability::HasPlaceholders;
@ -226,8 +236,12 @@ pub fn snippet_with_macro_callsite<'a, T: LintContext>(cx: &T, span: Span, defau
}
/// Converts a span to a code snippet. Returns `None` if not available.
pub fn snippet_opt<T: LintContext>(cx: &T, span: Span) -> Option<String> {
cx.sess().source_map().span_to_snippet(span).ok()
pub fn snippet_opt(cx: &impl LintContext, span: Span) -> Option<String> {
snippet_opt_sess(cx.sess(), span)
}
fn snippet_opt_sess(sess: &Session, span: Span) -> Option<String> {
sess.source_map().span_to_snippet(span).ok()
}
/// Converts a span (from a block) to a code snippet if available, otherwise use default.
@ -277,8 +291,8 @@ pub fn snippet_block<'a, T: LintContext>(
/// Same as `snippet_block`, but adapts the applicability level by the rules of
/// `snippet_with_applicability`.
pub fn snippet_block_with_applicability<'a, T: LintContext>(
cx: &T,
pub fn snippet_block_with_applicability<'a>(
cx: &impl LintContext,
span: Span,
default: &'a str,
indent_relative_to: Option<Span>,
@ -299,7 +313,17 @@ pub fn snippet_block_with_applicability<'a, T: LintContext>(
///
/// This will also return whether or not the snippet is a macro call.
pub fn snippet_with_context<'a>(
cx: &LateContext<'_>,
cx: &impl LintContext,
span: Span,
outer: SyntaxContext,
default: &'a str,
applicability: &mut Applicability,
) -> (Cow<'a, str>, bool) {
snippet_with_context_sess(cx.sess(), span, outer, default, applicability)
}
fn snippet_with_context_sess<'a>(
sess: &Session,
span: Span,
outer: SyntaxContext,
default: &'a str,
@ -318,7 +342,7 @@ pub fn snippet_with_context<'a>(
);
(
snippet_with_applicability(cx, span, default, applicability),
snippet_with_applicability_sess(sess, span, default, applicability),
is_macro_call,
)
}

View File

@ -176,25 +176,28 @@ impl<'a> Sugg<'a> {
}
/// Prepare a suggestion from an expression.
pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
pub fn ast(
cx: &EarlyContext<'_>,
expr: &ast::Expr,
default: &'a str,
ctxt: SyntaxContext,
app: &mut Applicability,
) -> Self {
use rustc_ast::ast::RangeLimits;
let snippet_without_expansion = |cx, span: Span, default| {
if span.from_expansion() {
snippet_with_macro_callsite(cx, span, default)
} else {
snippet(cx, span, default)
}
};
#[expect(clippy::match_wildcard_for_single_variants)]
match expr.kind {
_ if expr.span.ctxt() != ctxt => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
ast::ExprKind::AddrOf(..)
| ast::ExprKind::Box(..)
| ast::ExprKind::Closure { .. }
| ast::ExprKind::If(..)
| ast::ExprKind::Let(..)
| ast::ExprKind::Unary(..)
| ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet_without_expansion(cx, expr.span, default)),
| ast::ExprKind::Match(..) => match snippet_with_context(cx, expr.span, ctxt, default, app) {
(snip, false) => Sugg::MaybeParen(snip),
(snip, true) => Sugg::NonParen(snip),
},
ast::ExprKind::Async(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::Break(..)
@ -224,45 +227,49 @@ impl<'a> Sugg<'a> {
| ast::ExprKind::Array(..)
| ast::ExprKind::While(..)
| ast::ExprKind::Await(..)
| ast::ExprKind::Err => Sugg::NonParen(snippet_without_expansion(cx, expr.span, default)),
| ast::ExprKind::Err => Sugg::NonParen(snippet_with_context(cx, expr.span, ctxt, default, app).0),
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
AssocOp::DotDot,
lhs.as_ref()
.map_or("".into(), |lhs| snippet_without_expansion(cx, lhs.span, default)),
rhs.as_ref()
.map_or("".into(), |rhs| snippet_without_expansion(cx, rhs.span, default)),
lhs.as_ref().map_or("".into(), |lhs| {
snippet_with_context(cx, lhs.span, ctxt, default, app).0
}),
rhs.as_ref().map_or("".into(), |rhs| {
snippet_with_context(cx, rhs.span, ctxt, default, app).0
}),
),
ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
AssocOp::DotDotEq,
lhs.as_ref()
.map_or("".into(), |lhs| snippet_without_expansion(cx, lhs.span, default)),
rhs.as_ref()
.map_or("".into(), |rhs| snippet_without_expansion(cx, rhs.span, default)),
lhs.as_ref().map_or("".into(), |lhs| {
snippet_with_context(cx, lhs.span, ctxt, default, app).0
}),
rhs.as_ref().map_or("".into(), |rhs| {
snippet_with_context(cx, rhs.span, ctxt, default, app).0
}),
),
ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
AssocOp::Assign,
snippet_without_expansion(cx, lhs.span, default),
snippet_without_expansion(cx, rhs.span, default),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
),
ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
astbinop2assignop(op),
snippet_without_expansion(cx, lhs.span, default),
snippet_without_expansion(cx, rhs.span, default),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
),
ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
AssocOp::from_ast_binop(op.node),
snippet_without_expansion(cx, lhs.span, default),
snippet_without_expansion(cx, rhs.span, default),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, rhs.span, ctxt, default, app).0,
),
ast::ExprKind::Cast(ref lhs, ref ty) => Sugg::BinOp(
AssocOp::As,
snippet_without_expansion(cx, lhs.span, default),
snippet_without_expansion(cx, ty.span, default),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, ty.span, ctxt, default, app).0,
),
ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
AssocOp::Colon,
snippet_without_expansion(cx, lhs.span, default),
snippet_without_expansion(cx, ty.span, default),
snippet_with_context(cx, lhs.span, ctxt, default, app).0,
snippet_with_context(cx, ty.span, ctxt, default, app).0,
),
}
}

View File

@ -9,7 +9,10 @@ use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety};
use rustc_infer::infer::{TyCtxtInferExt, type_variable::{TypeVariableOrigin, TypeVariableOriginKind}};
use rustc_infer::infer::{
type_variable::{TypeVariableOrigin, TypeVariableOriginKind},
TyCtxtInferExt,
};
use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{ConstValue, Scalar};
use rustc_middle::ty::{
@ -189,7 +192,13 @@ pub fn implements_trait<'tcx>(
trait_id: DefId,
ty_params: &[GenericArg<'tcx>],
) -> bool {
implements_trait_with_env(cx.tcx, cx.param_env, ty, trait_id, ty_params.iter().map(|&arg| Some(arg)))
implements_trait_with_env(
cx.tcx,
cx.param_env,
ty,
trait_id,
ty_params.iter().map(|&arg| Some(arg)),
)
}
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
@ -212,7 +221,11 @@ pub fn implements_trait_with_env<'tcx>(
kind: TypeVariableOriginKind::MiscVariable,
span: DUMMY_SP,
};
let ty_params = tcx.mk_substs(ty_params.into_iter().map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())));
let ty_params = tcx.mk_substs(
ty_params
.into_iter()
.map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())),
);
infcx
.type_implements_trait(trait_id, [ty.into()].into_iter().chain(ty_params), param_env)
.must_apply_modulo_regions()
@ -712,7 +725,9 @@ fn sig_for_projection<'tcx>(cx: &LateContext<'tcx>, ty: ProjectionTy<'tcx>) -> O
}
inputs = Some(i);
},
PredicateKind::Clause(ty::Clause::Projection(p)) if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() => {
PredicateKind::Clause(ty::Clause::Projection(p))
if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() =>
{
if output.is_some() {
// Multiple different fn trait impls. Is this even allowed?
return None;
@ -992,14 +1007,12 @@ pub fn make_projection<'tcx>(
debug_assert!(
generic_count == substs.len(),
"wrong number of substs for `{:?}`: found `{}` expected `{}`.\n\
"wrong number of substs for `{:?}`: found `{}` expected `{generic_count}`.\n\
note: the expected parameters are: {:#?}\n\
the given arguments are: `{:#?}`",
the given arguments are: `{substs:#?}`",
assoc_item.def_id,
substs.len(),
generic_count,
params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>(),
substs,
);
if let Some((idx, (param, arg))) = params
@ -1017,14 +1030,11 @@ pub fn make_projection<'tcx>(
{
debug_assert!(
false,
"mismatched subst type at index {}: expected a {}, found `{:?}`\n\
"mismatched subst type at index {idx}: expected a {}, found `{arg:?}`\n\
note: the expected parameters are {:#?}\n\
the given arguments are {:#?}",
idx,
the given arguments are {substs:#?}",
param.descr(),
arg,
params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>(),
substs,
params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>()
);
}
}

View File

@ -170,22 +170,22 @@ where
cb: F,
}
struct WithStmtGuarg<'a, F> {
struct WithStmtGuard<'a, F> {
val: &'a mut RetFinder<F>,
prev_in_stmt: bool,
}
impl<F> RetFinder<F> {
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuarg<'_, F> {
fn inside_stmt(&mut self, in_stmt: bool) -> WithStmtGuard<'_, F> {
let prev_in_stmt = std::mem::replace(&mut self.in_stmt, in_stmt);
WithStmtGuarg {
WithStmtGuard {
val: self,
prev_in_stmt,
}
}
}
impl<F> std::ops::Deref for WithStmtGuarg<'_, F> {
impl<F> std::ops::Deref for WithStmtGuard<'_, F> {
type Target = RetFinder<F>;
fn deref(&self) -> &Self::Target {
@ -193,13 +193,13 @@ where
}
}
impl<F> std::ops::DerefMut for WithStmtGuarg<'_, F> {
impl<F> std::ops::DerefMut for WithStmtGuard<'_, F> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.val
}
}
impl<F> Drop for WithStmtGuarg<'_, F> {
impl<F> Drop for WithStmtGuard<'_, F> {
fn drop(&mut self) {
self.val.in_stmt = self.prev_in_stmt;
}

View File

@ -120,8 +120,8 @@ impl ClippyWarning {
format!("$CARGO_HOME/{}", stripped.display())
} else {
format!(
"target/lintcheck/sources/{}-{}/{}",
crate_name, crate_version, span.file_name
"target/lintcheck/sources/{crate_name}-{crate_version}/{}",
span.file_name
)
};
@ -322,13 +322,13 @@ impl Crate {
if config.max_jobs == 1 {
println!(
"{}/{} {}% Linting {} {}",
index, total_crates_to_lint, perc, &self.name, &self.version
"{index}/{total_crates_to_lint} {perc}% Linting {} {}",
&self.name, &self.version
);
} else {
println!(
"{}/{} {}% Linting {} {} in target dir {:?}",
index, total_crates_to_lint, perc, &self.name, &self.version, thread_index
"{index}/{total_crates_to_lint} {perc}% Linting {} {} in target dir {thread_index:?}",
&self.name, &self.version
);
}
@ -398,8 +398,7 @@ impl Crate {
.output()
.unwrap_or_else(|error| {
panic!(
"Encountered error:\n{:?}\ncargo_clippy_path: {}\ncrate path:{}\n",
error,
"Encountered error:\n{error:?}\ncargo_clippy_path: {}\ncrate path:{}\n",
&cargo_clippy_path.display(),
&self.path.display()
);

View File

@ -1,6 +1,7 @@
#![feature(rustc_private)]
#![feature(let_chains)]
#![feature(once_cell)]
#![feature(lint_reasons)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
// warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)]
@ -90,6 +91,10 @@ fn track_files(parse_sess: &mut ParseSess, conf_path_string: Option<String>) {
// During development track the `clippy-driver` executable so that cargo will re-run clippy whenever
// it is rebuilt
#[expect(
clippy::collapsible_if,
reason = "Due to a bug in let_chains this if statement can't be collapsed"
)]
if cfg!(debug_assertions) {
if let Ok(current_exe) = env::current_exe()
&& let Some(current_exe) = current_exe.to_str()

View File

@ -11,9 +11,9 @@ extern crate rustc_middle;
#[macro_use]
extern crate rustc_session;
use clippy_utils::extract_msrv_attr;
use clippy_utils::msrvs::Msrv;
use rustc_hir::Expr;
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_semver::RustcVersion;
declare_lint! {
pub TEST_LINT,
@ -22,7 +22,7 @@ declare_lint! {
}
struct Pass {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl_lint_pass!(Pass => [TEST_LINT]);

View File

@ -11,9 +11,9 @@ extern crate rustc_middle;
#[macro_use]
extern crate rustc_session;
use clippy_utils::extract_msrv_attr;
use clippy_utils::msrvs::Msrv;
use rustc_hir::Expr;
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_semver::RustcVersion;
declare_lint! {
pub TEST_LINT,
@ -22,7 +22,7 @@ declare_lint! {
}
struct Pass {
msrv: Option<RustcVersion>,
msrv: Msrv,
}
impl_lint_pass!(Pass => [TEST_LINT]);

View File

@ -1,12 +1,3 @@
error: hardcoded path to a language item
--> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40
|
LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: convert all references to use `LangItem::DerefMut`
= note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
error: hardcoded path to a diagnostic item
--> $DIR/unnecessary_def_path_hardcoded_path.rs:10:36
|
@ -14,6 +5,7 @@ LL | const DEREF_TRAIT: [&str; 4] = ["core", "ops", "deref", "Deref"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: convert all references to use `sym::Deref`
= note: `-D clippy::unnecessary-def-path` implied by `-D warnings`
error: hardcoded path to a diagnostic item
--> $DIR/unnecessary_def_path_hardcoded_path.rs:12:43
@ -23,5 +15,13 @@ LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref",
|
= help: convert all references to use `sym::deref_method`
error: hardcoded path to a language item
--> $DIR/unnecessary_def_path_hardcoded_path.rs:11:40
|
LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: convert all references to use `LangItem::DerefMut`
error: aborting due to 3 previous errors

View File

@ -0,0 +1 @@
allow-mixed-uninlined-format-args = false

View File

@ -0,0 +1,14 @@
// run-rustfix
#![warn(clippy::uninlined_format_args)]
fn main() {
let local_i32 = 1;
let local_f64 = 2.0;
let local_opt: Option<i32> = Some(3);
println!("val='{local_i32}'");
println!("Hello x is {local_f64:.local_i32$}");
println!("Hello {local_i32} is {local_f64:.*}", 5);
println!("Hello {local_i32} is {local_f64:.*}", 5);
println!("{local_i32}, {}", local_opt.unwrap());
}

Some files were not shown because too many files have changed in this diff Show More