Merge commit '27afd6ade4bb1123a8bf82001629b69d23d62aff' into clippyup

This commit is contained in:
flip1995 2021-09-08 16:31:47 +02:00
parent f7aaa2a200
commit 091ed44b50
183 changed files with 4171 additions and 1313 deletions

View file

@ -5,4 +5,5 @@ lintcheck = "run --target-dir lintcheck/target --package lintcheck --bin lintche
collect-metadata = "test --test dogfood --features metadata-collector-lint -- run_metadata_collection_lint --ignored"
[build]
rustflags = ["-Zunstable-options"]
# -Zbinary-dep-depinfo allows us to track which rlib files to use for compiling UI tests
rustflags = ["-Zunstable-options", "-Zbinary-dep-depinfo"]

View file

@ -2,3 +2,17 @@
name: Blank Issue
about: Create a blank issue.
---
<!--
Additional labels can be added to this issue by including the following command
(without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* C-an-interesting-project
* C-enhancement
* C-question
* C-tracking-issue
-->

View file

@ -20,28 +20,24 @@ Instead, this happened: *explanation*
### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20)
- `rustc -Vv`:
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
**Rust version (`rustc -Vv`):**
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
<!--
Include a backtrace in the code block by setting `RUST_BACKTRACE=1` in your
environment. E.g. `RUST_BACKTRACE=1 cargo clippy`.
Additional labels can be added to this issue by including the following command
(without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* `I-suggestion-causes-error`
-->
<details><summary>Backtrace</summary>
<p>
```
<backtrace>
```
</p>
</details>

View file

@ -22,14 +22,14 @@ Instead, this happened: *explanation*
### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20)
- `rustc -Vv`:
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
**Rust version (`rustc -Vv`):**
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```

View file

@ -22,14 +22,23 @@ Instead, this happened: *explanation*
### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20)
- `rustc -Vv`:
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
**Rust version (`rustc -Vv`):**
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
<!--
Additional labels can be added to this issue by including the following command
(without the space after the @ symbol):
`@rustbot label +<label>`
Common labels for this issue type are:
* I-suggestion-causes-error
-->

View file

@ -20,17 +20,16 @@ http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
### Meta
- `cargo clippy -V`: e.g. clippy 0.0.212 (f455e46 2020-06-20)
- `rustc -Vv`:
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
**Rust version (`rustc -Vv`):**
```
rustc 1.46.0-nightly (f455e46ea 2020-06-20)
binary: rustc
commit-hash: f455e46eae1a227d735091091144601b467e1565
commit-date: 2020-06-20
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0
```
### Error output

View file

@ -42,9 +42,6 @@ jobs:
run: cargo build --features deny-warnings
working-directory: clippy_dev
- name: Test limit_stderr_length
run: cargo dev limit_stderr_length
- name: Test update_lints
run: cargo dev update_lints --check

View file

@ -964,7 +964,7 @@ Released 2020-11-19
[#5907](https://github.com/rust-lang/rust-clippy/pull/5907)
* [`suspicious_arithmetic_impl`]: extend to implementations of `BitAnd`, `BitOr`, `BitXor`, `Rem`, `Shl`, and `Shr`
[#5884](https://github.com/rust-lang/rust-clippy/pull/5884)
* [`invalid_atomic_ordering`]: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
* `invalid_atomic_ordering`: detect misuse of `compare_exchange`, `compare_exchange_weak`, and `fetch_update`
[#6025](https://github.com/rust-lang/rust-clippy/pull/6025)
* Avoid [`redundant_pattern_matching`] triggering in macros
[#6069](https://github.com/rust-lang/rust-clippy/pull/6069)
@ -1451,7 +1451,7 @@ Released 2020-03-12
* [`option_as_ref_deref`] [#4945](https://github.com/rust-lang/rust-clippy/pull/4945)
* [`wildcard_in_or_patterns`] [#4960](https://github.com/rust-lang/rust-clippy/pull/4960)
* [`iter_nth_zero`] [#4966](https://github.com/rust-lang/rust-clippy/pull/4966)
* [`invalid_atomic_ordering`] [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
* `invalid_atomic_ordering` [#4999](https://github.com/rust-lang/rust-clippy/pull/4999)
* [`skip_while_next`] [#5067](https://github.com/rust-lang/rust-clippy/pull/5067)
### Moves and Deprecations
@ -2613,6 +2613,7 @@ Released 2018-09-13
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
[`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls
[`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq
[`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord
[`disallowed_method`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_method
@ -2712,7 +2713,6 @@ Released 2018-09-13
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
@ -2754,6 +2754,7 @@ Released 2018-09-13
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or
[`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
[`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once
[`manual_str_repeat`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
@ -2795,6 +2796,7 @@ Released 2018-09-13
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
[`module_inception`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_inception
[`module_name_repetitions`]: https://rust-lang.github.io/rust-clippy/master/index.html#module_name_repetitions
[`modulo_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic
@ -2821,6 +2823,7 @@ Released 2018-09-13
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
[`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop
@ -2828,6 +2831,7 @@ Released 2018-09-13
[`needless_update`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_update
[`neg_cmp_op_on_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_cmp_op_on_partial_ord
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
@ -2881,6 +2885,7 @@ Released 2018-09-13
[`redundant_closure_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_call
[`redundant_closure_for_method_calls`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls
[`redundant_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else
[`redundant_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_feature_names
[`redundant_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names
[`redundant_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern
[`redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
@ -2903,6 +2908,7 @@ Released 2018-09-13
[`search_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
[`self_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_assignment
[`self_named_constructors`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_constructors
[`self_named_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#self_named_module_files
[`semicolon_if_nothing_returned`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_if_nothing_returned
[`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse
[`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.56"
version = "0.1.57"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@ -32,11 +32,7 @@ tempfile = { version = "3.1.0", optional = true }
cargo_metadata = "0.12"
compiletest_rs = { version = "0.6.0", features = ["tmp"] }
tester = "0.9"
serde = { version = "1.0", features = ["derive"] }
derive-new = "0.5"
regex = "1.4"
quote = "1"
syn = { version = "1", features = ["full"] }
# This is used by the `collect-metadata` alias.
filetime = "0.2"
@ -45,6 +41,15 @@ filetime = "0.2"
# for more information.
rustc-workspace-hack = "1.0.0"
# UI test dependencies
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"
itertools = "0.10.1"
quote = "1"
serde = { version = "1.0", features = ["derive"] }
syn = { version = "1", features = ["full"] }
[build-dependencies]
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }

View file

@ -45,13 +45,13 @@ or in Travis CI.
One way to use Clippy is by installing Clippy through rustup as a cargo
subcommand.
#### Step 1: Install rustup
#### Step 1: Install Rustup
You can install [rustup](https://rustup.rs/) on supported platforms. This will help
You can install [Rustup](https://rustup.rs/) on supported platforms. This will help
us install Clippy and its dependencies.
If you already have rustup installed, update to ensure you have the latest
rustup and compiler:
If you already have Rustup installed, update to ensure you have the latest
Rustup and compiler:
```terminal
rustup update

View file

@ -17,7 +17,6 @@ pub mod fmt;
pub mod new_lint;
pub mod serve;
pub mod setup;
pub mod stderr_length_check;
pub mod update_lints;
static DEC_CLIPPY_LINT_RE: SyncLazy<Regex> = SyncLazy::new(|| {

View file

@ -3,7 +3,7 @@
#![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use clippy_dev::{bless, fmt, new_lint, serve, setup, stderr_length_check, update_lints};
use clippy_dev::{bless, fmt, new_lint, serve, setup, update_lints};
fn main() {
let matches = get_clap_config();
@ -33,9 +33,6 @@ fn main() {
Err(e) => eprintln!("Unable to create lint: {}", e),
}
},
("limit_stderr_length", _) => {
stderr_length_check::check();
},
("setup", Some(sub_command)) => match sub_command.subcommand() {
("intellij", Some(matches)) => setup::intellij::setup_rustc_src(
matches
@ -152,10 +149,6 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.takes_value(true),
),
)
.subcommand(
SubCommand::with_name("limit_stderr_length")
.about("Ensures that stderr files do not grow longer than a certain amount of lines."),
)
.subcommand(
SubCommand::with_name("setup")
.about("Support for setting up your personal development environment")

View file

@ -1,51 +0,0 @@
use crate::clippy_project_root;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
// The maximum length allowed for stderr files.
//
// We limit this because small files are easier to deal with than bigger files.
const LENGTH_LIMIT: usize = 200;
pub fn check() {
let exceeding_files: Vec<_> = exceeding_stderr_files();
if !exceeding_files.is_empty() {
eprintln!("Error: stderr files exceeding limit of {} lines:", LENGTH_LIMIT);
for (path, count) in exceeding_files {
println!("{}: {}", path.display(), count);
}
std::process::exit(1);
}
}
fn exceeding_stderr_files() -> Vec<(PathBuf, usize)> {
// We use `WalkDir` instead of `fs::read_dir` here in order to recurse into subdirectories.
WalkDir::new(clippy_project_root().join("tests/ui"))
.into_iter()
.filter_map(Result::ok)
.filter(|f| !f.file_type().is_dir())
.filter_map(|e| {
let p = e.into_path();
let count = count_linenumbers(&p);
if p.extension() == Some(OsStr::new("stderr")) && count > LENGTH_LIMIT {
Some((p, count))
} else {
None
}
})
.collect()
}
#[must_use]
fn count_linenumbers(filepath: &Path) -> usize {
match fs::read(filepath) {
Ok(content) => bytecount::count(&content, b'\n'),
Err(e) => {
eprintln!("Failed to read file: {}", e);
0
},
}
}

View file

@ -1,8 +1,6 @@
[package]
name = "clippy_lints"
# begin automatic update
version = "0.1.56"
# end automatic update
version = "0.1.57"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -1,8 +1,10 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{meets_msrv, msrvs};
use rustc_ast::ast::{FloatTy, LitFloatType, LitKind};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol;
use std::f64::consts as f64;
@ -36,68 +38,82 @@ declare_clippy_lint! {
"the approximate of a known float constant (in `std::fXX::consts`)"
}
// Tuples are of the form (constant, name, min_digits)
const KNOWN_CONSTS: [(f64, &str, usize); 18] = [
(f64::E, "E", 4),
(f64::FRAC_1_PI, "FRAC_1_PI", 4),
(f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5),
(f64::FRAC_2_PI, "FRAC_2_PI", 5),
(f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5),
(f64::FRAC_PI_2, "FRAC_PI_2", 5),
(f64::FRAC_PI_3, "FRAC_PI_3", 5),
(f64::FRAC_PI_4, "FRAC_PI_4", 5),
(f64::FRAC_PI_6, "FRAC_PI_6", 5),
(f64::FRAC_PI_8, "FRAC_PI_8", 5),
(f64::LN_10, "LN_10", 5),
(f64::LN_2, "LN_2", 5),
(f64::LOG10_E, "LOG10_E", 5),
(f64::LOG2_E, "LOG2_E", 5),
(f64::LOG2_10, "LOG2_10", 5),
(f64::LOG10_2, "LOG10_2", 5),
(f64::PI, "PI", 3),
(f64::SQRT_2, "SQRT_2", 5),
// Tuples are of the form (constant, name, min_digits, msrv)
const KNOWN_CONSTS: [(f64, &str, usize, Option<RustcVersion>); 19] = [
(f64::E, "E", 4, None),
(f64::FRAC_1_PI, "FRAC_1_PI", 4, None),
(f64::FRAC_1_SQRT_2, "FRAC_1_SQRT_2", 5, None),
(f64::FRAC_2_PI, "FRAC_2_PI", 5, None),
(f64::FRAC_2_SQRT_PI, "FRAC_2_SQRT_PI", 5, None),
(f64::FRAC_PI_2, "FRAC_PI_2", 5, None),
(f64::FRAC_PI_3, "FRAC_PI_3", 5, None),
(f64::FRAC_PI_4, "FRAC_PI_4", 5, None),
(f64::FRAC_PI_6, "FRAC_PI_6", 5, None),
(f64::FRAC_PI_8, "FRAC_PI_8", 5, None),
(f64::LN_2, "LN_2", 5, None),
(f64::LN_10, "LN_10", 5, None),
(f64::LOG2_10, "LOG2_10", 5, Some(msrvs::LOG2_10)),
(f64::LOG2_E, "LOG2_E", 5, None),
(f64::LOG10_2, "LOG10_2", 5, Some(msrvs::LOG10_2)),
(f64::LOG10_E, "LOG10_E", 5, None),
(f64::PI, "PI", 3, None),
(f64::SQRT_2, "SQRT_2", 5, None),
(f64::TAU, "TAU", 3, Some(msrvs::TAU)),
];
declare_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
pub struct ApproxConstant {
msrv: Option<RustcVersion>,
}
impl ApproxConstant {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
fn check_lit(&self, cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
match *lit {
LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
FloatTy::F32 => self.check_known_consts(cx, e, s, "f32"),
FloatTy::F64 => self.check_known_consts(cx, e, s, "f64"),
},
LitKind::Float(s, LitFloatType::Unsuffixed) => self.check_known_consts(cx, e, s, "f{32, 64}"),
_ => (),
}
}
fn check_known_consts(&self, cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
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.as_ref().map_or(true, |msrv| meets_msrv(self.msrv.as_ref(), msrv))
{
span_lint_and_help(
cx,
APPROX_CONSTANT,
e.span,
&format!("approximate value of `{}::consts::{}` found", module, &name),
None,
"consider using the constant directly",
);
return;
}
}
}
}
}
impl_lint_pass!(ApproxConstant => [APPROX_CONSTANT]);
impl<'tcx> LateLintPass<'tcx> for ApproxConstant {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Lit(lit) = &e.kind {
check_lit(cx, &lit.node, e);
self.check_lit(cx, &lit.node, e);
}
}
}
fn check_lit(cx: &LateContext<'_>, lit: &LitKind, e: &Expr<'_>) {
match *lit {
LitKind::Float(s, LitFloatType::Suffixed(fty)) => match fty {
FloatTy::F32 => check_known_consts(cx, e, s, "f32"),
FloatTy::F64 => check_known_consts(cx, e, s, "f64"),
},
LitKind::Float(s, LitFloatType::Unsuffixed) => check_known_consts(cx, e, s, "f{32, 64}"),
_ => (),
}
}
fn check_known_consts(cx: &LateContext<'_>, e: &Expr<'_>, s: symbol::Symbol, module: &str) {
let s = s.as_str();
if s.parse::<f64>().is_ok() {
for &(constant, name, min_digits) in &KNOWN_CONSTS {
if is_approx_const(constant, &s, min_digits) {
span_lint(
cx,
APPROX_CONSTANT,
e.span,
&format!(
"approximate value of `{}::consts::{}` found. \
Consider using it directly",
module, &name
),
);
return;
}
}
}
extract_msrv_attr!(LateContext);
}
/// Returns `false` if the number of significant figures in `value` are

View file

@ -118,7 +118,7 @@ enum AssertKind {
fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
if_chain! {
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr);
if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind;
if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
// bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
// block

View file

@ -61,8 +61,8 @@ impl<'a, 'tcx> Visitor<'tcx> for ExVisitor<'a, 'tcx> {
// do not lint if the closure is called using an iterator (see #1141)
if_chain! {
if let Some(parent) = get_parent_expr(self.cx, expr);
if let ExprKind::MethodCall(_, _, args, _) = parent.kind;
let caller = self.cx.typeck_results().expr_ty(&args[0]);
if let ExprKind::MethodCall(_, _, [self_arg, ..], _) = &parent.kind;
let caller = self.cx.typeck_results().expr_ty(self_arg);
if let Some(iter_id) = self.cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(self.cx, caller, iter_id, &[]);
then {

View file

@ -1,9 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{ast_utils, is_direct_expn_of};
use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
use clippy_utils::{diagnostics::span_lint_and_sugg, higher, is_direct_expn_of, ty::implements_trait};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_hir::{Expr, ExprKind, Lit};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Ident;
declare_clippy_lint! {
/// ### What it does
@ -28,45 +30,77 @@ declare_clippy_lint! {
declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
fn is_bool_lit(e: &Expr) -> bool {
fn is_bool_lit(e: &Expr<'_>) -> bool {
matches!(
e.kind,
ExprKind::Lit(Lit {
kind: LitKind::Bool(_),
node: LitKind::Bool(_),
..
})
) && !e.span.from_expansion()
}
impl EarlyLintPass for BoolAssertComparison {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
fn is_impl_not_trait_with_bool_out(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(e);
cx.tcx
.lang_items()
.not_trait()
.filter(|trait_id| implements_trait(cx, ty, *trait_id, &[]))
.and_then(|trait_id| {
cx.tcx.associated_items(trait_id).find_by_name_and_kind(
cx.tcx,
Ident::from_str("Output"),
ty::AssocKind::Type,
trait_id,
)
})
.map_or(false, |assoc_item| {
let proj = cx.tcx.mk_projection(assoc_item.def_id, cx.tcx.mk_substs_trait(ty, &[]));
let nty = cx.tcx.normalize_erasing_regions(cx.param_env, proj);
nty.is_bool()
})
}
impl<'tcx> LateLintPass<'tcx> for BoolAssertComparison {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let macros = ["assert_eq", "debug_assert_eq"];
let inverted_macros = ["assert_ne", "debug_assert_ne"];
for mac in macros.iter().chain(inverted_macros.iter()) {
if let Some(span) = is_direct_expn_of(e.span, mac) {
if let Some([a, b]) = ast_utils::extract_assert_macro_args(e) {
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
if let Some(span) = is_direct_expn_of(expr.span, mac) {
if let Some(args) = higher::extract_assert_macro_args(expr) {
if let [a, b, ..] = args[..] {
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
if nb_bool_args != 1 {
// If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is...
//
// Or there is simply no boolean and then we can leave things as is!
if nb_bool_args != 1 {
// If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is...
//
// Or there is simply no boolean and then we can leave things as is!
return;
}
if !is_impl_not_trait_with_bool_out(cx, a) || !is_impl_not_trait_with_bool_out(cx, b) {
// At this point the expression which is not a boolean
// literal does not implement Not trait with a bool output,
// so we cannot suggest to rewrite our code
return;
}
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return;
}
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return;
}
}
}

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::match_type;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{path_to_local_id, paths, peel_ref_operators, remove_blocks, strip_pat_refs};
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -65,7 +65,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
return;
};
if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(needle).peel_refs().kind();
if !LocalUsedVisitor::new(cx, arg_id).check_expr(needle);
if !is_local_used(cx, needle, arg_id);
then {
let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_recv.kind {

View file

@ -19,7 +19,7 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
cx.typeck_results().expr_ty(expr),
);
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
} else if let ExprKind::MethodCall(method_path, _, args, _) = expr.kind {
} else if let ExprKind::MethodCall(method_path, _, [self_arg, ..], _) = &expr.kind {
if_chain! {
if method_path.ident.name == sym!(cast);
if let Some(generic_args) = method_path.args;
@ -28,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !is_hir_ty_cfg_dependant(cx, cast_to);
then {
let (cast_from, cast_to) =
(cx.typeck_results().expr_ty(&args[0]), cx.typeck_results().expr_ty(expr));
(cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr));
lint_cast_ptr_alignment(cx, expr, cast_from, cast_to);
}
}

View file

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{higher, is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq};
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_ref_operators, SpanlessEq};
use if_chain::if_chain;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, MatchSource, Pat, PatKind, StmtKind};
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span};
@ -56,11 +57,11 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
}
}
}
},
Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
check_arm(cx, false, pat, body, None, els);
}
None => {}
},
None => {},
}
}
}
@ -71,7 +72,7 @@ fn check_arm<'tcx>(
outer_pat: &'tcx Pat<'tcx>,
outer_then_body: &'tcx Expr<'tcx>,
outer_guard: Option<&'tcx Guard<'tcx>>,
outer_else_body: Option<&'tcx Expr<'tcx>>
outer_else_body: Option<&'tcx Expr<'tcx>>,
) {
let inner_expr = strip_singleton_blocks(outer_then_body);
if_chain! {
@ -106,14 +107,13 @@ fn check_arm<'tcx>(
(Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b),
};
// the binding must not be used in the if guard
let mut used_visitor = LocalUsedVisitor::new(cx, binding_id);
if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !used_visitor.check_expr(e));
if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !is_local_used(cx, *e, binding_id));
// ...or anywhere in the inner expression
if match inner {
IfLetOrMatch::IfLet(_, _, body, els) => {
!used_visitor.check_expr(body) && els.map_or(true, |e| !used_visitor.check_expr(e))
!is_local_used(cx, body, binding_id) && els.map_or(true, |e| !is_local_used(cx, e, binding_id))
},
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| used_visitor.check_arm(arm)),
IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)),
};
then {
let msg = format!(
@ -151,23 +151,6 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
expr
}
enum IfLetOrMatch<'hir> {
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
/// scrutinee, pattern, then block, else block
IfLet(&'hir Expr<'hir>, &'hir Pat<'hir>, &'hir Expr<'hir>, Option<&'hir Expr<'hir>>),
}
impl<'hir> IfLetOrMatch<'hir> {
fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
match expr.kind {
ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
_ => higher::IfLet::hir(cx, expr).map(|higher::IfLet { let_expr, let_pat, if_then, if_else }| {
Self::IfLet(let_expr, let_pat, if_then, if_else)
})
}
}
}
/// A "wild-like" arm has a wild (`_`) or `None` pattern and no guard. Such arms can be "collapsed"
/// into a single wild arm without any significant loss in semantics or readability.
fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {

View file

@ -148,7 +148,7 @@ declare_clippy_lint! {
/// };
/// ```
pub BRANCHES_SHARING_CODE,
complexity,
nursery,
"`if` statement with shared code in all blocks"
}

View file

@ -0,0 +1,108 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{in_macro, is_automatically_derived, is_default_equivalent, remove_blocks};
use rustc_hir::{
def::{DefKind, Res},
Body, Expr, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node, QPath,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TypeFoldable;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Detects manual `std::default::Default` implementations that are identical to a derived implementation.
///
/// ### Why is this bad?
/// It is less concise.
///
/// ### Example
/// ```rust
/// struct Foo {
/// bar: bool
/// }
///
/// impl std::default::Default for Foo {
/// fn default() -> Self {
/// Self {
/// bar: false
/// }
/// }
/// }
/// ```
///
/// Could be written as:
///
/// ```rust
/// #[derive(Default)]
/// struct Foo {
/// bar: bool
/// }
/// ```
///
/// ### Known problems
/// Derive macros [sometimes use incorrect bounds](https://github.com/rust-lang/rust/issues/26925)
/// in generic types and the user defined `impl` maybe is more generalized or
/// specialized than what derive will produce. This lint can't detect the manual `impl`
/// has exactly equal bounds, and therefore this lint is disabled for types with
/// generic parameters.
///
pub DERIVABLE_IMPLS,
complexity,
"manual implementation of the `Default` trait which is equal to a derive"
}
declare_lint_pass!(DerivableImpls => [DERIVABLE_IMPLS]);
fn is_path_self(e: &Expr<'_>) -> bool {
if let ExprKind::Path(QPath::Resolved(_, p)) = e.kind {
matches!(p.res, Res::SelfCtor(..) | Res::Def(DefKind::Ctor(..), _))
} else {
false
}
}
impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
items: [child],
..
}) = item.kind;
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !is_automatically_derived(attrs);
if !in_macro(item.span);
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
if let impl_item_hir = child.id.hir_id();
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def();
then {
if cx.tcx.type_of(item.def_id).definitely_has_param_types_or_consts(cx.tcx) {
return;
}
let should_emit = match remove_blocks(func_expr).kind {
ExprKind::Tup(fields) => fields.iter().all(|e| is_default_equivalent(cx, e)),
ExprKind::Call(callee, args)
if is_path_self(callee) => args.iter().all(|e| is_default_equivalent(cx, e)),
ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_equivalent(cx, ef.expr)),
_ => false,
};
if should_emit {
let path_string = cx.tcx.def_path_str(adt_def.did);
span_lint_and_help(
cx,
DERIVABLE_IMPLS,
item.span,
"this `impl` can be derived",
None,
&format!("try annotating `{}` with `#[derive(Default)]`", path_string),
);
}
}
}
}
}

View file

@ -105,9 +105,6 @@ declare_clippy_lint! {
/// nothing more than copy the object, which is what `#[derive(Copy, Clone)]`
/// gets you.
///
/// ### Known problems
/// Bounds of generic types are sometimes wrong: https://github.com/rust-lang/rust/issues/26925
///
/// ### Example
/// ```rust,ignore
/// #[derive(Copy)]

View file

@ -9,8 +9,9 @@ use clippy_utils::{
use core::fmt::Write;
use rustc_errors::Applicability;
use rustc_hir::{
hir_id::HirIdSet,
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
Block, Expr, ExprKind, Guard, HirId, Local, Stmt, StmtKind, UnOp,
Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -338,6 +339,8 @@ struct InsertSearcher<'cx, 'tcx> {
edits: Vec<Edit<'tcx>>,
/// A stack of loops the visitor is currently in.
loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
}
impl<'tcx> InsertSearcher<'_, 'tcx> {
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@ -385,13 +388,16 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
}
},
StmtKind::Expr(e) => self.visit_expr(e),
StmtKind::Local(Local { init: Some(e), .. }) => {
self.allow_insert_closure &= !self.in_tail_pos;
self.in_tail_pos = false;
self.is_single_insert = false;
self.visit_expr(e);
StmtKind::Local(l) => {
self.visit_pat(l.pat);
if let Some(e) = l.init {
self.allow_insert_closure &= !self.in_tail_pos;
self.in_tail_pos = false;
self.is_single_insert = false;
self.visit_expr(e);
}
},
_ => {
StmtKind::Item(_) => {
self.allow_insert_closure &= !self.in_tail_pos;
self.is_single_insert = false;
},
@ -473,6 +479,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
// Each branch may contain it's own insert expression.
let mut is_map_used = self.is_map_used;
for arm in arms {
self.visit_pat(arm.pat);
if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
self.visit_non_tail_expr(guard);
}
@ -498,7 +505,8 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
},
_ => {
self.allow_insert_closure &= !self.in_tail_pos;
self.allow_insert_closure &= can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops);
self.allow_insert_closure &=
can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops, &self.locals);
// Sub expressions are no longer in the tail position.
self.is_single_insert = false;
self.in_tail_pos = false;
@ -507,6 +515,12 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
},
}
}
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
p.each_binding_or_first(&mut |_, id, _, _| {
self.locals.insert(id);
});
}
}
struct InsertSearchResults<'tcx> {
@ -632,6 +646,7 @@ fn find_insert_calls(
in_tail_pos: true,
is_single_insert: true,
loops: Vec::new(),
locals: HirIdSet::default(),
};
s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure;

View file

@ -15,8 +15,8 @@ declare_clippy_lint! {
/// order of sub-expressions.
///
/// ### Why is this bad?
/// It is often confusing to read. In addition, the
/// sub-expression evaluation order for Rust is not well documented.
/// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands),
/// the operands of these expressions are evaluated before applying the effects of the expression.
///
/// ### Known problems
/// Code which intentionally depends on the evaluation

View file

@ -0,0 +1,164 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{diagnostics::span_lint, is_lint_allowed};
use rustc_hir::{Crate, CRATE_HIR_ID};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::DUMMY_SP;
declare_clippy_lint! {
/// ### What it does
/// Checks for feature names with prefix `use-`, `with-` or suffix `-support`
///
/// ### Why is this bad?
/// These prefixes and suffixes have no significant meaning.
///
/// ### Example
/// ```toml
/// # The `Cargo.toml` with feature name redundancy
/// [features]
/// default = ["use-abc", "with-def", "ghi-support"]
/// use-abc = [] // redundant
/// with-def = [] // redundant
/// ghi-support = [] // redundant
/// ```
///
/// Use instead:
/// ```toml
/// [features]
/// default = ["abc", "def", "ghi"]
/// abc = []
/// def = []
/// ghi = []
/// ```
///
pub REDUNDANT_FEATURE_NAMES,
cargo,
"usage of a redundant feature name"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for negative feature names with prefix `no-` or `not-`
///
/// ### Why is this bad?
/// Features are supposed to be additive, and negatively-named features violate it.
///
/// ### Example
/// ```toml
/// # The `Cargo.toml` with negative feature names
/// [features]
/// default = []
/// no-abc = []
/// not-def = []
///
/// ```
/// Use instead:
/// ```toml
/// [features]
/// default = ["abc", "def"]
/// abc = []
/// def = []
///
/// ```
pub NEGATIVE_FEATURE_NAMES,
cargo,
"usage of a negative feature name"
}
declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]);
static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"];
static SUFFIXES: [&str; 2] = ["-support", "_support"];
fn is_negative_prefix(s: &str) -> bool {
s.starts_with("no")
}
fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) {
let is_negative = is_prefix && is_negative_prefix(substring);
span_lint_and_help(
cx,
if is_negative {
NEGATIVE_FEATURE_NAMES
} else {
REDUNDANT_FEATURE_NAMES
},
DUMMY_SP,
&format!(
"the \"{}\" {} in the feature name \"{}\" is {}",
substring,
if is_prefix { "prefix" } else { "suffix" },
feature,
if is_negative { "negative" } else { "redundant" }
),
None,
&format!(
"consider renaming the feature to \"{}\"{}",
if is_prefix {
feature.strip_prefix(substring)
} else {
feature.strip_suffix(substring)
}
.unwrap(),
if is_negative {
", but make sure the feature adds functionality"
} else {
""
}
),
);
}
impl LateLintPass<'_> for FeatureName {
fn check_crate(&mut self, cx: &LateContext<'_>, _: &Crate<'_>) {
if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID)
&& is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID)
{
return;
}
let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false);
for package in metadata.packages {
let mut features: Vec<&String> = package.features.keys().collect();
features.sort();
for feature in features {
let prefix_opt = {
let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str());
if i > 0 && feature.starts_with(PREFIXES[i - 1]) {
Some(PREFIXES[i - 1])
} else {
None
}
};
if let Some(prefix) = prefix_opt {
lint(cx, feature, prefix, true);
}
let suffix_opt: Option<&str> = {
let i = SUFFIXES.partition_point(|suffix| {
suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less
});
if i > 0 && feature.ends_with(SUFFIXES[i - 1]) {
Some(SUFFIXES[i - 1])
} else {
None
}
};
if let Some(suffix) = suffix_opt {
lint(cx, feature, suffix, false);
}
}
}
}
}
#[test]
fn test_prefixes_sorted() {
let mut sorted_prefixes = PREFIXES;
sorted_prefixes.sort_unstable();
assert_eq!(PREFIXES, sorted_prefixes);
let mut sorted_suffixes = SUFFIXES;
sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev()));
assert_eq!(SUFFIXES, sorted_suffixes);
}

View file

@ -332,8 +332,6 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
),
Applicability::MachineApplicable,
);
return;
}
}
}
@ -364,22 +362,22 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
if_chain! {
if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. },
ref _lspan,
largs,
_lspan,
[largs_0, largs_1, ..],
_
) = add_lhs.kind;
) = &add_lhs.kind;
if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. },
ref _rspan,
rargs,
_rspan,
[rargs_0, rargs_1, ..],
_
) = add_rhs.kind;
) = &add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), &largs[1]);
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), &rargs[1]);
if let Some((lvalue, _)) = constant(cx, cx.typeck_results(), largs_1);
if let Some((rvalue, _)) = constant(cx, cx.typeck_results(), rargs_1);
if Int(2) == lvalue && Int(2) == rvalue;
then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], "..")));
return Some(format!("{}.hypot({})", Sugg::hir(cx, largs_0, ".."), Sugg::hir(cx, rargs_0, "..")));
}
}
}
@ -409,8 +407,8 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(path, _, method_args, _) = lhs.kind;
if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point();
if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &lhs.kind;
if cx.typeck_results().expr_ty(self_arg).is_floating_point();
if path.ident.name.as_str() == "exp";
then {
span_lint_and_sugg(
@ -421,7 +419,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
"consider using",
format!(
"{}.exp_m1()",
Sugg::hir(cx, &method_args[0], "..")
Sugg::hir(cx, self_arg, "..")
),
Applicability::MachineApplicable,
);
@ -619,8 +617,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs,
) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, _, largs, _) = lhs.kind;
if let ExprKind::MethodCall(_, _, rargs, _) = rhs.kind;
if let ExprKind::MethodCall(_, _, [largs_self, ..], _) = &lhs.kind;
if let ExprKind::MethodCall(_, _, [rargs_self, ..], _) = &rhs.kind;
then {
span_lint_and_sugg(
cx,
@ -628,7 +626,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span,
"log base can be expressed more clearly",
"consider using",
format!("{}.log({})", Sugg::hir(cx, &largs[0], ".."), Sugg::hir(cx, &rargs[0], ".."),),
format!("{}.log({})", Sugg::hir(cx, largs_self, ".."), Sugg::hir(cx, rargs_self, ".."),),
Applicability::MachineApplicable,
);
}

View file

@ -26,7 +26,6 @@ pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
return;
} else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
check_must_use_candidate(
cx,

View file

@ -138,12 +138,12 @@ impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::MethodCall(path, _span, args, _) = &expr.kind;
if let ExprKind::MethodCall(path, _span, [self_arg, ..], _) = &expr.kind;
if path.ident.as_str() == "lock";
let ty = cx.typeck_results().expr_ty(&args[0]);
let ty = cx.typeck_results().expr_ty(self_arg);
if is_type_diagnostic_item(cx, ty, sym!(mutex_type));
then {
Some(&args[0])
Some(self_arg)
} else {
None
}

View file

@ -46,10 +46,10 @@ impl<'tcx> LateLintPass<'tcx> for OkIfLet {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { //begin checking variables
if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr);
if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = let_pat.kind; //get operation
if let ExprKind::MethodCall(_, ok_span, [ref result_types_0, ..], _) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&result_types[0]), sym::result_type);
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::result_type);
if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
then {

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::{path_to_local_id, visitors::LocalUsedVisitor};
use clippy_utils::{path_to_local_id, visitors::is_local_used};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -65,11 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(if_) = expr.kind;
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id);
if !used_visitor.check_expr(cond);
if !is_local_used(cx, *cond, canonical_id);
if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, &*then);
if !used_visitor.check_expr(value);
if !is_local_used(cx, value, canonical_id);
then {
let span = stmt.span.to(if_.span);
@ -148,15 +147,13 @@ fn check_assign<'tcx>(
if let hir::ExprKind::Assign(var, value, _) = expr.kind;
if path_to_local_id(var, decl);
then {
let mut v = LocalUsedVisitor::new(cx, decl);
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) {
return None;
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| is_local_used(cx, stmt, decl)) {
None
} else {
Some(value)
}
return Some(value);
} else {
None
}
}
None
}

View file

@ -187,6 +187,7 @@ mod dbg_macro;
mod default;
mod default_numeric_fallback;
mod dereference;
mod derivable_impls;
mod derive;
mod disallowed_method;
mod disallowed_script_idents;
@ -211,6 +212,7 @@ mod exhaustive_items;
mod exit;
mod explicit_write;
mod fallible_impl_from;
mod feature_name;
mod float_equality_without_abs;
mod float_literal;
mod floating_point_arithmetic;
@ -272,6 +274,7 @@ mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
mod missing_inline;
mod module_style;
mod modulo_arithmetic;
mod multiple_crate_versions;
mod mut_key;
@ -287,6 +290,7 @@ mod needless_borrow;
mod needless_borrowed_ref;
mod needless_continue;
mod needless_for_each;
mod needless_option_as_deref;
mod needless_pass_by_value;
mod needless_question_mark;
mod needless_update;
@ -584,6 +588,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
default::FIELD_REASSIGN_WITH_DEFAULT,
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
dereference::EXPLICIT_DEREF_METHODS,
derivable_impls::DERIVABLE_IMPLS,
derive::DERIVE_HASH_XOR_EQ,
derive::DERIVE_ORD_XOR_PARTIAL_ORD,
derive::EXPL_IMPL_CLONE_ON_COPY,
@ -625,6 +630,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
exit::EXIT,
explicit_write::EXPLICIT_WRITE,
fallible_impl_from::FALLIBLE_IMPL_FROM,
feature_name::NEGATIVE_FEATURE_NAMES,
feature_name::REDUNDANT_FEATURE_NAMES,
float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS,
float_literal::EXCESSIVE_PRECISION,
float_literal::LOSSY_FLOAT_LITERAL,
@ -770,6 +777,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
methods::MANUAL_FILTER_MAP,
methods::MANUAL_FIND_MAP,
methods::MANUAL_SATURATING_ARITHMETIC,
methods::MANUAL_SPLIT_ONCE,
methods::MANUAL_STR_REPEAT,
methods::MAP_COLLECT_RESULT_UNIT,
methods::MAP_FLATTEN,
@ -822,6 +830,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS,
module_style::MOD_MODULE_FILES,
module_style::SELF_NAMED_MODULE_FILES,
modulo_arithmetic::MODULO_ARITHMETIC,
multiple_crate_versions::MULTIPLE_CRATE_VERSIONS,
mut_key::MUTABLE_KEY_TYPE,
@ -840,6 +850,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE,
needless_continue::NEEDLESS_CONTINUE,
needless_for_each::NEEDLESS_FOR_EACH,
needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF,
needless_pass_by_value::NEEDLESS_PASS_BY_VALUE,
needless_question_mark::NEEDLESS_QUESTION_MARK,
needless_update::NEEDLESS_UPDATE,
@ -1031,6 +1042,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS),
LintId::of(missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES),
LintId::of(missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS),
LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
LintId::of(panic_unimplemented::PANIC),
@ -1122,7 +1135,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_for_each::NEEDLESS_FOR_EACH),
LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE),
LintId::of(non_expressive_names::SIMILAR_NAMES),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE),
LintId::of(pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF),
LintId::of(ranges::RANGE_MINUS_ONE),
@ -1193,10 +1205,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(collapsible_if::COLLAPSIBLE_IF),
LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(copies::IFS_SAME_COND),
LintId::of(copies::IF_SAME_THEN_ELSE),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(derive::DERIVE_HASH_XOR_EQ),
LintId::of(derive::DERIVE_ORD_XOR_PARTIAL_ORD),
LintId::of(doc::MISSING_SAFETY_DOC),
@ -1316,6 +1328,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
LintId::of(methods::MANUAL_SPLIT_ONCE),
LintId::of(methods::MANUAL_STR_REPEAT),
LintId::of(methods::MAP_COLLECT_RESULT_UNIT),
LintId::of(methods::MAP_IDENTITY),
@ -1366,6 +1379,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrow::NEEDLESS_BORROW),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
@ -1581,7 +1595,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(derivable_impls::DERIVABLE_IMPLS),
LintId::of(double_comparison::DOUBLE_COMPARISONS),
LintId::of(double_parens::DOUBLE_PARENS),
LintId::of(duration_subsec::DURATION_SUBSEC),
@ -1614,6 +1628,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(methods::ITER_COUNT),
LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SPLIT_ONCE),
LintId::of(methods::MAP_IDENTITY),
LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP),
@ -1628,6 +1643,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(needless_bool::BOOL_COMPARISON),
LintId::of(needless_bool::NEEDLESS_BOOL),
LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE),
LintId::of(needless_option_as_deref::NEEDLESS_OPTION_AS_DEREF),
LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK),
LintId::of(needless_update::NEEDLESS_UPDATE),
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
@ -1779,6 +1795,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![
LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA),
LintId::of(feature_name::NEGATIVE_FEATURE_NAMES),
LintId::of(feature_name::REDUNDANT_FEATURE_NAMES),
LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS),
LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES),
]);
@ -1786,6 +1804,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
LintId::of(cognitive_complexity::COGNITIVE_COMPLEXITY),
LintId::of(copies::BRANCHES_SHARING_CODE),
LintId::of(disallowed_method::DISALLOWED_METHOD),
LintId::of(disallowed_type::DISALLOWED_TYPE),
LintId::of(fallible_impl_from::FALLIBLE_IMPL_FROM),
@ -1797,6 +1816,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
LintId::of(mutex_atomic::MUTEX_INTEGER),
LintId::of(nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES),
LintId::of(option_if_let_else::OPTION_IF_LET_ELSE),
LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
LintId::of(regex::TRIVIAL_REGEX),
@ -1835,7 +1855,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(serde_api::SerdeApi));
let vec_box_size_threshold = conf.vec_box_size_threshold;
let type_complexity_threshold = conf.type_complexity_threshold;
store.register_late_pass(move || Box::new(types::Types::new(vec_box_size_threshold, type_complexity_threshold)));
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
store.register_late_pass(move || Box::new(types::Types::new(
vec_box_size_threshold,
type_complexity_threshold,
avoid_breaking_exported_api,
)));
store.register_late_pass(|| Box::new(booleans::NonminimalBool));
store.register_late_pass(|| Box::new(needless_bitwise_bool::NeedlessBitwiseBool));
store.register_late_pass(|| Box::new(eq_op::EqOp));
@ -1846,9 +1871,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(ptr::Ptr));
store.register_late_pass(|| Box::new(ptr_eq::PtrEq));
store.register_late_pass(|| Box::new(needless_bool::NeedlessBool));
store.register_late_pass(|| Box::new(needless_option_as_deref::OptionNeedlessDeref));
store.register_late_pass(|| Box::new(needless_bool::BoolComparison));
store.register_late_pass(|| Box::new(needless_for_each::NeedlessForEach));
store.register_late_pass(|| Box::new(approx_const::ApproxConstant));
store.register_late_pass(|| Box::new(misc::MiscLints));
store.register_late_pass(|| Box::new(eta_reduction::EtaReduction));
store.register_late_pass(|| Box::new(identity_op::IdentityOp));
@ -1877,6 +1902,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
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)));
store.register_late_pass(move || Box::new(matches::Matches::new(msrv)));
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustive::new(msrv)));
@ -1920,6 +1946,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(panic_unimplemented::PanicUnimplemented));
store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
store.register_late_pass(|| Box::new(derive::Derive));
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
@ -2092,7 +2119,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10));
store.register_late_pass(|| Box::new(manual_map::ManualMap));
store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
store.register_early_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
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));
let disallowed_types = conf.disallowed_types.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move || Box::new(disallowed_type::DisallowedType::new(&disallowed_types)));
@ -2102,6 +2130,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts)));
store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings));
store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors));
store.register_late_pass(move || Box::new(feature_name::FeatureName));
}
#[rustfmt::skip]

View file

@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::visitors::is_local_used;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
@ -66,9 +66,7 @@ pub(super) fn check<'tcx>(
fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
match *pat {
PatKind::Wild => true,
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => {
!LocalUsedVisitor::new(cx, id).check_expr(body)
},
PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => !is_local_used(cx, body, id),
_ => false,
}
}

View file

@ -2,6 +2,7 @@ use super::utils::make_iterator_snippet;
use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{is_lang_ctor, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -37,7 +38,8 @@ pub(super) fn check<'tcx>(
if_chain! {
if let Some(inner_expr) = inner_expr;
if let Some(higher::IfLet { let_pat, let_expr, if_else: None, .. }) = higher::IfLet::hir(cx, inner_expr);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: None })
= higher::IfLet::hir(cx, inner_expr);
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
if path_to_local_id(let_expr, pat_hir_id);
@ -46,6 +48,8 @@ pub(super) fn check<'tcx>(
let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
if some_ctor || ok_ctor;
// Ensure epxr in `if let` is not used afterwards
if !is_local_used(cx, if_then, pat_hir_id);
then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message

View file

@ -397,6 +397,21 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// One might think that modifying the mutable variable changes the loop bounds
///
/// ### Known problems
/// False positive when mutation is followed by a `break`, but the `break` is not immediately
/// after the mutation:
///
/// ```rust
/// let mut x = 5;
/// for _ in 0..x {
/// x += 1; // x is a range bound that is mutated
/// ..; // some other expression
/// break; // leaves the loop, so mutation is not an issue
/// }
/// ```
///
/// False positive on nested loops ([#6072](https://github.com/rust-lang/rust-clippy/issues/6072))
///
/// ### Example
/// ```rust
/// let mut foo = 42;
@ -580,8 +595,8 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
while_let_on_iterator::check(cx, expr);
if let Some(higher::While { if_cond, if_then, .. }) = higher::While::hir(&expr) {
while_immutable_condition::check(cx, if_cond, if_then);
if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
while_immutable_condition::check(cx, condition, body);
}
needless_collect::check(expr, cx);

View file

@ -1,24 +1,27 @@
use super::MUT_RANGE_BOUND;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{higher, path_to_local};
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::{get_enclosing_block, higher, path_to_local};
use if_chain::if_chain;
use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{BindingAnnotation, Expr, ExprKind, HirId, Node, PatKind};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_middle::{mir::FakeReadCause, ty};
use rustc_span::source_map::Span;
use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg)
{
let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)];
if mut_ids[0].is_some() || mut_ids[1].is_some() {
let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids);
if_chain! {
if let Some(higher::Range {
start: Some(start),
end: Some(end),
..
}) = higher::Range::hir(arg);
let (mut_id_start, mut_id_end) = (check_for_mutability(cx, start), check_for_mutability(cx, end));
if mut_id_start.is_some() || mut_id_end.is_some();
then {
let (span_low, span_high) = check_for_mutation(cx, body, mut_id_start, mut_id_end);
mut_warn_with_span(cx, span_low);
mut_warn_with_span(cx, span_high);
}
@ -27,11 +30,13 @@ pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) {
fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) {
if let Some(sp) = span {
span_lint(
span_lint_and_note(
cx,
MUT_RANGE_BOUND,
sp,
"attempt to mutate range bound within loop; note that the range of the loop is unchanged",
"attempt to mutate range bound within loop",
None,
"the range of the loop is unchanged",
);
}
}
@ -51,12 +56,13 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
fn check_for_mutation<'tcx>(
cx: &LateContext<'tcx>,
body: &Expr<'_>,
bound_ids: &[Option<HirId>],
bound_id_start: Option<HirId>,
bound_id_end: Option<HirId>,
) -> (Option<Span>, Option<Span>) {
let mut delegate = MutatePairDelegate {
cx,
hir_id_low: bound_ids[0],
hir_id_high: bound_ids[1],
hir_id_low: bound_id_start,
hir_id_high: bound_id_end,
span_low: None,
span_high: None,
};
@ -70,6 +76,7 @@ fn check_for_mutation<'tcx>(
)
.walk_expr(body);
});
delegate.mutation_span()
}
@ -87,10 +94,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) {
if let ty::BorrowKind::MutBorrow = bk {
if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low {
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
}
if Some(id) == self.hir_id_high {
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
}
}
@ -99,10 +106,10 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> {
fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) {
if let PlaceBase::Local(id) = cmt.place.base {
if Some(id) == self.hir_id_low {
if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id));
}
if Some(id) == self.hir_id_high {
if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) {
self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id));
}
}
@ -116,3 +123,52 @@ impl MutatePairDelegate<'_, '_> {
(self.span_low, self.span_high)
}
}
struct BreakAfterExprVisitor {
hir_id: HirId,
past_expr: bool,
past_candidate: bool,
break_after_expr: bool,
}
impl BreakAfterExprVisitor {
pub fn is_found(cx: &LateContext<'_>, hir_id: HirId) -> bool {
let mut visitor = BreakAfterExprVisitor {
hir_id,
past_expr: false,
past_candidate: false,
break_after_expr: false,
};
get_enclosing_block(cx, hir_id).map_or(false, |block| {
visitor.visit_block(block);
visitor.break_after_expr
})
}
}
impl intravisit::Visitor<'tcx> for BreakAfterExprVisitor {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if self.past_candidate {
return;
}
if expr.hir_id == self.hir_id {
self.past_expr = true;
} else if self.past_expr {
if matches!(&expr.kind, ExprKind::Break(..)) {
self.break_after_expr = true;
}
self.past_candidate = true;
} else {
intravisit::walk_expr(self, expr);
}
}
}

View file

@ -26,7 +26,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
then {
let ty = cx.typeck_results().expr_ty(&args[0]);
let mut applicability = Applicability::MachineApplicable;
let mut applicability = Applicability::MaybeIncorrect;
let is_empty_sugg = "next().is_none()".to_string();
let method_name = &*method.ident.name.as_str();
let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
@ -113,7 +113,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
(stmt.span, String::new()),
(iter_call.span, iter_replacement)
],
Applicability::MachineApplicable,// MaybeIncorrect,
Applicability::MaybeIncorrect,
);
},
);

View file

@ -2,10 +2,8 @@ use super::NEEDLESS_RANGE_LOOP;
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{
contains_name, higher, is_integer_const, match_trait_method, path_to_local_id, paths, sugg, SpanlessEq,
};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -256,43 +254,36 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, seqvar) = *seqpath;
if seqvar.segments.len() == 1;
let index_used_directly = path_to_local_id(idx, self.var);
let indexed_indirectly = {
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
walk_expr(&mut used_visitor, idx);
used_visitor.used
};
if indexed_indirectly || index_used_directly;
if is_local_used(self.cx, idx, self.var);
then {
if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name);
}
let index_used_directly = matches!(idx.kind, ExprKind::Path(_));
let res = self.cx.qpath_res(seqpath, seqexpr.hir_id);
match res {
Res::Local(hir_id) => {
let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id);
let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id);
let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id);
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
}
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent));
}
return false; // no need to walk further *on the variable*
}
Res::Def(DefKind::Static | DefKind::Const, ..) => {
if indexed_indirectly {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
if index_used_directly {
self.indexed_directly.insert(
seqvar.segments[0].ident.name,
(None, self.cx.typeck_results().node_type(seqexpr.hir_id)),
);
} else {
self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None);
}
return false; // no need to walk further *on the variable*
}
@ -310,10 +301,10 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
// a range index op
if let ExprKind::MethodCall(meth, _, args, _) = expr.kind;
if let ExprKind::MethodCall(meth, _, [args_0, args_1, ..], _) = &expr.kind;
if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
|| (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
if !self.check(&args[1], &args[0], expr);
if !self.check(args_1, args_0, expr);
then { return }
}

View file

@ -87,7 +87,7 @@ fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult
fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult {
let stmts = block.stmts.iter().map(stmt_to_expr);
let expr = once(block.expr.as_deref());
let expr = once(block.expr);
let mut iter = stmts.chain(expr).flatten();
never_loop_expr_seq(&mut iter, main_loop_id)
}
@ -100,7 +100,7 @@ fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_lo
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match stmt.kind {
StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e),
StmtKind::Local(local) => local.init.as_deref(),
StmtKind::Local(local) => local.init,
StmtKind::Item(..) => None,
}
}

View file

@ -24,13 +24,13 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'
}
}
if let ExprKind::Match(ref matchexpr, ref arms, MatchSource::Normal) = inner.kind {
if let ExprKind::Match(matchexpr, arms, MatchSource::Normal) = inner.kind {
if arms.len() == 2
&& arms[0].guard.is_none()
&& arms[1].guard.is_none()
&& is_simple_break_expr(&arms[1].body)
&& is_simple_break_expr(arms[1].body)
{
could_be_while_let(cx, expr, &arms[0].pat, matchexpr);
could_be_while_let(cx, expr, arms[0].pat, matchexpr);
}
}
}

View file

@ -14,12 +14,7 @@ use rustc_span::{symbol::sym, Span, Symbol};
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! {
if let Some(higher::WhileLet {
if_then,
let_pat,
let_expr,
..
}) = higher::WhileLet::hir(expr);
if let Some(higher::WhileLet { if_then, let_pat, let_expr }) = higher::WhileLet::hir(expr);
// check for `Some(..)` pattern
if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = let_pat.kind;
if let Res::Def(_, pat_did) = pat_path.res;

View file

@ -48,8 +48,7 @@ pub struct MacroRefData {
impl MacroRefData {
pub fn new(name: String, callee: Span, cx: &LateContext<'_>) -> Self {
let sm = cx.sess().source_map();
let mut path = sm.filename_for_diagnostics(&sm.span_to_filename(callee))
.to_string();
let mut path = sm.filename_for_diagnostics(&sm.span_to_filename(callee)).to_string();
// std lib paths are <::std::module::file type>
// so remove brackets, space and type.

View file

@ -1,16 +1,18 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{
can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
peel_hir_expr_refs,
peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind};
use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -44,168 +46,169 @@ declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl LateLintPass<'_> for ManualMap {
#[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(higher::IfLet {
let_pat,
let_expr,
if_then,
if_else: Some(if_else),
}) = higher::IfLet::hir(cx, expr)
{
manage_lint(cx, expr, (&let_pat.kind, if_then), (&PatKind::Wild, if_else), let_expr);
}
if let ExprKind::Match(scrutinee, [then @ Arm { guard: None, .. }, r#else @ Arm { guard: None, .. }], _) =
expr.kind
{
manage_lint(
cx,
expr,
(&then.pat.kind, then.body),
(&r#else.pat.kind, r#else.body),
let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) {
Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else),
Some(IfLetOrMatch::Match(
scrutinee,
);
[arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
_,
)) => (scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body),
_ => return,
};
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
}
}
fn manage_lint<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
then: (&'tcx PatKind<'_>, &'tcx Expr<'_>),
r#else: (&'tcx PatKind<'_>, &'tcx Expr<'_>),
scrut: &'tcx Expr<'_>,
) {
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type))
{
return;
}
let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrut));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type))
{
return;
}
let (then_pat, then_expr) = then;
let (else_pat, else_expr) = r#else;
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
try_parse_pattern(cx, else_pat, expr_ctxt),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_expr) => {
(else_expr, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_expr) => {
(else_expr, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_expr) => {
(then_expr, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_expr) => {
(then_expr, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
Some(expr) => expr,
None => return,
};
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit && !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id) {
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
return;
}
if !can_move_expr_to_closure(cx, some_expr) {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrut).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
},
_ => {
if path_to_local_id(some_expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) {
Some(expr) => expr,
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
},
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
None => return,
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if is_else_clause(cx.tcx, expr) {
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str =
if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
match can_pass_as_func(cx, id, some_expr) {
Some(func) if func.span.ctxt() == some_expr.span.ctxt() => {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
},
_ => {
if path_to_local_id(some_expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
format!(
"|{}{}| {}",
annotation,
some_binding,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
},
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
format!(
"|{}| {}",
snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0,
snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app).0
)
} else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
},
app,
);
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
} else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
},
app,
);
}
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
@ -213,7 +216,7 @@ fn manage_lint<'tcx>(
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id (arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
if path_to_local_id(arg, binding) && cx.typeck_results().expr_adjustments(arg).is_empty() =>
{
Some(func)
},
@ -235,28 +238,21 @@ enum OptionPat<'a> {
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern(
cx: &LateContext<'tcx>,
pat_kind: &'tcx PatKind<'_>,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
fn f(
cx: &LateContext<'tcx>,
pat_kind: &'tcx PatKind<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat_kind {
fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(ref_pat, _) => f(cx, &ref_pat.kind, ref_count + 1, ctxt),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
PatKind::TupleStruct(ref qpath, [pattern], _) if is_lang_ctor(cx, qpath, OptionSome) => {
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat_kind, 0, ctxt)
f(cx, pat, 0, ctxt)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.

View file

@ -6,7 +6,7 @@ use clippy_utils::higher;
use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{
get_parent_expr, in_macro, is_expn_of, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild,
meets_msrv, msrvs, path_to_local, path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns,
@ -631,7 +631,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
check_match_single_binding(cx, ex, arms, expr);
}
}
if let ExprKind::Match(ref ex, ref arms, _) = expr.kind {
if let ExprKind::Match(ex, arms, _) = expr.kind {
check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr);
}
if let Some(higher::IfLet { let_pat, let_expr, .. }) = higher::IfLet::hir(cx, expr) {
@ -959,9 +959,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
// Looking for unused bindings (i.e.: `_e`)
for pat in inner.iter() {
if let PatKind::Binding(_, id, ident, None) = pat.kind {
if ident.as_str().starts_with('_')
&& !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
{
if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) {
ident_bind_name = (&ident.name.as_str()).to_string();
matching_wild = true;
}
@ -1196,7 +1194,7 @@ where
let (first_sugg, msg, title);
let span = ex.span.source_callsite();
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
msg = "try";
title = "you don't need to add `&` to both the expression and the patterns";
@ -1207,7 +1205,7 @@ where
}
let remaining_suggs = pats.filter_map(|pat| {
if let PatKind::Ref(ref refp, _) = pat.kind {
if let PatKind::Ref(refp, _) = pat.kind {
Some((pat.span, snippet(cx, refp.span, "..").to_string()))
} else {
None
@ -1367,7 +1365,7 @@ where
find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty()
});
then {
if let Some(ref last_pat) = last_pat_opt {
if let Some(last_pat) = last_pat_opt {
if !is_wild(last_pat) {
return false;
}
@ -1829,13 +1827,13 @@ mod redundant_pattern_match {
..
}) = higher::IfLet::hir(cx, expr)
{
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some())
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
}
if let ExprKind::Match(op, arms, MatchSource::Normal) = &expr.kind {
find_sugg_for_match(cx, expr, op, arms)
find_sugg_for_match(cx, expr, op, arms);
}
if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false)
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
}
}

View file

@ -28,11 +28,11 @@ declare_lint_pass!(MemForget => [MEM_FORGET]);
impl<'tcx> LateLintPass<'tcx> for MemForget {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Call(path_expr, args) = e.kind {
if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.kind {
if let ExprKind::Path(ref qpath) = path_expr.kind {
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
if match_def_path(cx, def_id, &paths::MEM_FORGET) {
let forgot_ty = cx.typeck_results().expr_ty(&args[0]);
let forgot_ty = cx.typeck_results().expr_ty(first_arg);
if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) {
span_lint(cx, MEM_FORGET, e.span, "usage of `mem::forget` on `Drop` type");

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::{in_macro, is_diag_trait_item, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths};
use clippy_utils::ty::is_non_aggregate_primitive_type;
use clippy_utils::{in_macro, is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -194,64 +194,37 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
}
}
/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
/// constructor from the std library
fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
let std_types_symbols = &[
sym::string_type,
sym::vec_type,
sym::vecdeque_type,
sym::LinkedList,
sym::hashmap_type,
sym::BTreeMap,
sym::hashset_type,
sym::BTreeSet,
sym::BinaryHeap,
];
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
.any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
}
}
fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
// disable lint for primitives
let expr_type = cx.typeck_results().expr_ty_adjusted(src);
if is_non_aggregate_primitive_type(expr_type) {
return;
}
// disable lint for Option since it is covered in another lint
if let ExprKind::Path(q) = &src.kind {
if is_lang_ctor(cx, q, OptionNone) {
return;
}
}
false
}
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
span_lint_and_then(
cx,
MEM_REPLACE_WITH_DEFAULT,
expr_span,
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|diag| {
if !in_macro(expr_span) {
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
if_chain! {
if let ExprKind::Call(repl_func, _) = src.kind;
if !in_external_macro(cx.tcx.sess, expr_span);
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then {
span_lint_and_then(
cx,
MEM_REPLACE_WITH_DEFAULT,
expr_span,
"replacing a value of type `T` with `T::default()` is better expressed using `std::mem::take`",
|diag| {
if !in_macro(expr_span) {
let suggestion = format!("std::mem::take({})", snippet(cx, dest.span, ""));
diag.span_suggestion(
expr_span,
"consider using",
suggestion,
Applicability::MachineApplicable
);
}
diag.span_suggestion(
expr_span,
"consider using",
suggestion,
Applicability::MachineApplicable,
);
}
);
}
},
);
}
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::is_trait_method;
use clippy_utils::source::snippet;
use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -16,7 +16,10 @@ pub(super) fn check<'tcx>(
filter_arg: &'tcx hir::Expr<'_>,
) {
// lint if caller of `.filter().next()` is an Iterator
if is_trait_method(cx, expr, sym::Iterator) {
let recv_impls_iterator = cx.tcx.get_diagnostic_item(sym::Iterator).map_or(false, |id| {
implements_trait(cx, cx.typeck_results().expr_ty(recv), id, &[])
});
if recv_impls_iterator {
let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \
`.find(..)` instead";
let filter_snippet = snippet(cx, filter_arg.span, "..");

View file

@ -0,0 +1,213 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::{is_diag_item_method, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, HirId, LangItem, Node, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, adjustment::Adjust};
use rustc_span::{symbol::sym, Span, SyntaxContext};
use super::MANUAL_SPLIT_ONCE;
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, pat_arg: &Expr<'_>) {
if !cx.typeck_results().expr_ty_adjusted(self_arg).peel_refs().is_str() {
return;
}
let ctxt = expr.span.ctxt();
let usage = match parse_iter_usage(cx, ctxt, cx.tcx.hir().parent_iter(expr.hir_id)) {
Some(x) => x,
None => return,
};
let (method_name, msg) = if method_name == "splitn" {
("split_once", "manual implementation of `split_once`")
} else {
("rsplit_once", "manual implementation of `rsplit_once`")
};
let mut app = Applicability::MachineApplicable;
let self_snip = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
let pat_snip = snippet_with_context(cx, pat_arg.span, ctxt, "..", &mut app).0;
match usage.kind {
IterUsageKind::NextTuple => {
span_lint_and_sugg(
cx,
MANUAL_SPLIT_ONCE,
usage.span,
msg,
"try this",
format!("{}.{}({})", self_snip, method_name, pat_snip),
app,
);
},
IterUsageKind::Next => {
let self_deref = {
let adjust = cx.typeck_results().expr_adjustments(self_arg);
if adjust.is_empty() {
String::new()
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|| adjust
.iter()
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
{
format!("&{}", "*".repeat(adjust.len() - 1))
} else {
"*".repeat(adjust.len() - 2)
}
};
let sugg = if usage.unwrap_kind.is_some() {
format!(
"{}.{}({}).map_or({}{}, |x| x.0)",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
} else {
format!(
"Some({}.{}({}).map_or({}{}, |x| x.0))",
&self_snip, method_name, pat_snip, self_deref, &self_snip
)
};
span_lint_and_sugg(cx, MANUAL_SPLIT_ONCE, usage.span, msg, "try this", sugg, app);
},
IterUsageKind::Second => {
let access_str = match usage.unwrap_kind {
Some(UnwrapKind::Unwrap) => ".unwrap().1",
Some(UnwrapKind::QuestionMark) => "?.1",
None => ".map(|x| x.1)",
};
span_lint_and_sugg(
cx,
MANUAL_SPLIT_ONCE,
usage.span,
msg,
"try this",
format!("{}.{}({}){}", self_snip, method_name, pat_snip, access_str),
app,
);
},
}
}
enum IterUsageKind {
Next,
Second,
NextTuple,
}
enum UnwrapKind {
Unwrap,
QuestionMark,
}
struct IterUsage {
kind: IterUsageKind,
unwrap_kind: Option<UnwrapKind>,
span: Span,
}
fn parse_iter_usage(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
mut iter: impl Iterator<Item = (HirId, Node<'tcx>)>,
) -> Option<IterUsage> {
let (kind, span) = match iter.next() {
Some((_, Node::Expr(e))) if e.span.ctxt() == ctxt => {
let (name, args) = if let ExprKind::MethodCall(name, _, [_, args @ ..], _) = e.kind {
(name, args)
} else {
return None;
};
let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?;
match (&*name.ident.as_str(), args) {
("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Next, e.span),
("next_tuple", []) => {
if_chain! {
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did);
if let ty::Tuple(subs) = subs.type_at(0).kind();
if subs.len() == 2;
then {
return Some(IterUsage { kind: IterUsageKind::NextTuple, span: e.span, unwrap_kind: None });
} else {
return None;
}
}
},
("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => {
if let Some((Constant::Int(idx), _)) = constant(cx, cx.typeck_results(), idx_expr) {
let span = if name.ident.as_str() == "nth" {
e.span
} else {
if_chain! {
if let Some((_, Node::Expr(next_expr))) = iter.next();
if let ExprKind::MethodCall(next_name, _, [_], _) = next_expr.kind;
if next_name.ident.name == sym::next;
if next_expr.span.ctxt() == ctxt;
if let Some(next_id) = cx.typeck_results().type_dependent_def_id(next_expr.hir_id);
if cx.tcx.trait_of_item(next_id) == Some(iter_id);
then {
next_expr.span
} else {
return None;
}
}
};
match idx {
0 => (IterUsageKind::Next, span),
1 => (IterUsageKind::Second, span),
_ => return None,
}
} else {
return None;
}
},
_ => return None,
}
},
_ => return None,
};
let (unwrap_kind, span) = if let Some((_, Node::Expr(e))) = iter.next() {
match e.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)),
..
},
_,
) => {
let parent_span = e.span.parent().unwrap();
if parent_span.ctxt() == ctxt {
(Some(UnwrapKind::QuestionMark), parent_span)
} else {
(None, span)
}
},
_ if e.span.ctxt() != ctxt => (None, span),
ExprKind::MethodCall(name, _, [_], _)
if name.ident.name == sym::unwrap
&& cx
.typeck_results()
.type_dependent_def_id(e.hir_id)
.map_or(false, |id| is_diag_item_method(cx, id, sym::option_type)) =>
{
(Some(UnwrapKind::Unwrap), e.span)
},
_ => (None, span),
}
} else {
(None, span)
};
Some(IterUsage {
kind,
unwrap_kind,
span,
})
}

View file

@ -33,6 +33,7 @@ mod iter_nth_zero;
mod iter_skip_next;
mod iterator_step_by_zero;
mod manual_saturating_arithmetic;
mod manual_split_once;
mod manual_str_repeat;
mod map_collect_result_unit;
mod map_flatten;
@ -64,6 +65,7 @@ mod wrong_self_convention;
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::ty::{contains_adt_constructor, contains_ty, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, get_trait_def_id, in_macro, iter_input_pats, meets_msrv, msrvs, paths, return_ty};
@ -1771,6 +1773,29 @@ declare_clippy_lint! {
"manual implementation of `str::repeat`"
}
declare_clippy_lint! {
/// **What it does:** Checks for usages of `str::splitn(2, _)`
///
/// **Why is this bad?** `split_once` is both clearer in intent and slightly more efficient.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust,ignore
/// // Bad
/// let (key, value) = _.splitn(2, '=').next_tuple()?;
/// let value = _.splitn(2, '=').nth(1)?;
///
/// // Good
/// let (key, value) = _.split_once('=')?;
/// let value = _.split_once('=')?.1;
/// ```
pub MANUAL_SPLIT_ONCE,
complexity,
"replace `.splitn(2, pat)` with `.split_once(pat)`"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
@ -1848,7 +1873,8 @@ impl_lint_pass!(Methods => [
IMPLICIT_CLONE,
SUSPICIOUS_SPLITN,
MANUAL_STR_REPEAT,
EXTEND_WITH_DRAIN
EXTEND_WITH_DRAIN,
MANUAL_SPLIT_ONCE
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -2176,8 +2202,18 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
unnecessary_lazy_eval::check(cx, expr, recv, arg, "or");
}
},
("splitn" | "splitn_mut" | "rsplitn" | "rsplitn_mut", [count_arg, _]) => {
suspicious_splitn::check(cx, name, expr, recv, count_arg);
("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);
if count == 2 && meets_msrv(msrv, &msrvs::STR_SPLIT_ONCE) {
manual_split_once::check(cx, name, expr, recv, pat_arg);
}
}
},
("splitn_mut" | "rsplitn_mut", [count_arg, _]) => {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg) {
suspicious_splitn::check(cx, name, expr, recv, count);
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {

View file

@ -96,9 +96,9 @@ pub(super) fn check<'tcx>(
(&paths::RESULT, true, &["or", "unwrap_or"], "else"),
];
if let hir::ExprKind::MethodCall(path, _, args, _) = &arg.kind {
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &arg.kind {
if path.ident.name == sym::len {
let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs();
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
match ty.kind() {
ty::Slice(_) | ty::Array(_, _) | ty::Str => return,

View file

@ -1,4 +1,3 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_note;
use if_chain::if_chain;
use rustc_ast::LitKind;
@ -8,15 +7,8 @@ use rustc_span::source_map::Spanned;
use super::SUSPICIOUS_SPLITN;
pub(super) fn check(
cx: &LateContext<'_>,
method_name: &str,
expr: &Expr<'_>,
self_arg: &Expr<'_>,
count_arg: &Expr<'_>,
) {
pub(super) fn check(cx: &LateContext<'_>, method_name: &str, expr: &Expr<'_>, self_arg: &Expr<'_>, count: u128) {
if_chain! {
if let Some((Constant::Int(count), _)) = constant(cx, cx.typeck_results(), count_arg);
if count <= 1;
if let Some(call_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(call_id);
@ -24,9 +16,9 @@ pub(super) fn check(
if lang_items.slice_impl() == Some(impl_id) || lang_items.str_impl() == Some(impl_id);
then {
// Ignore empty slice and string literals when used with a literal count.
if (matches!(self_arg.kind, ExprKind::Array([]))
if matches!(self_arg.kind, ExprKind::Array([]))
|| matches!(self_arg.kind, ExprKind::Lit(Spanned { node: LitKind::Str(s, _), .. }) if s.is_empty())
) && matches!(count_arg.kind, ExprKind::Lit(_))
{
return;
}

View file

@ -24,9 +24,9 @@ pub(super) fn derefs_to_slice<'tcx>(
}
}
if let hir::ExprKind::MethodCall(path, _, args, _) = expr.kind {
if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(&args[0])) {
Some(&args[0])
if let hir::ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind {
if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(self_arg)) {
Some(self_arg)
} else {
None
}

View file

@ -513,12 +513,12 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
if_chain! {
if let ExprKind::MethodCall(method_name, _, expressions, _) = expr.kind;
if let ExprKind::MethodCall(method_name, _, [ref self_arg, ..], _) = expr.kind;
if sym!(signum) == method_name.ident.name;
// Check that the receiver of the signum() is a float (expressions[0] is the receiver of
// the method call)
then {
return is_float(cx, &expressions[0]);
return is_float(cx, self_arg);
}
}
false

View file

@ -0,0 +1,178 @@
use std::{
ffi::OsString,
path::{Component, Path},
};
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{FileName, RealFileName, SourceFile, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only self named module files, bans mod.rs files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
pub MOD_MODULE_FILES,
restriction,
"checks that module layout is consistent"
}
declare_clippy_lint! {
/// ### What it does
/// Checks that module layout uses only mod.rs files.
///
/// ### Why is this bad?
/// Having multiple module layout styles in a project can be confusing.
///
/// ### Example
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// stuff.rs
/// lib.rs
/// ```
/// Use instead:
/// ```text
/// src/
/// stuff/
/// stuff_files.rs
/// mod.rs
/// lib.rs
/// ```
pub SELF_NAMED_MODULE_FILES,
restriction,
"checks that module layout is consistent"
}
pub struct ModStyle;
impl_lint_pass!(ModStyle => [MOD_MODULE_FILES, SELF_NAMED_MODULE_FILES]);
impl EarlyLintPass for ModStyle {
fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &ast::Crate) {
if cx.builder.lint_level(MOD_MODULE_FILES).0 == Level::Allow
&& cx.builder.lint_level(SELF_NAMED_MODULE_FILES).0 == Level::Allow
{
return;
}
let files = cx.sess.source_map().files();
let trim_to_src = if let RealFileName::LocalPath(p) = &cx.sess.opts.working_dir {
p.to_string_lossy()
} else {
return;
};
// `folder_segments` is all unique folder path segments `path/to/foo.rs` gives
// `[path, to]` but not foo
let mut folder_segments = FxHashSet::default();
// `mod_folders` is all the unique folder names that contain a mod.rs file
let mut mod_folders = FxHashSet::default();
// `file_map` maps file names to the full path including the file name
// `{ foo => path/to/foo.rs, .. }
let mut file_map = FxHashMap::default();
for file in files.iter() {
match &file.name {
FileName::Real(RealFileName::LocalPath(lp))
if lp.to_string_lossy().starts_with(trim_to_src.as_ref()) =>
{
let p = lp.to_string_lossy();
let path = Path::new(p.trim_start_matches(trim_to_src.as_ref()));
if let Some(stem) = path.file_stem() {
file_map.insert(stem.to_os_string(), (file, path.to_owned()));
}
process_paths_for_mod_files(path, &mut folder_segments, &mut mod_folders);
check_self_named_mod_exists(cx, path, file);
}
_ => {},
}
}
for folder in &folder_segments {
if !mod_folders.contains(folder) {
if let Some((file, path)) = file_map.get(folder) {
let mut correct = path.clone();
correct.pop();
correct.push(folder);
correct.push("mod.rs");
cx.struct_span_lint(
SELF_NAMED_MODULE_FILES,
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
|build| {
let mut lint =
build.build(&format!("`mod.rs` files are required, found `{}`", path.display()));
lint.help(&format!("move `{}` to `{}`", path.display(), correct.display(),));
lint.emit();
},
);
}
}
}
}
}
/// For each `path` we add each folder component to `folder_segments` and if the file name
/// is `mod.rs` we add it's parent folder to `mod_folders`.
fn process_paths_for_mod_files(
path: &Path,
folder_segments: &mut FxHashSet<OsString>,
mod_folders: &mut FxHashSet<OsString>,
) {
let mut comp = path.components().rev().peekable();
let _ = comp.next();
if path.ends_with("mod.rs") {
mod_folders.insert(comp.peek().map(|c| c.as_os_str().to_owned()).unwrap_or_default());
}
let folders = comp
.filter_map(|c| {
if let Component::Normal(s) = c {
Some(s.to_os_string())
} else {
None
}
})
.collect::<Vec<_>>();
folder_segments.extend(folders);
}
/// Checks every path for the presence of `mod.rs` files and emits the lint if found.
fn check_self_named_mod_exists(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) {
if path.ends_with("mod.rs") {
let mut mod_file = path.to_path_buf();
mod_file.pop();
mod_file.set_extension("rs");
cx.struct_span_lint(
MOD_MODULE_FILES,
Span::new(file.start_pos, file.start_pos, SyntaxContext::root()),
|build| {
let mut lint = build.build(&format!("`mod.rs` files are not allowed, found `{}`", path.display()));
lint.help(&format!("move `{}` to `{}`", path.display(), mod_file.display(),));
lint.emit();
},
);
}
}

View file

@ -47,9 +47,9 @@ declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]);
impl<'tcx> LateLintPass<'tcx> for MutMutexLock {
fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) {
if_chain! {
if let ExprKind::MethodCall(path, method_span, args, _) = &ex.kind;
if let ExprKind::MethodCall(path, method_span, [self_arg, ..], _) = &ex.kind;
if path.ident.name == sym!(lock);
let ty = cx.typeck_results().expr_ty(&args[0]);
let ty = cx.typeck_results().expr_ty(self_arg);
if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind();
if is_type_diagnostic_item(cx, inner_ty, sym!(mutex_type));
then {

View file

@ -0,0 +1,66 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_macro;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyS;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for no-op uses of Option::{as_deref,as_deref_mut},
/// for example, `Option<&T>::as_deref()` returns the same type.
///
/// ### Why is this bad?
/// Redundant code and improving readability.
///
/// ### Example
/// ```rust
/// let a = Some(&1);
/// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32>
/// ```
/// Could be written as:
/// ```rust
/// let a = Some(&1);
/// let b = a;
/// ```
pub NEEDLESS_OPTION_AS_DEREF,
complexity,
"no-op use of `deref` or `deref_mut` method to `Option`."
}
declare_lint_pass!(OptionNeedlessDeref=> [
NEEDLESS_OPTION_AS_DEREF,
]);
impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() || in_macro(expr.span) {
return;
}
let typeck = cx.typeck_results();
let outer_ty = typeck.expr_ty(expr);
if_chain! {
if is_type_diagnostic_item(cx,outer_ty,sym::option_type);
if let ExprKind::MethodCall(path, _, [sub_expr], _) = expr.kind;
let symbol = path.ident.as_str();
if symbol=="as_deref" || symbol=="as_deref_mut";
if TyS::same_type( outer_ty, typeck.expr_ty(sub_expr) );
then{
span_lint_and_sugg(
cx,
NEEDLESS_OPTION_AS_DEREF,
expr.span,
"derefed type is same as origin",
"try this",
snippet_opt(cx,sub_expr.span).unwrap(),
Applicability::MachineApplicable
);
}
}
}
}

View file

@ -96,28 +96,63 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
if has_no_effect(cx, expr) {
span_lint_hir(cx, NO_EFFECT, expr.hir_id, stmt.span, "statement with no effect");
} else if let Some(reduced) = reduce_expression(cx, expr) {
let mut snippet = String::new();
for e in reduced {
for e in &reduced {
if e.span.from_expansion() {
return;
}
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"statement can be reduced",
|diag| {
diag.span_suggestion(stmt.span, "replace it with", snippet, Applicability::MachineApplicable);
},
);
if let ExprKind::Index(..) = &expr.kind {
let snippet;
if_chain! {
if let Some(arr) = snippet_opt(cx, reduced[0].span);
if let Some(func) = snippet_opt(cx, reduced[1].span);
then {
snippet = format!("assert!({}.len() > {});", &arr, &func);
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be written as",
snippet,
Applicability::MaybeIncorrect,
);
},
);
} else {
let mut snippet = String::new();
for e in reduced {
if let Some(snip) = snippet_opt(cx, e.span) {
snippet.push_str(&snip);
snippet.push(';');
} else {
return;
}
}
span_lint_hir_and_then(
cx,
UNNECESSARY_OPERATION,
expr.hir_id,
stmt.span,
"unnecessary operation",
|diag| {
diag.span_suggestion(
stmt.span,
"statement can be reduced to",
snippet,
Applicability::MachineApplicable,
);
},
);
}
}
}
}

View file

@ -31,11 +31,11 @@ declare_lint_pass!(OpenOptions => [NONSENSICAL_OPEN_OPTIONS]);
impl<'tcx> LateLintPass<'tcx> for OpenOptions {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::MethodCall(path, _, arguments, _) = e.kind {
let obj_ty = cx.typeck_results().expr_ty(&arguments[0]).peel_refs();
if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &e.kind {
let obj_ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if path.ident.name == sym!(open) && match_type(cx, obj_ty, &paths::OPEN_OPTIONS) {
let mut options = Vec::new();
get_open_options(cx, &arguments[0], &mut options);
get_open_options(cx, self_arg, &mut options);
check_open_options(cx, &options, e.span);
}
}

View file

@ -2,12 +2,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{eager_or_lazy, in_macro, is_else_clause, is_lang_ctor};
use clippy_utils::{
can_move_expr_to_closure, eager_or_lazy, in_constant, in_macro, is_else_clause, is_lang_ctor, peel_hir_expr_while,
CaptureKind,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionSome;
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, UnOp};
use rustc_hir::{def::Res, BindingAnnotation, Block, Expr, ExprKind, Mutability, PatKind, Path, QPath, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@ -58,7 +60,7 @@ declare_clippy_lint! {
/// }, |foo| foo);
/// ```
pub OPTION_IF_LET_ELSE,
pedantic,
nursery,
"reimplementation of Option::map_or"
}
@ -125,20 +127,30 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionIfLetElseOccurence> {
if_chain! {
if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) = higher::IfLet::hir(cx, expr);
if !in_constant(cx, expr.hir_id);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if !is_else_clause(cx.tcx, expr);
if !is_result_ok(cx, let_expr); // Don't lint on Result::ok because a different lint does it already
if let PatKind::TupleStruct(struct_qpath, [inner_pat], _) = &let_pat.kind;
if is_lang_ctor(cx, struct_qpath, OptionSome);
if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind;
if !contains_return_break_continue_macro(if_then);
if !contains_return_break_continue_macro(if_else);
if let Some(some_captures) = can_move_expr_to_closure(cx, if_then);
if let Some(none_captures) = can_move_expr_to_closure(cx, if_else);
if some_captures
.iter()
.filter_map(|(id, &c)| none_captures.get(id).map(|&c2| (c, c2)))
.all(|(x, y)| x.is_imm_ref() && y.is_imm_ref());
then {
let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" };
let some_body = extract_body_from_expr(if_then)?;
let none_body = extract_body_from_expr(if_else)?;
let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { "map_or" } else { "map_or_else" };
let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) {
"map_or"
} else {
"map_or_else"
};
let capture_name = id.name.to_ident_string();
let (as_ref, as_mut) = match &let_expr.kind {
ExprKind::AddrOf(_, Mutability::Not, _) => (true, false),
@ -150,6 +162,24 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
ExprKind::Unary(UnOp::Deref, expr) | ExprKind::AddrOf(_, _, expr) => expr,
_ => let_expr,
};
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if as_ref || as_mut {
let e = peel_hir_expr_while(cond_expr, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
match some_captures.get(local_id)
.or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id)))
{
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
Some(OptionIfLetElseOccurence {
option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
method_sugg: method_sugg.to_string(),

View file

@ -118,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for PatternTypeMismatch {
}
}
if let ExprKind::Let(let_pat, let_expr, _) = expr.kind {
if let Some(ref expr_ty) = cx.typeck_results().node_type_opt(let_expr.hir_id) {
if let Some(expr_ty) = cx.typeck_results().node_type_opt(let_expr.hir_id) {
if in_external_macro(cx.sess(), let_pat.span) {
return;
}

View file

@ -92,13 +92,13 @@ fn expr_as_ptr_offset_call<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> {
if let ExprKind::MethodCall(path_segment, _, args, _) = expr.kind {
if is_expr_ty_raw_ptr(cx, &args[0]) {
if let ExprKind::MethodCall(path_segment, _, [arg_0, arg_1, ..], _) = &expr.kind {
if is_expr_ty_raw_ptr(cx, arg_0) {
if path_segment.ident.name == sym::offset {
return Some((&args[0], &args[1], Method::Offset));
return Some((arg_0, arg_1, Method::Offset));
}
if path_segment.ident.name == sym!(wrapping_offset) {
return Some((&args[0], &args[1], Method::WrappingOffset));
return Some((arg_0, arg_1, Method::WrappingOffset));
}
}
}

View file

@ -97,7 +97,8 @@ impl QuestionMark {
fn check_if_let_some_and_early_return_none(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) = higher::IfLet::hir(cx, expr);
if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) })
= higher::IfLet::hir(cx, expr);
if Self::is_option(cx, let_expr);
if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind;
@ -105,7 +106,7 @@ impl QuestionMark {
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Block(ref block, None) = if_then.kind;
if let ExprKind::Block(block, None) = if_then.kind;
if block.stmts.is_empty();
if let Some(trailing_expr) = &block.expr;
if path_to_local_id(trailing_expr, bind_id);

View file

@ -625,7 +625,10 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
.flat_map(HybridBitSet::iter)
.collect();
if ContainsRegion(self.cx.tcx).visit_ty(self.body.local_decls[*dest].ty).is_break() {
if ContainsRegion(self.cx.tcx)
.visit_ty(self.body.local_decls[*dest].ty)
.is_break()
{
mutable_variables.push(*dest);
}

View file

@ -51,9 +51,7 @@ impl ReturnVisitor {
impl<'ast> ast_visit::Visitor<'ast> for ReturnVisitor {
fn visit_expr(&mut self, ex: &'ast ast::Expr) {
if let ast::ExprKind::Ret(_) = ex.kind {
self.found_return = true;
} else if let ast::ExprKind::Try(_) = ex.kind {
if let ast::ExprKind::Ret(_) | ast::ExprKind::Try(_) = ex.kind {
self.found_return = true;
}

View file

@ -206,13 +206,10 @@ fn check_final_expr<'tcx>(
// an if/if let expr, check both exprs
// note, if without else is going to be a type checking error anyways
// (except for unit type functions) so we don't match it
ExprKind::Match(_, arms, source) => match source {
MatchSource::Normal => {
for arm in arms.iter() {
check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Block);
}
},
_ => (),
ExprKind::Match(_, arms, MatchSource::Normal) => {
for arm in arms.iter() {
check_final_expr(cx, arm.body, Some(arm.body.span), RetReplacement::Block);
}
},
ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty),
_ => (),

View file

@ -345,9 +345,9 @@ declare_lint_pass!(StrToString => [STR_TO_STRING]);
impl LateLintPass<'_> for StrToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]);
let ty = cx.typeck_results().expr_ty(self_arg);
if let ty::Ref(_, ty, ..) = ty.kind();
if *ty.kind() == ty::Str;
then {
@ -394,9 +394,9 @@ declare_lint_pass!(StringToString => [STRING_TO_STRING]);
impl LateLintPass<'_> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]);
let ty = cx.typeck_results().expr_ty(self_arg);
if is_type_diagnostic_item(cx, ty, sym::string_type);
then {
span_lint_and_help(

View file

@ -92,11 +92,11 @@ impl LateLintPass<'_> for ToStringInDisplay {
if_chain! {
if self.in_display_impl;
if let Some(self_hir_id) = self.self_hir_id;
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
if let ExprKind::MethodCall(path, _, [ref self_arg, ..], _) = expr.kind;
if path.ident.name == sym!(to_string);
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
if path_to_local_id(&args[0], self_hir_id);
if path_to_local_id(self_arg, self_hir_id);
then {
span_lint(
cx,

View file

@ -295,6 +295,7 @@ declare_clippy_lint! {
pub struct Types {
vec_box_size_threshold: u64,
type_complexity_threshold: u64,
avoid_breaking_exported_api: bool,
}
impl_lint_pass!(Types => [BOX_VEC, VEC_BOX, OPTION_OPTION, LINKEDLIST, BORROWED_BOX, REDUNDANT_ALLOCATION, RC_BUFFER, RC_MUTEX, TYPE_COMPLEXITY]);
@ -308,19 +309,31 @@ impl<'tcx> LateLintPass<'tcx> for Types {
false
};
let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(id));
self.check_fn_decl(
cx,
decl,
CheckTyContext {
is_in_trait_impl,
is_exported,
..CheckTyContext::default()
},
);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
let is_exported = cx.access_levels.is_exported(item.def_id);
match item.kind {
ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(cx, ty, CheckTyContext::default()),
ItemKind::Static(ty, _, _) | ItemKind::Const(ty, _) => self.check_ty(
cx,
ty,
CheckTyContext {
is_exported,
..CheckTyContext::default()
},
),
// functions, enums, structs, impls and traits are covered
_ => (),
}
@ -342,15 +355,31 @@ impl<'tcx> LateLintPass<'tcx> for Types {
}
fn check_field_def(&mut self, cx: &LateContext<'_>, field: &hir::FieldDef<'_>) {
self.check_ty(cx, field.ty, CheckTyContext::default());
let is_exported = cx.access_levels.is_exported(cx.tcx.hir().local_def_id(field.hir_id));
self.check_ty(
cx,
field.ty,
CheckTyContext {
is_exported,
..CheckTyContext::default()
},
);
}
fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &TraitItem<'_>) {
let is_exported = cx.access_levels.is_exported(item.def_id);
let context = CheckTyContext {
is_exported,
..CheckTyContext::default()
};
match item.kind {
TraitItemKind::Const(ty, _) | TraitItemKind::Type(_, Some(ty)) => {
self.check_ty(cx, ty, CheckTyContext::default());
self.check_ty(cx, ty, context);
},
TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, CheckTyContext::default()),
TraitItemKind::Fn(ref sig, _) => self.check_fn_decl(cx, sig.decl, context),
TraitItemKind::Type(..) => (),
}
}
@ -370,10 +399,11 @@ impl<'tcx> LateLintPass<'tcx> for Types {
}
impl Types {
pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64) -> Self {
pub fn new(vec_box_size_threshold: u64, type_complexity_threshold: u64, avoid_breaking_exported_api: bool) -> Self {
Self {
vec_box_size_threshold,
type_complexity_threshold,
avoid_breaking_exported_api,
}
}
@ -410,17 +440,24 @@ impl Types {
let hir_id = hir_ty.hir_id;
let res = cx.qpath_res(qpath, hir_id);
if let Some(def_id) = res.opt_def_id() {
let mut triggered = false;
triggered |= box_vec::check(cx, hir_ty, qpath, def_id);
triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
triggered |= option_option::check(cx, hir_ty, qpath, def_id);
triggered |= linked_list::check(cx, hir_ty, def_id);
triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
if self.is_type_change_allowed(context) {
// All lints that are being checked in this block are guarded by
// the `avoid_breaking_exported_api` configuration. When adding a
// new lint, please also add the name to the configuration documentation
// in `clippy_lints::utils::conf.rs`
if triggered {
return;
let mut triggered = false;
triggered |= box_vec::check(cx, hir_ty, qpath, def_id);
triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id);
triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id);
triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold);
triggered |= option_option::check(cx, hir_ty, qpath, def_id);
triggered |= linked_list::check(cx, hir_ty, def_id);
triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id);
if triggered {
return;
}
}
}
match *qpath {
@ -487,11 +524,21 @@ impl Types {
_ => {},
}
}
/// This function checks if the type is allowed to change in the current context
/// based on the `avoid_breaking_exported_api` configuration
fn is_type_change_allowed(&self, context: CheckTyContext) -> bool {
!(context.is_exported && self.avoid_breaking_exported_api)
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Clone, Copy, Default)]
struct CheckTyContext {
is_in_trait_impl: bool,
/// `true` for types on local variables.
is_local: bool,
/// `true` for types that are part of the public API.
is_exported: bool,
is_nested_call: bool,
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_ty_param_diagnostic_item;
use if_chain::if_chain;
use rustc_hir::{self as hir, def_id::DefId, QPath};
@ -11,13 +11,14 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
if_chain! {
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ;
if let Some(_) = is_ty_param_diagnostic_item(cx, qpath, sym!(mutex_type)) ;
then{
span_lint(
then {
span_lint_and_help(
cx,
RC_MUTEX,
hir_ty.span,
"found `Rc<Mutex<_>>`. Consider using `Rc<RefCell<_>>` or `Arc<Mutex<_>>` instead",
"usage of `Rc<Mutex<_>>`",
None,
"consider using `Rc<RefCell<_>>` or `Arc<Mutex<_>>` instead",
);
return true;
}

View file

@ -54,7 +54,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
_ => return false,
};
let inner_span = match get_qpath_generic_tys(inner_qpath).next() {
Some(ty) => ty.span,
Some(ty) => {
// Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
if matches!(ty.kind, TyKind::TraitObject(..)) {
return false;
}
ty.span
},
None => return false,
};
if inner_sym == outer_sym {

View file

@ -37,8 +37,8 @@ declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]);
impl LateLintPass<'tcx> for UndroppedManuallyDrops {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(args) = match_function_call(cx, expr, &paths::DROP) {
let ty = cx.typeck_results().expr_ty(&args[0]);
if let Some([arg_0, ..]) = match_function_call(cx, expr, &paths::DROP) {
let ty = cx.typeck_results().expr_ty(arg_0);
if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) {
span_lint_and_help(
cx,

View file

@ -218,7 +218,10 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
matches!(ty.kind(), ty::Ref(..)) || ty.walk(cx.tcx).any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
matches!(ty.kind(), ty::Ref(..))
|| ty
.walk(cx.tcx)
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
}
impl LateLintPass<'_> for UnnecessarySortBy {

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, Item, ItemKind, YieldSource};
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, YieldSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -57,11 +57,6 @@ impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> {
}
impl<'tcx> LateLintPass<'tcx> for UnusedAsync {
fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Trait(..) = item.kind {
return;
}
}
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,

View file

@ -45,20 +45,20 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
match expr.kind {
hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
if let hir::ExprKind::Call(func, args) = res.kind {
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
if matches!(
func.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, _))
) {
check_map_error(cx, &args[0], expr);
check_map_error(cx, arg_0, expr);
}
} else {
check_map_error(cx, res, expr);
}
},
hir::ExprKind::MethodCall(path, _, args, _) => match &*path.ident.as_str() {
hir::ExprKind::MethodCall(path, _, [ref arg_0, ..], _) => match &*path.ident.as_str() {
"expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
check_map_error(cx, &args[0], expr);
check_map_error(cx, arg_0, expr);
},
_ => (),
},

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::visitors::is_local_used;
use if_chain::if_chain;
use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
if let ImplItemKind::Fn(.., body_id) = &impl_item.kind;
let body = cx.tcx.hir().body(*body_id);
if let [self_param, ..] = body.params;
let self_hir_id = self_param.pat.hir_id;
if !LocalUsedVisitor::new(cx, self_hir_id).check_body(body);
if !is_local_used(cx, body, self_param.pat.hir_id);
then {
span_lint_and_help(
cx,

View file

@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{differing_macro_contexts, usage::is_potentially_mutated};
use clippy_utils::{differing_macro_contexts, path_to_local, usage::is_potentially_mutated};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Path, QPath, UnOp};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
@ -74,26 +75,61 @@ struct UnwrappableVariablesVisitor<'a, 'tcx> {
unwrappables: Vec<UnwrapInfo<'tcx>>,
cx: &'a LateContext<'tcx>,
}
/// What kind of unwrappable this is.
#[derive(Copy, Clone, Debug)]
enum UnwrappableKind {
Option,
Result,
}
impl UnwrappableKind {
fn success_variant_pattern(self) -> &'static str {
match self {
UnwrappableKind::Option => "Some(..)",
UnwrappableKind::Result => "Ok(..)",
}
}
fn error_variant_pattern(self) -> &'static str {
match self {
UnwrappableKind::Option => "None",
UnwrappableKind::Result => "Err(..)",
}
}
}
/// Contains information about whether a variable can be unwrapped.
#[derive(Copy, Clone, Debug)]
struct UnwrapInfo<'tcx> {
/// The variable that is checked
ident: &'tcx Path<'tcx>,
local_id: HirId,
/// The if itself
if_expr: &'tcx Expr<'tcx>,
/// The check, like `x.is_ok()`
check: &'tcx Expr<'tcx>,
/// The check's name, like `is_ok`
check_name: &'tcx PathSegment<'tcx>,
/// The branch where the check takes place, like `if x.is_ok() { .. }`
branch: &'tcx Expr<'tcx>,
/// Whether `is_some()` or `is_ok()` was called (as opposed to `is_err()` or `is_none()`).
safe_to_unwrap: bool,
/// What kind of unwrappable this is.
kind: UnwrappableKind,
/// If the check is the entire condition (`if x.is_ok()`) or only a part of it (`foo() &&
/// x.is_ok()`)
is_entire_condition: bool,
}
/// Collects the information about unwrappable variables from an if condition
/// The `invert` argument tells us whether the condition is negated.
fn collect_unwrap_info<'tcx>(
cx: &LateContext<'tcx>,
if_expr: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
branch: &'tcx Expr<'_>,
invert: bool,
is_entire_condition: bool,
) -> Vec<UnwrapInfo<'tcx>> {
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: &str) -> bool {
is_type_diagnostic_item(cx, ty, sym::option_type) && ["is_some", "is_none"].contains(&method_name)
@ -106,18 +142,18 @@ fn collect_unwrap_info<'tcx>(
if let ExprKind::Binary(op, left, right) = &expr.kind {
match (invert, op.node) {
(false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) => {
let mut unwrap_info = collect_unwrap_info(cx, left, branch, invert);
unwrap_info.append(&mut collect_unwrap_info(cx, right, branch, invert));
let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false);
unwrap_info.append(&mut collect_unwrap_info(cx, if_expr, right, branch, invert, false));
return unwrap_info;
},
_ => (),
}
} else if let ExprKind::Unary(UnOp::Not, expr) = &expr.kind {
return collect_unwrap_info(cx, expr, branch, !invert);
return collect_unwrap_info(cx, if_expr, expr, branch, !invert, false);
} else {
if_chain! {
if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
if let ExprKind::Path(QPath::Resolved(None, path)) = &args[0].kind;
if let Some(local_id) = path_to_local(&args[0]);
let ty = cx.typeck_results().expr_ty(&args[0]);
let name = method_name.ident.as_str();
if is_relevant_option_call(cx, ty, &name) || is_relevant_result_call(cx, ty, &name);
@ -129,7 +165,24 @@ fn collect_unwrap_info<'tcx>(
_ => unreachable!(),
};
let safe_to_unwrap = unwrappable != invert;
return vec![UnwrapInfo { ident: path, check: expr, branch, safe_to_unwrap }];
let kind = if is_type_diagnostic_item(cx, ty, sym::option_type) {
UnwrappableKind::Option
} else {
UnwrappableKind::Result
};
return vec![
UnwrapInfo {
local_id,
if_expr,
check: expr,
check_name: method_name,
branch,
safe_to_unwrap,
kind,
is_entire_condition,
}
]
}
}
}
@ -137,11 +190,17 @@ fn collect_unwrap_info<'tcx>(
}
impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
fn visit_branch(&mut self, cond: &'tcx Expr<'_>, branch: &'tcx Expr<'_>, else_branch: bool) {
fn visit_branch(
&mut self,
if_expr: &'tcx Expr<'_>,
cond: &'tcx Expr<'_>,
branch: &'tcx Expr<'_>,
else_branch: bool,
) {
let prev_len = self.unwrappables.len();
for unwrap_info in collect_unwrap_info(self.cx, cond, branch, else_branch) {
if is_potentially_mutated(unwrap_info.ident, cond, self.cx)
|| is_potentially_mutated(unwrap_info.ident, branch, self.cx)
for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) {
if is_potentially_mutated(unwrap_info.local_id, cond, self.cx)
|| is_potentially_mutated(unwrap_info.local_id, branch, self.cx)
{
// if the variable is mutated, we don't know whether it can be unwrapped:
continue;
@ -163,32 +222,62 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
}
if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr) {
walk_expr(self, cond);
self.visit_branch(cond, then, false);
self.visit_branch(expr, cond, then, false);
if let Some(else_inner) = r#else {
self.visit_branch(cond, else_inner, true);
self.visit_branch(expr, cond, else_inner, true);
}
} else {
// find `unwrap[_err]()` calls:
if_chain! {
if let ExprKind::MethodCall(method_name, _, args, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(None, path)) = args[0].kind;
if [sym::unwrap, sym!(unwrap_err)].contains(&method_name.ident.name);
let call_to_unwrap = method_name.ident.name == sym::unwrap;
if let Some(id) = path_to_local(&args[0]);
if [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name);
let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name);
if let Some(unwrappable) = self.unwrappables.iter()
.find(|u| u.ident.res == path.res);
.find(|u| u.local_id == id);
// Span contexts should not differ with the conditional branch
if !differing_macro_contexts(unwrappable.branch.span, expr.span);
if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
then {
if call_to_unwrap == unwrappable.safe_to_unwrap {
let is_entire_condition = unwrappable.is_entire_condition;
let unwrappable_variable_name = self.cx.tcx.hir().name(unwrappable.local_id);
let suggested_pattern = if call_to_unwrap {
unwrappable.kind.success_variant_pattern()
} else {
unwrappable.kind.error_variant_pattern()
};
span_lint_and_then(
self.cx,
UNNECESSARY_UNWRAP,
expr.span,
&format!("you checked before that `{}()` cannot fail, \
instead of checking and unwrapping, it's better to use `if let` or `match`",
method_name.ident.name),
|diag| { diag.span_label(unwrappable.check.span, "the check is happening here"); },
&format!(
"called `{}` on `{}` after checking its variant with `{}`",
method_name.ident.name,
unwrappable_variable_name,
unwrappable.check_name.ident.as_str(),
),
|diag| {
if is_entire_condition {
diag.span_suggestion(
unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()),
"try",
format!(
"if let {} = {}",
suggested_pattern,
unwrappable_variable_name,
),
// We don't track how the unwrapped value is used inside the
// block or suggest deleting the unwrap, so we can't offer a
// fixable solution.
Applicability::Unspecified,
);
} else {
diag.span_label(unwrappable.check.span, "the check is happening here");
diag.help("try using `if let` or `match`");
}
},
);
} else {
span_lint_and_then(

View file

@ -31,9 +31,6 @@ impl TryConf {
}
}
/// Note that the configuration parsing currently doesn't support documentation that will
/// that spans over several lines. This will be possible with the new implementation
/// See (rust-clippy#7172)
macro_rules! define_Conf {
($(
$(#[doc = $doc:literal])+
@ -130,13 +127,12 @@ macro_rules! define_Conf {
};
}
// N.B., this macro is parsed by util/lintlib.py
define_Conf! {
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION.
/// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_VEC, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE.
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT.
///
/// The minimum rust version that the project supports
(msrv: Option<String> = None),

View file

@ -142,7 +142,7 @@ fn print_expr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, indent: usize) {
print_expr(cx, arg, indent + 1);
}
},
hir::ExprKind::Let(ref pat, ref expr, _) => {
hir::ExprKind::Let(pat, expr, _) => {
print_pat(cx, pat, indent + 1);
print_expr(cx, expr, indent + 1);
},

View file

@ -1,5 +1,6 @@
use clippy_utils::consts::{constant_simple, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::higher;
use clippy_utils::source::snippet;
use clippy_utils::ty::match_type;
use clippy_utils::{
@ -17,8 +18,8 @@ use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::CRATE_HIR_ID;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::{
BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MatchSource, MutTy, Mutability, Node, Path, Stmt,
StmtKind, Ty, TyKind, UnOp,
BinOpKind, Block, Crate, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty,
TyKind, UnOp,
};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
@ -503,10 +504,10 @@ impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions {
}
if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
if let ExprKind::MethodCall(path, _, [self_arg, ..], _) = &expr.kind;
let fn_name = path.ident;
if let Some(sugg) = self.map.get(&*fn_name.as_str());
let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs();
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
if match_type(cx, ty, &paths::EARLY_CONTEXT)
|| match_type(cx, ty, &paths::LATE_CONTEXT);
then {
@ -1106,16 +1107,10 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
let (cond, then, els) = match expr.kind {
ExprKind::If(cond, then, els) => (Some(cond), then, els.is_some()),
ExprKind::Match(
_,
[arm, ..],
MatchSource::IfLetDesugar {
contains_else_clause: els,
},
) => (None, arm.body, els),
_ => return,
let (cond, then, els) = if let Some(higher::IfOrIfLet { cond, r#else, then }) = higher::IfOrIfLet::hir(expr) {
(cond, then, r#else.is_some())
} else {
return;
};
let then_block = match then.kind {
ExprKind::Block(block, _) => block,
@ -1131,7 +1126,6 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
};
// check for `if a && b;`
if_chain! {
if let Some(cond) = cond;
if let ExprKind::Binary(op, _, _) = cond.kind;
if op.node == BinOpKind::And;
if cx.sess().source_map().is_multiline(cond.span);
@ -1166,9 +1160,7 @@ fn check_nested_if_chains(
_ => return,
};
if_chain! {
if matches!(tail.kind,
ExprKind::If(_, _, None)
| ExprKind::Match(.., MatchSource::IfLetDesugar { contains_else_clause: false }));
if let Some(higher::IfOrIfLet { r#else: None, .. }) = higher::IfOrIfLet::hir(tail);
let sm = cx.sess().source_map();
if head
.iter()

View file

@ -82,7 +82,7 @@ This lint has the following configuration variables:
/// `default`
macro_rules! CONFIGURATION_VALUE_TEMPLATE {
() => {
"* {name}: `{ty}`: {doc} (defaults to `{default}`)\n"
"* `{name}`: `{ty}`: {doc} (defaults to `{default}`)\n"
};
}
@ -786,8 +786,6 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
}
};
// TODO xFrednet 2021-03-01: support function arguments?
intravisit::walk_expr(self, expr);
}
}

View file

@ -49,7 +49,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
if_chain! {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(expr).kind();
if let ty::Slice(..) = ty.kind();
if let ExprKind::AddrOf(BorrowKind::Ref, mutability, ref addressee) = expr.kind;
if let ExprKind::AddrOf(BorrowKind::Ref, mutability, addressee) = expr.kind;
if let Some(vec_args) = higher::VecArgs::hir(cx, addressee);
then {
self.check_vec_macro(cx, &vec_args, mutability, expr.span);

View file

@ -1,15 +1,11 @@
[package]
name = "clippy_utils"
version = "0.1.56"
version = "0.1.57"
edition = "2018"
publish = false
[dependencies]
if_chain = "1.0.0"
itertools = "0.9"
regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] }
unicode-normalization = "0.1"
rustc-semver="1.1.0"
[features]

View file

@ -21,7 +21,7 @@ fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
"for further information visit https://rust-lang.github.io/rust-clippy/{}/index.html#{}",
&option_env!("RUST_RELEASE_NUM").map_or("master".to_string(), |n| {
// extract just major + minor version and ignore patch versions
format!("rust-{}", n.rsplitn(2, '.').nth(1).unwrap())
format!("rust-{}", n.rsplit_once('.').unwrap().1)
}),
lint
));

View file

@ -6,33 +6,38 @@ use crate::{is_expn_of, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir;
use rustc_hir::{Block, BorrowKind, Expr, ExprKind, LoopSource, Node, Pat, StmtKind, UnOp};
use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
use rustc_lint::LateContext;
use rustc_span::{sym, ExpnKind, Span, Symbol};
/// The essential nodes of a desugared for loop as well as the entire span:
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
pub struct ForLoop<'tcx> {
/// `for` loop item
pub pat: &'tcx hir::Pat<'tcx>,
/// `IntoIterator` argument
pub arg: &'tcx hir::Expr<'tcx>,
/// `for` loop body
pub body: &'tcx hir::Expr<'tcx>,
/// entire `for` loop span
pub span: Span,
}
impl<'tcx> ForLoop<'tcx> {
#[inline]
/// Parses a desugared `for` loop
pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
if_chain! {
if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
if let Some(first_arm) = arms.get(0);
if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
if let Some(first_arg) = iterargs.get(0);
if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none();
if let hir::ExprKind::Loop(ref block, ..) = first_arm.body.kind;
if let hir::ExprKind::Loop(block, ..) = first_arm.body.kind;
if block.expr.is_none();
if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
if let hir::StmtKind::Local(ref local) = let_stmt.kind;
if let hir::StmtKind::Expr(ref body_expr) = body.kind;
if let hir::StmtKind::Local(local) = let_stmt.kind;
if let hir::StmtKind::Expr(body_expr) = body.kind;
then {
return Some(Self {
pat: &*local.pat,
@ -46,14 +51,19 @@ impl<'tcx> ForLoop<'tcx> {
}
}
/// An `if` expression without `DropTemps`
pub struct If<'hir> {
/// `if` condition
pub cond: &'hir Expr<'hir>,
pub r#else: Option<&'hir Expr<'hir>>,
/// `if` then expression
pub then: &'hir Expr<'hir>,
/// `else` expression
pub r#else: Option<&'hir Expr<'hir>>,
}
impl<'hir> If<'hir> {
#[inline]
/// Parses an `if` expression
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If(
Expr {
@ -64,21 +74,27 @@ impl<'hir> If<'hir> {
r#else,
) = expr.kind
{
Some(Self { cond, r#else, then })
Some(Self { cond, then, r#else })
} else {
None
}
}
}
/// An `if let` expression
pub struct IfLet<'hir> {
/// `if let` pattern
pub let_pat: &'hir Pat<'hir>,
/// `if let` scrutinee
pub let_expr: &'hir Expr<'hir>,
/// `if let` then expression
pub if_then: &'hir Expr<'hir>,
/// `if let` else expression
pub if_else: Option<&'hir Expr<'hir>>,
}
impl<'hir> IfLet<'hir> {
/// Parses an `if let` expression
pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If(
Expr {
@ -92,7 +108,14 @@ impl<'hir> IfLet<'hir> {
let hir = cx.tcx.hir();
let mut iter = hir.parent_iter(expr.hir_id);
if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() {
if let Some((_, Node::Expr(Expr { kind: ExprKind::Loop(_, _, LoopSource::While, _), .. }))) = iter.next() {
if let Some((
_,
Node::Expr(Expr {
kind: ExprKind::Loop(_, _, LoopSource::While, _),
..
}),
)) = iter.next()
{
// while loop desugar
return None;
}
@ -108,14 +131,49 @@ impl<'hir> IfLet<'hir> {
}
}
/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
pub enum IfLetOrMatch<'hir> {
/// Any `match` expression
Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
/// scrutinee, pattern, then block, else block
IfLet(
&'hir Expr<'hir>,
&'hir Pat<'hir>,
&'hir Expr<'hir>,
Option<&'hir Expr<'hir>>,
),
}
impl<'hir> IfLetOrMatch<'hir> {
/// Parses an `if let` or `match` expression
pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
match expr.kind {
ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
_ => IfLet::hir(cx, expr).map(
|IfLet {
let_expr,
let_pat,
if_then,
if_else,
}| { Self::IfLet(let_expr, let_pat, if_then, if_else) },
),
}
}
}
/// An `if` or `if let` expression
pub struct IfOrIfLet<'hir> {
/// `if` condition that is maybe a `let` expression
pub cond: &'hir Expr<'hir>,
pub r#else: Option<&'hir Expr<'hir>>,
/// `if` then expression
pub then: &'hir Expr<'hir>,
/// `else` expression
pub r#else: Option<&'hir Expr<'hir>>,
}
impl<'hir> IfOrIfLet<'hir> {
#[inline]
/// Parses an `if` or `if let` expression
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::If(cond, then, r#else) = expr.kind {
if let ExprKind::DropTemps(new_cond) = cond.kind {
@ -126,7 +184,7 @@ impl<'hir> IfOrIfLet<'hir> {
});
}
if let ExprKind::Let(..) = cond.kind {
return Some(Self { cond, r#else, then });
return Some(Self { cond, then, r#else });
}
}
None
@ -155,7 +213,7 @@ impl<'a> Range<'a> {
}
match expr.kind {
hir::ExprKind::Call(ref path, ref args)
hir::ExprKind::Call(path, args)
if matches!(
path.kind,
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
@ -167,7 +225,7 @@ impl<'a> Range<'a> {
limits: ast::RangeLimits::Closed,
})
},
hir::ExprKind::Struct(ref path, ref fields, None) => match path {
hir::ExprKind::Struct(path, fields, None) => match &path {
hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
start: None,
end: None,
@ -213,7 +271,7 @@ impl<'a> VecArgs<'a> {
/// from `vec!`.
pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> {
if_chain! {
if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
if let hir::ExprKind::Call(fun, args) = expr.kind;
if let hir::ExprKind::Path(ref qpath) = fun.kind;
if is_expn_of(fun.span, "vec").is_some();
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
@ -225,10 +283,10 @@ impl<'a> VecArgs<'a> {
else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
// `vec![a, b, c]` case
if_chain! {
if let hir::ExprKind::Box(ref boxed) = args[0].kind;
if let hir::ExprKind::Array(ref args) = boxed.kind;
if let hir::ExprKind::Box(boxed) = args[0].kind;
if let hir::ExprKind::Array(args) = boxed.kind;
then {
return Some(VecArgs::Vec(&*args));
return Some(VecArgs::Vec(args));
}
}
@ -247,14 +305,17 @@ impl<'a> VecArgs<'a> {
}
}
/// A desugared `while` loop
pub struct While<'hir> {
pub if_cond: &'hir Expr<'hir>,
pub if_then: &'hir Expr<'hir>,
pub if_else: Option<&'hir Expr<'hir>>,
/// `while` loop condition
pub condition: &'hir Expr<'hir>,
/// `while` loop body
pub body: &'hir Expr<'hir>,
}
impl<'hir> While<'hir> {
#[inline]
/// Parses a desugared `while` loop
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::Loop(
Block {
@ -263,11 +324,11 @@ impl<'hir> While<'hir> {
kind:
ExprKind::If(
Expr {
kind: ExprKind::DropTemps(if_cond),
kind: ExprKind::DropTemps(condition),
..
},
if_then,
if_else_ref,
body,
_,
),
..
}),
@ -278,59 +339,53 @@ impl<'hir> While<'hir> {
_,
) = expr.kind
{
let if_else = *if_else_ref;
return Some(Self {
if_cond,
if_then,
if_else,
});
return Some(Self { condition, body });
}
None
}
}
/// A desugared `while let` loop
pub struct WhileLet<'hir> {
pub if_expr: &'hir Expr<'hir>,
/// `while let` loop item pattern
pub let_pat: &'hir Pat<'hir>,
/// `while let` loop scrutinee
pub let_expr: &'hir Expr<'hir>,
/// `while let` loop body
pub if_then: &'hir Expr<'hir>,
pub if_else: Option<&'hir Expr<'hir>>,
}
impl<'hir> WhileLet<'hir> {
#[inline]
/// Parses a desugared `while let` loop
pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
if let ExprKind::Loop(
Block {
expr: Some(if_expr), ..
expr:
Some(Expr {
kind:
ExprKind::If(
Expr {
kind: ExprKind::Let(let_pat, let_expr, _),
..
},
if_then,
_,
),
..
}),
..
},
_,
LoopSource::While,
_,
) = expr.kind
{
if let Expr {
kind:
ExprKind::If(
Expr {
kind: ExprKind::Let(let_pat, let_expr, _),
..
},
if_then,
if_else_ref,
),
..
} = if_expr
{
let if_else = *if_else_ref;
return Some(Self {
if_expr,
let_pat,
let_expr,
if_then,
if_else,
});
}
return Some(Self {
let_pat,
let_expr,
if_then,
});
}
None
}
@ -532,7 +587,7 @@ pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
// }
// ```
if_chain! {
if let Some(ref expr) = local.init;
if let Some(expr) = local.init;
if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
then {
return true;

View file

@ -232,9 +232,7 @@ impl HirEqInterExpr<'_, '_, '_> {
(&ExprKind::If(lc, lt, ref le), &ExprKind::If(rc, rt, ref re)) => {
self.eq_expr(lc, rc) && self.eq_expr(&**lt, &**rt) && both(le, re, |l, r| self.eq_expr(l, r))
},
(&ExprKind::Let(ref lp, ref le, _), &ExprKind::Let(ref rp, ref re, _)) => {
self.eq_pat(lp, rp) && self.eq_expr(le, re)
},
(&ExprKind::Let(lp, le, _), &ExprKind::Let(rp, re, _)) => self.eq_pat(lp, rp) && self.eq_expr(le, re),
(&ExprKind::Lit(ref l), &ExprKind::Lit(ref r)) => l.node == r.node,
(&ExprKind::Loop(lb, ref ll, ref lls, _), &ExprKind::Loop(rb, ref rl, ref rls, _)) => {
lls == rls && self.eq_block(lb, rb) && both(ll, rl, |l, r| l.ident.name == r.ident.name)
@ -668,7 +666,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
}
}
},
ExprKind::Let(ref pat, ref expr, _) => {
ExprKind::Let(pat, expr, _) => {
self.hash_expr(expr);
self.hash_pat(pat);
},

View file

@ -2,6 +2,7 @@
#![feature(in_band_lifetimes)]
#![feature(iter_zip)]
#![feature(rustc_private)]
#![feature(control_flow_enum)]
#![recursion_limit = "512"]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
@ -62,23 +63,27 @@ use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault;
use if_chain::if_chain;
use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind};
use rustc_ast::ast::{self, Attribute, LitKind};
use rustc_data_structures::unhash::UnhashMap;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, Param, Pat,
PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export;
use rustc_middle::hir::map::Map;
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::ty as rustc_ty;
use rustc_middle::ty::{layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
use rustc_middle::ty::binding::BindingMode;
use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeFoldable, UpvarCapture};
use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind};
@ -89,7 +94,7 @@ use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::Integer;
use crate::consts::{constant, Constant};
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
@ -255,7 +260,17 @@ pub fn in_macro(span: Span) -> bool {
}
pub fn is_unit_expr(expr: &Expr<'_>) -> bool {
matches!(expr.kind, ExprKind::Block(Block { stmts: [], expr: None, .. }, _) | ExprKind::Tup([]))
matches!(
expr.kind,
ExprKind::Block(
Block {
stmts: [],
expr: None,
..
},
_
) | ExprKind::Tup([])
)
}
/// Checks if given pattern is a wildcard (`_`)
@ -629,12 +644,106 @@ pub fn can_mut_borrow_both(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>) -
false
}
/// Returns true if the `def_id` associated with the `path` is recognized as a "default-equivalent"
/// constructor from the std library
fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<'_>) -> bool {
let std_types_symbols = &[
sym::string_type,
sym::vec_type,
sym::vecdeque_type,
sym::LinkedList,
sym::hashmap_type,
sym::BTreeMap,
sym::hashset_type,
sym::BTreeSet,
sym::BinaryHeap,
];
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
.any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
}
}
}
}
false
}
/// Returns true if the expr is equal to `Default::default()` of it's type when evaluated.
/// It doesn't cover all cases, for example indirect function calls (some of std
/// functions are supported) but it is the best we have.
pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
match &e.kind {
ExprKind::Lit(lit) => match lit.node {
LitKind::Bool(false) | LitKind::Int(0, _) => true,
LitKind::Str(s, _) => s.is_empty(),
_ => false,
},
ExprKind::Tup(items) | ExprKind::Array(items) => items.iter().all(|x| is_default_equivalent(cx, x)),
ExprKind::Repeat(x, _) => is_default_equivalent(cx, x),
ExprKind::Call(repl_func, _) => if_chain! {
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then {
true
}
else {
false
}
},
ExprKind::Path(qpath) => is_lang_ctor(cx, qpath, OptionNone),
ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])),
_ => false,
}
}
/// Checks if the top level expression can be moved into a closure as is.
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
/// Currently checks for:
/// * Break/Continue outside the given loop HIR ids.
/// * Yield/Return statments.
/// * Inline assembly.
/// * Usages of a field of a local where the type of the local can be partially moved.
///
/// For example, given the following function:
///
/// ```
/// fn f<'a>(iter: &mut impl Iterator<Item = (usize, &'a mut String)>) {
/// for item in iter {
/// let s = item.1;
/// if item.0 > 10 {
/// continue;
/// } else {
/// s.clear();
/// }
/// }
/// }
/// ```
///
/// When called on the expression `item.0` this will return false unless the local `item` is in the
/// `ignore_locals` set. The type `(usize, &mut String)` can have the second element moved, so it
/// isn't always safe to move into a closure when only a single field is needed.
///
/// When called on the `continue` expression this will return false unless the outer loop expression
/// is in the `loop_ids` set.
///
/// Note that this check is not recursive, so passing the `if` expression will always return true
/// even though sub-expressions might return false.
pub fn can_move_expr_to_closure_no_visit(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
loop_ids: &[HirId],
ignore_locals: &HirIdSet,
) -> bool {
match expr.kind {
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
if jump_targets.contains(&id) =>
if loop_ids.contains(&id) =>
{
true
},
@ -646,25 +755,170 @@ pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Exp
| ExprKind::LlvmInlineAsm(_) => false,
// Accessing a field of a local value can only be done if the type isn't
// partially moved.
ExprKind::Field(base_expr, _)
if matches!(
base_expr.kind,
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
{
ExprKind::Field(
&Expr {
hir_id,
kind:
ExprKind::Path(QPath::Resolved(
_,
Path {
res: Res::Local(local_id),
..
},
)),
..
},
_,
) if !ignore_locals.contains(local_id) && can_partially_move_ty(cx, cx.typeck_results().node_type(hir_id)) => {
// TODO: check if the local has been partially moved. Assume it has for now.
false
}
},
_ => true,
}
}
/// Checks if the expression can be moved into a closure as is.
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
/// How a local is captured by a closure
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptureKind {
Value,
Ref(Mutability),
}
impl CaptureKind {
pub fn is_imm_ref(self) -> bool {
self == Self::Ref(Mutability::Not)
}
}
impl std::ops::BitOr for CaptureKind {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
match (self, rhs) {
(CaptureKind::Value, _) | (_, CaptureKind::Value) => CaptureKind::Value,
(CaptureKind::Ref(Mutability::Mut), CaptureKind::Ref(_))
| (CaptureKind::Ref(_), CaptureKind::Ref(Mutability::Mut)) => CaptureKind::Ref(Mutability::Mut),
(CaptureKind::Ref(Mutability::Not), CaptureKind::Ref(Mutability::Not)) => CaptureKind::Ref(Mutability::Not),
}
}
}
impl std::ops::BitOrAssign for CaptureKind {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
/// Given an expression referencing a local, determines how it would be captured in a closure.
/// Note as this will walk up to parent expressions until the capture can be determined it should
/// only be used while making a closure somewhere a value is consumed. e.g. a block, match arm, or
/// function argument (other than a receiver).
pub fn capture_local_usage(cx: &LateContext<'tcx>, e: &Expr<'_>) -> CaptureKind {
fn pat_capture_kind(cx: &LateContext<'_>, pat: &Pat<'_>) -> CaptureKind {
let mut capture = CaptureKind::Ref(Mutability::Not);
pat.each_binding_or_first(&mut |_, id, span, _| match cx
.typeck_results()
.extract_binding_mode(cx.sess(), id, span)
.unwrap()
{
BindingMode::BindByValue(_) if !is_copy(cx, cx.typeck_results().node_type(id)) => {
capture = CaptureKind::Value;
},
BindingMode::BindByReference(Mutability::Mut) if capture != CaptureKind::Value => {
capture = CaptureKind::Ref(Mutability::Mut);
},
_ => (),
});
capture
}
debug_assert!(matches!(
e.kind,
ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(_), .. }))
));
let map = cx.tcx.hir();
let mut child_id = e.hir_id;
let mut capture = CaptureKind::Value;
let mut capture_expr_ty = e;
for (parent_id, parent) in map.parent_iter(e.hir_id) {
if let [Adjustment {
kind: Adjust::Deref(_) | Adjust::Borrow(AutoBorrow::Ref(..)),
target,
}, ref adjust @ ..] = *cx
.typeck_results()
.adjustments()
.get(child_id)
.map_or(&[][..], |x| &**x)
{
if let rustc_ty::RawPtr(TypeAndMut { mutbl: mutability, .. }) | rustc_ty::Ref(_, _, mutability) =
*adjust.last().map_or(target, |a| a.target).kind()
{
return CaptureKind::Ref(mutability);
}
}
match parent {
Node::Expr(e) => match e.kind {
ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability),
ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not),
ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => {
return CaptureKind::Ref(Mutability::Mut);
},
ExprKind::Field(..) => {
if capture == CaptureKind::Value {
capture_expr_ty = e;
}
},
ExprKind::Let(pat, ..) => {
let mutability = match pat_capture_kind(cx, pat) {
CaptureKind::Value => Mutability::Not,
CaptureKind::Ref(m) => m,
};
return CaptureKind::Ref(mutability);
},
ExprKind::Match(_, arms, _) => {
let mut mutability = Mutability::Not;
for capture in arms.iter().map(|arm| pat_capture_kind(cx, arm.pat)) {
match capture {
CaptureKind::Value => break,
CaptureKind::Ref(Mutability::Mut) => mutability = Mutability::Mut,
CaptureKind::Ref(Mutability::Not) => (),
}
}
return CaptureKind::Ref(mutability);
},
_ => break,
},
Node::Local(l) => match pat_capture_kind(cx, l.pat) {
CaptureKind::Value => break,
capture @ CaptureKind::Ref(_) => return capture,
},
_ => break,
}
child_id = parent_id;
}
if capture == CaptureKind::Value && is_copy(cx, cx.typeck_results().expr_ty(capture_expr_ty)) {
// Copy types are never automatically captured by value.
CaptureKind::Ref(Mutability::Not)
} else {
capture
}
}
/// Checks if the expression can be moved into a closure as is. This will return a list of captures
/// if so, otherwise, `None`.
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<HirIdMap<CaptureKind>> {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
// Stack of potential break targets contained in the expression.
loops: Vec<HirId>,
/// Local variables created in the expression. These don't need to be captured.
locals: HirIdSet,
/// Whether this expression can be turned into a closure.
allow_closure: bool,
/// Locals which need to be captured, and whether they need to be by value, reference, or
/// mutable reference.
captures: HirIdMap<CaptureKind>,
}
impl Visitor<'tcx> for V<'_, 'tcx> {
type Map = ErasedMap<'tcx>;
@ -676,24 +930,67 @@ pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) ->
if !self.allow_closure {
return;
}
if let ExprKind::Loop(b, ..) = e.kind {
self.loops.push(e.hir_id);
self.visit_block(b);
self.loops.pop();
} else {
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
walk_expr(self, e);
match e.kind {
ExprKind::Path(QPath::Resolved(None, &Path { res: Res::Local(l), .. })) => {
if !self.locals.contains(&l) {
let cap = capture_local_usage(self.cx, e);
self.captures.entry(l).and_modify(|e| *e |= cap).or_insert(cap);
}
},
ExprKind::Closure(..) => {
let closure_id = self.cx.tcx.hir().local_def_id(e.hir_id).to_def_id();
for capture in self.cx.typeck_results().closure_min_captures_flattened(closure_id) {
let local_id = match capture.place.base {
PlaceBase::Local(id) => id,
PlaceBase::Upvar(var) => var.var_path.hir_id,
_ => continue,
};
if !self.locals.contains(&local_id) {
let capture = match capture.info.capture_kind {
UpvarCapture::ByValue(_) => CaptureKind::Value,
UpvarCapture::ByRef(borrow) => match borrow.kind {
BorrowKind::ImmBorrow => CaptureKind::Ref(Mutability::Not),
BorrowKind::UniqueImmBorrow | BorrowKind::MutBorrow => {
CaptureKind::Ref(Mutability::Mut)
},
},
};
self.captures
.entry(local_id)
.and_modify(|e| *e |= capture)
.or_insert(capture);
}
}
},
ExprKind::Loop(b, ..) => {
self.loops.push(e.hir_id);
self.visit_block(b);
self.loops.pop();
},
_ => {
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops, &self.locals);
walk_expr(self, e);
},
}
}
fn visit_pat(&mut self, p: &'tcx Pat<'tcx>) {
p.each_binding_or_first(&mut |_, id, _, _| {
self.locals.insert(id);
});
}
}
let mut v = V {
cx,
allow_closure: true,
loops: Vec::new(),
locals: HirIdSet::default(),
captures: HirIdMap::default(),
};
v.visit_expr(expr);
v.allow_closure
v.allow_closure.then(|| v.captures)
}
/// Returns the method names and argument list of nested method call expressions that make up
@ -1365,13 +1662,13 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
while let Some(higher::IfOrIfLet { cond, then, r#else }) = higher::IfOrIfLet::hir(expr) {
conds.push(&*cond);
if let ExprKind::Block(ref block, _) = then.kind {
if let ExprKind::Block(block, _) = then.kind {
blocks.push(block);
} else {
panic!("ExprKind::If node is not an ExprKind::Block");
}
if let Some(ref else_expr) = r#else {
if let Some(else_expr) = r#else {
expr = else_expr;
} else {
break;
@ -1708,7 +2005,7 @@ pub fn peel_hir_expr_while<'tcx>(
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
let mut remaining = count;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) if remaining != 0 => {
remaining -= 1;
Some(e)
},
@ -1722,7 +2019,7 @@ pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>,
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
let mut count = 0;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
ExprKind::AddrOf(ast::BorrowKind::Ref, _, e) => {
count += 1;
Some(e)
},

View file

@ -13,9 +13,12 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,53,0 { OR_PATTERNS }
1,52,0 { STR_SPLIT_ONCE }
1,50,0 { BOOL_THEN }
1,47,0 { TAU }
1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2 }
1,42,0 { MATCHES_MACRO }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }

View file

@ -68,6 +68,7 @@ pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
pub const ITERTOOLS_NEXT_TUPLE: [&str; 3] = ["itertools", "Itertools", "next_tuple"];
#[cfg(feature = "internal-lints")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")]

View file

@ -36,9 +36,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&Ru
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
ty::PredicateKind::Coerce(_) => {
panic!("coerce predicate on function: {:#?}", predicate)
},
ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate),
ty::PredicateKind::Trait(pred) => {
if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
continue;

View file

@ -329,7 +329,7 @@ fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
}
}
// Copied from the rust standart library, and then edited
/// Copied from the rust standard library, and then edited
macro_rules! forward_binop_impls_to_ref {
(impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
impl $imp<$t> for &$t {

View file

@ -10,7 +10,7 @@ use rustc_hir::{TyKind, Unsafety};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
use rustc_middle::ty::{self, TyCtxt, AdtDef, IntTy, Ty, TypeFoldable, UintTy};
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, TypeFoldable, UintTy};
use rustc_span::sym;
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::DUMMY_SP;
@ -224,6 +224,13 @@ fn is_normalizable_helper<'tcx>(
result
}
/// Returns true iff the given type is a non aggregate primitive (a bool or char, any integer or
/// floating-point number type). For checking aggregation of primitive types (e.g. tuples and slices
/// of primitive type) see `is_recursively_primitive_type`
pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool {
matches!(ty.kind(), ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_))
}
/// Returns true iff the given type is a primitive (a bool or char, any integer or floating-point
/// number type, a str, or an array, slice, or tuple of those types).
pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool {

View file

@ -1,10 +1,9 @@
use crate as utils;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit;
use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
use rustc_hir::HirIdSet;
use rustc_hir::{Expr, ExprKind, HirId, Path};
use rustc_hir::{Expr, ExprKind, HirId};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
@ -35,12 +34,8 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) ->
Some(delegate.used_mutably)
}
pub fn is_potentially_mutated<'tcx>(variable: &'tcx Path<'_>, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
if let Res::Local(id) = variable.res {
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&id))
} else {
true
}
pub fn is_potentially_mutated<'tcx>(variable: HirId, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool {
mutated_variables(expr, cx).map_or(true, |mutated| mutated.contains(&variable))
}
struct MutVarsDelegate {

View file

@ -4,6 +4,7 @@ use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visito
use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use std::ops::ControlFlow;
/// returns `true` if expr contains match expr desugared from try
fn contains_try(expr: &hir::Expr<'_>) -> bool {
@ -133,62 +134,6 @@ where
}
}
pub struct LocalUsedVisitor<'hir> {
hir: Map<'hir>,
pub local_hir_id: HirId,
pub used: bool,
}
impl<'hir> LocalUsedVisitor<'hir> {
pub fn new(cx: &LateContext<'hir>, local_hir_id: HirId) -> Self {
Self {
hir: cx.tcx.hir(),
local_hir_id,
used: false,
}
}
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
visit(self, t);
std::mem::replace(&mut self.used, false)
}
pub fn check_arm(&mut self, arm: &'hir Arm<'_>) -> bool {
self.check(arm, Self::visit_arm)
}
pub fn check_body(&mut self, body: &'hir Body<'_>) -> bool {
self.check(body, Self::visit_body)
}
pub fn check_expr(&mut self, expr: &'hir Expr<'_>) -> bool {
self.check(expr, Self::visit_expr)
}
pub fn check_stmt(&mut self, stmt: &'hir Stmt<'_>) -> bool {
self.check(stmt, Self::visit_stmt)
}
}
impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
type Map = Map<'v>;
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
if self.used {
return;
}
if path_to_local_id(expr, self.local_hir_id) {
self.used = true;
} else {
walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.hir)
}
}
/// A type which can be visited.
pub trait Visitable<'tcx> {
/// Calls the corresponding `visit_*` function on the visitor.
@ -203,7 +148,22 @@ macro_rules! visitable_ref {
}
};
}
visitable_ref!(Arm, visit_arm);
visitable_ref!(Block, visit_block);
visitable_ref!(Body, visit_body);
visitable_ref!(Expr, visit_expr);
visitable_ref!(Stmt, visit_stmt);
// impl<'tcx, I: IntoIterator> Visitable<'tcx> for I
// where
// I::Item: Visitable<'tcx>,
// {
// fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
// for x in self {
// x.visit(visitor);
// }
// }
// }
/// Calls the given function for each break expression.
pub fn visit_break_exprs<'tcx>(
@ -260,3 +220,48 @@ pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool {
v.visit_expr(&cx.tcx.hir().body(body).value);
v.found
}
/// Calls the given function for each usage of the given local.
pub fn for_each_local_usage<'tcx, B>(
cx: &LateContext<'tcx>,
visitable: impl Visitable<'tcx>,
id: HirId,
f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>,
) -> ControlFlow<B> {
struct V<'tcx, B, F> {
map: Map<'tcx>,
id: HirId,
f: F,
res: ControlFlow<B>,
}
impl<'tcx, B, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow<B>> Visitor<'tcx> for V<'tcx, B, F> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.map)
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if self.res.is_continue() {
if path_to_local_id(e, self.id) {
self.res = (self.f)(e);
} else {
walk_expr(self, e);
}
}
}
}
let mut v = V {
map: cx.tcx.hir(),
id,
f,
res: ControlFlow::CONTINUE,
};
visitable.visit(&mut v);
v.res
}
/// Checks if the given local is used.
pub fn is_local_used(cx: &LateContext<'tcx>, visitable: impl Visitable<'tcx>, id: HirId) -> bool {
for_each_local_usage(cx, visitable, id, |_| ControlFlow::BREAK).is_break()
}

View file

@ -556,14 +556,15 @@ directory. Adding a configuration to a lint can be useful for thresholds or to c
behavior that can be seen as a false positive for some users. Adding a configuration is done
in the following steps:
1. Adding a new configuration entry to [clippy_utils::conf](/clippy_utils/src/conf.rs)
1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs)
like this:
```rust
/// Lint: LINT_NAME. <The configuration field doc comment>
/// Lint: LINT_NAME.
///
/// <The configuration field doc comment>
(configuration_ident: Type = DefaultValue),
```
The configuration value and identifier should usually be the same. The doc comment will be
automatically added to the lint documentation.
The doc comment will be automatically added to the lint documentation.
2. Adding the configuration value to the lint impl struct:
1. This first requires the definition of a lint impl struct. Lint impl structs are usually
generated with the `declare_lint_pass!` macro. This struct needs to be defined manually

View file

@ -11,6 +11,7 @@ You may need following tooltips to catch up with common operations.
Useful Rustc dev guide links:
- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation)
- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html)
- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html)
- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html)
@ -75,20 +76,21 @@ impl LateLintPass<'_> for MyStructLint {
# Checking if a type implements a specific trait
There are two ways to do this, depending if the target trait is part of lang items.
There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither.
```rust
use clippy_utils::{implements_trait, match_trait_method, paths};
use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths};
use rustc_span::symbol::sym;
impl LateLintPass<'_> for MyStructLint {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
// 1. Using expression and Clippy's convenient method
// we use `match_trait_method` function from Clippy's toolbox
if match_trait_method(cx, expr, &paths::INTO) {
// `expr` implements `Into` trait
// 1. Using diagnostic items with the expression
// we use `is_trait_method` function from Clippy's utils
if is_trait_method(cx, expr, sym::Iterator) {
// method call in `expr` belongs to `Iterator` trait
}
// 2. Using type context `TyCtxt`
// 2. Using lang items with the expression type
let ty = cx.typeck_results().expr_ty(expr);
if cx.tcx.lang_items()
// we are looking for the `DefId` of `Drop` trait in lang items
@ -97,15 +99,20 @@ impl LateLintPass<'_> for MyStructLint {
.map_or(false, |id| implements_trait(cx, ty, id, &[])) {
// `expr` implements `Drop` trait
}
// 3. Using the type path with the expression
// we use `match_trait_method` function from Clippy's utils
if match_trait_method(cx, expr, &paths::INTO) {
// `expr` implements `Into` trait
}
}
}
```
> Prefer using lang items, if the target trait is available there.
A list of defined paths for Clippy can be found in [paths.rs][paths]
> Prefer using diagnostic and lang items, if the target trait has one.
We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate.
A list of defined paths for Clippy can be found in [paths.rs][paths]
# Checking if a type defines a specific method

View file

@ -19,6 +19,7 @@ serde_json = {version = "1.0"}
tar = {version = "0.4.30"}
toml = {version = "0.5"}
ureq = {version = "2.0.0-rc3"}
walkdir = {version = "2.3.2"}
[features]
deny-warnings = []

View file

@ -21,6 +21,7 @@ use clap::{App, Arg, ArgMatches};
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use walkdir::{DirEntry, WalkDir};
#[cfg(not(windows))]
const CLIPPY_DRIVER_PATH: &str = "target/debug/clippy-driver";
@ -193,32 +194,41 @@ impl CrateSource {
}
},
CrateSource::Path { name, path, options } => {
use fs_extra::dir;
// copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.
// The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory
// as a result of this filter.
let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name);
if dest_crate_root.exists() {
println!("Deleting existing directory at {:?}", dest_crate_root);
std::fs::remove_dir_all(&dest_crate_root).unwrap();
}
// simply copy the entire directory into our target dir
let copy_dest = PathBuf::from(format!("{}/", LINTCHECK_SOURCES));
println!("Copying {:?} to {:?}", path, dest_crate_root);
// the source path of the crate we copied, ${copy_dest}/crate_name
let crate_root = copy_dest.join(name); // .../crates/local_crate
fn is_cache_dir(entry: &DirEntry) -> bool {
std::fs::read(entry.path().join("CACHEDIR.TAG"))
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
.unwrap_or(false)
}
if crate_root.exists() {
println!(
"Not copying {} to {}, destination already exists",
path.display(),
crate_root.display()
);
} else {
println!("Copying {} to {}", path.display(), copy_dest.display());
for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) {
let entry = entry.unwrap();
let entry_path = entry.path();
let relative_entry_path = entry_path.strip_prefix(path).unwrap();
let dest_path = dest_crate_root.join(relative_entry_path);
let metadata = entry_path.symlink_metadata().unwrap();
dir::copy(path, &copy_dest, &dir::CopyOptions::new()).unwrap_or_else(|_| {
panic!("Failed to copy from {}, to {}", path.display(), crate_root.display())
});
if metadata.is_dir() {
std::fs::create_dir(dest_path).unwrap();
} else if metadata.is_file() {
std::fs::copy(entry_path, dest_path).unwrap();
}
}
Crate {
version: String::from("local"),
name: name.clone(),
path: crate_root,
path: dest_crate_root,
options: options.clone(),
}
},

View file

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2021-08-12"
channel = "nightly-2021-09-08"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"]

View file

@ -1,10 +1,12 @@
#![feature(test)] // compiletest_rs requires this attribute
#![feature(once_cell)]
#![feature(try_blocks)]
#![cfg_attr(feature = "deny-warnings", deny(warnings))]
#![warn(rust_2018_idioms, unused_lifetimes)]
use compiletest_rs as compiletest;
use compiletest_rs::common::Mode as TestMode;
use std::collections::HashMap;
use std::env::{self, remove_var, set_var, var_os};
use std::ffi::{OsStr, OsString};
use std::fs;
@ -16,6 +18,34 @@ mod cargo;
// whether to run internal tests or not
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
/// All crates used in UI tests are listed here
static TEST_DEPENDENCIES: &[&str] = &[
"clippy_utils",
"derive_new",
"if_chain",
"itertools",
"quote",
"regex",
"serde",
"serde_derive",
"syn",
];
// Test dependencies may need an `extern crate` here to ensure that they show up
// in the depinfo file (otherwise cargo thinks they are unused)
#[allow(unused_extern_crates)]
extern crate clippy_utils;
#[allow(unused_extern_crates)]
extern crate derive_new;
#[allow(unused_extern_crates)]
extern crate if_chain;
#[allow(unused_extern_crates)]
extern crate itertools;
#[allow(unused_extern_crates)]
extern crate quote;
#[allow(unused_extern_crates)]
extern crate syn;
fn host_lib() -> PathBuf {
option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
}
@ -24,71 +54,58 @@ fn clippy_driver_path() -> PathBuf {
option_env!("CLIPPY_DRIVER_PATH").map_or(cargo::TARGET_LIB.join("clippy-driver"), PathBuf::from)
}
// When we'll want to use `extern crate ..` for a dependency that is used
// both by the crate and the compiler itself, we can't simply pass -L flags
// as we'll get a duplicate matching versions. Instead, disambiguate with
// `--extern dep=path`.
// See https://github.com/rust-lang/rust-clippy/issues/4015.
//
// FIXME: We cannot use `cargo build --message-format=json` to resolve to dependency files.
// Because it would force-rebuild if the options passed to `build` command is not the same
// as what we manually pass to `cargo` invocation
fn third_party_crates() -> String {
use std::collections::HashMap;
static CRATES: &[&str] = &[
"clippy_lints",
"clippy_utils",
"if_chain",
"quote",
"regex",
"serde",
"serde_derive",
"syn",
];
let dep_dir = cargo::TARGET_LIB.join("deps");
let mut crates: HashMap<&str, Vec<PathBuf>> = HashMap::with_capacity(CRATES.len());
let mut flags = String::new();
for entry in fs::read_dir(dep_dir).unwrap().flatten() {
let path = entry.path();
if let Some(name) = try {
let name = path.file_name()?.to_str()?;
let (name, _) = name.strip_suffix(".rlib")?.strip_prefix("lib")?.split_once('-')?;
CRATES.iter().copied().find(|&c| c == name)?
} {
flags += &format!(" --extern {}={}", name, path.display());
crates.entry(name).or_default().push(path.clone());
/// Produces a string with an `--extern` flag for all UI test crate
/// dependencies.
///
/// The dependency files are located by parsing the depinfo file for this test
/// module. This assumes the `-Z binary-dep-depinfo` flag is enabled. All test
/// dependencies must be added to Cargo.toml at the project root. Test
/// dependencies that are not *directly* used by this test module require an
/// `extern crate` declaration.
fn extern_flags() -> String {
let current_exe_depinfo = {
let mut path = env::current_exe().unwrap();
path.set_extension("d");
std::fs::read_to_string(path).unwrap()
};
let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
for line in current_exe_depinfo.lines() {
// each dependency is expected to have a Makefile rule like `/path/to/crate-hash.rlib:`
let parse_name_path = || {
if line.starts_with(char::is_whitespace) {
return None;
}
let path_str = line.strip_suffix(':')?;
let path = Path::new(path_str);
if !matches!(path.extension()?.to_str()?, "rlib" | "so" | "dylib" | "dll") {
return None;
}
let (name, _hash) = path.file_stem()?.to_str()?.rsplit_once('-')?;
// the "lib" prefix is not present for dll files
let name = name.strip_prefix("lib").unwrap_or(name);
Some((name, path_str))
};
if let Some((name, path)) = parse_name_path() {
if TEST_DEPENDENCIES.contains(&name) {
// A dependency may be listed twice if it is available in sysroot,
// and the sysroot dependencies are listed first. As of the writing,
// this only seems to apply to if_chain.
crates.insert(name, path);
}
}
}
crates.retain(|_, paths| paths.len() > 1);
if !crates.is_empty() {
let crate_names = crates.keys().map(|s| format!("`{}`", s)).collect::<Vec<_>>().join(", ");
// add backslashes for an easy copy-paste `rm` command
let paths = crates
.into_values()
.flatten()
.map(|p| strip_current_dir(&p).display().to_string())
.collect::<Vec<_>>()
.join(" \\\n");
// Check which action should be done in order to remove compiled deps.
// If pre-installed version of compiler is used, `cargo clean` will do.
// Otherwise (for bootstrapped compiler), the dependencies directory
// must be removed manually.
let suggested_action = if std::env::var_os("RUSTC_BOOTSTRAP").is_some() {
"removing the stageN-tools directory"
} else {
"running `cargo clean`"
};
panic!(
"\n----------------------------------------------------------------------\n\
ERROR: Found multiple rlibs for crates: {}\n\
Try {} or remove the following files:\n\n{}\n\n\
For details on this error see https://github.com/rust-lang/rust-clippy/issues/7343\n\
----------------------------------------------------------------------\n",
crate_names, suggested_action, paths
);
let not_found: Vec<&str> = TEST_DEPENDENCIES
.iter()
.copied()
.filter(|n| !crates.contains_key(n))
.collect();
if !not_found.is_empty() {
panic!("dependencies not found in depinfo: {:?}", not_found);
}
flags
crates
.into_iter()
.map(|(name, path)| format!("--extern {}={} ", name, path))
.collect()
}
fn default_config() -> compiletest::Config {
@ -104,11 +121,14 @@ fn default_config() -> compiletest::Config {
config.compile_lib_path = path;
}
// Using `-L dependency={}` enforces that external dependencies are added with `--extern`.
// This is valuable because a) it allows us to monitor what external dependencies are used
// and b) it ensures that conflicting rlibs are resolved properly.
config.target_rustcflags = Some(format!(
"--emit=metadata -L {0} -L {1} -Dwarnings -Zui-testing {2}",
"--emit=metadata -L dependency={} -L dependency={} -Dwarnings -Zui-testing {}",
host_lib().join("deps").display(),
cargo::TARGET_LIB.join("deps").display(),
third_party_crates(),
extern_flags(),
));
config.build_base = host_lib().join("test_build_base");
@ -315,12 +335,3 @@ impl Drop for VarGuard {
}
}
}
fn strip_current_dir(path: &Path) -> &Path {
if let Ok(curr) = env::current_dir() {
if let Ok(stripped) = path.strip_prefix(curr) {
return stripped;
}
}
path
}

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