From a5a07e503f524a0e8b92938301857b3007579115 Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Thu, 20 Jan 2022 14:50:14 +0300 Subject: [PATCH 01/67] single_match: Don't lint non-exhaustive matches; support tuples This commit changes the behavior of `single_match` lint. After that, we won't lint non-exhaustive matches like this: ```rust match Some(v) { Some(a) => println!("${:?}", a), None => {}, } ``` The rationale is that, because the type of `a` could be changed, so the user can get non-exhaustive match after applying the suggested lint (see https://github.com/rust-lang/rust-clippy/issues/8282#issuecomment-1013566068 for context). We also will lint `match` constructions with tuples. When we see the tuples on the both arms, we will check them both at the same time, and if they form exhaustive match, we could display the warning. Closes #8282 --- clippy_lints/src/matches.rs | 145 ++++++++++++++++++++++++++---- tests/ui/single_match.rs | 50 +++++++++++ tests/ui/single_match.stderr | 38 +++++--- tests/ui/single_match_else.stderr | 42 +-------- 4 files changed, 206 insertions(+), 69 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 60dd957db01f8..9da052450694c 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{ use clippy_utils::macros::{is_panic, root_macro_call}; 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::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs}; use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, @@ -31,7 +31,7 @@ use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; use rustc_span::sym; -use std::cmp::Ordering; +use std::cmp::{max, Ordering}; use std::collections::hash_map::Entry; declare_clippy_lint! { @@ -741,7 +741,7 @@ fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], exp let ty = cx.typeck_results().expr_ty(ex); if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { check_single_match_single_pattern(cx, ex, arms, expr, els); - check_single_match_opt_like(cx, ex, arms, expr, ty, els); + check_single_match_opt_like(cx, ex, arms, expr, els); } } } @@ -835,7 +835,6 @@ fn check_single_match_opt_like( ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, - ty: Ty<'_>, els: Option<&Expr<'_>>, ) { // list of candidate `Enum`s we know will never get any more members @@ -849,25 +848,135 @@ fn check_single_match_opt_like( (&paths::RESULT, "Ok"), ]; - let path = match arms[1].pat.kind { - PatKind::TupleStruct(ref path, inner, _) => { - // Contains any non wildcard patterns (e.g., `Err(err)`)? - if !inner.iter().all(is_wild) { - return; + // We want to suggest to exclude an arm that contains only wildcards or forms the ehaustive + // match with the second branch. + if !contains_only_wilds(arms[1].pat) && !form_exhaustive_tuples(arms[0].pat, arms[1].pat) { + return; + } + + let mut paths = Vec::new(); + if !collect_pat_paths(&mut paths, arms[1].pat) { + return; + } + + let in_candidate_enum = |path: &String| -> bool { + for &(_, pat_path) in candidates { + if path == pat_path { + return true; + } + } + false + }; + if paths.iter().all(in_candidate_enum) { + report_single_match_single_pattern(cx, ex, arms, expr, els); + } +} + +/// Collects paths from the given paths. Returns true if the given pattern could be simplified, +/// false otherwise. +fn collect_pat_paths(acc: &mut Vec, pat: &Pat<'_>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) => { + for p in inner { + if !collect_pat_paths(acc, p) { + return false; + } } - rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + true + }, + PatKind::TupleStruct(ref path, ..) => { + acc.push(rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + })); + true + }, + PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { + acc.push(ident.to_string()); + true }, - PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => ident.to_string(), PatKind::Path(ref path) => { - rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) + acc.push(rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + })); + true }, - _ => return, - }; + _ => false, + } +} - for &(ty_path, pat_path) in candidates { - if path == *pat_path && match_type(cx, ty, ty_path) { - report_single_match_single_pattern(cx, ex, arms, expr, els); - } +/// Returns true if the given arm of pattern matching contains wildcard patterns. +fn contains_only_wilds(pat: &Pat<'_>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), + _ => false, + } +} + +/// Returns true if the given patterns form the tuples that exhaustively matches all possible +/// parameters. +/// +/// Here are some examples: +/// +/// ``` +/// // Doesn't form exhaustive match, because the first arm may contain not only E::V. +/// match x { +/// (Some(E::V), _) => todo!(), +/// (None, _) => {} +/// } +/// +/// // Forms exhaustive match, because the patterns cover all possible cases at the same positions. +/// match x { +/// (Some(_), _) => todo!(), +/// (None, _) => {} +/// } +/// ``` +fn form_exhaustive_tuples(left: &Pat<'_>, right: &Pat<'_>) -> bool { + match (&left.kind, &right.kind) { + (PatKind::Wild, _) | (_, PatKind::Wild) => true, + (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { + // We don't actually know the position and presence of the `..` (dotdot) operator in + // the arms, so we need to evaluate the correct offsets here in order to iterate in + // both arms at the same time. + let len = max( + left_in.len() + { + if left_pos.is_some() { 1 } else { 0 } + }, + right_in.len() + { + if right_pos.is_some() { 1 } else { 0 } + }, + ); + let mut left_pos = left_pos.unwrap_or(usize::MAX); + let mut right_pos = right_pos.unwrap_or(usize::MAX); + let mut left_span = 0; + let mut right_span = 0; + for i in 0..len { + let mut found_dotdot = false; + if i == left_pos { + left_span += 1; + if left_span < len - left_in.len() { + left_pos += 1; + } + found_dotdot = true; + } + if i == right_pos { + right_span += 1; + if right_span < len - right_in.len() { + right_pos += 1; + } + found_dotdot = true; + } + if found_dotdot { + continue; + } + if !contains_only_wilds(&left_in[i - left_span]) && !contains_only_wilds(&right_in[i - right_span]) { + return false; + } + } + true + }, + _ => false, } } diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index b1819e08d53bf..aa93f4d8ca56e 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -145,6 +145,56 @@ fn if_suggestion() { }; } +// See: issue #8282 +fn ranges() { + enum E { + V, + } + let x = (Some(E::V), Some(42)); + + // don't lint + match x { + (Some(E::V), _) => {}, + (None, _) => {}, + } + + // lint + match x { + (Some(_), _) => {}, + (None, _) => {}, + } + + // lint + match x { + (Some(E::V), _) => todo!(), + (_, _) => {}, + } + + // lint + match (Some(42), Some(E::V), Some(42)) { + (.., Some(E::V), _) => {}, + (..) => {}, + } + + // don't lint + match (Some(E::V), Some(E::V), Some(E::V)) { + (.., Some(E::V), _) => {}, + (.., None, _) => {}, + } + + // don't lint + match (Some(E::V), Some(E::V), Some(E::V)) { + (Some(E::V), ..) => {}, + (None, ..) => {}, + } + + // don't lint + match (Some(E::V), Some(E::V), Some(E::V)) { + (_, Some(E::V), ..) => {}, + (_, None, ..) => {}, + } +} + macro_rules! single_match { ($num:literal) => { match $num { diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index c261b5111c8bf..f72ad853fed88 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -38,15 +38,6 @@ LL | | _ => {}, LL | | }; | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` -error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:54:5 - | -LL | / match x { -LL | | Some(y) => dummy(), -LL | | None => (), -LL | | }; - | |_____^ help: try this: `if let Some(y) = x { dummy() }` - error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> $DIR/single_match.rs:59:5 | @@ -128,5 +119,32 @@ LL | | _ => (), LL | | }; | |_____^ help: try this: `if let None = x { println!() }` -error: aborting due to 13 previous errors +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:162:5 + | +LL | / match x { +LL | | (Some(_), _) => {}, +LL | | (None, _) => {}, +LL | | } + | |_____^ help: try this: `if let (Some(_), _) = x {}` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:168:5 + | +LL | / match x { +LL | | (Some(E::V), _) => todo!(), +LL | | (_, _) => {}, +LL | | } + | |_____^ help: try this: `if let (Some(E::V), _) = x { todo!() }` + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:174:5 + | +LL | / match (Some(42), Some(E::V), Some(42)) { +LL | | (.., Some(E::V), _) => {}, +LL | | (..) => {}, +LL | | } + | |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` + +error: aborting due to 15 previous errors diff --git a/tests/ui/single_match_else.stderr b/tests/ui/single_match_else.stderr index c61d80a905c9f..21ea704b62ab5 100644 --- a/tests/ui/single_match_else.stderr +++ b/tests/ui/single_match_else.stderr @@ -19,45 +19,5 @@ LL + None LL + } | -error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match_else.rs:70:5 - | -LL | / match Some(1) { -LL | | Some(a) => println!("${:?}", a), -LL | | None => { -LL | | println!("else block"); -LL | | return -LL | | }, -LL | | } - | |_____^ - | -help: try this - | -LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { -LL + println!("else block"); -LL + return -LL + } - | - -error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match_else.rs:79:5 - | -LL | / match Some(1) { -LL | | Some(a) => println!("${:?}", a), -LL | | None => { -LL | | println!("else block"); -LL | | return; -LL | | }, -LL | | } - | |_____^ - | -help: try this - | -LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { -LL + println!("else block"); -LL + return; -LL + } - | - -error: aborting due to 3 previous errors +error: aborting due to previous error From a0c50875202e8d13b70d4cf8e4d347ddb04b876c Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Fri, 21 Jan 2022 07:24:07 +0300 Subject: [PATCH 02/67] single_match: Clarify the `don't lint` test case --- tests/ui/single_match.rs | 10 ++++++---- tests/ui/single_match.stderr | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index aa93f4d8ca56e..0e50b3e4a6e25 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -152,7 +152,9 @@ fn ranges() { } let x = (Some(E::V), Some(42)); - // don't lint + // Don't lint, because the `E` enum can be extended with additional fields later. Thus, the + // proposed replacement to `if let Some(E::V)` may hide non-exhaustive warnings that appeared + // because of `match` construction. match x { (Some(E::V), _) => {}, (None, _) => {}, @@ -176,19 +178,19 @@ fn ranges() { (..) => {}, } - // don't lint + // Don't lint, see above. match (Some(E::V), Some(E::V), Some(E::V)) { (.., Some(E::V), _) => {}, (.., None, _) => {}, } - // don't lint + // Don't lint, see above. match (Some(E::V), Some(E::V), Some(E::V)) { (Some(E::V), ..) => {}, (None, ..) => {}, } - // don't lint + // Don't lint, see above. match (Some(E::V), Some(E::V), Some(E::V)) { (_, Some(E::V), ..) => {}, (_, None, ..) => {}, diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index f72ad853fed88..318faf2571758 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -120,7 +120,7 @@ LL | | }; | |_____^ help: try this: `if let None = x { println!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:162:5 + --> $DIR/single_match.rs:164:5 | LL | / match x { LL | | (Some(_), _) => {}, @@ -129,7 +129,7 @@ LL | | } | |_____^ help: try this: `if let (Some(_), _) = x {}` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:168:5 + --> $DIR/single_match.rs:170:5 | LL | / match x { LL | | (Some(E::V), _) => todo!(), @@ -138,7 +138,7 @@ LL | | } | |_____^ help: try this: `if let (Some(E::V), _) = x { todo!() }` error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` - --> $DIR/single_match.rs:174:5 + --> $DIR/single_match.rs:176:5 | LL | / match (Some(42), Some(E::V), Some(42)) { LL | | (.., Some(E::V), _) => {}, From 49ae73b450b61f0240e629ced2024a9474f35adc Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Fri, 21 Jan 2022 07:28:40 +0300 Subject: [PATCH 03/67] matches: Simplify code --- clippy_lints/src/matches.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 9da052450694c..be9eff4237bab 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -877,14 +877,7 @@ fn check_single_match_opt_like( fn collect_pat_paths(acc: &mut Vec, pat: &Pat<'_>) -> bool { match pat.kind { PatKind::Wild => true, - PatKind::Tuple(inner, _) => { - for p in inner { - if !collect_pat_paths(acc, p) { - return false; - } - } - true - }, + PatKind::Tuple(inner, _) => inner.iter().all(|p| collect_pat_paths(acc, p)), PatKind::TupleStruct(ref path, ..) => { acc.push(rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { s.print_qpath(path, false); From 64f5b3611ec86ac16dd671c9acf73a17bcba5305 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Fri, 14 Jan 2022 17:29:54 +0100 Subject: [PATCH 04/67] Update Changelog --- CHANGELOG.md | 179 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 258a8256f5317..7504e3afe018f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,185 @@ document. ## Unreleased / In Rust Nightly -[e181011...master](https://github.com/rust-lang/rust-clippy/compare/e181011...master) +[0eff589...master](https://github.com/rust-lang/rust-clippy/compare/0eff589...master) -## Rust 1.58 (beta) +## Rust 1.59 (beta) -Current beta, release 2022-01-13 +Current beta, release 2022-02-24 + +[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589) + +### New Lints + +* [`index_refutable_slice`] + [#7643](https://github.com/rust-lang/rust-clippy/pull/7643) +* [`needless_splitn`] + [#7896](https://github.com/rust-lang/rust-clippy/pull/7896) +* [`unnecessary_to_owned`] + [#7978](https://github.com/rust-lang/rust-clippy/pull/7978) +* [`needless_late_init`] + [#7995](https://github.com/rust-lang/rust-clippy/pull/7995) +* [`octal_escapes`] [#8007](https://github.com/rust-lang/rust-clippy/pull/8007) +* [`return_self_not_must_use`] + [#8071](https://github.com/rust-lang/rust-clippy/pull/8071) +* [`init_numbered_fields`] + [#8170](https://github.com/rust-lang/rust-clippy/pull/8170) + +### Moves and Deprecations + +* Move `if_then_panic` to `pedantic` and rename to [`manual_assert`] (now + allow-by-default) [#7810](https://github.com/rust-lang/rust-clippy/pull/7810) +* Rename `disallow_type` to [`disallowed_types`] and `disallowed_method` to + [`disallowed_methods`] + [#7984](https://github.com/rust-lang/rust-clippy/pull/7984) +* Move [`map_flatten`] to `complexity` (now warn-by-default) + [#8054](https://github.com/rust-lang/rust-clippy/pull/8054) + +### Enhancements + +* [`match_overlapping_arm`]: Fix false negative where after included ranges, + overlapping ranges weren't linted anymore + [#7909](https://github.com/rust-lang/rust-clippy/pull/7909) +* [`deprecated_cfg_attr`]: Now takes the specified MSRV into account + [#7944](https://github.com/rust-lang/rust-clippy/pull/7944) +* [`cast_lossless`]: Now also lints for `bool` to integer casts + [#7948](https://github.com/rust-lang/rust-clippy/pull/7948) +* [`let_underscore_lock`]: Also emit lints for the `parking_lot` crate + [#7957](https://github.com/rust-lang/rust-clippy/pull/7957) +* [`needless_borrow`] + [#7977](https://github.com/rust-lang/rust-clippy/pull/7977) + * Lint when a borrow is auto-dereffed more than once + * Lint in the trailing expression of a block for a match arm +* [`strlen_on_c_strings`] + [8001](https://github.com/rust-lang/rust-clippy/pull/8001) + * Lint when used without a fully-qualified path + * Suggest removing the surrounding unsafe block when possible +* [`non_ascii_literal`]: Now also lints on `char`s, not just `string`s + [#8034](https://github.com/rust-lang/rust-clippy/pull/8034) +* [`single_char_pattern`]: Now also lints on `split_inclusive`, `split_once`, + `rsplit_once`, `replace`, and `replacen` + [#8077](https://github.com/rust-lang/rust-clippy/pull/8077) +* [`unwrap_or_else_default`]: Now also lints on `std` constructors like + `Vec::new`, `HashSet::new`, and `HashMap::new` + [#8163](https://github.com/rust-lang/rust-clippy/pull/8163) +* [`shadow_reuse`]: Now also lints on shadowed `if let` bindings, instead of + [`shadow_unrelated`] + [#8165](https://github.com/rust-lang/rust-clippy/pull/8165) + +### False Positive Fixes + +* [`or_fun_call`], [`unnecessary_lazy_evaluations`]: Improve heuristics, so that + cheap functions (e.g. calling `.len()` on a `Vec`) won't get linted anymore + [#7639](https://github.com/rust-lang/rust-clippy/pull/7639) +* [`manual_split_once`]: No longer suggests code changing the original behavior + [#7896](https://github.com/rust-lang/rust-clippy/pull/7896) +* Don't show [`no_effect`] or [`unnecessary_operation`] warning for unit struct + implementing `FnOnce` + [#7898](https://github.com/rust-lang/rust-clippy/pull/7898) +* [`semicolon_if_nothing_returned`]: Fixed a bug, where the lint wrongly + triggered on `let-else` statements + [#7955](https://github.com/rust-lang/rust-clippy/pull/7955) +* [`if_then_some_else_none`]: No longer lints if there is an early return + [#7980](https://github.com/rust-lang/rust-clippy/pull/7980) +* [`needless_collect`]: No longer suggests removal of `collect` when removal + would create code requiring mutably borrowing a value multiple times + [#7982](https://github.com/rust-lang/rust-clippy/pull/7982) +* [`shadow_same`]: Fix false positive for `async` function's params + [#7997](https://github.com/rust-lang/rust-clippy/pull/7997) +* [`suboptimal_flops`]: No longer triggers in constant functions + [#8009](https://github.com/rust-lang/rust-clippy/pull/8009) +* [`type_complexity`]: No longer lints on associated types in traits + [#8030](https://github.com/rust-lang/rust-clippy/pull/8030) +* [`question_mark`]: No longer lints if returned object is not local + [#8080](https://github.com/rust-lang/rust-clippy/pull/8080) +* [`option_if_let_else`]: No longer lint on complex sub-patterns + [#8086](https://github.com/rust-lang/rust-clippy/pull/8086) +* [`blocks_in_if_conditions`]: No longer lints on empty closures + [#8100](https://github.com/rust-lang/rust-clippy/pull/8100) +* [`enum_variant_names`]: No longer lint when first prefix is only a substring + of a camel-case word + [#8127](https://github.com/rust-lang/rust-clippy/pull/8127) +* [`identity_op`]: Only lint on integral operands + [#8183](https://github.com/rust-lang/rust-clippy/pull/8183) + +### Suggestion Fixes/Improvements + +* [`search_is_some`]: Fix suggestion for `any()` not taking item by reference + [#7463](https://github.com/rust-lang/rust-clippy/pull/7463) +* [`almost_swapped`]: Now detects if there is a `no_std` or `no_core` attribute + and adapts the suggestion accordingly + [#7877](https://github.com/rust-lang/rust-clippy/pull/7877) +* [`redundant_pattern_matching`]: Fix suggestion for deref expressions + [#7949](https://github.com/rust-lang/rust-clippy/pull/7949) +* [`explicit_counter_loop`]: Now also produces a suggestion for non-`usize` + types [#7950](https://github.com/rust-lang/rust-clippy/pull/7950) +* [`manual_map`]: Fix suggestion when used with unsafe functions and blocks + [#7968](https://github.com/rust-lang/rust-clippy/pull/7968) +* [`option_map_or_none`]: Suggest `map` over `and_then` when possible + [#7971](https://github.com/rust-lang/rust-clippy/pull/7971) +* [`option_if_let_else`]: No longer expands macros in the suggestion + [#7974](https://github.com/rust-lang/rust-clippy/pull/7974) +* [`iter_cloned_collect`]: Suggest `copied` over `cloned` when possible + [#8006](https://github.com/rust-lang/rust-clippy/pull/8006) +* [`doc_markdown`]: No longer uses inline hints to improve readability of + suggestion [#8011](https://github.com/rust-lang/rust-clippy/pull/8011) +* [`needless_question_mark`]: Now better explains the suggestion + [#8028](https://github.com/rust-lang/rust-clippy/pull/8028) +* [`single_char_pattern`]: Escape backslash `\` in suggestion + [#8067](https://github.com/rust-lang/rust-clippy/pull/8067) +* [`needless_bool`]: Suggest `a != b` over `!(a == b)` + [#8117](https://github.com/rust-lang/rust-clippy/pull/8117) +* [`iter_skip_next`]: Suggest to add a `mut` if it is necessary in order to + apply this lints suggestion + [#8133](https://github.com/rust-lang/rust-clippy/pull/8133) +* [`neg_multiply`]: Now produces a suggestion + [#8144](https://github.com/rust-lang/rust-clippy/pull/8144) +* [`needless_return`]: Now suggests the unit type `()` over an empty block `{}` + in match arms [#8185](https://github.com/rust-lang/rust-clippy/pull/8185) +* [`suboptimal_flops`]: Now gives a syntactically correct suggestion for + `to_radians` and `to_degrees` + [#8187](https://github.com/rust-lang/rust-clippy/pull/8187) + +### ICE Fixes + +* [`undocumented_unsafe_blocks`] + [#7945](https://github.com/rust-lang/rust-clippy/pull/7945) + [#7988](https://github.com/rust-lang/rust-clippy/pull/7988) +* [`unnecessary_cast`] + [#8167](https://github.com/rust-lang/rust-clippy/pull/8167) + +### Documentation Improvements + +* [`print_stdout`], [`print_stderr`], [`dbg_macro`]: Document how the lint level + can be changed crate-wide + [#8040](https://github.com/rust-lang/rust-clippy/pull/8040) +* Added a note to the `README` that config changes don't apply to already + compiled code [#8175](https://github.com/rust-lang/rust-clippy/pull/8175) + +### Others + +* [Clippy's lint + list](https://rust-lang.github.io/rust-clippy/master/index.html) now displays + the version a lint was added. :tada: + [#7813](https://github.com/rust-lang/rust-clippy/pull/7813) +* New and improved issue templates + [#8032](https://github.com/rust-lang/rust-clippy/pull/8032) +* _Dev:_ Add `cargo dev lint` command, to run your modified Clippy version on a + file [#7917](https://github.com/rust-lang/rust-clippy/pull/7917) + +## Rust 1.58 + +Current stable, released 2022-01-13 [00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011) +### Rust 1.58.1 + +* Move [`non_send_fields_in_send_ty`] to `nursery` (now allow-by-default) + [#8075](https://github.com/rust-lang/rust-clippy/pull/8075) +* [`useless_format`]: Handle implicit named arguments + [#8295](https://github.com/rust-lang/rust-clippy/pull/8295) + ### New lints * [`transmute_num_to_bytes`] @@ -124,7 +295,7 @@ Current beta, release 2022-01-13 ## Rust 1.57 -Current stable, released 2021-12-02 +Released 2021-12-02 [7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa) From 4aee3b1f1e977136b401cc6633da5f1234501347 Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Wed, 26 Jan 2022 14:46:30 +0300 Subject: [PATCH 05/67] matches: Clarify the behavior of exhaustive check --- clippy_lints/src/matches.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index dc6e4f96969d4..71842ded69d6d 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -848,9 +848,9 @@ fn check_single_match_opt_like( (&paths::RESULT, "Ok"), ]; - // We want to suggest to exclude an arm that contains only wildcards or forms the ehaustive - // match with the second branch. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_tuples(arms[0].pat, arms[1].pat) { + // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive + // match with the second branch, without enum variants in matches. + if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { return; } @@ -907,30 +907,30 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool { } } -/// Returns true if the given patterns form the tuples that exhaustively matches all possible -/// parameters. +/// Returns true if the given patterns forms only exhaustive matches that don't contain enum +/// patterns without a wildcard. /// -/// Here are some examples: +/// For example: /// /// ``` -/// // Doesn't form exhaustive match, because the first arm may contain not only E::V. +/// // Returns false, because the first arm contain enum without a wildcard. /// match x { /// (Some(E::V), _) => todo!(), /// (None, _) => {} /// } /// -/// // Forms exhaustive match, because the patterns cover all possible cases at the same positions. +/// // Returns true, because the both arms form exhaustive matches and without enum variants. /// match x { /// (Some(_), _) => todo!(), /// (None, _) => {} /// } /// ``` -fn form_exhaustive_tuples(left: &Pat<'_>, right: &Pat<'_>) -> bool { +fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { match (&left.kind, &right.kind) { (PatKind::Wild, _) | (_, PatKind::Wild) => true, (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { - // We don't actually know the position and presence of the `..` (dotdot) operator in - // the arms, so we need to evaluate the correct offsets here in order to iterate in + // We don't actually know the position and the presence of the `..` (dotdot) operator + // in the arms, so we need to evaluate the correct offsets here in order to iterate in // both arms at the same time. let len = max( left_in.len() + { From 81015870df891084b24497718af086f0575a121b Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Wed, 26 Jan 2022 18:02:32 +0300 Subject: [PATCH 06/67] matches: Improve naming. NFC. --- clippy_lints/src/matches.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 71842ded69d6d..e8789a92f1334 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -942,20 +942,20 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { ); let mut left_pos = left_pos.unwrap_or(usize::MAX); let mut right_pos = right_pos.unwrap_or(usize::MAX); - let mut left_span = 0; - let mut right_span = 0; + let mut left_dot_space = 0; + let mut right_dot_space = 0; for i in 0..len { let mut found_dotdot = false; if i == left_pos { - left_span += 1; - if left_span < len - left_in.len() { + left_dot_space += 1; + if left_dot_space < len - left_in.len() { left_pos += 1; } found_dotdot = true; } if i == right_pos { - right_span += 1; - if right_span < len - right_in.len() { + right_dot_space += 1; + if right_dot_space < len - right_in.len() { right_pos += 1; } found_dotdot = true; @@ -963,7 +963,9 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { if found_dotdot { continue; } - if !contains_only_wilds(&left_in[i - left_span]) && !contains_only_wilds(&right_in[i - right_span]) { + if !contains_only_wilds(&left_in[i - left_dot_space]) + && !contains_only_wilds(&right_in[i - right_dot_space]) + { return false; } } From 467a0bfdea3ba03de05b90a3de85d4467a28762e Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Wed, 26 Jan 2022 18:20:35 +0300 Subject: [PATCH 07/67] matches: Restore `match_type` logic; add tests for these cases --- clippy_lints/src/matches.rs | 45 +++++++++++++++++++++---------------- tests/ui/single_match.rs | 26 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index e8789a92f1334..47c02e2282151 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -5,7 +5,7 @@ use clippy_utils::diagnostics::{ use clippy_utils::macros::{is_panic, root_macro_call}; 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, peel_mid_ty_refs}; +use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs}; use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, @@ -741,7 +741,7 @@ fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], exp let ty = cx.typeck_results().expr_ty(ex); if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { check_single_match_single_pattern(cx, ex, arms, expr, els); - check_single_match_opt_like(cx, ex, arms, expr, els); + check_single_match_opt_like(cx, ex, arms, expr, ty, els); } } } @@ -830,11 +830,12 @@ fn report_single_match_single_pattern( ); } -fn check_single_match_opt_like( - cx: &LateContext<'_>, +fn check_single_match_opt_like<'a>( + cx: &LateContext<'a>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, + ty: Ty<'a>, els: Option<&Expr<'_>>, ) { // list of candidate `Enum`s we know will never get any more members @@ -854,44 +855,50 @@ fn check_single_match_opt_like( return; } - let mut paths = Vec::new(); - if !collect_pat_paths(&mut paths, arms[1].pat) { + let mut paths_and_types = Vec::new(); + if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { return; } - let in_candidate_enum = |path: &String| -> bool { - for &(_, pat_path) in candidates { - if path == pat_path { + let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool { + let (path, ty) = path_info; + for &(ty_path, pat_path) in candidates { + if path == pat_path && match_type(cx, ty, ty_path) { return true; } } false }; - if paths.iter().all(in_candidate_enum) { + if paths_and_types.iter().all(in_candidate_enum) { report_single_match_single_pattern(cx, ex, arms, expr, els); } } -/// Collects paths from the given paths. Returns true if the given pattern could be simplified, -/// false otherwise. -fn collect_pat_paths(acc: &mut Vec, pat: &Pat<'_>) -> bool { +/// Collects paths and their types from the given patterns. Returns true if the given pattern could +/// be simplified, false otherwise. +fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { match pat.kind { PatKind::Wild => true, - PatKind::Tuple(inner, _) => inner.iter().all(|p| collect_pat_paths(acc, p)), + PatKind::Tuple(inner, _) => inner.iter().all(|p| { + let p_ty = cx.typeck_results().pat_ty(p); + collect_pat_paths(acc, cx, p, p_ty) + }), PatKind::TupleStruct(ref path, ..) => { - acc.push(rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { s.print_qpath(path, false); - })); + }); + acc.push((path, ty)); true }, PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push(ident.to_string()); + acc.push((ident.to_string(), ty)); true }, PatKind::Path(ref path) => { - acc.push(rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { s.print_qpath(path, false); - })); + }); + acc.push((path, ty)); true }, _ => false, diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index 0e50b3e4a6e25..bd37188804636 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -197,6 +197,32 @@ fn ranges() { } } +fn skip_type_aliases() { + enum OptionEx { + Some(i32), + None, + } + enum ResultEx { + Err(i32), + Ok(i32), + } + + use OptionEx::{None, Some}; + use ResultEx::{Err, Ok}; + + // don't lint + match Err(42) { + Ok(_) => dummy(), + Err(_) => (), + }; + + // don't lint + match Some(1i32) { + Some(_) => dummy(), + None => (), + }; +} + macro_rules! single_match { ($num:literal) => { match $num { From a8fdf5ca8aab08ba56678a4bce36f9da5ae8e52d Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Wed, 26 Jan 2022 19:09:31 +0300 Subject: [PATCH 08/67] matches: Remove extra comment --- clippy_lints/src/matches.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 47c02e2282151..ab030ffb4063a 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -916,22 +916,6 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool { /// Returns true if the given patterns forms only exhaustive matches that don't contain enum /// patterns without a wildcard. -/// -/// For example: -/// -/// ``` -/// // Returns false, because the first arm contain enum without a wildcard. -/// match x { -/// (Some(E::V), _) => todo!(), -/// (None, _) => {} -/// } -/// -/// // Returns true, because the both arms form exhaustive matches and without enum variants. -/// match x { -/// (Some(_), _) => todo!(), -/// (None, _) => {} -/// } -/// ``` fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { match (&left.kind, &right.kind) { (PatKind::Wild, _) | (_, PatKind::Wild) => true, From bf66aeda0a8410f2730584e24cc96b3a53449ea0 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 27 Jan 2022 15:12:45 +0100 Subject: [PATCH 09/67] Merge commit 'a98e7ab8b94485be6bd03e0c6b8682ecab5b52e6' into clippyup --- CHANGELOG.md | 3 +- clippy_dev/src/bless.rs | 7 +- clippy_dev/src/lint.rs | 1 - clippy_lints/src/borrow_as_ptr.rs | 2 + clippy_lints/src/casts/unnecessary_cast.rs | 11 +- clippy_lints/src/dereference.rs | 199 ++++-- clippy_lints/src/enum_variants.rs | 3 + clippy_lints/src/eq_op.rs | 69 +- clippy_lints/src/implicit_hasher.rs | 2 +- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_complexity.rs | 1 - clippy_lints/src/lib.register_lints.rs | 1 - clippy_lints/src/lib.rs | 4 +- clippy_lints/src/lifetimes.rs | 52 +- clippy_lints/src/manual_async_fn.rs | 4 +- clippy_lints/src/manual_bits.rs | 2 + clippy_lints/src/map_clone.rs | 64 +- clippy_lints/src/matches.rs | 8 +- clippy_lints/src/methods/expect_fun_call.rs | 2 +- clippy_lints/src/methods/mod.rs | 4 +- clippy_lints/src/needless_question_mark.rs | 23 +- clippy_lints/src/octal_escapes.rs | 2 +- clippy_lints/src/ptr.rs | 613 ++++++++++++------ clippy_lints/src/reference.rs | 57 -- clippy_lints/src/trait_bounds.rs | 58 +- clippy_lints/src/unit_return_expecting_ord.rs | 7 +- clippy_lints/src/unnested_or_patterns.rs | 4 +- clippy_lints/src/utils/conf.rs | 2 +- clippy_lints/src/utils/internal_lints.rs | 1 + .../internal_lints/metadata_collector.rs | 10 +- clippy_utils/src/ast_utils.rs | 10 +- clippy_utils/src/lib.rs | 32 +- clippy_utils/src/numeric_literal.rs | 9 +- clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/ty.rs | 113 +++- rust-toolchain | 2 +- src/driver.rs | 3 +- tests/compile-test.rs | 8 +- .../min_rust_version/min_rust_version.rs | 24 +- .../min_rust_version/min_rust_version.stderr | 10 + .../borrow_interior_mutable_const/others.rs | 6 +- .../others.stderr | 28 +- tests/ui/bytecount.rs | 2 + tests/ui/bytecount.stderr | 8 +- tests/ui/clone_on_copy.fixed | 3 +- tests/ui/clone_on_copy.rs | 3 +- tests/ui/clone_on_copy.stderr | 16 +- tests/ui/duration_subsec.fixed | 2 +- tests/ui/duration_subsec.rs | 2 +- tests/ui/enum_variants.rs | 7 + tests/ui/eta.fixed | 9 +- tests/ui/eta.rs | 7 +- tests/ui/eta.stderr | 50 +- tests/ui/explicit_deref_methods.fixed | 2 +- tests/ui/explicit_deref_methods.rs | 2 +- tests/ui/for_loop_fixable.fixed | 7 +- tests/ui/for_loop_fixable.rs | 7 +- tests/ui/for_loop_fixable.stderr | 30 +- tests/ui/format.fixed | 7 +- tests/ui/format.rs | 7 +- tests/ui/format.stderr | 34 +- tests/ui/needless_borrow.fixed | 27 + tests/ui/needless_borrow.rs | 27 + tests/ui/needless_borrow.stderr | 76 ++- tests/ui/needless_lifetimes.rs | 51 +- tests/ui/needless_lifetimes.stderr | 94 ++- tests/ui/needless_question_mark.fixed | 13 + tests/ui/needless_question_mark.rs | 13 + tests/ui/needless_question_mark.stderr | 14 +- tests/ui/op_ref.rs | 39 +- tests/ui/op_ref.stderr | 18 +- tests/ui/ptr_arg.rs | 29 +- tests/ui/ptr_arg.stderr | 158 ++--- .../redundant_pattern_matching_option.fixed | 2 +- tests/ui/redundant_pattern_matching_option.rs | 2 +- tests/ui/rename.fixed | 1 + tests/ui/rename.rs | 1 + tests/ui/rename.stderr | 26 +- tests/ui/search_is_some_fixable_none.rs | 2 +- tests/ui/search_is_some_fixable_none.stderr | 10 +- tests/ui/search_is_some_fixable_some.rs | 2 +- tests/ui/search_is_some_fixable_some.stderr | 10 +- tests/ui/slow_vector_initialization.rs | 2 +- tests/ui/trait_duplication_in_bounds.rs | 22 + tests/ui/trait_duplication_in_bounds.stderr | 24 +- tests/ui/unnecessary_cast.rs | 7 + tests/ui/unnecessary_cast.stderr | 38 +- tests/ui/unnecessary_ref.fixed | 23 - tests/ui/unnecessary_ref.rs | 23 - tests/ui/unnecessary_ref.stderr | 22 - tests/ui/useless_asref.fixed | 2 +- tests/ui/useless_asref.rs | 2 +- tests/ui/write_literal.rs | 48 +- tests/ui/write_literal.stderr | 110 ++-- tests/ui/write_literal_2.rs | 16 +- tests/ui/write_literal_2.stderr | 50 +- tests/ui/write_with_newline.rs | 56 +- tests/ui/write_with_newline.stderr | 72 +- tests/ui/writeln_empty_string.fixed | 10 +- tests/ui/writeln_empty_string.rs | 10 +- tests/ui/writeln_empty_string.stderr | 12 +- util/gh-pages/index.html | 5 + 102 files changed, 1841 insertions(+), 997 deletions(-) create mode 100644 tests/ui-toml/min_rust_version/min_rust_version.stderr delete mode 100644 tests/ui/unnecessary_ref.fixed delete mode 100644 tests/ui/unnecessary_ref.rs delete mode 100644 tests/ui/unnecessary_ref.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 258a8256f5317..d66e6cf7fb659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -981,7 +981,7 @@ Released 2021-03-25 [#6532](https://github.com/rust-lang/rust-clippy/pull/6532) * [`single_match`] Suggest `if` over `if let` when possible [#6574](https://github.com/rust-lang/rust-clippy/pull/6574) -* [`ref_in_deref`] Use parentheses correctly in suggestion +* `ref_in_deref` Use parentheses correctly in suggestion [#6609](https://github.com/rust-lang/rust-clippy/pull/6609) * [`stable_sort_primitive`] Clarify error message [#6611](https://github.com/rust-lang/rust-clippy/pull/6611) @@ -3227,7 +3227,6 @@ Released 2018-09-13 [`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing [`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes [`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference -[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref [`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref [`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro [`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once diff --git a/clippy_dev/src/bless.rs b/clippy_dev/src/bless.rs index daf0fcc993bad..dcc2502e4c59a 100644 --- a/clippy_dev/src/bless.rs +++ b/clippy_dev/src/bless.rs @@ -9,9 +9,14 @@ use walkdir::WalkDir; use crate::clippy_project_root; +#[cfg(not(windows))] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; +#[cfg(windows)] +static CARGO_CLIPPY_EXE: &str = "cargo-clippy.exe"; + static CLIPPY_BUILD_TIME: SyncLazy> = SyncLazy::new(|| { let mut path = std::env::current_exe().unwrap(); - path.set_file_name("cargo-clippy"); + path.set_file_name(CARGO_CLIPPY_EXE); fs::metadata(path).ok()?.modified().ok() }); diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index dfd16f7105438..b8287980a4bac 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -7,7 +7,6 @@ pub fn run(filename: &str) { .args(["-Z", "no-codegen"]) .args(["--edition", "2021"]) .arg(filename) - .env("__CLIPPY_INTERNAL_TESTS", "true") .status() .expect("failed to run cargo") .code(); diff --git a/clippy_lints/src/borrow_as_ptr.rs b/clippy_lints/src/borrow_as_ptr.rs index b8f5217af2b7d..9f8eb488c29ba 100644 --- a/clippy_lints/src/borrow_as_ptr.rs +++ b/clippy_lints/src/borrow_as_ptr.rs @@ -94,4 +94,6 @@ impl<'tcx> LateLintPass<'tcx> for BorrowAsPtr { } } } + + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 1915d990c126c..470c8c7ea26a6 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -23,15 +23,14 @@ pub(super) fn check( if_chain! { if let LitKind::Int(n, _) = lit.node; - if let Some(src) = snippet_opt(cx, lit.span); + if let Some(src) = snippet_opt(cx, cast_expr.span); if cast_to.is_floating_point(); if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); let from_nbits = 128 - n.leading_zeros(); let to_nbits = fp_ty_mantissa_nbits(cast_to); if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); then { - let literal_str = if is_unary_neg(cast_expr) { format!("-{}", num_lit.integer) } else { num_lit.integer.into() }; - lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to); return true } } @@ -48,7 +47,7 @@ pub(super) fn check( | LitKind::Float(_, LitFloatType::Suffixed(_)) if cast_from.kind() == cast_to.kind() => { - if let Some(src) = snippet_opt(cx, lit.span) { + if let Some(src) = snippet_opt(cx, cast_expr.span) { if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) { lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to); } @@ -113,7 +112,3 @@ fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 { _ => 0, } } - -fn is_unary_neg(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::Unary(UnOp::Neg, _)) -} diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index feb5f100de5d5..c0adab790f0d5 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; +use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::peel_mid_ty_refs; use clippy_utils::{get_parent_expr, get_parent_node, is_lint_allowed, path_to_local}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; @@ -10,11 +11,10 @@ use rustc_hir::{ Pat, PatKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span}; -use std::iter; declare_clippy_lint! { /// ### What it does @@ -131,8 +131,6 @@ pub struct Dereferencing { struct StateData { /// Span of the top level expression span: Span, - /// The required mutability - target_mut: Mutability, } enum State { @@ -141,9 +139,13 @@ enum State { // The number of calls in a sequence which changed the referenced type ty_changed_count: usize, is_final_ufcs: bool, + /// The required mutability + target_mut: Mutability, }, DerefedBorrow { - count: u32, + count: usize, + required_precedence: i8, + msg: &'static str, }, } @@ -214,59 +216,98 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { 1 }, is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), - }, - StateData { - span: expr.span, target_mut, }, + StateData { span: expr.span }, )); }, RefOp::AddrOf => { // Find the number of times the borrow is auto-derefed. let mut iter = find_adjustments(cx.tcx, typeck, expr).iter(); - if let Some((i, adjust)) = iter.by_ref().enumerate().find_map(|(i, adjust)| { - if !matches!(adjust.kind, Adjust::Deref(_)) { - Some((i, adjust)) - } else if !adjust.target.is_ref() { - // Add one to the number of references found. - Some((i + 1, adjust)) + let mut deref_count = 0usize; + let next_adjust = loop { + match iter.next() { + Some(adjust) => { + if !matches!(adjust.kind, Adjust::Deref(_)) { + break Some(adjust); + } else if !adjust.target.is_ref() { + deref_count += 1; + break iter.next(); + } + deref_count += 1; + }, + None => break None, + }; + }; + + // Determine the required number of references before any can be removed. In all cases the + // reference made by the current expression will be removed. After that there are four cases to + // handle. + // + // 1. Auto-borrow will trigger in the current position, so no further references are required. + // 2. Auto-deref ends at a reference, or the underlying type, so one extra needs to be left to + // handle the automatically inserted re-borrow. + // 3. Auto-deref hits a user-defined `Deref` impl, so at least one reference needs to exist to + // start auto-deref. + // 4. If the chain of non-user-defined derefs ends with a mutable re-borrow, and re-borrow + // adjustments will not be inserted automatically, then leave one further reference to avoid + // moving a mutable borrow. + // e.g. + // fn foo(x: &mut Option<&mut T>, y: &mut T) { + // let x = match x { + // // Removing the borrow will cause `x` to be moved + // Some(x) => &mut *x, + // None => y + // }; + // } + let deref_msg = + "this expression creates a reference which is immediately dereferenced by the compiler"; + let borrow_msg = "this expression borrows a value the compiler would automatically borrow"; + + let (required_refs, required_precedence, msg) = if is_auto_borrow_position(parent, expr.hir_id) + { + (1, PREC_POSTFIX, if deref_count == 1 { borrow_msg } else { deref_msg }) + } else if let Some(&Adjust::Borrow(AutoBorrow::Ref(_, mutability))) = + next_adjust.map(|a| &a.kind) + { + if matches!(mutability, AutoBorrowMutability::Mut { .. }) + && !is_auto_reborrow_position(parent) + { + (3, 0, deref_msg) } else { - None - } - }) { - // Found two consecutive derefs. At least one can be removed. - if i > 1 { - let target_mut = iter::once(adjust) - .chain(iter) - .find_map(|adjust| match adjust.kind { - Adjust::Borrow(AutoBorrow::Ref(_, m)) => Some(m.into()), - _ => None, - }) - // This default should never happen. Auto-deref always reborrows. - .unwrap_or(Mutability::Not); - self.state = Some(( - // Subtract one for the current borrow expression, and one to cover the last - // reference which can't be removed (it's either reborrowed, or needed for - // auto-deref to happen). - State::DerefedBorrow { - count: - // Truncation here would require more than a `u32::MAX` level reference. The compiler - // does not support this. - #[allow(clippy::cast_possible_truncation)] - { i as u32 - 2 } - }, - StateData { - span: expr.span, - target_mut, - }, - )); + (2, 0, deref_msg) } + } else { + (2, 0, deref_msg) + }; + + if deref_count >= required_refs { + self.state = Some(( + State::DerefedBorrow { + // One of the required refs is for the current borrow expression, the remaining ones + // can't be removed without breaking the code. See earlier comment. + count: deref_count - required_refs, + required_precedence, + msg, + }, + StateData { span: expr.span }, + )); } }, _ => (), } }, - (Some((State::DerefMethod { ty_changed_count, .. }, data)), RefOp::Method(_)) => { + ( + Some(( + State::DerefMethod { + target_mut, + ty_changed_count, + .. + }, + data, + )), + RefOp::Method(_), + ) => { self.state = Some(( State::DerefMethod { ty_changed_count: if deref_method_same_type(typeck.expr_ty(expr), typeck.expr_ty(sub_expr)) { @@ -275,12 +316,30 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { ty_changed_count + 1 }, is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), + target_mut, }, data, )); }, - (Some((State::DerefedBorrow { count }, data)), RefOp::AddrOf) if count != 0 => { - self.state = Some((State::DerefedBorrow { count: count - 1 }, data)); + ( + Some(( + State::DerefedBorrow { + count, + required_precedence, + msg, + }, + data, + )), + RefOp::AddrOf, + ) if count != 0 => { + self.state = Some(( + State::DerefedBorrow { + count: count - 1, + required_precedence, + msg, + }, + data, + )); }, (Some((state, data)), _) => report(cx, expr, state, data), @@ -455,6 +514,30 @@ fn is_linted_explicit_deref_position(parent: Option>, child_id: HirId, } } +/// Checks if the given expression is in a position which can be auto-reborrowed. +/// Note: This is only correct assuming auto-deref is already occurring. +fn is_auto_reborrow_position(parent: Option>) -> bool { + match parent { + Some(Node::Expr(parent)) => matches!(parent.kind, ExprKind::MethodCall(..) | ExprKind::Call(..)), + Some(Node::Local(_)) => true, + _ => false, + } +} + +/// Checks if the given expression is a position which can auto-borrow. +fn is_auto_borrow_position(parent: Option>, child_id: HirId) -> bool { + if let Some(Node::Expr(parent)) = parent { + match parent.kind { + ExprKind::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id, + ExprKind::Field(..) => true, + ExprKind::Call(f, _) => f.hir_id == child_id, + _ => false, + } + } else { + false + } +} + /// Adjustments are sometimes made in the parent block rather than the expression itself. fn find_adjustments<'tcx>( tcx: TyCtxt<'tcx>, @@ -503,6 +586,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) State::DerefMethod { ty_changed_count, is_final_ufcs, + target_mut, } => { let mut app = Applicability::MachineApplicable; let (expr_str, expr_is_macro_call) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); @@ -517,12 +601,12 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) }; let addr_of_str = if ty_changed_count < ref_count { // Check if a reborrow from &mut T -> &T is required. - if data.target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { + if target_mut == Mutability::Not && matches!(ty.kind(), ty::Ref(_, _, Mutability::Mut)) { "&*" } else { "" } - } else if data.target_mut == Mutability::Mut { + } else if target_mut == Mutability::Mut { "&mut " } else { "&" @@ -538,7 +622,7 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) cx, EXPLICIT_DEREF_METHODS, data.span, - match data.target_mut { + match target_mut { Mutability::Not => "explicit `deref` method call", Mutability::Mut => "explicit `deref_mut` method call", }, @@ -547,19 +631,24 @@ fn report(cx: &LateContext<'_>, expr: &Expr<'_>, state: State, data: StateData) app, ); }, - State::DerefedBorrow { .. } => { + State::DerefedBorrow { + required_precedence, + msg, + .. + } => { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; span_lint_and_sugg( cx, NEEDLESS_BORROW, data.span, - &format!( - "this expression borrows a reference (`{}`) that is immediately dereferenced by the compiler", - cx.typeck_results().expr_ty(expr), - ), + msg, "change this to", - snip.into(), + if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { + format!("({})", snip) + } else { + snip.into() + }, app, ); }, diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 4f89e56743068..1f4353fa4f72b 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -172,6 +172,9 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n let name = var.ident.name.as_str(); let variant_split = camel_case_split(name); + if variant_split.len() == 1 { + return; + } pre = pre .iter() diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index df75b815436b8..24d7613e6f8ca 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -1,12 +1,16 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint, span_lint_and_then}; +use clippy_utils::get_enclosing_block; use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; use clippy_utils::source::snippet; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_test_function}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind}; +use rustc_hir::{ + def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, Ty, TyKind, +}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -146,6 +150,13 @@ impl<'tcx> LateLintPass<'tcx> for EqOp { let rty = cx.typeck_results().expr_ty(r); let lcpy = is_copy(cx, lty); let rcpy = is_copy(cx, rty); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } // either operator autorefs or both args are copyable if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { span_lint_and_then( @@ -206,6 +217,14 @@ impl<'tcx> LateLintPass<'tcx> for EqOp { // &foo == bar (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => { let lty = cx.typeck_results().expr_ty(l); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + let rty = cx.typeck_results().expr_ty(right); + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } let lcpy = is_copy(cx, lty); if (requires_ref || lcpy) && implements_trait(cx, lty, trait_id, &[cx.typeck_results().expr_ty(right).into()]) @@ -230,6 +249,14 @@ impl<'tcx> LateLintPass<'tcx> for EqOp { // foo == &bar (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => { let rty = cx.typeck_results().expr_ty(r); + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { + let lty = cx.typeck_results().expr_ty(left); + if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) + { + return; // Don't lint + } + } let rcpy = is_copy(cx, rty); if (requires_ref || rcpy) && implements_trait(cx, cx.typeck_results().expr_ty(left), trait_id, &[rty.into()]) @@ -251,3 +278,43 @@ impl<'tcx> LateLintPass<'tcx> for EqOp { } } } + +fn in_impl<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, bin_op: DefId) -> Option<(&'tcx Ty<'tcx>, &'tcx Ty<'tcx>)> { + if_chain! { + if let Some(block) = get_enclosing_block(cx, e.hir_id); + if let Some(impl_def_id) = cx.tcx.impl_of_method(block.hir_id.owner.to_def_id()); + let item = cx.tcx.hir().expect_item(impl_def_id.expect_local()); + if let ItemKind::Impl(item) = &item.kind; + if let Some(of_trait) = &item.of_trait; + if let Some(seg) = of_trait.path.segments.last(); + if let Some(Res::Def(_, trait_id)) = seg.res; + if trait_id == bin_op; + if let Some(generic_args) = seg.args; + if let Some(GenericArg::Type(other_ty)) = generic_args.args.last(); + + then { + Some((item.self_ty, other_ty)) + } + else { + None + } + } +} + +fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: &TyS<'_>, hir_ty: &Ty<'_>) -> bool { + if_chain! { + if let ty::Adt(adt_def, _) = middle_ty.kind(); + if let Some(local_did) = adt_def.did.as_local(); + let item = cx.tcx.hir().expect_item(local_did); + let middle_ty_id = item.def_id.to_def_id(); + if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; + if let Res::Def(_, hir_ty_id) = path.res; + + then { + hir_ty_id == middle_ty_id + } + else { + false + } + } +} diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 104de0ff62f8a..eed25e9bc0ea8 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -179,7 +179,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { ) .and_then(|snip| { let i = snip.find("fn")?; - Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32)) + Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32)) }) .expect("failed to create span for type parameters"); Span::new(pos, pos, item.span.ctxt(), item.span.parent()) diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 87fd7f99748a1..4721b7f2b472b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -247,7 +247,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(redundant_slicing::REDUNDANT_SLICING), LintId::of(redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(reference::DEREF_ADDROF), - LintId::of(reference::REF_IN_DEREF), LintId::of(regex::INVALID_REGEX), LintId::of(repeat_once::REPEAT_ONCE), LintId::of(returns::LET_AND_RETURN), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index a21ddf73a115e..bd5ff613447cd 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -71,7 +71,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_slicing::REDUNDANT_SLICING), LintId::of(reference::DEREF_ADDROF), - LintId::of(reference::REF_IN_DEREF), LintId::of(repeat_once::REPEAT_ONCE), LintId::of(strings::STRING_FROM_UTF8_AS_BYTES), LintId::of(strlen_on_c_strings::STRLEN_ON_C_STRINGS), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 56146a0fd3a75..2d2693832e971 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -423,7 +423,6 @@ store.register_lints(&[ redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, ref_option_ref::REF_OPTION_REF, reference::DEREF_ADDROF, - reference::REF_IN_DEREF, regex::INVALID_REGEX, regex::TRIVIAL_REGEX, repeat_once::REPEAT_ONCE, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 79e9882fef4c4..f2a7e925dd395 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -581,6 +581,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(needless_question_mark::NeedlessQuestionMark)); store.register_late_pass(move || Box::new(casts::Casts::new(msrv))); store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(msrv))); + store.register_late_pass(move || Box::new(map_clone::MapClone::new(msrv))); store.register_late_pass(|| Box::new(size_of_in_element_count::SizeOfInElementCount)); store.register_late_pass(|| Box::new(same_name_method::SameNameMethod)); @@ -591,7 +592,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: msrv, )) }); - store.register_late_pass(|| Box::new(map_clone::MapClone)); store.register_late_pass(|| Box::new(map_err_ignore::MapErrIgnore)); store.register_late_pass(|| Box::new(shadow::Shadow::default())); store.register_late_pass(|| Box::new(unit_types::UnitTypes)); @@ -703,7 +703,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(mut_key::MutableKeyType)); store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic)); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); - store.register_early_pass(|| Box::new(reference::RefInDeref)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); store.register_late_pass(|| Box::new(to_string_in_display::ToStringInDisplay::new())); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); @@ -935,6 +934,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { ls.register_renamed("clippy::if_let_some_result", "clippy::match_result_ok"); ls.register_renamed("clippy::disallowed_type", "clippy::disallowed_types"); ls.register_renamed("clippy::disallowed_method", "clippy::disallowed_methods"); + ls.register_renamed("clippy::ref_in_deref", "clippy::needless_borrow"); // uplifted lints ls.register_renamed("clippy::invalid_ref", "invalid_value"); diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 565057140454d..b09c23f31e970 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -13,7 +13,7 @@ use rustc_hir::{ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; -use rustc_span::symbol::{kw, Symbol}; +use rustc_span::symbol::{kw, Ident, Symbol}; declare_clippy_lint! { /// ### What it does @@ -83,7 +83,7 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); impl<'tcx> LateLintPass<'tcx> for Lifetimes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Fn(ref sig, ref generics, id) = item.kind { - check_fn_inner(cx, sig.decl, Some(id), generics, item.span, true); + check_fn_inner(cx, sig.decl, Some(id), None, generics, item.span, true); } } @@ -94,6 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { cx, sig.decl, Some(id), + None, &item.generics, item.span, report_extra_lifetimes, @@ -103,11 +104,11 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { if let TraitItemKind::Fn(ref sig, ref body) = item.kind { - let body = match *body { - TraitFn::Required(_) => None, - TraitFn::Provided(id) => Some(id), + let (body, trait_sig) = match *body { + TraitFn::Required(sig) => (None, Some(sig)), + TraitFn::Provided(id) => (Some(id), None), }; - check_fn_inner(cx, sig.decl, body, &item.generics, item.span, true); + check_fn_inner(cx, sig.decl, body, trait_sig, &item.generics, item.span, true); } } } @@ -124,6 +125,7 @@ fn check_fn_inner<'tcx>( cx: &LateContext<'tcx>, decl: &'tcx FnDecl<'_>, body: Option, + trait_sig: Option<&[Ident]>, generics: &'tcx Generics<'_>, span: Span, report_extra_lifetimes: bool, @@ -165,7 +167,7 @@ fn check_fn_inner<'tcx>( } } } - if could_use_elision(cx, decl, body, generics.params) { + if could_use_elision(cx, decl, body, trait_sig, generics.params) { span_lint( cx, NEEDLESS_LIFETIMES, @@ -179,10 +181,31 @@ fn check_fn_inner<'tcx>( } } +// elision doesn't work for explicit self types, see rust-lang/rust#69064 +fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option) -> bool { + if_chain! { + if let Some(ident) = ident; + if ident.name == kw::SelfLower; + if !func.implicit_self.has_implicit_self(); + + if let Some(self_ty) = func.inputs.first(); + then { + let mut visitor = RefVisitor::new(cx); + visitor.visit_ty(self_ty); + + !visitor.all_lts().is_empty() + } + else { + false + } + } +} + fn could_use_elision<'tcx>( cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, body: Option, + trait_sig: Option<&[Ident]>, named_generics: &'tcx [GenericParam<'_>], ) -> bool { // There are two scenarios where elision works: @@ -233,11 +256,24 @@ fn could_use_elision<'tcx>( let input_lts = input_visitor.lts; let output_lts = output_visitor.lts; + if let Some(trait_sig) = trait_sig { + if explicit_self_type(cx, func, trait_sig.first().copied()) { + return false; + } + } + if let Some(body_id) = body { + let body = cx.tcx.hir().body(body_id); + + let first_ident = body.params.first().and_then(|param| param.pat.simple_ident()); + if explicit_self_type(cx, func, first_ident) { + return false; + } + let mut checker = BodyLifetimeChecker { lifetimes_used_in_body: false, }; - checker.visit_expr(&cx.tcx.hir().body(body_id).value); + checker.visit_expr(&body.value); if checker.lifetimes_used_in_body { return false; } diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index 2af3555e370a4..babc6fab3c0fb 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -6,8 +6,8 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - Term, AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId, - IsAsync, ItemKind, LifetimeName, TraitRef, Ty, TyKind, TypeBindingKind, + AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId, + IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 50bf2527e39a8..809aa168a7a0e 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -72,6 +72,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { } } } + + extract_msrv_attr!(LateContext); } fn get_one_size_of_ty<'tcx>( diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index 22a2552b283a5..3f8eeb736fbd2 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; -use clippy_utils::{is_trait_method, peel_blocks}; +use clippy_utils::{is_trait_method, meets_msrv, msrvs, peel_blocks}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; @@ -9,7 +9,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::Mutability; use rustc_middle::ty; use rustc_middle::ty::adjustment::Adjust; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::symbol::Ident; use rustc_span::{sym, Span}; @@ -42,7 +43,17 @@ declare_clippy_lint! { "using `iterator.map(|x| x.clone())`, or dereferencing closures for `Copy` types" } -declare_lint_pass!(MapClone => [MAP_CLONE]); +pub struct MapClone { + msrv: Option, +} + +impl_lint_pass!(MapClone => [MAP_CLONE]); + +impl MapClone { + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} impl<'tcx> LateLintPass<'tcx> for MapClone { fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) { @@ -65,7 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { hir::BindingAnnotation::Unannotated, .., name, None ) = inner.kind { if ident_eq(name, closure_expr) { - lint(cx, e.span, args[0].span, true); + self.lint_explicit_closure(cx, e.span, args[0].span, true); } }, hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { @@ -73,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { if ident_eq(name, inner) { if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { - lint(cx, e.span, args[0].span, true); + self.lint_explicit_closure(cx, e.span, args[0].span, true); } } }, @@ -90,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { if let ty::Ref(_, ty, mutability) = obj_ty.kind() { if matches!(mutability, Mutability::Not) { let copy = is_copy(cx, ty); - lint(cx, e.span, args[0].span, copy); + self.lint_explicit_closure(cx, e.span, args[0].span, copy); } } else { lint_needless_cloning(cx, e.span, args[0].span); @@ -105,6 +116,8 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { } } } + + extract_msrv_attr!(LateContext); } fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { @@ -127,31 +140,30 @@ fn lint_needless_cloning(cx: &LateContext<'_>, root: Span, receiver: Span) { ); } -fn lint(cx: &LateContext<'_>, replace: Span, root: Span, copied: bool) { - let mut applicability = Applicability::MachineApplicable; - if copied { - span_lint_and_sugg( - cx, - MAP_CLONE, - replace, - "you are using an explicit closure for copying elements", - "consider calling the dedicated `copied` method", - format!( - "{}.copied()", - snippet_with_applicability(cx, root, "..", &mut applicability) - ), - applicability, - ); - } else { +impl MapClone { + fn lint_explicit_closure(&self, cx: &LateContext<'_>, replace: Span, root: Span, is_copy: bool) { + let mut applicability = Applicability::MachineApplicable; + let message = if is_copy { + "you are using an explicit closure for copying elements" + } else { + "you are using an explicit closure for cloning elements" + }; + let sugg_method = if is_copy && meets_msrv(self.msrv.as_ref(), &msrvs::ITERATOR_COPIED) { + "copied" + } else { + "cloned" + }; + span_lint_and_sugg( cx, MAP_CLONE, replace, - "you are using an explicit closure for cloning elements", - "consider calling the dedicated `cloned` method", + message, + &format!("consider calling the dedicated `{}` method", sugg_method), format!( - "{}.cloned()", - snippet_with_applicability(cx, root, "..", &mut applicability) + "{}.{}()", + snippet_with_applicability(cx, root, "..", &mut applicability), + sugg_method, ), applicability, ); diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index dfb450c8848ad..2579404fb18cc 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -30,7 +30,7 @@ use rustc_middle::ty::{self, Ty, TyS, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; -use rustc_span::sym; +use rustc_span::{sym, symbol::kw}; use std::cmp::Ordering; use std::collections::hash_map::Entry; @@ -961,13 +961,13 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); if path_str == "Err" { let mut matching_wild = inner.iter().any(is_wild); - let mut ident_bind_name = String::from("_"); + let mut ident_bind_name = kw::Underscore; if !matching_wild { // 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('_') && !is_local_used(cx, arm.body, id) { - ident_bind_name = ident.name.as_str().to_string(); + ident_bind_name = ident.name; matching_wild = true; } } @@ -982,7 +982,7 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm span_lint_and_note(cx, MATCH_WILD_ERR_ARM, arm.pat.span, - &format!("`Err({})` matches all errors", &ident_bind_name), + &format!("`Err({})` matches all errors", ident_bind_name), None, "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", ); diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index e7d2d550a3032..d813edab687e8 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>( hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, hir::ExprKind::MethodCall(method_name, call_args, _) => { if call_args.len() == 1 - && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref)) + && (method_name.ident.name == sym::as_str || method_name.ident.name == sym::as_ref) && { let arg_type = cx.typeck_results().expr_ty(&call_args[0]); let base_type = arg_type.peel_refs(); diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 137c9628eb45c..4b43448bf7b98 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2180,8 +2180,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods { for &(predicate, _span) in cx.tcx.explicit_item_bounds(def_id) { if let ty::PredicateKind::Projection(projection_predicate) = predicate.kind().skip_binder() { let assoc_ty = match projection_predicate.term { - ty::Term::Ty(ty) => ty, - ty::Term::Const(_c) => continue, + ty::Term::Ty(ty) => ty, + ty::Term::Const(_c) => continue, }; // walk the associated type and check for Self if let Some(self_adt) = self_ty.ty_adt_def() { diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 0e7ae43ce2dd5..d4c823d1c1ab7 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -4,7 +4,7 @@ use clippy_utils::source::snippet; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; -use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath}; +use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyS; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -88,7 +88,26 @@ impl LateLintPass<'_> for NeedlessQuestionMark { } fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { - check(cx, body.value.peel_blocks()); + if let Some(GeneratorKind::Async(AsyncGeneratorKind::Fn)) = body.generator_kind { + if let ExprKind::Block( + Block { + expr: + Some(Expr { + kind: ExprKind::DropTemps(async_body), + .. + }), + .. + }, + _, + ) = body.value.kind + { + if let ExprKind::Block(Block { expr: Some(expr), .. }, ..) = async_body.kind { + check(cx, expr); + } + } + } else { + check(cx, body.value.peel_blocks()); + } } } diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index d81481ade044d..c19cea661042d 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -101,7 +101,7 @@ fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) { // construct a replacement escape // the maximum value is \077, or \x3f, so u8 is sufficient here if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) { - write!(&mut suggest_1, "\\x{:02x}", n).unwrap(); + write!(suggest_1, "\\x{:02x}", n).unwrap(); } // append the null byte as \x00 and the following digits literally diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 63de117a6f1de..b5d65542de0bf 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -1,30 +1,36 @@ //! Checks for usage of `&Vec[_]` and `&String`. use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::ptr::get_spans; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::walk_ptrs_hir_ty; -use clippy_utils::{expr_path_res, is_lint_allowed, match_any_diagnostic_items, paths}; +use clippy_utils::ty::expr_sig; +use clippy_utils::{ + expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths, +}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::def::Res; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::HirIdMap; +use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{ - BinOpKind, BodyId, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, Impl, ImplItem, ImplItemKind, Item, ItemKind, - Lifetime, MutTy, Mutability, Node, PathSegment, QPath, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, + self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, + ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn, + TraitItem, TraitItemKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::nested_filter; +use rustc_middle::ty::{self, AssocItems, AssocKind, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::Symbol; use rustc_span::{sym, MultiSpan}; -use std::borrow::Cow; +use std::fmt; +use std::iter; declare_clippy_lint! { /// ### What it does - /// This lint checks for function arguments of type `&String` - /// or `&Vec` unless the references are mutable. It will also suggest you - /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()` - /// calls. + /// This lint checks for function arguments of type `&String`, `&Vec`, + /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls + /// with the appropriate `.to_owned()`/`to_string()` calls. /// /// ### Why is this bad? /// Requiring the argument to be of the specific size @@ -32,28 +38,7 @@ declare_clippy_lint! { /// or `&str` usually suffice and can be obtained from other types, too. /// /// ### Known problems - /// The lint does not follow data. So if you have an - /// argument `x` and write `let y = x; y.clone()` the lint will not suggest - /// changing that `.clone()` to `.to_owned()`. - /// - /// Other functions called from this function taking a `&String` or `&Vec` - /// argument may also fail to compile if you change the argument. Applying - /// this lint on them will fix the problem, but they may be in other crates. - /// - /// One notable example of a function that may cause issues, and which cannot - /// easily be changed due to being in the standard library is `Vec::contains`. - /// when called on a `Vec>`. If a `&Vec` is passed to that method then - /// it will compile, but if a `&[T]` is passed then it will not compile. - /// - /// ```ignore - /// fn cannot_take_a_slice(v: &Vec) -> bool { - /// let vec_of_vecs: Vec> = some_other_fn(); - /// - /// vec_of_vecs.contains(v) - /// } - /// ``` - /// - /// Also there may be `fn(&Vec)`-typed references pointing to your function. + /// There may be `fn(&Vec)`-typed references pointing to your function. /// If you have them, you will get a compiler error after applying this lint's /// suggestions. You then have the choice to undo your changes or change the /// type of the reference. @@ -155,32 +140,86 @@ declare_clippy_lint! { declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]); impl<'tcx> LateLintPass<'tcx> for Ptr { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Fn(ref sig, _, body_id) = item.kind { - check_fn(cx, sig.decl, Some(body_id)); - } - } + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(sig, trait_method) = &item.kind { + if matches!(trait_method, TraitFn::Provided(_)) { + // Handled by check body. + return; + } - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { - if let ImplItemKind::Fn(ref sig, body_id) = item.kind { - let parent_item = cx.tcx.hir().get_parent_item(item.hir_id()); - if let Some(Node::Item(it)) = cx.tcx.hir().find_by_def_id(parent_item) { - if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = it.kind { - return; // ignore trait impls - } + check_mut_from_ref(cx, sig.decl); + for arg in check_fn_args( + cx, + cx.tcx.fn_sig(item.def_id).skip_binder().inputs(), + sig.decl.inputs, + &[], + ) { + span_lint_and_sugg( + cx, + PTR_ARG, + arg.span, + &arg.build_msg(), + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, + ); } - check_fn(cx, sig.decl, Some(body_id)); } } - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(ref sig, ref trait_method) = item.kind { - let body_id = if let TraitFn::Provided(b) = *trait_method { - Some(b) - } else { - None - }; - check_fn(cx, sig.decl, body_id); + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + let hir = cx.tcx.hir(); + let mut parents = hir.parent_iter(body.value.hir_id); + let (item_id, decl) = match parents.next() { + Some((_, Node::Item(i))) => { + if let ItemKind::Fn(sig, ..) = &i.kind { + (i.def_id, sig.decl) + } else { + return; + } + }, + Some((_, Node::ImplItem(i))) => { + if !matches!(parents.next(), + Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) + ) { + return; + } + if let ImplItemKind::Fn(sig, _) = &i.kind { + (i.def_id, sig.decl) + } else { + return; + } + }, + Some((_, Node::TraitItem(i))) => { + if let TraitItemKind::Fn(sig, _) = &i.kind { + (i.def_id, sig.decl) + } else { + return; + } + }, + _ => return, + }; + + check_mut_from_ref(cx, decl); + let sig = cx.tcx.fn_sig(item_id).skip_binder(); + let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params).collect(); + let results = check_ptr_arg_usage(cx, body, &lint_args); + + for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) { + span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| { + diag.multipart_suggestion( + "change this to", + iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) + .chain(result.replacements.iter().map(|r| { + ( + r.expr_span, + format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement), + ) + })) + .collect(), + Applicability::Unspecified, + ); + }); } } @@ -247,154 +286,206 @@ fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } } -#[allow(clippy::too_many_lines)] -fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, opt_body_id: Option) { - let body = opt_body_id.map(|id| cx.tcx.hir().body(id)); - - for (idx, arg) in decl.inputs.iter().enumerate() { - // Honor the allow attribute on parameters. See issue 5644. - if let Some(body) = &body { - if is_lint_allowed(cx, PTR_ARG, body.params[idx].hir_id) { - continue; - } - } +#[derive(Default)] +struct PtrArgResult { + skip: bool, + replacements: Vec, +} - let (item_name, path) = if_chain! { - if let TyKind::Rptr(_, MutTy { ty, mutbl: Mutability::Not }) = arg.kind; - if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind; - if let Res::Def(_, did) = path.res; - if let Some(item_name) = cx.tcx.get_diagnostic_name(did); - then { - (item_name, path) - } else { - continue - } - }; +struct PtrArgReplacement { + expr_span: Span, + self_span: Span, + replacement: &'static str, +} - match item_name { - sym::Vec => { - if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) { - span_lint_and_then( - cx, - PTR_ARG, - arg.span, - "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \ - with non-Vec-based slices", - |diag| { - if let Some(ref snippet) = get_only_generic_arg_snippet(cx, arg) { - diag.span_suggestion( - arg.span, - "change this to", - format!("&[{}]", snippet), - Applicability::Unspecified, - ); - } - for (clonespan, suggestion) in spans { - diag.span_suggestion( - clonespan, - &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { - Cow::Owned(format!("change `{}` to", x)) - }), - suggestion.into(), - Applicability::Unspecified, - ); - } - }, - ); - } +struct PtrArg<'tcx> { + idx: usize, + span: Span, + ty_did: DefId, + ty_name: Symbol, + method_renames: &'static [(&'static str, &'static str)], + ref_prefix: RefPrefix, + deref_ty: DerefTy<'tcx>, + deref_assoc_items: Option<(DefId, &'tcx AssocItems<'tcx>)>, +} +impl PtrArg<'_> { + fn build_msg(&self) -> String { + format!( + "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do", + self.ref_prefix.mutability.prefix_str(), + self.ty_name, + self.ref_prefix.mutability.prefix_str(), + self.deref_ty.argless_str(), + ) + } +} + +struct RefPrefix { + lt: LifetimeName, + mutability: Mutability, +} +impl fmt::Display for RefPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Write; + f.write_char('&')?; + match self.lt { + LifetimeName::Param(ParamName::Plain(name)) => { + name.fmt(f)?; + f.write_char(' ')?; }, - sym::String => { - if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) { - span_lint_and_then( - cx, - PTR_ARG, - arg.span, - "writing `&String` instead of `&str` involves a new object where a slice will do", - |diag| { - diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified); - for (clonespan, suggestion) in spans { - diag.span_suggestion_short( - clonespan, - &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { - Cow::Owned(format!("change `{}` to", x)) - }), - suggestion.into(), - Applicability::Unspecified, - ); - } - }, - ); + LifetimeName::Underscore => f.write_str("'_ ")?, + LifetimeName::Static => f.write_str("'static ")?, + _ => (), + } + f.write_str(self.mutability.prefix_str()) + } +} + +struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>); +impl fmt::Display for DerefTyDisplay<'_, '_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use std::fmt::Write; + match self.1 { + DerefTy::Str => f.write_str("str"), + DerefTy::Path => f.write_str("Path"), + DerefTy::Slice(hir_ty, ty) => { + f.write_char('[')?; + match hir_ty.and_then(|s| snippet_opt(self.0, s)) { + Some(s) => f.write_str(&s)?, + None => ty.fmt(f)?, } + f.write_char(']') }, - sym::PathBuf => { - if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_path_buf()"), ("as_path", "")]) { - span_lint_and_then( - cx, - PTR_ARG, - arg.span, - "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do", - |diag| { - diag.span_suggestion( - arg.span, + } + } +} + +enum DerefTy<'tcx> { + Str, + Path, + Slice(Option, Ty<'tcx>), +} +impl<'tcx> DerefTy<'tcx> { + fn argless_str(&self) -> &'static str { + match *self { + Self::Str => "str", + Self::Path => "Path", + Self::Slice(..) => "[_]", + } + } + + fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> { + DerefTyDisplay(cx, self) + } +} + +fn check_fn_args<'cx, 'tcx: 'cx>( + cx: &'cx LateContext<'tcx>, + tys: &'tcx [Ty<'_>], + hir_tys: &'tcx [hir::Ty<'_>], + params: &'tcx [Param<'_>], +) -> impl Iterator> + 'cx { + tys.iter() + .zip(hir_tys.iter()) + .enumerate() + .filter_map(|(i, (ty, hir_ty))| { + if_chain! { + if let ty::Ref(_, ty, mutability) = *ty.kind(); + if let ty::Adt(adt, substs) = *ty.kind(); + + if let TyKind::Rptr(lt, ref ty) = hir_ty.kind; + if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind; + + // Check that the name as typed matches the actual name of the type. + // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec` + if let [.., name] = path.segments; + if cx.tcx.item_name(adt.did) == name.ident.name; + + if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id); + if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id)); + + then { + let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) { + Some(sym::Vec) => ( + [("clone", ".to_owned()")].as_slice(), + DerefTy::Slice( + name.args + .and_then(|args| args.args.first()) + .and_then(|arg| if let GenericArg::Type(ty) = arg { + Some(ty.span) + } else { + None + }), + substs.type_at(0), + ), + cx.tcx.lang_items().slice_impl() + ), + Some(sym::String) => ( + [("clone", ".to_owned()"), ("as_str", "")].as_slice(), + DerefTy::Str, + cx.tcx.lang_items().str_impl() + ), + Some(sym::PathBuf) => ( + [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(), + DerefTy::Path, + None, + ), + Some(sym::Cow) => { + let ty_name = name.args + .and_then(|args| { + args.args.iter().find_map(|a| match a { + GenericArg::Type(x) => Some(x), + _ => None, + }) + }) + .and_then(|arg| snippet_opt(cx, arg.span)) + .unwrap_or_else(|| substs.type_at(1).to_string()); + span_lint_and_sugg( + cx, + PTR_ARG, + hir_ty.span, + "using a reference to `Cow` is not recommended", "change this to", - "&Path".into(), + format!("&{}{}", mutability.prefix_str(), ty_name), Applicability::Unspecified, ); - for (clonespan, suggestion) in spans { - diag.span_suggestion_short( - clonespan, - &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| { - Cow::Owned(format!("change `{}` to", x)) - }), - suggestion.into(), - Applicability::Unspecified, - ); - } + return None; }, - ); - } - }, - sym::Cow => { - if_chain! { - if let [ref bx] = *path.segments; - if let Some(params) = bx.args; - if !params.parenthesized; - if let Some(inner) = params.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, + _ => return None, + }; + return Some(PtrArg { + idx: i, + span: hir_ty.span, + ty_did: adt.did, + ty_name: name.ident.name, + method_renames, + ref_prefix: RefPrefix { + lt: lt.name, + mutability, + }, + deref_ty, + deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))), }); - let replacement = snippet_opt(cx, inner.span); - if let Some(r) = replacement; - then { - span_lint_and_sugg( - cx, - PTR_ARG, - arg.span, - "using a reference to `Cow` is not recommended", - "change this to", - "&".to_owned() + &r, - Applicability::Unspecified, - ); - } } - }, - _ => {}, - } - } + } + None + }) +} +fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) { if let FnRetTy::Return(ty) = decl.output { if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) { let mut immutables = vec![]; - for (_, ref mutbl, ref argspan) in decl + for (_, mutbl, argspan) in decl .inputs .iter() .filter_map(get_rptr_lm) .filter(|&(lt, _, _)| lt.name == out.name) { - if *mutbl == Mutability::Mut { + if mutbl == Mutability::Mut { return; } - immutables.push(*argspan); + immutables.push(argspan); } if immutables.is_empty() { return; @@ -413,24 +504,158 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, opt_body_id: Option } } -fn get_only_generic_arg_snippet(cx: &LateContext<'_>, arg: &Ty<'_>) -> Option { - if_chain! { - if let TyKind::Path(QPath::Resolved(_, path)) = walk_ptrs_hir_ty(arg).kind; - if let Some(&PathSegment{args: Some(parameters), ..}) = path.segments.last(); - let types: Vec<_> = parameters.args.iter().filter_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }).collect(); - if types.len() == 1; - then { - snippet_opt(cx, types[0].span) - } else { - None +#[allow(clippy::too_many_lines)] +fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec { + struct V<'cx, 'tcx> { + cx: &'cx LateContext<'tcx>, + /// Map from a local id to which argument it came from (index into `Self::args` and + /// `Self::results`) + bindings: HirIdMap, + /// The arguments being checked. + args: &'cx [PtrArg<'tcx>], + /// The results for each argument (len should match args.len) + results: Vec, + /// The number of arguments which can't be linted. Used to return early. + skip_count: usize, + } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.skip_count == self.args.len() { + return; + } + + // Check if this is local we care about + let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) { + Some(&i) => i, + None => return walk_expr(self, e), + }; + let args = &self.args[args_idx]; + let result = &mut self.results[args_idx]; + + // Helper function to handle early returns. + let mut set_skip_flag = || { + if result.skip { + self.skip_count += 1; + } + result.skip = true; + }; + + match get_expr_use_or_unification_node(self.cx.tcx, e) { + Some((Node::Stmt(_), _)) => (), + Some((Node::Local(l), _)) => { + // Only trace simple bindings. e.g `let x = y;` + if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind { + self.bindings.insert(id, args_idx); + } else { + set_skip_flag(); + } + }, + Some((Node::Expr(e), child_id)) => match e.kind { + ExprKind::Call(f, expr_args) => { + let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0); + if expr_sig(self.cx, f) + .map(|sig| sig.input(i).skip_binder().peel_refs()) + .map_or(true, |ty| match *ty.kind() { + ty::Param(_) => true, + ty::Adt(def, _) => def.did == args.ty_did, + _ => false, + }) + { + // Passed to a function taking the non-dereferenced type. + set_skip_flag(); + } + }, + ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => { + let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0); + if i == 0 { + // Check if the method can be renamed. + let name = name.ident.as_str(); + if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { + result.replacements.push(PtrArgReplacement { + expr_span: e.span, + self_span: self_arg.span, + replacement, + }); + return; + } + } + + let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) { + x + } else { + set_skip_flag(); + return; + }; + + match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() { + ty::Param(_) => { + set_skip_flag(); + }, + // If the types match check for methods which exist on both types. e.g. `Vec::len` and + // `slice::len` + ty::Adt(def, _) + if def.did == args.ty_did + && (i != 0 + || self.cx.tcx.trait_of_item(id).is_some() + || !args.deref_assoc_items.map_or(false, |(id, items)| { + items + .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id) + .is_some() + })) => + { + set_skip_flag(); + }, + _ => (), + } + }, + // Indexing is fine for currently supported types. + ExprKind::Index(e, _) if e.hir_id == child_id => (), + _ => set_skip_flag(), + }, + _ => set_skip_flag(), + } } } + + let mut skip_count = 0; + let mut results = args.iter().map(|_| PtrArgResult::default()).collect::>(); + let mut v = V { + cx, + bindings: args + .iter() + .enumerate() + .filter_map(|(i, arg)| { + let param = &body.params[arg.idx]; + match param.pat.kind { + PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) + if !is_lint_allowed(cx, PTR_ARG, param.hir_id) => + { + Some((id, i)) + }, + _ => { + skip_count += 1; + results[arg.idx].skip = true; + None + }, + } + }) + .collect(), + args, + results, + skip_count, + }; + v.visit_expr(&body.value); + v.results } -fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> { +fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> { if let TyKind::Rptr(ref lt, ref m) = ty.kind { Some((lt, m.mutbl, ty.span)) } else { diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index b24483723700c..811a7bb9c153a 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -1,6 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{snippet_opt, snippet_with_applicability}; -use clippy_utils::sugg::Sugg; use if_chain::if_chain; use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; use rustc_errors::Applicability; @@ -104,59 +103,3 @@ impl EarlyLintPass for DerefAddrOf { } } } - -declare_clippy_lint! { - /// ### What it does - /// Checks for references in expressions that use - /// auto dereference. - /// - /// ### Why is this bad? - /// The reference is a no-op and is automatically - /// dereferenced by the compiler and makes the code less clear. - /// - /// ### Example - /// ```rust - /// struct Point(u32, u32); - /// let point = Point(30, 20); - /// let x = (&point).0; - /// ``` - /// Use instead: - /// ```rust - /// # struct Point(u32, u32); - /// # let point = Point(30, 20); - /// let x = point.0; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub REF_IN_DEREF, - complexity, - "Use of reference in auto dereference expression." -} - -declare_lint_pass!(RefInDeref => [REF_IN_DEREF]); - -impl EarlyLintPass for RefInDeref { - fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { - if_chain! { - if let ExprKind::Field(ref object, _) = e.kind; - if let ExprKind::Paren(ref parened) = object.kind; - if let ExprKind::AddrOf(_, _, ref inner) = parened.kind; - then { - let applicability = if inner.span.from_expansion() { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - let sugg = Sugg::ast(cx, inner, "_").maybe_par(); - span_lint_and_sugg( - cx, - REF_IN_DEREF, - object.span, - "creating a reference that is immediately dereferenced", - "try this", - sugg.to_string(), - applicability, - ); - } - } - } -} diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 6369aafe3f973..5257f5302cd90 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -3,7 +3,7 @@ use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::{SpanlessEq, SpanlessHash}; use core::hash::{Hash, Hasher}; use if_chain::if_chain; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -91,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) { let Generics { where_clause, .. } = &item.generics; - let mut self_bounds_set = FxHashSet::default(); + let mut self_bounds_map = FxHashMap::default(); for predicate in where_clause.predicates { if_chain! { @@ -108,27 +108,29 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { ) ) = cx.tcx.hir().get_if_local(*def_id); then { - if self_bounds_set.is_empty() { + if self_bounds_map.is_empty() { for bound in self_bounds.iter() { - let Some((self_res, _)) = get_trait_res_span_from_bound(bound) else { continue }; - self_bounds_set.insert(self_res); + let Some((self_res, self_segments, _)) = get_trait_info_from_bound(bound) else { continue }; + self_bounds_map.insert(self_res, self_segments); } } bound_predicate .bounds .iter() - .filter_map(get_trait_res_span_from_bound) - .for_each(|(trait_item_res, span)| { - if self_bounds_set.get(&trait_item_res).is_some() { - span_lint_and_help( - cx, - TRAIT_DUPLICATION_IN_BOUNDS, - span, - "this trait bound is already specified in trait declaration", - None, - "consider removing this trait bound", - ); + .filter_map(get_trait_info_from_bound) + .for_each(|(trait_item_res, trait_item_segments, span)| { + if let Some(self_segments) = self_bounds_map.get(&trait_item_res) { + if SpanlessEq::new(cx).eq_path_segments(self_segments, trait_item_segments) { + span_lint_and_help( + cx, + TRAIT_DUPLICATION_IN_BOUNDS, + span, + "this trait bound is already specified in trait declaration", + None, + "consider removing this trait bound", + ); + } } }); } @@ -137,14 +139,6 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { } } -fn get_trait_res_span_from_bound(bound: &GenericBound<'_>) -> Option<(Res, Span)> { - if let GenericBound::Trait(t, _) = bound { - Some((t.trait_ref.path.res, t.span)) - } else { - None - } -} - impl TraitBounds { fn check_type_repetition<'tcx>(self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) { struct SpanlessTy<'cx, 'tcx> { @@ -231,7 +225,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { let res = param .bounds .iter() - .filter_map(get_trait_res_span_from_bound) + .filter_map(get_trait_info_from_bound) .collect::>(); map.insert(*ident, res); } @@ -245,10 +239,10 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { if let Some(segment) = segments.first(); if let Some(trait_resolutions_direct) = map.get(&segment.ident); then { - for (res_where, _) in bound_predicate.bounds.iter().filter_map(get_trait_res_span_from_bound) { - if let Some((_, span_direct)) = trait_resolutions_direct + for (res_where, _, _) in bound_predicate.bounds.iter().filter_map(get_trait_info_from_bound) { + if let Some((_, _, span_direct)) = trait_resolutions_direct .iter() - .find(|(res_direct, _)| *res_direct == res_where) { + .find(|(res_direct, _, _)| *res_direct == res_where) { span_lint_and_help( cx, TRAIT_DUPLICATION_IN_BOUNDS, @@ -263,3 +257,11 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { } } } + +fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> { + if let GenericBound::Trait(t, _) = bound { + Some((t.trait_ref.path.res, t.trait_ref.path.segments, t.span)) + } else { + None + } +} diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index 141f260487264..eee1229e1ef03 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -98,10 +98,11 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve if trait_pred.self_ty() == inp; if let Some(return_ty_pred) = get_projection_pred(cx, generics, *trait_pred); then { - if ord_preds.iter().any(|ord| Some(ord.self_ty()) == - return_ty_pred.term.ty()) { + if ord_preds.iter().any(|ord| Some(ord.self_ty()) == return_ty_pred.term.ty()) { args_to_check.push((i, "Ord".to_string())); - } else if partial_ord_preds.iter().any(|pord| pord.self_ty() == return_ty_pred.term.ty().unwrap()) { + } else if partial_ord_preds.iter().any(|pord| { + pord.self_ty() == return_ty_pred.term.ty().unwrap() + }) { args_to_check.push((i, "PartialOrd".to_string())); } } diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 0bd151fed91cc..1d4fe9cfd3cf4 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -291,7 +291,7 @@ fn transform_with_focus_on_idx(alternatives: &mut Vec>, focus_idx: usize) fn extend_with_struct_pat( qself1: &Option, path1: &ast::Path, - fps1: &mut Vec, + fps1: &mut [ast::PatField], rest1: bool, start: usize, alternatives: &mut Vec>, @@ -332,7 +332,7 @@ fn extend_with_struct_pat( /// while also requiring `ps1[..n] ~ ps2[..n]` (pre) and `ps1[n + 1..] ~ ps2[n + 1..]` (post), /// where `~` denotes semantic equality. fn extend_with_matching_product( - targets: &mut Vec>, + targets: &mut [P], start: usize, alternatives: &mut Vec>, predicate: impl Fn(&PatKind, &[P], usize) -> bool, diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index d6deb50cc9073..c9d99617c1e28 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -156,7 +156,7 @@ define_Conf! { /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), - /// 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, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE. + /// 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, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS. /// /// The minimum rust version that the project supports (msrv: Option = None), diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index ec1b5a499d472..f170ff69154b6 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -23,6 +23,7 @@ use rustc_hir::{ UnOp, }; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; +use rustc_middle::hir::nested_filter; use rustc_middle::mir::interpret::ConstValue; use rustc_middle::ty; use rustc_semver::RustcVersion; diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 512c39389c1bd..3547f0b4e0ae0 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -19,7 +19,9 @@ use rustc_hir::{ self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath, }; use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId}; +use rustc_middle::hir::nested_filter; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::Ident; use rustc_span::{sym, Loc, Span, Symbol}; use serde::{ser::SerializeStruct, Serialize, Serializer}; use std::collections::BinaryHeap; @@ -578,9 +580,11 @@ fn get_lint_group_and_level_or_lint( lint_name: &str, item: &Item<'_>, ) -> Option<(String, &'static str)> { - let result = cx - .lint_store - .check_lint_name(lint_name, Some(sym::clippy), &[]); + let result = cx.lint_store.check_lint_name( + lint_name, + Some(sym::clippy), + &[Ident::with_dummy_span(sym::clippy)].into_iter().collect(), + ); if let CheckLintNameResult::Tool(Ok(lint_lst)) = result { if let Some(group) = get_lint_group(cx, lint_lst[0]) { if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) { diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 604c95d2bc81f..3f4043ad052a0 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -646,11 +646,11 @@ pub fn eq_generic_bound(l: &GenericBound, r: &GenericBound) -> bool { } fn eq_term(l: &Term, r: &Term) -> bool { - match (l, r) { - (Term::Ty(l), Term::Ty(r)) => eq_ty(l,r), - (Term::Const(l), Term::Const(r)) => eq_anon_const(l,r), - _ => false, - } + match (l, r) { + (Term::Ty(l), Term::Ty(r)) => eq_ty(l, r), + (Term::Const(l), Term::Const(r)) => eq_anon_const(l, r), + _ => false, + } } pub fn eq_assoc_constraint(l: &AssocConstraint, r: &AssocConstraint) -> bool { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 9233903e98a00..a2f1f4696513e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1827,7 +1827,8 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool } /// Gets the node where an expression is either used, or it's type is unified with another branch. -pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option> { +/// Returns both the node and the `HirId` of the closest child node. +pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<(Node<'tcx>, HirId)> { let mut child_id = expr.hir_id; let mut iter = tcx.hir().parent_iter(child_id); loop { @@ -1839,9 +1840,9 @@ pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_> ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id, ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id, ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None, - _ => break Some(Node::Expr(expr)), + _ => break Some((Node::Expr(expr), child_id)), }, - Some((_, node)) => break Some(node), + Some((_, node)) => break Some((node, child_id)), } } } @@ -1850,18 +1851,21 @@ pub fn get_expr_use_or_unification_node<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_> pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { !matches!( get_expr_use_or_unification_node(tcx, expr), - None | Some(Node::Stmt(Stmt { - kind: StmtKind::Expr(_) - | StmtKind::Semi(_) - | StmtKind::Local(Local { - pat: Pat { - kind: PatKind::Wild, + None | Some(( + Node::Stmt(Stmt { + kind: StmtKind::Expr(_) + | StmtKind::Semi(_) + | StmtKind::Local(Local { + pat: Pat { + kind: PatKind::Wild, + .. + }, .. - }, - .. - }), - .. - })) + }), + .. + }), + _ + )) ) } diff --git a/clippy_utils/src/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs index 68dd1b29845a5..908ff822712ff 100644 --- a/clippy_utils/src/numeric_literal.rs +++ b/clippy_utils/src/numeric_literal.rs @@ -51,7 +51,14 @@ impl<'a> NumericLiteral<'a> { } pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option> { - if lit_kind.is_numeric() && src.chars().next().map_or(false, |c| c.is_digit(10)) { + let unsigned_src = src.strip_prefix('-').map_or(src, |s| s); + if lit_kind.is_numeric() + && unsigned_src + .trim_start() + .chars() + .next() + .map_or(false, |c| c.is_digit(10)) + { let (unsuffixed, suffix) = split_suffix(src, lit_kind); let float = matches!(lit_kind, LitKind::Float(..)); Some(NumericLiteral::new(unsuffixed, suffix, float)) diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 48525f9a5725e..fa63ddff253cf 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -386,7 +386,7 @@ fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String { } /// Return `true` if `sugg` is enclosed in parenthesis. -fn has_enclosing_paren(sugg: impl AsRef) -> bool { +pub fn has_enclosing_paren(sugg: impl AsRef) -> bool { let mut chars = sugg.as_ref().chars(); if chars.next() == Some('(') { let mut depth = 1; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index f109b7845b4bd..d057da73302a2 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -5,19 +5,22 @@ use rustc_ast::ast::Mutability; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; +use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{TyKind, Unsafety}; +use rustc_hir::{Expr, TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; -use rustc_middle::ty::{self, AdtDef, IntTy, Predicate, Ty, TyCtxt, TypeFoldable, UintTy}; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; +use rustc_middle::ty::{ + self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, +}; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::normalize::AtExt; use std::iter; -use crate::{match_def_path, must_use_attr}; +use crate::{expr_path_res, match_def_path, must_use_attr}; // Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { @@ -410,3 +413,105 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { + Sig(Binder<'tcx, FnSig<'tcx>>), + Closure(Binder<'tcx, FnSig<'tcx>>), + Trait(Binder<'tcx, Ty<'tcx>>, Option>>), +} +impl<'tcx> ExprFnSig<'tcx> { + /// Gets the argument type at the given offset. + pub fn input(self, i: usize) -> Binder<'tcx, Ty<'tcx>> { + match self { + Self::Sig(sig) => sig.input(i), + Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_element_ty(i).unwrap()), + Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_element_ty(i).unwrap()), + } + } + + /// Gets the result type, if one could be found. Note that the result type of a trait may not be + /// specified. + pub fn output(self) -> Option>> { + match self { + Self::Sig(sig) | Self::Closure(sig) => Some(sig.output()), + Self::Trait(_, output) => output, + } + } +} + +/// If the expression is function like, get the signature for it. +pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { + if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = expr_path_res(cx, expr) { + Some(ExprFnSig::Sig(cx.tcx.fn_sig(id))) + } else { + let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs(); + match *ty.kind() { + ty::Closure(_, subs) => Some(ExprFnSig::Closure(subs.as_closure().sig())), + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).subst(cx.tcx, subs))), + ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), + ty::Dynamic(bounds, _) => { + let lang_items = cx.tcx.lang_items(); + match bounds.principal() { + Some(bound) + if Some(bound.def_id()) == lang_items.fn_trait() + || Some(bound.def_id()) == lang_items.fn_once_trait() + || Some(bound.def_id()) == lang_items.fn_mut_trait() => + { + let output = bounds + .projection_bounds() + .find(|p| lang_items.fn_once_output().map_or(false, |id| id == p.item_def_id())) + .map(|p| p.map_bound(|p| p.term.ty().expect("return type was a const"))); + Some(ExprFnSig::Trait(bound.map_bound(|b| b.substs.type_at(0)), output)) + }, + _ => None, + } + }, + ty::Param(_) | ty::Projection(..) => { + let mut inputs = None; + let mut output = None; + let lang_items = cx.tcx.lang_items(); + + for (pred, _) in all_predicates_of(cx.tcx, cx.typeck_results().hir_owner.to_def_id()) { + let mut is_input = false; + if let Some(ty) = pred + .kind() + .map_bound(|pred| match pred { + PredicateKind::Trait(p) + if (lang_items.fn_trait() == Some(p.def_id()) + || lang_items.fn_mut_trait() == Some(p.def_id()) + || lang_items.fn_once_trait() == Some(p.def_id())) + && p.self_ty() == ty => + { + is_input = true; + Some(p.trait_ref.substs.type_at(1)) + }, + PredicateKind::Projection(p) + if Some(p.projection_ty.item_def_id) == lang_items.fn_once_output() + && p.projection_ty.self_ty() == ty => + { + is_input = false; + p.term.ty() + }, + _ => None, + }) + .transpose() + { + if is_input && inputs.is_none() { + inputs = Some(ty); + } else if !is_input && output.is_none() { + output = Some(ty); + } else { + // Multiple different fn trait impls. Is this even allowed? + return None; + } + } + } + + inputs.map(|ty| ExprFnSig::Trait(ty, output)) + }, + _ => None, + } + } +} diff --git a/rust-toolchain b/rust-toolchain index e6a58e9207250..e23dc73ab08c1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-01-13" +channel = "nightly-2022-01-27" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/src/driver.rs b/src/driver.rs index a8aa3a76abc6c..8f8f1140a3da6 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -331,11 +331,10 @@ pub fn main() { // - IF Clippy is run on the main crate, not on deps (`!cap_lints_allow`) THEN // - IF `--no-deps` is not set (`!no_deps`) OR // - IF `--no-deps` is set and Clippy is run on the specified primary package - let clippy_tests_set = env::var("__CLIPPY_INTERNAL_TESTS").map_or(false, |val| val == "true"); let cap_lints_allow = arg_value(&orig_args, "--cap-lints", |val| val == "allow").is_some(); let in_primary_package = env::var("CARGO_PRIMARY_PACKAGE").is_ok(); - let clippy_enabled = clippy_tests_set || (!cap_lints_allow && (!no_deps || in_primary_package)); + let clippy_enabled = !cap_lints_allow && (!no_deps || in_primary_package); if clippy_enabled { args.extend(clippy_args); } diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 531890c863f5e..6505028db9fad 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -328,15 +328,9 @@ fn run_ui_cargo(config: &mut compiletest::Config) { } } -fn prepare_env() { - set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); - set_var("__CLIPPY_INTERNAL_TESTS", "true"); - //set_var("RUST_BACKTRACE", "0"); -} - #[test] fn compile_test() { - prepare_env(); + set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); let mut config = default_config(); run_ui(&mut config); run_ui_test(&mut config); diff --git a/tests/ui-toml/min_rust_version/min_rust_version.rs b/tests/ui-toml/min_rust_version/min_rust_version.rs index 8e104926524e1..1e3ec123a3c8b 100644 --- a/tests/ui-toml/min_rust_version/min_rust_version.rs +++ b/tests/ui-toml/min_rust_version/min_rust_version.rs @@ -1,6 +1,7 @@ -#![allow(clippy::redundant_clone)] -#![warn(clippy::manual_non_exhaustive)] +#![allow(clippy::redundant_clone, clippy::unnecessary_operation)] +#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)] +use std::mem::{size_of, size_of_val}; use std::ops::Deref; mod enums { @@ -68,6 +69,24 @@ fn check_index_refutable_slice() { } } +fn map_clone_suggest_copied() { + // This should still trigger the lint but suggest `cloned()` instead of `copied()` + let _: Option = Some(&16).map(|b| *b); +} + +fn borrow_as_ptr() { + let val = 1; + let _p = &val as *const i32; + + let mut val_mut = 1; + let _p_mut = &mut val_mut as *mut i32; +} + +fn manual_bits() { + size_of::() * 8; + size_of_val(&0u32) * 8; +} + fn main() { option_as_ref_deref(); match_like_matches(); @@ -75,4 +94,5 @@ fn main() { match_same_arms2(); manual_strip_msrv(); check_index_refutable_slice(); + borrow_as_ptr(); } diff --git a/tests/ui-toml/min_rust_version/min_rust_version.stderr b/tests/ui-toml/min_rust_version/min_rust_version.stderr new file mode 100644 index 0000000000000..a1e7361c0cb32 --- /dev/null +++ b/tests/ui-toml/min_rust_version/min_rust_version.stderr @@ -0,0 +1,10 @@ +error: you are using an explicit closure for copying elements + --> $DIR/min_rust_version.rs:74:26 + | +LL | let _: Option = Some(&16).map(|b| *b); + | ^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `Some(&16).cloned()` + | + = note: `-D clippy::map-clone` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/borrow_interior_mutable_const/others.rs b/tests/ui/borrow_interior_mutable_const/others.rs index 4327f12c37c8e..eefeb1decb69e 100644 --- a/tests/ui/borrow_interior_mutable_const/others.rs +++ b/tests/ui/borrow_interior_mutable_const/others.rs @@ -1,9 +1,5 @@ #![warn(clippy::borrow_interior_mutable_const)] -#![allow( - clippy::declare_interior_mutable_const, - clippy::ref_in_deref, - clippy::needless_borrow -)] +#![allow(clippy::declare_interior_mutable_const, clippy::needless_borrow)] #![allow(const_item_mutation)] use std::borrow::Cow; diff --git a/tests/ui/borrow_interior_mutable_const/others.stderr b/tests/ui/borrow_interior_mutable_const/others.stderr index f146b97cf6116..9a908cf30e945 100644 --- a/tests/ui/borrow_interior_mutable_const/others.stderr +++ b/tests/ui/borrow_interior_mutable_const/others.stderr @@ -1,5 +1,5 @@ error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:58:5 + --> $DIR/others.rs:54:5 | LL | ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability | ^^^^^^ @@ -8,7 +8,7 @@ LL | ATOMIC.store(1, Ordering::SeqCst); //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:59:16 + --> $DIR/others.rs:55:16 | LL | assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutability | ^^^^^^ @@ -16,7 +16,7 @@ LL | assert_eq!(ATOMIC.load(Ordering::SeqCst), 5); //~ ERROR interior mutabi = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:62:22 + --> $DIR/others.rs:58:22 | LL | let _once_ref = &ONCE_INIT; //~ ERROR interior mutability | ^^^^^^^^^ @@ -24,7 +24,7 @@ LL | let _once_ref = &ONCE_INIT; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:63:25 + --> $DIR/others.rs:59:25 | LL | let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability | ^^^^^^^^^ @@ -32,7 +32,7 @@ LL | let _once_ref_2 = &&ONCE_INIT; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:64:27 + --> $DIR/others.rs:60:27 | LL | let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability | ^^^^^^^^^ @@ -40,7 +40,7 @@ LL | let _once_ref_4 = &&&&ONCE_INIT; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:65:26 + --> $DIR/others.rs:61:26 | LL | let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability | ^^^^^^^^^ @@ -48,7 +48,7 @@ LL | let _once_mut = &mut ONCE_INIT; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:76:14 + --> $DIR/others.rs:72:14 | LL | let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = &ATOMIC_TUPLE; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:77:14 + --> $DIR/others.rs:73:14 | LL | let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | let _ = &ATOMIC_TUPLE.0; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:78:19 + --> $DIR/others.rs:74:19 | LL | let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -72,7 +72,7 @@ LL | let _ = &(&&&&ATOMIC_TUPLE).0; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:79:14 + --> $DIR/others.rs:75:14 | LL | let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -80,7 +80,7 @@ LL | let _ = &ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:80:13 + --> $DIR/others.rs:76:13 | LL | let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -88,7 +88,7 @@ LL | let _ = ATOMIC_TUPLE.0[0].load(Ordering::SeqCst); //~ ERROR interior mu = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:86:13 + --> $DIR/others.rs:82:13 | LL | let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability | ^^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL | let _ = ATOMIC_TUPLE.0[0]; //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:91:5 + --> $DIR/others.rs:87:5 | LL | CELL.set(2); //~ ERROR interior mutability | ^^^^ @@ -104,7 +104,7 @@ LL | CELL.set(2); //~ ERROR interior mutability = help: assign this const to a local or static variable, and use the variable here error: a `const` item with interior mutability should not be borrowed - --> $DIR/others.rs:92:16 + --> $DIR/others.rs:88:16 | LL | assert_eq!(CELL.get(), 6); //~ ERROR interior mutability | ^^^^ diff --git a/tests/ui/bytecount.rs b/tests/ui/bytecount.rs index c724ee21be310..d3ad26921bffa 100644 --- a/tests/ui/bytecount.rs +++ b/tests/ui/bytecount.rs @@ -1,3 +1,5 @@ +#![allow(clippy::needless_borrow)] + #[deny(clippy::naive_bytecount)] fn main() { let x = vec![0_u8; 16]; diff --git a/tests/ui/bytecount.stderr b/tests/ui/bytecount.stderr index 1dc37fc8b259f..68d838c1f828a 100644 --- a/tests/ui/bytecount.stderr +++ b/tests/ui/bytecount.stderr @@ -1,23 +1,23 @@ error: you appear to be counting bytes the naive way - --> $DIR/bytecount.rs:5:13 + --> $DIR/bytecount.rs:7:13 | LL | let _ = x.iter().filter(|&&a| a == 0).count(); // naive byte count | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, 0)` | note: the lint level is defined here - --> $DIR/bytecount.rs:1:8 + --> $DIR/bytecount.rs:3:8 | LL | #[deny(clippy::naive_bytecount)] | ^^^^^^^^^^^^^^^^^^^^^^^ error: you appear to be counting bytes the naive way - --> $DIR/bytecount.rs:7:13 + --> $DIR/bytecount.rs:9:13 | LL | let _ = (&x[..]).iter().filter(|&a| *a == 0).count(); // naive byte count | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count((&x[..]), 0)` error: you appear to be counting bytes the naive way - --> $DIR/bytecount.rs:19:13 + --> $DIR/bytecount.rs:21:13 | LL | let _ = x.iter().filter(|a| b + 1 == **a).count(); // naive byte count | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the bytecount crate: `bytecount::count(x, b + 1)` diff --git a/tests/ui/clone_on_copy.fixed b/tests/ui/clone_on_copy.fixed index 8d43f64768d41..dc062762604e0 100644 --- a/tests/ui/clone_on_copy.fixed +++ b/tests/ui/clone_on_copy.fixed @@ -7,7 +7,8 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::vec_init_then_push, - clippy::toplevel_ref_arg + clippy::toplevel_ref_arg, + clippy::needless_borrow )] use std::cell::RefCell; diff --git a/tests/ui/clone_on_copy.rs b/tests/ui/clone_on_copy.rs index f15501f71844f..8c39d0d55dd8b 100644 --- a/tests/ui/clone_on_copy.rs +++ b/tests/ui/clone_on_copy.rs @@ -7,7 +7,8 @@ clippy::no_effect, clippy::unnecessary_operation, clippy::vec_init_then_push, - clippy::toplevel_ref_arg + clippy::toplevel_ref_arg, + clippy::needless_borrow )] use std::cell::RefCell; diff --git a/tests/ui/clone_on_copy.stderr b/tests/ui/clone_on_copy.stderr index e7d28b4320bc8..861543d0aa904 100644 --- a/tests/ui/clone_on_copy.stderr +++ b/tests/ui/clone_on_copy.stderr @@ -1,5 +1,5 @@ error: using `clone` on type `i32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:24:5 + --> $DIR/clone_on_copy.rs:25:5 | LL | 42.clone(); | ^^^^^^^^^^ help: try removing the `clone` call: `42` @@ -7,43 +7,43 @@ LL | 42.clone(); = note: `-D clippy::clone-on-copy` implied by `-D warnings` error: using `clone` on type `i32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:28:5 + --> $DIR/clone_on_copy.rs:29:5 | LL | (&42).clone(); | ^^^^^^^^^^^^^ help: try dereferencing it: `*(&42)` error: using `clone` on type `i32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:31:5 + --> $DIR/clone_on_copy.rs:32:5 | LL | rc.borrow().clone(); | ^^^^^^^^^^^^^^^^^^^ help: try dereferencing it: `*rc.borrow()` error: using `clone` on type `u32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:34:5 + --> $DIR/clone_on_copy.rs:35:5 | LL | x.clone().rotate_left(1); | ^^^^^^^^^ help: try removing the `clone` call: `x` error: using `clone` on type `i32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:48:5 + --> $DIR/clone_on_copy.rs:49:5 | LL | m!(42).clone(); | ^^^^^^^^^^^^^^ help: try removing the `clone` call: `m!(42)` error: using `clone` on type `[u32; 2]` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:58:5 + --> $DIR/clone_on_copy.rs:59:5 | LL | x.clone()[0]; | ^^^^^^^^^ help: try dereferencing it: `(*x)` error: using `clone` on type `char` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:68:14 + --> $DIR/clone_on_copy.rs:69:14 | LL | is_ascii('z'.clone()); | ^^^^^^^^^^^ help: try removing the `clone` call: `'z'` error: using `clone` on type `i32` which implements the `Copy` trait - --> $DIR/clone_on_copy.rs:72:14 + --> $DIR/clone_on_copy.rs:73:14 | LL | vec.push(42.clone()); | ^^^^^^^^^^ help: try removing the `clone` call: `42` diff --git a/tests/ui/duration_subsec.fixed b/tests/ui/duration_subsec.fixed index ee5c7863effcb..d92b8998e8805 100644 --- a/tests/ui/duration_subsec.fixed +++ b/tests/ui/duration_subsec.fixed @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::needless_borrow)] #![warn(clippy::duration_subsec)] use std::time::Duration; diff --git a/tests/ui/duration_subsec.rs b/tests/ui/duration_subsec.rs index 3c9d2a2862110..08da804996d1b 100644 --- a/tests/ui/duration_subsec.rs +++ b/tests/ui/duration_subsec.rs @@ -1,5 +1,5 @@ // run-rustfix -#![allow(dead_code)] +#![allow(dead_code, clippy::needless_borrow)] #![warn(clippy::duration_subsec)] use std::time::Duration; diff --git a/tests/ui/enum_variants.rs b/tests/ui/enum_variants.rs index d3662a0a213d2..b2bf7c4e360a0 100644 --- a/tests/ui/enum_variants.rs +++ b/tests/ui/enum_variants.rs @@ -151,4 +151,11 @@ enum North { NoRight, } +// #8324 +enum Phase { + PreLookup, + Lookup, + PostLookup, +} + fn main() {} diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index f938f71068844..618f80cdcf849 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -5,13 +5,10 @@ clippy::no_effect, clippy::redundant_closure_call, clippy::needless_pass_by_value, - clippy::option_map_unit_fn -)] -#![warn( - clippy::redundant_closure, - clippy::redundant_closure_for_method_calls, + clippy::option_map_unit_fn, clippy::needless_borrow )] +#![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)] use std::path::{Path, PathBuf}; @@ -34,7 +31,7 @@ fn main() { Some(1).map(closure_mac!()); // don't lint closure in macro expansion let _: Option> = true.then(std::vec::Vec::new); // special case vec! let d = Some(1u8).map(|a| foo(foo2(a))); //is adjusted? - all(&[1, 2, 3], &2, below); //is adjusted + all(&[1, 2, 3], &&2, below); //is adjusted unsafe { Some(1u8).map(|a| unsafe_fn(a)); // unsafe fn } diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index 075bbc74922f9..a759e6eb514b4 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -5,13 +5,10 @@ clippy::no_effect, clippy::redundant_closure_call, clippy::needless_pass_by_value, - clippy::option_map_unit_fn -)] -#![warn( - clippy::redundant_closure, - clippy::redundant_closure_for_method_calls, + clippy::option_map_unit_fn, clippy::needless_borrow )] +#![warn(clippy::redundant_closure, clippy::redundant_closure_for_method_calls)] use std::path::{Path, PathBuf}; diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index 8092f04c3fc3d..cda84982c9b75 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -1,5 +1,5 @@ error: redundant closure - --> $DIR/eta.rs:31:27 + --> $DIR/eta.rs:28:27 | LL | let a = Some(1u8).map(|a| foo(a)); | ^^^^^^^^^^ help: replace the closure with the function itself: `foo` @@ -7,45 +7,37 @@ LL | let a = Some(1u8).map(|a| foo(a)); = note: `-D clippy::redundant-closure` implied by `-D warnings` error: redundant closure - --> $DIR/eta.rs:35:40 + --> $DIR/eta.rs:32:40 | LL | let _: Option> = true.then(|| vec![]); // special case vec! | ^^^^^^^^^ help: replace the closure with `Vec::new`: `std::vec::Vec::new` error: redundant closure - --> $DIR/eta.rs:36:35 + --> $DIR/eta.rs:33:35 | LL | let d = Some(1u8).map(|a| foo((|b| foo2(b))(a))); //is adjusted? | ^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo2` -error: this expression borrows a reference (`&u8`) that is immediately dereferenced by the compiler - --> $DIR/eta.rs:37:21 - | -LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted - | ^^^ help: change this to: `&2` - | - = note: `-D clippy::needless-borrow` implied by `-D warnings` - error: redundant closure - --> $DIR/eta.rs:37:26 + --> $DIR/eta.rs:34:26 | LL | all(&[1, 2, 3], &&2, |x, y| below(x, y)); //is adjusted | ^^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `below` error: redundant closure - --> $DIR/eta.rs:43:27 + --> $DIR/eta.rs:40:27 | LL | let e = Some(1u8).map(|a| divergent(a)); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `divergent` error: redundant closure - --> $DIR/eta.rs:44:27 + --> $DIR/eta.rs:41:27 | LL | let e = Some(1u8).map(|a| generic(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `generic` error: redundant closure - --> $DIR/eta.rs:90:51 + --> $DIR/eta.rs:87:51 | LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); | ^^^^^^^^^^^ help: replace the closure with the method itself: `TestStruct::foo` @@ -53,82 +45,82 @@ LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.foo()); = note: `-D clippy::redundant-closure-for-method-calls` implied by `-D warnings` error: redundant closure - --> $DIR/eta.rs:91:51 + --> $DIR/eta.rs:88:51 | LL | let e = Some(TestStruct { some_ref: &i }).map(|a| a.trait_foo()); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `TestTrait::trait_foo` error: redundant closure - --> $DIR/eta.rs:93:42 + --> $DIR/eta.rs:90:42 | LL | let e = Some(&mut vec![1, 2, 3]).map(|v| v.clear()); | ^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::vec::Vec::clear` error: redundant closure - --> $DIR/eta.rs:97:29 + --> $DIR/eta.rs:94:29 | LL | let e = Some("str").map(|s| s.to_string()); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::string::ToString::to_string` error: redundant closure - --> $DIR/eta.rs:98:27 + --> $DIR/eta.rs:95:27 | LL | let e = Some('a').map(|s| s.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_uppercase` error: redundant closure - --> $DIR/eta.rs:100:65 + --> $DIR/eta.rs:97:65 | LL | let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_ascii_uppercase()).collect(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase` error: redundant closure - --> $DIR/eta.rs:163:22 + --> $DIR/eta.rs:160:22 | LL | requires_fn_once(|| x()); | ^^^^^^ help: replace the closure with the function itself: `x` error: redundant closure - --> $DIR/eta.rs:170:27 + --> $DIR/eta.rs:167:27 | LL | let a = Some(1u8).map(|a| foo_ptr(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr` error: redundant closure - --> $DIR/eta.rs:175:27 + --> $DIR/eta.rs:172:27 | LL | let a = Some(1u8).map(|a| closure(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure` error: redundant closure - --> $DIR/eta.rs:207:28 + --> $DIR/eta.rs:204:28 | LL | x.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> $DIR/eta.rs:208:28 + --> $DIR/eta.rs:205:28 | LL | y.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> $DIR/eta.rs:209:28 + --> $DIR/eta.rs:206:28 | LL | z.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res` error: redundant closure - --> $DIR/eta.rs:216:21 + --> $DIR/eta.rs:213:21 | LL | Some(1).map(|n| closure(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure` error: redundant closure - --> $DIR/eta.rs:235:21 + --> $DIR/eta.rs:232:21 | LL | map_str_to_path(|s| s.as_ref()); | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `std::convert::AsRef::as_ref` -error: aborting due to 21 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/explicit_deref_methods.fixed b/tests/ui/explicit_deref_methods.fixed index 48e2aae75d0bf..3de2a51ffa5f3 100644 --- a/tests/ui/explicit_deref_methods.fixed +++ b/tests/ui/explicit_deref_methods.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_variables, clippy::clone_double_ref)] +#![allow(unused_variables, clippy::clone_double_ref, clippy::needless_borrow)] #![warn(clippy::explicit_deref_methods)] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/explicit_deref_methods.rs b/tests/ui/explicit_deref_methods.rs index d8c8c0c5ca329..a08d75964220a 100644 --- a/tests/ui/explicit_deref_methods.rs +++ b/tests/ui/explicit_deref_methods.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused_variables, clippy::clone_double_ref)] +#![allow(unused_variables, clippy::clone_double_ref, clippy::needless_borrow)] #![warn(clippy::explicit_deref_methods)] use std::ops::{Deref, DerefMut}; diff --git a/tests/ui/for_loop_fixable.fixed b/tests/ui/for_loop_fixable.fixed index f373e905d05ca..aa69781d15a6b 100644 --- a/tests/ui/for_loop_fixable.fixed +++ b/tests/ui/for_loop_fixable.fixed @@ -23,7 +23,12 @@ impl Unrelated { clippy::iter_next_loop, clippy::for_kv_map )] -#[allow(clippy::linkedlist, clippy::unnecessary_mut_passed, clippy::similar_names)] +#[allow( + clippy::linkedlist, + clippy::unnecessary_mut_passed, + clippy::similar_names, + clippy::needless_borrow +)] #[allow(unused_variables)] fn main() { let mut vec = vec![1, 2, 3, 4]; diff --git a/tests/ui/for_loop_fixable.rs b/tests/ui/for_loop_fixable.rs index 3814583bb6ef4..7c063d99511d8 100644 --- a/tests/ui/for_loop_fixable.rs +++ b/tests/ui/for_loop_fixable.rs @@ -23,7 +23,12 @@ impl Unrelated { clippy::iter_next_loop, clippy::for_kv_map )] -#[allow(clippy::linkedlist, clippy::unnecessary_mut_passed, clippy::similar_names)] +#[allow( + clippy::linkedlist, + clippy::unnecessary_mut_passed, + clippy::similar_names, + clippy::needless_borrow +)] #[allow(unused_variables)] fn main() { let mut vec = vec![1, 2, 3, 4]; diff --git a/tests/ui/for_loop_fixable.stderr b/tests/ui/for_loop_fixable.stderr index 009dbe1a0bfaf..ddfe66d675f91 100644 --- a/tests/ui/for_loop_fixable.stderr +++ b/tests/ui/for_loop_fixable.stderr @@ -1,5 +1,5 @@ error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:38:15 + --> $DIR/for_loop_fixable.rs:43:15 | LL | for _v in vec.iter() {} | ^^^^^^^^^^ help: to write this more concisely, try: `&vec` @@ -7,13 +7,13 @@ LL | for _v in vec.iter() {} = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:40:15 + --> $DIR/for_loop_fixable.rs:45:15 | LL | for _v in vec.iter_mut() {} | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&mut vec` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:43:15 + --> $DIR/for_loop_fixable.rs:48:15 | LL | for _v in out_vec.into_iter() {} | ^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `out_vec` @@ -21,73 +21,73 @@ LL | for _v in out_vec.into_iter() {} = note: `-D clippy::explicit-into-iter-loop` implied by `-D warnings` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:48:15 + --> $DIR/for_loop_fixable.rs:53:15 | LL | for _v in [1, 2, 3].iter() {} | ^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[1, 2, 3]` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:52:15 + --> $DIR/for_loop_fixable.rs:57:15 | LL | for _v in [0; 32].iter() {} | ^^^^^^^^^^^^^^ help: to write this more concisely, try: `&[0; 32]` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:57:15 + --> $DIR/for_loop_fixable.rs:62:15 | LL | for _v in ll.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&ll` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:60:15 + --> $DIR/for_loop_fixable.rs:65:15 | LL | for _v in vd.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&vd` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:63:15 + --> $DIR/for_loop_fixable.rs:68:15 | LL | for _v in bh.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bh` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:66:15 + --> $DIR/for_loop_fixable.rs:71:15 | LL | for _v in hm.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&hm` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:69:15 + --> $DIR/for_loop_fixable.rs:74:15 | LL | for _v in bt.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bt` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:72:15 + --> $DIR/for_loop_fixable.rs:77:15 | LL | for _v in hs.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&hs` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:75:15 + --> $DIR/for_loop_fixable.rs:80:15 | LL | for _v in bs.iter() {} | ^^^^^^^^^ help: to write this more concisely, try: `&bs` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:250:18 + --> $DIR/for_loop_fixable.rs:255:18 | LL | for i in iterator.into_iter() { | ^^^^^^^^^^^^^^^^^^^^ help: to write this more concisely, try: `iterator` error: it is more concise to loop over references to containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:270:18 + --> $DIR/for_loop_fixable.rs:275:18 | LL | for _ in t.into_iter() {} | ^^^^^^^^^^^^^ help: to write this more concisely, try: `&t` error: it is more concise to loop over containers instead of using explicit iteration methods - --> $DIR/for_loop_fixable.rs:272:18 + --> $DIR/for_loop_fixable.rs:277:18 | LL | for _ in r.into_iter() {} | ^^^^^^^^^^^^^ help: to write this more concisely, try: `r` diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed index 78d2bfd474e4a..d08f8f52495ad 100644 --- a/tests/ui/format.fixed +++ b/tests/ui/format.fixed @@ -1,6 +1,11 @@ // run-rustfix -#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)] +#![allow( + clippy::print_literal, + clippy::redundant_clone, + clippy::to_string_in_format_args, + clippy::needless_borrow +)] #![warn(clippy::useless_format)] struct Foo(pub String); diff --git a/tests/ui/format.rs b/tests/ui/format.rs index 009c1aa216fcd..4a10b580d2600 100644 --- a/tests/ui/format.rs +++ b/tests/ui/format.rs @@ -1,6 +1,11 @@ // run-rustfix -#![allow(clippy::print_literal, clippy::redundant_clone, clippy::to_string_in_format_args)] +#![allow( + clippy::print_literal, + clippy::redundant_clone, + clippy::to_string_in_format_args, + clippy::needless_borrow +)] #![warn(clippy::useless_format)] struct Foo(pub String); diff --git a/tests/ui/format.stderr b/tests/ui/format.stderr index 660be57585e37..f25c7fb1ff1cb 100644 --- a/tests/ui/format.stderr +++ b/tests/ui/format.stderr @@ -1,5 +1,5 @@ error: useless use of `format!` - --> $DIR/format.rs:13:5 + --> $DIR/format.rs:18:5 | LL | format!("foo"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` @@ -7,19 +7,19 @@ LL | format!("foo"); = note: `-D clippy::useless-format` implied by `-D warnings` error: useless use of `format!` - --> $DIR/format.rs:14:5 + --> $DIR/format.rs:19:5 | LL | format!("{{}}"); | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:15:5 + --> $DIR/format.rs:20:5 | LL | format!("{{}} abc {{}}"); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"{} abc {}".to_string()` error: useless use of `format!` - --> $DIR/format.rs:16:5 + --> $DIR/format.rs:21:5 | LL | / format!( LL | | r##"foo {{}} @@ -34,79 +34,79 @@ LL ~ " bar"##.to_string(); | error: useless use of `format!` - --> $DIR/format.rs:21:13 + --> $DIR/format.rs:26:13 | LL | let _ = format!(""); | ^^^^^^^^^^^ help: consider using `String::new()`: `String::new()` error: useless use of `format!` - --> $DIR/format.rs:23:5 + --> $DIR/format.rs:28:5 | LL | format!("{}", "foo"); | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:27:5 + --> $DIR/format.rs:32:5 | LL | format!("{:+}", "foo"); // Warn when the format makes no difference. | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:28:5 + --> $DIR/format.rs:33:5 | LL | format!("{:<}", "foo"); // Warn when the format makes no difference. | ^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `"foo".to_string()` error: useless use of `format!` - --> $DIR/format.rs:33:5 + --> $DIR/format.rs:38:5 | LL | format!("{}", arg); | ^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:37:5 + --> $DIR/format.rs:42:5 | LL | format!("{:+}", arg); // Warn when the format makes no difference. | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:38:5 + --> $DIR/format.rs:43:5 | LL | format!("{:<}", arg); // Warn when the format makes no difference. | ^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `arg.to_string()` error: useless use of `format!` - --> $DIR/format.rs:65:5 + --> $DIR/format.rs:70:5 | LL | format!("{}", 42.to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `42.to_string()` error: useless use of `format!` - --> $DIR/format.rs:67:5 + --> $DIR/format.rs:72:5 | LL | format!("{}", x.display().to_string()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.display().to_string()` error: useless use of `format!` - --> $DIR/format.rs:71:18 + --> $DIR/format.rs:76:18 | LL | let _ = Some(format!("{}", a + "bar")); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `a + "bar"` error: useless use of `format!` - --> $DIR/format.rs:75:22 + --> $DIR/format.rs:80:22 | LL | let _s: String = format!("{}", &*v.join("/n")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `(&*v.join("/n")).to_string()` error: useless use of `format!` - --> $DIR/format.rs:81:13 + --> $DIR/format.rs:86:13 | LL | let _ = format!("{x}"); | ^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` error: useless use of `format!` - --> $DIR/format.rs:83:13 + --> $DIR/format.rs:88:13 | LL | let _ = format!("{y}", y = x); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index 9e37fb9255984..b856f1375d303 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -45,6 +45,33 @@ fn main() { let b = &mut b; x(b); } + + // Issue #8191 + let mut x = 5; + let mut x = &mut x; + + mut_ref(x); + mut_ref(x); + let y: &mut i32 = x; + let y: &mut i32 = x; + + let y = match 0 { + // Don't lint. Removing the borrow would move 'x' + 0 => &mut x, + _ => &mut *x, + }; + + *x = 5; + + let s = String::new(); + let _ = s.len(); + let _ = s.capacity(); + let _ = s.capacity(); + + let x = (1, 2); + let _ = x.0; + let x = &x as *const (i32, i32); + let _ = unsafe { (*x).0 }; } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 093277784beb2..0bfe222a3dc17 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -45,6 +45,33 @@ fn main() { let b = &mut b; x(&b); } + + // Issue #8191 + let mut x = 5; + let mut x = &mut x; + + mut_ref(&mut x); + mut_ref(&mut &mut x); + let y: &mut i32 = &mut x; + let y: &mut i32 = &mut &mut x; + + let y = match 0 { + // Don't lint. Removing the borrow would move 'x' + 0 => &mut x, + _ => &mut *x, + }; + + *x = 5; + + let s = String::new(); + let _ = (&s).len(); + let _ = (&s).capacity(); + let _ = (&&s).capacity(); + + let x = (1, 2); + let _ = (&x).0; + let x = &x as *const (i32, i32); + let _ = unsafe { (&*x).0 }; } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index 03a5b3d260e6a..b90e8448db0a3 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -1,4 +1,4 @@ -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:9:15 | LL | let _ = x(&&a); // warn @@ -6,59 +6,113 @@ LL | let _ = x(&&a); // warn | = note: `-D clippy::needless-borrow` implied by `-D warnings` -error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:13:13 | LL | mut_ref(&mut &mut b); // warn | ^^^^^^^^^^^ help: change this to: `&mut b` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:25:13 | LL | &&a | ^^^ help: change this to: `&a` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:27:15 | LL | 46 => &&a, | ^^^ help: change this to: `&a` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:33:27 | LL | break &ref_a; | ^^^^^^ help: change this to: `ref_a` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:40:15 | LL | let _ = x(&&&a); | ^^^^ help: change this to: `&a` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:41:15 | LL | let _ = x(&mut &&a); | ^^^^^^^^ help: change this to: `&a` -error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:42:15 | LL | let _ = x(&&&mut b); | ^^^^^^^^ help: change this to: `&mut b` -error: this expression borrows a reference (`&i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:43:15 | LL | let _ = x(&&ref_a); | ^^^^^^^ help: change this to: `ref_a` -error: this expression borrows a reference (`&mut i32`) that is immediately dereferenced by the compiler +error: this expression creates a reference which is immediately dereferenced by the compiler --> $DIR/needless_borrow.rs:46:11 | LL | x(&b); | ^^ help: change this to: `b` -error: aborting due to 10 previous errors +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:53:13 + | +LL | mut_ref(&mut x); + | ^^^^^^ help: change this to: `x` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:54:13 + | +LL | mut_ref(&mut &mut x); + | ^^^^^^^^^^^ help: change this to: `x` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:55:23 + | +LL | let y: &mut i32 = &mut x; + | ^^^^^^ help: change this to: `x` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:56:23 + | +LL | let y: &mut i32 = &mut &mut x; + | ^^^^^^^^^^^ help: change this to: `x` + +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:67:13 + | +LL | let _ = (&s).len(); + | ^^^^ help: change this to: `s` + +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:68:13 + | +LL | let _ = (&s).capacity(); + | ^^^^ help: change this to: `s` + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> $DIR/needless_borrow.rs:69:13 + | +LL | let _ = (&&s).capacity(); + | ^^^^^ help: change this to: `s` + +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:72:13 + | +LL | let _ = (&x).0; + | ^^^^ help: change this to: `x` + +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:74:22 + | +LL | let _ = unsafe { (&*x).0 }; + | ^^^^^ help: change this to: `(*x)` + +error: aborting due to 19 previous errors diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index b07c4a2381031..f3eafe8e9279e 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -1,5 +1,11 @@ #![warn(clippy::needless_lifetimes)] -#![allow(dead_code, clippy::needless_pass_by_value, clippy::unnecessary_wraps, dyn_drop)] +#![allow( + dead_code, + clippy::boxed_local, + clippy::needless_pass_by_value, + clippy::unnecessary_wraps, + dyn_drop +)] fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} @@ -369,4 +375,47 @@ mod issue6159 { } } +mod issue7296 { + use std::rc::Rc; + use std::sync::Arc; + + struct Foo; + impl Foo { + fn implicit<'a>(&'a self) -> &'a () { + &() + } + fn implicit_mut<'a>(&'a mut self) -> &'a () { + &() + } + + fn explicit<'a>(self: &'a Arc) -> &'a () { + &() + } + fn explicit_mut<'a>(self: &'a mut Rc) -> &'a () { + &() + } + + fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a () { + &() + } + } + + trait Bar { + fn implicit<'a>(&'a self) -> &'a (); + fn implicit_provided<'a>(&'a self) -> &'a () { + &() + } + + fn explicit<'a>(self: &'a Arc) -> &'a (); + fn explicit_provided<'a>(self: &'a Arc) -> &'a () { + &() + } + + fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a (); + fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { + &() + } + } +} + fn main() {} diff --git a/tests/ui/needless_lifetimes.stderr b/tests/ui/needless_lifetimes.stderr index 4114e6f1832fc..ffa152427a977 100644 --- a/tests/ui/needless_lifetimes.stderr +++ b/tests/ui/needless_lifetimes.stderr @@ -1,5 +1,5 @@ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:4:1 + --> $DIR/needless_lifetimes.rs:10:1 | LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,148 +7,190 @@ LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} = note: `-D clippy::needless-lifetimes` implied by `-D warnings` error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:6:1 + --> $DIR/needless_lifetimes.rs:12:1 | LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:16:1 + --> $DIR/needless_lifetimes.rs:22:1 | LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:50:1 + --> $DIR/needless_lifetimes.rs:56:1 | LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:55:1 + --> $DIR/needless_lifetimes.rs:61:1 | LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:67:1 + --> $DIR/needless_lifetimes.rs:73:1 | LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:91:1 + --> $DIR/needless_lifetimes.rs:97:1 | LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:121:5 + --> $DIR/needless_lifetimes.rs:127:5 | LL | fn self_and_out<'s>(&'s self) -> &'s u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:130:5 + --> $DIR/needless_lifetimes.rs:136:5 | LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:149:1 + --> $DIR/needless_lifetimes.rs:155:1 | LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:179:1 + --> $DIR/needless_lifetimes.rs:185:1 | LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:185:1 + --> $DIR/needless_lifetimes.rs:191:1 | LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:204:1 + --> $DIR/needless_lifetimes.rs:210:1 | LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:212:1 + --> $DIR/needless_lifetimes.rs:218:1 | LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:248:1 + --> $DIR/needless_lifetimes.rs:254:1 | LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:255:9 + --> $DIR/needless_lifetimes.rs:261:9 | LL | fn needless_lt<'a>(x: &'a u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:259:9 + --> $DIR/needless_lifetimes.rs:265:9 | LL | fn needless_lt<'a>(_x: &'a u8) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:272:9 + --> $DIR/needless_lifetimes.rs:278:9 | LL | fn baz<'a>(&'a self) -> impl Foo + 'a { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:301:5 + --> $DIR/needless_lifetimes.rs:307:5 | LL | fn impl_trait_elidable_nested_named_lifetimes<'a>(i: &'a i32, f: impl for<'b> Fn(&'b i32) -> &'b i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:304:5 + --> $DIR/needless_lifetimes.rs:310:5 | LL | fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:313:5 + --> $DIR/needless_lifetimes.rs:319:5 | LL | fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:325:5 + --> $DIR/needless_lifetimes.rs:331:5 | LL | fn where_clause_elidadable<'a, T>(i: &'a i32, f: T) -> &'a i32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:340:5 + --> $DIR/needless_lifetimes.rs:346:5 | LL | fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:353:5 + --> $DIR/needless_lifetimes.rs:359:5 | LL | fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) - --> $DIR/needless_lifetimes.rs:356:5 + --> $DIR/needless_lifetimes.rs:362:5 | LL | fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 25 previous errors +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:384:9 + | +LL | fn implicit<'a>(&'a self) -> &'a () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:387:9 + | +LL | fn implicit_mut<'a>(&'a mut self) -> &'a () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:398:9 + | +LL | fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:404:9 + | +LL | fn implicit<'a>(&'a self) -> &'a (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:405:9 + | +LL | fn implicit_provided<'a>(&'a self) -> &'a () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:414:9 + | +LL | fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: explicit lifetimes given in parameter types where they could be elided (or replaced with `'_` if needed by type declaration) + --> $DIR/needless_lifetimes.rs:415:9 + | +LL | fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 32 previous errors diff --git a/tests/ui/needless_question_mark.fixed b/tests/ui/needless_question_mark.fixed index f1fc81aa12b9e..ba9d15e59d0e4 100644 --- a/tests/ui/needless_question_mark.fixed +++ b/tests/ui/needless_question_mark.fixed @@ -125,3 +125,16 @@ pub fn test2() { let x = Some(3); let _x = some_and_qmark_in_macro!(x?); } + +async fn async_option_bad(to: TO) -> Option { + let _ = Some(3); + to.magic +} + +async fn async_deref_ref(s: Option<&String>) -> Option<&str> { + Some(s?) +} + +async fn async_result_bad(s: TR) -> Result { + s.magic +} diff --git a/tests/ui/needless_question_mark.rs b/tests/ui/needless_question_mark.rs index 44a0c5f61b5d5..3a6523e8fe872 100644 --- a/tests/ui/needless_question_mark.rs +++ b/tests/ui/needless_question_mark.rs @@ -125,3 +125,16 @@ pub fn test2() { let x = Some(3); let _x = some_and_qmark_in_macro!(x?); } + +async fn async_option_bad(to: TO) -> Option { + let _ = Some(3); + Some(to.magic?) +} + +async fn async_deref_ref(s: Option<&String>) -> Option<&str> { + Some(s?) +} + +async fn async_result_bad(s: TR) -> Result { + Ok(s.magic?) +} diff --git a/tests/ui/needless_question_mark.stderr b/tests/ui/needless_question_mark.stderr index 57c3d48c7611c..f8308e24e7712 100644 --- a/tests/ui/needless_question_mark.stderr +++ b/tests/ui/needless_question_mark.stderr @@ -77,5 +77,17 @@ LL | let _x = some_and_qmark_in_macro!(x?); | = note: this error originates in the macro `some_and_qmark_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 12 previous errors +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:131:5 + | +LL | Some(to.magic?) + | ^^^^^^^^^^^^^^^ help: try removing question mark and `Some()`: `to.magic` + +error: question mark operator is useless here + --> $DIR/needless_question_mark.rs:139:5 + | +LL | Ok(s.magic?) + | ^^^^^^^^^^^^ help: try removing question mark and `Ok()`: `s.magic` + +error: aborting due to 14 previous errors diff --git a/tests/ui/op_ref.rs b/tests/ui/op_ref.rs index ab9c4d34c88f6..d8bf66603d9f5 100644 --- a/tests/ui/op_ref.rs +++ b/tests/ui/op_ref.rs @@ -1,7 +1,7 @@ #![allow(unused_variables, clippy::blacklisted_name)] #![warn(clippy::op_ref)] use std::collections::HashSet; -use std::ops::BitAnd; +use std::ops::{BitAnd, Mul}; fn main() { let tracked_fds: HashSet = HashSet::new(); @@ -55,3 +55,40 @@ fn main() { let y = Y(2); let z = x & &y; } + +#[derive(Clone, Copy)] +struct A(i32); +#[derive(Clone, Copy)] +struct B(i32); + +impl Mul<&A> for B { + type Output = i32; + fn mul(self, rhs: &A) -> Self::Output { + self.0 * rhs.0 + } +} +impl Mul for B { + type Output = i32; + fn mul(self, rhs: A) -> Self::Output { + // Should not lint because removing the reference would lead to unconditional recursion + self * &rhs + } +} +impl Mul<&A> for A { + type Output = i32; + fn mul(self, rhs: &A) -> Self::Output { + self.0 * rhs.0 + } +} +impl Mul for A { + type Output = i32; + fn mul(self, rhs: A) -> Self::Output { + let one = B(1); + let two = 2; + let three = 3; + let _ = one * &self; + let _ = two + &three; + // Removing the reference would lead to unconditional recursion + self * &rhs + } +} diff --git a/tests/ui/op_ref.stderr b/tests/ui/op_ref.stderr index 992417084bda2..fe36c01166ff7 100644 --- a/tests/ui/op_ref.stderr +++ b/tests/ui/op_ref.stderr @@ -18,5 +18,21 @@ LL | let z = x & &y; | | | help: use the right value directly: `y` -error: aborting due to 2 previous errors +error: taken reference of right operand + --> $DIR/op_ref.rs:89:17 + | +LL | let _ = one * &self; + | ^^^^^^----- + | | + | help: use the right value directly: `self` + +error: taken reference of right operand + --> $DIR/op_ref.rs:90:17 + | +LL | let _ = two + &three; + | ^^^^^^------ + | | + | help: use the right value directly: `three` + +error: aborting due to 4 previous errors diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 67bfef06a05e8..ed72423780821 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -9,7 +9,6 @@ fn do_vec(x: &Vec) { } fn do_vec_mut(x: &mut Vec) { - // no error here //Nothing here } @@ -18,7 +17,6 @@ fn do_str(x: &String) { } fn do_str_mut(x: &mut String) { - // no error here //Nothing here either } @@ -27,7 +25,6 @@ fn do_path(x: &PathBuf) { } fn do_path_mut(x: &mut PathBuf) { - // no error here //Nothing here either } @@ -52,7 +49,7 @@ fn cloned(x: &Vec) -> Vec { let e = x.clone(); let f = e.clone(); // OK let g = x; - let h = g.clone(); // Alas, we cannot reliably detect this without following data. + let h = g.clone(); let i = (e).clone(); x.clone() } @@ -156,6 +153,30 @@ mod issue6509 { } } +fn mut_vec_slice_methods(v: &mut Vec) { + v.copy_within(1..5, 10); +} + +fn mut_vec_vec_methods(v: &mut Vec) { + v.clear(); +} + +fn vec_contains(v: &Vec) -> bool { + [vec![], vec![0]].as_slice().contains(v) +} + +fn fn_requires_vec(v: &Vec) -> bool { + vec_contains(v) +} + +fn impl_fn_requires_vec(v: &Vec, f: impl Fn(&Vec)) { + f(v); +} + +fn dyn_fn_requires_vec(v: &Vec, f: &dyn Fn(&Vec)) { + f(v); +} + // No error for types behind an alias (#7699) type A = Vec; fn aliased(a: &A) {} diff --git a/tests/ui/ptr_arg.stderr b/tests/ui/ptr_arg.stderr index 64594eb870c2c..a9613daadde10 100644 --- a/tests/ui/ptr_arg.stderr +++ b/tests/ui/ptr_arg.stderr @@ -1,4 +1,4 @@ -error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do --> $DIR/ptr_arg.rs:7:14 | LL | fn do_vec(x: &Vec) { @@ -6,170 +6,154 @@ LL | fn do_vec(x: &Vec) { | = note: `-D clippy::ptr-arg` implied by `-D warnings` +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:11:18 + | +LL | fn do_vec_mut(x: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [i64]` + error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:16:14 + --> $DIR/ptr_arg.rs:15:14 | LL | fn do_str(x: &String) { | ^^^^^^^ help: change this to: `&str` +error: writing `&mut String` instead of `&mut str` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:19:18 + | +LL | fn do_str_mut(x: &mut String) { + | ^^^^^^^^^^^ help: change this to: `&mut str` + error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:25:15 + --> $DIR/ptr_arg.rs:23:15 | LL | fn do_path(x: &PathBuf) { | ^^^^^^^^ help: change this to: `&Path` -error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices - --> $DIR/ptr_arg.rs:38:18 +error: writing `&mut PathBuf` instead of `&mut Path` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:27:19 + | +LL | fn do_path_mut(x: &mut PathBuf) { + | ^^^^^^^^^^^^ help: change this to: `&mut Path` + +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:35:18 | LL | fn do_vec(x: &Vec); | ^^^^^^^^^ help: change this to: `&[i64]` -error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices - --> $DIR/ptr_arg.rs:51:14 +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:48:14 | LL | fn cloned(x: &Vec) -> Vec { | ^^^^^^^^ | help: change this to | -LL | fn cloned(x: &[u8]) -> Vec { - | ~~~~~ -help: change `x.clone()` to - | -LL | let e = x.to_owned(); - | ~~~~~~~~~~~~ -help: change `x.clone()` to - | -LL | x.to_owned() - | +LL ~ fn cloned(x: &[u8]) -> Vec { +LL ~ let e = x.to_owned(); +LL | let f = e.clone(); // OK +LL | let g = x; +LL ~ let h = g.to_owned(); +LL | let i = (e).clone(); + ... error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:60:18 + --> $DIR/ptr_arg.rs:57:18 | LL | fn str_cloned(x: &String) -> String { | ^^^^^^^ | help: change this to | -LL | fn str_cloned(x: &str) -> String { - | ~~~~ -help: change `x.clone()` to - | -LL | let a = x.to_string(); - | ~~~~~~~~~~~~~ -help: change `x.clone()` to - | -LL | let b = x.to_string(); - | ~~~~~~~~~~~~~ -help: change `x.clone()` to - | -LL | x.to_string() +LL ~ fn str_cloned(x: &str) -> String { +LL ~ let a = x.to_owned(); +LL ~ let b = x.to_owned(); +LL | let c = b.clone(); +LL | let d = a.clone().clone().clone(); +LL ~ x.to_owned() | error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:68:19 + --> $DIR/ptr_arg.rs:65:19 | LL | fn path_cloned(x: &PathBuf) -> PathBuf { | ^^^^^^^^ | help: change this to | -LL | fn path_cloned(x: &Path) -> PathBuf { - | ~~~~~ -help: change `x.clone()` to - | -LL | let a = x.to_path_buf(); - | ~~~~~~~~~~~~~~~ -help: change `x.clone()` to - | -LL | let b = x.to_path_buf(); - | ~~~~~~~~~~~~~~~ -help: change `x.clone()` to - | -LL | x.to_path_buf() +LL ~ fn path_cloned(x: &Path) -> PathBuf { +LL ~ let a = x.to_path_buf(); +LL ~ let b = x.to_path_buf(); +LL | let c = b.clone(); +LL | let d = a.clone().clone().clone(); +LL ~ x.to_path_buf() | error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:76:44 + --> $DIR/ptr_arg.rs:73:44 | LL | fn false_positive_capacity(x: &Vec, y: &String) { | ^^^^^^^ | help: change this to | -LL | fn false_positive_capacity(x: &Vec, y: &str) { - | ~~~~ -help: change `y.clone()` to +LL ~ fn false_positive_capacity(x: &Vec, y: &str) { +LL | let a = x.capacity(); +LL ~ let b = y.to_owned(); +LL ~ let c = y; | -LL | let b = y.to_string(); - | ~~~~~~~~~~~~~ -help: change `y.as_str()` to - | -LL | let c = y; - | ~ error: using a reference to `Cow` is not recommended - --> $DIR/ptr_arg.rs:90:25 + --> $DIR/ptr_arg.rs:87:25 | LL | fn test_cow_with_ref(c: &Cow<[i32]>) {} | ^^^^^^^^^^^ help: change this to: `&[i32]` -error: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices - --> $DIR/ptr_arg.rs:143:21 +error: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:140:21 | LL | fn foo_vec(vec: &Vec) { | ^^^^^^^^ | help: change this to | -LL | fn foo_vec(vec: &[u8]) { - | ~~~~~ -help: change `vec.clone()` to - | -LL | let _ = vec.to_owned().pop(); - | ~~~~~~~~~~~~~~ -help: change `vec.clone()` to +LL ~ fn foo_vec(vec: &[u8]) { +LL ~ let _ = vec.to_owned().pop(); +LL ~ let _ = vec.to_owned().clone(); | -LL | let _ = vec.to_owned().clone(); - | ~~~~~~~~~~~~~~ error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:148:23 + --> $DIR/ptr_arg.rs:145:23 | LL | fn foo_path(path: &PathBuf) { | ^^^^^^^^ | help: change this to | -LL | fn foo_path(path: &Path) { - | ~~~~~ -help: change `path.clone()` to +LL ~ fn foo_path(path: &Path) { +LL ~ let _ = path.to_path_buf().pop(); +LL ~ let _ = path.to_path_buf().clone(); | -LL | let _ = path.to_path_buf().pop(); - | ~~~~~~~~~~~~~~~~~~ -help: change `path.clone()` to - | -LL | let _ = path.to_path_buf().clone(); - | ~~~~~~~~~~~~~~~~~~ error: writing `&PathBuf` instead of `&Path` involves a new object where a slice will do - --> $DIR/ptr_arg.rs:153:21 + --> $DIR/ptr_arg.rs:150:21 | LL | fn foo_str(str: &PathBuf) { | ^^^^^^^^ | help: change this to | -LL | fn foo_str(str: &Path) { - | ~~~~~ -help: change `str.clone()` to +LL ~ fn foo_str(str: &Path) { +LL ~ let _ = str.to_path_buf().pop(); +LL ~ let _ = str.to_path_buf().clone(); | -LL | let _ = str.to_path_buf().pop(); - | ~~~~~~~~~~~~~~~~~ -help: change `str.clone()` to + +error: writing `&mut Vec` instead of `&mut [_]` involves a new object where a slice will do + --> $DIR/ptr_arg.rs:156:29 | -LL | let _ = str.to_path_buf().clone(); - | ~~~~~~~~~~~~~~~~~ +LL | fn mut_vec_slice_methods(v: &mut Vec) { + | ^^^^^^^^^^^^^ help: change this to: `&mut [u32]` -error: aborting due to 12 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/redundant_pattern_matching_option.fixed b/tests/ui/redundant_pattern_matching_option.fixed index cc93859269c22..a89845c1dd32b 100644 --- a/tests/ui/redundant_pattern_matching_option.fixed +++ b/tests/ui/redundant_pattern_matching_option.fixed @@ -81,7 +81,7 @@ const fn issue6067() { None::<()>.is_none(); } -#[allow(clippy::deref_addrof, dead_code)] +#[allow(clippy::deref_addrof, dead_code, clippy::needless_borrow)] fn issue7921() { if (&None::<()>).is_none() {} if (&None::<()>).is_none() {} diff --git a/tests/ui/redundant_pattern_matching_option.rs b/tests/ui/redundant_pattern_matching_option.rs index 280dca40c011d..d6f44403487ef 100644 --- a/tests/ui/redundant_pattern_matching_option.rs +++ b/tests/ui/redundant_pattern_matching_option.rs @@ -96,7 +96,7 @@ const fn issue6067() { }; } -#[allow(clippy::deref_addrof, dead_code)] +#[allow(clippy::deref_addrof, dead_code, clippy::needless_borrow)] fn issue7921() { if let None = *(&None::<()>) {} if let None = *&None::<()> {} diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index b9425733a8b27..8bddec576ed14 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -54,6 +54,7 @@ #![warn(clippy::match_result_ok)] #![warn(clippy::disallowed_types)] #![warn(clippy::disallowed_methods)] +#![warn(clippy::needless_borrow)] // uplifted lints #![warn(invalid_value)] #![warn(array_into_iter)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index 341c003b9df30..d2010d71d2c11 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -54,6 +54,7 @@ #![warn(clippy::if_let_some_result)] #![warn(clippy::disallowed_type)] #![warn(clippy::disallowed_method)] +#![warn(clippy::ref_in_deref)] // uplifted lints #![warn(clippy::invalid_ref)] #![warn(clippy::into_iter_on_array)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index cdec2808f1d41..45cb8b786f5f3 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -138,59 +138,65 @@ error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_ LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` +error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` + --> $DIR/rename.rs:57:9 + | +LL | #![warn(clippy::ref_in_deref)] + | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` + error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> $DIR/rename.rs:58:9 + --> $DIR/rename.rs:59:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> $DIR/rename.rs:59:9 + --> $DIR/rename.rs:60:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> $DIR/rename.rs:60:9 + --> $DIR/rename.rs:61:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> $DIR/rename.rs:61:9 + --> $DIR/rename.rs:62:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr` - --> $DIR/rename.rs:62:9 + --> $DIR/rename.rs:63:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> $DIR/rename.rs:63:9 + --> $DIR/rename.rs:64:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> $DIR/rename.rs:64:9 + --> $DIR/rename.rs:65:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> $DIR/rename.rs:65:9 + --> $DIR/rename.rs:66:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> $DIR/rename.rs:66:9 + --> $DIR/rename.rs:67:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` -error: aborting due to 32 previous errors +error: aborting due to 33 previous errors diff --git a/tests/ui/search_is_some_fixable_none.rs b/tests/ui/search_is_some_fixable_none.rs index 778f4f6fa257e..767518ab0c0f5 100644 --- a/tests/ui/search_is_some_fixable_none.rs +++ b/tests/ui/search_is_some_fixable_none.rs @@ -189,7 +189,7 @@ mod issue7392 { let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_none(); } - fn test_string_1(s: &String) -> bool { + fn test_string_1(s: &str) -> bool { s.is_empty() } diff --git a/tests/ui/search_is_some_fixable_none.stderr b/tests/ui/search_is_some_fixable_none.stderr index 7c5e5eb589c1a..933ce5cf42d2e 100644 --- a/tests/ui/search_is_some_fixable_none.stderr +++ b/tests/ui/search_is_some_fixable_none.stderr @@ -251,14 +251,6 @@ error: called `is_none()` after searching an `Iterator` with `find` LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `![&(&1, 2), &(&3, 4), &(&5, 4)].iter().any(|(&x, y)| x == *y)` -error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/search_is_some_fixable_none.rs:192:25 - | -LL | fn test_string_1(s: &String) -> bool { - | ^^^^^^^ help: change this to: `&str` - | - = note: `-D clippy::ptr-arg` implied by `-D warnings` - error: called `is_none()` after searching an `Iterator` with `find` --> $DIR/search_is_some_fixable_none.rs:208:17 | @@ -289,5 +281,5 @@ error: called `is_none()` after searching an `Iterator` with `find` LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_none(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `!_.any()` instead: `!v.iter().any(|fp| test_u32_2(*fp.field))` -error: aborting due to 44 previous errors +error: aborting due to 43 previous errors diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index 241641fceae3d..77fd52e4ce769 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -188,7 +188,7 @@ mod issue7392 { let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_some(); } - fn test_string_1(s: &String) -> bool { + fn test_string_1(s: &str) -> bool { s.is_empty() } diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr index 9212c6e71ff54..8b424f18ef5b5 100644 --- a/tests/ui/search_is_some_fixable_some.stderr +++ b/tests/ui/search_is_some_fixable_some.stderr @@ -234,14 +234,6 @@ error: called `is_some()` after searching an `Iterator` with `find` LL | let _ = [&(&1, 2), &(&3, 4), &(&5, 4)].iter().find(|&(&x, y)| x == *y).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|(&x, y)| x == *y)` -error: writing `&String` instead of `&str` involves a new object where a slice will do - --> $DIR/search_is_some_fixable_some.rs:191:25 - | -LL | fn test_string_1(s: &String) -> bool { - | ^^^^^^^ help: change this to: `&str` - | - = note: `-D clippy::ptr-arg` implied by `-D warnings` - error: called `is_some()` after searching an `Iterator` with `find` --> $DIR/search_is_some_fixable_some.rs:207:26 | @@ -272,5 +264,5 @@ error: called `is_some()` after searching an `Iterator` with `find` LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|fp| test_u32_2(*fp.field))` -error: aborting due to 44 previous errors +error: aborting due to 43 previous errors diff --git a/tests/ui/slow_vector_initialization.rs b/tests/ui/slow_vector_initialization.rs index c5ae3ff769b11..7e3d357ae50dc 100644 --- a/tests/ui/slow_vector_initialization.rs +++ b/tests/ui/slow_vector_initialization.rs @@ -53,7 +53,7 @@ fn resize_vector() { vec1.resize(10, 0); } -fn do_stuff(vec: &mut Vec) {} +fn do_stuff(vec: &mut [u8]) {} fn extend_vector_with_manipulations_between() { let len = 300; diff --git a/tests/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index 2edb202892afc..21de19a260147 100644 --- a/tests/ui/trait_duplication_in_bounds.rs +++ b/tests/ui/trait_duplication_in_bounds.rs @@ -1,5 +1,6 @@ #![deny(clippy::trait_duplication_in_bounds)] +use std::collections::BTreeMap; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; fn bad_foo(arg0: T, arg1: Z) @@ -73,4 +74,25 @@ impl U for Life { fn f() {} } +// should not warn +trait Iter: Iterator { + fn into_group_btreemap(self) -> BTreeMap> + where + Self: Iterator + Sized, + K: Ord + Eq, + { + unimplemented!(); + } +} + +struct Foo {} + +trait FooIter: Iterator { + fn bar() + where + Self: Iterator, + { + } +} + fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index e0c7a7ec618ed..6f8c8e47dfbf1 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -1,5 +1,5 @@ error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:5:15 + --> $DIR/trait_duplication_in_bounds.rs:6:15 | LL | fn bad_foo(arg0: T, arg1: Z) | ^^^^^ @@ -12,7 +12,7 @@ LL | #![deny(clippy::trait_duplication_in_bounds)] = help: consider removing this trait bound error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:5:23 + --> $DIR/trait_duplication_in_bounds.rs:6:23 | LL | fn bad_foo(arg0: T, arg1: Z) | ^^^^^^^ @@ -20,7 +20,7 @@ LL | fn bad_foo(arg0: T, arg1: Z) = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:34:15 + --> $DIR/trait_duplication_in_bounds.rs:35:15 | LL | Self: Default; | ^^^^^^^ @@ -28,7 +28,7 @@ LL | Self: Default; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:48:15 + --> $DIR/trait_duplication_in_bounds.rs:49:15 | LL | Self: Default + Clone; | ^^^^^^^ @@ -36,7 +36,7 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:54:15 + --> $DIR/trait_duplication_in_bounds.rs:55:15 | LL | Self: Default + Clone; | ^^^^^^^ @@ -44,7 +44,7 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:54:25 + --> $DIR/trait_duplication_in_bounds.rs:55:25 | LL | Self: Default + Clone; | ^^^^^ @@ -52,12 +52,20 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:57:15 + --> $DIR/trait_duplication_in_bounds.rs:58:15 | LL | Self: Default; | ^^^^^^^ | = help: consider removing this trait bound -error: aborting due to 7 previous errors +error: this trait bound is already specified in trait declaration + --> $DIR/trait_duplication_in_bounds.rs:93:15 + | +LL | Self: Iterator, + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index e8f2fb4666592..b77c19f2ba596 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -1,6 +1,7 @@ #![warn(clippy::unnecessary_cast)] #![allow(clippy::no_effect)] +#[rustfmt::skip] fn main() { // Test cast_unnecessary 1i32 as i32; @@ -8,6 +9,12 @@ fn main() { false as bool; &1i32 as &i32; + -1_i32 as i32; + - 1_i32 as i32; + -1f32 as f32; + 1_i32 as i32; + 1_f32 as f32; + // macro version macro_rules! foo { ($a:ident, $b:ident) => { diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index 70aa448af68ee..a5a93c6110c6a 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -1,5 +1,5 @@ error: casting integer literal to `i32` is unnecessary - --> $DIR/unnecessary_cast.rs:6:5 + --> $DIR/unnecessary_cast.rs:7:5 | LL | 1i32 as i32; | ^^^^^^^^^^^ help: try: `1_i32` @@ -7,16 +7,46 @@ LL | 1i32 as i32; = note: `-D clippy::unnecessary-cast` implied by `-D warnings` error: casting float literal to `f32` is unnecessary - --> $DIR/unnecessary_cast.rs:7:5 + --> $DIR/unnecessary_cast.rs:8:5 | LL | 1f32 as f32; | ^^^^^^^^^^^ help: try: `1_f32` error: casting to the same type is unnecessary (`bool` -> `bool`) - --> $DIR/unnecessary_cast.rs:8:5 + --> $DIR/unnecessary_cast.rs:9:5 | LL | false as bool; | ^^^^^^^^^^^^^ help: try: `false` -error: aborting due to 3 previous errors +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast.rs:12:5 + | +LL | -1_i32 as i32; + | ^^^^^^^^^^^^^ help: try: `-1_i32` + +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast.rs:13:5 + | +LL | - 1_i32 as i32; + | ^^^^^^^^^^^^^^ help: try: `- 1_i32` + +error: casting float literal to `f32` is unnecessary + --> $DIR/unnecessary_cast.rs:14:5 + | +LL | -1f32 as f32; + | ^^^^^^^^^^^^ help: try: `-1_f32` + +error: casting integer literal to `i32` is unnecessary + --> $DIR/unnecessary_cast.rs:15:5 + | +LL | 1_i32 as i32; + | ^^^^^^^^^^^^ help: try: `1_i32` + +error: casting float literal to `f32` is unnecessary + --> $DIR/unnecessary_cast.rs:16:5 + | +LL | 1_f32 as f32; + | ^^^^^^^^^^^^ help: try: `1_f32` + +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_ref.fixed b/tests/ui/unnecessary_ref.fixed deleted file mode 100644 index d927bae976f79..0000000000000 --- a/tests/ui/unnecessary_ref.fixed +++ /dev/null @@ -1,23 +0,0 @@ -// run-rustfix - -#![feature(stmt_expr_attributes)] -#![allow(unused_variables, dead_code)] - -struct Outer { - inner: u32, -} - -#[deny(clippy::ref_in_deref)] -fn main() { - let outer = Outer { inner: 0 }; - let inner = outer.inner; -} - -struct Apple; -impl Apple { - fn hello(&self) {} -} -struct Package(pub *const Apple); -fn foobar(package: *const Package) { - unsafe { &*(*package).0 }.hello(); -} diff --git a/tests/ui/unnecessary_ref.rs b/tests/ui/unnecessary_ref.rs deleted file mode 100644 index 86bfb76ec2619..0000000000000 --- a/tests/ui/unnecessary_ref.rs +++ /dev/null @@ -1,23 +0,0 @@ -// run-rustfix - -#![feature(stmt_expr_attributes)] -#![allow(unused_variables, dead_code)] - -struct Outer { - inner: u32, -} - -#[deny(clippy::ref_in_deref)] -fn main() { - let outer = Outer { inner: 0 }; - let inner = (&outer).inner; -} - -struct Apple; -impl Apple { - fn hello(&self) {} -} -struct Package(pub *const Apple); -fn foobar(package: *const Package) { - unsafe { &*(&*package).0 }.hello(); -} diff --git a/tests/ui/unnecessary_ref.stderr b/tests/ui/unnecessary_ref.stderr deleted file mode 100644 index 436f4bcf73804..0000000000000 --- a/tests/ui/unnecessary_ref.stderr +++ /dev/null @@ -1,22 +0,0 @@ -error: creating a reference that is immediately dereferenced - --> $DIR/unnecessary_ref.rs:13:17 - | -LL | let inner = (&outer).inner; - | ^^^^^^^^ help: try this: `outer` - | -note: the lint level is defined here - --> $DIR/unnecessary_ref.rs:10:8 - | -LL | #[deny(clippy::ref_in_deref)] - | ^^^^^^^^^^^^^^^^^^^^ - -error: creating a reference that is immediately dereferenced - --> $DIR/unnecessary_ref.rs:22:16 - | -LL | unsafe { &*(&*package).0 }.hello(); - | ^^^^^^^^^^^ help: try this: `(*package)` - | - = note: `-D clippy::ref-in-deref` implied by `-D warnings` - -error: aborting due to 2 previous errors - diff --git a/tests/ui/useless_asref.fixed b/tests/ui/useless_asref.fixed index e356f13d087b1..e431661d180de 100644 --- a/tests/ui/useless_asref.fixed +++ b/tests/ui/useless_asref.fixed @@ -67,7 +67,7 @@ fn not_ok() { foo_rslice(mrrrrrslice); foo_rslice(mrrrrrslice); } - #[allow(unused_parens, clippy::double_parens)] + #[allow(unused_parens, clippy::double_parens, clippy::needless_borrow)] foo_rrrrmr((&&&&MoreRef)); generic_not_ok(mrslice); diff --git a/tests/ui/useless_asref.rs b/tests/ui/useless_asref.rs index 2a80291f5d837..6ae931d7aa481 100644 --- a/tests/ui/useless_asref.rs +++ b/tests/ui/useless_asref.rs @@ -67,7 +67,7 @@ fn not_ok() { foo_rslice(mrrrrrslice.as_ref()); foo_rslice(mrrrrrslice); } - #[allow(unused_parens, clippy::double_parens)] + #[allow(unused_parens, clippy::double_parens, clippy::needless_borrow)] foo_rrrrmr((&&&&MoreRef).as_ref()); generic_not_ok(mrslice); diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index 0a127858defdf..4466917441162 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -7,37 +7,37 @@ fn main() { let mut v = Vec::new(); // these should be fine - write!(&mut v, "Hello"); - writeln!(&mut v, "Hello"); + write!(v, "Hello"); + writeln!(v, "Hello"); let world = "world"; - writeln!(&mut v, "Hello {}", world); - writeln!(&mut v, "Hello {world}", world = world); - writeln!(&mut v, "3 in hex is {:X}", 3); - writeln!(&mut v, "2 + 1 = {:.4}", 3); - writeln!(&mut v, "2 + 1 = {:5.4}", 3); - writeln!(&mut v, "Debug test {:?}", "hello, world"); - writeln!(&mut v, "{0:8} {1:>8}", "hello", "world"); - writeln!(&mut v, "{1:8} {0:>8}", "hello", "world"); - writeln!(&mut v, "{foo:8} {bar:>8}", foo = "hello", bar = "world"); - writeln!(&mut v, "{bar:8} {foo:>8}", foo = "hello", bar = "world"); - writeln!(&mut v, "{number:>width$}", number = 1, width = 6); - writeln!(&mut v, "{number:>0width$}", number = 1, width = 6); - writeln!(&mut v, "{} of {:b} people know binary, the other half doesn't", 1, 2); - writeln!(&mut v, "10 / 4 is {}", 2.5); - writeln!(&mut v, "2 + 1 = {}", 3); + writeln!(v, "Hello {}", world); + writeln!(v, "Hello {world}", world = world); + writeln!(v, "3 in hex is {:X}", 3); + writeln!(v, "2 + 1 = {:.4}", 3); + writeln!(v, "2 + 1 = {:5.4}", 3); + writeln!(v, "Debug test {:?}", "hello, world"); + writeln!(v, "{0:8} {1:>8}", "hello", "world"); + writeln!(v, "{1:8} {0:>8}", "hello", "world"); + writeln!(v, "{foo:8} {bar:>8}", foo = "hello", bar = "world"); + writeln!(v, "{bar:8} {foo:>8}", foo = "hello", bar = "world"); + writeln!(v, "{number:>width$}", number = 1, width = 6); + writeln!(v, "{number:>0width$}", number = 1, width = 6); + writeln!(v, "{} of {:b} people know binary, the other half doesn't", 1, 2); + writeln!(v, "10 / 4 is {}", 2.5); + writeln!(v, "2 + 1 = {}", 3); // these should throw warnings - write!(&mut v, "Hello {}", "world"); - writeln!(&mut v, "Hello {} {}", world, "world"); - writeln!(&mut v, "Hello {}", "world"); + write!(v, "Hello {}", "world"); + writeln!(v, "Hello {} {}", world, "world"); + writeln!(v, "Hello {}", "world"); // positional args don't change the fact // that we're using a literal -- this should // throw a warning - writeln!(&mut v, "{0} {1}", "hello", "world"); - writeln!(&mut v, "{1} {0}", "hello", "world"); + writeln!(v, "{0} {1}", "hello", "world"); + writeln!(v, "{1} {0}", "hello", "world"); // named args shouldn't change anything either - writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); - writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); + writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); + writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); } diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index e0297c0023156..593e9493ec590 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -1,134 +1,134 @@ error: literal with an empty format string - --> $DIR/write_literal.rs:30:32 + --> $DIR/write_literal.rs:30:27 | -LL | write!(&mut v, "Hello {}", "world"); - | ^^^^^^^ +LL | write!(v, "Hello {}", "world"); + | ^^^^^^^ | = note: `-D clippy::write-literal` implied by `-D warnings` help: try this | -LL - write!(&mut v, "Hello {}", "world"); -LL + write!(&mut v, "Hello world"); +LL - write!(v, "Hello {}", "world"); +LL + write!(v, "Hello world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:31:44 + --> $DIR/write_literal.rs:31:39 | -LL | writeln!(&mut v, "Hello {} {}", world, "world"); - | ^^^^^^^ +LL | writeln!(v, "Hello {} {}", world, "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "Hello {} {}", world, "world"); -LL + writeln!(&mut v, "Hello {} world", world); +LL - writeln!(v, "Hello {} {}", world, "world"); +LL + writeln!(v, "Hello {} world", world); | error: literal with an empty format string - --> $DIR/write_literal.rs:32:34 + --> $DIR/write_literal.rs:32:29 | -LL | writeln!(&mut v, "Hello {}", "world"); - | ^^^^^^^ +LL | writeln!(v, "Hello {}", "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "Hello {}", "world"); -LL + writeln!(&mut v, "Hello world"); +LL - writeln!(v, "Hello {}", "world"); +LL + writeln!(v, "Hello world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:37:33 + --> $DIR/write_literal.rs:37:28 | -LL | writeln!(&mut v, "{0} {1}", "hello", "world"); - | ^^^^^^^ +LL | writeln!(v, "{0} {1}", "hello", "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{0} {1}", "hello", "world"); -LL + writeln!(&mut v, "hello {1}", "world"); +LL - writeln!(v, "{0} {1}", "hello", "world"); +LL + writeln!(v, "hello {1}", "world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:37:42 + --> $DIR/write_literal.rs:37:37 | -LL | writeln!(&mut v, "{0} {1}", "hello", "world"); - | ^^^^^^^ +LL | writeln!(v, "{0} {1}", "hello", "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{0} {1}", "hello", "world"); -LL + writeln!(&mut v, "{0} world", "hello"); +LL - writeln!(v, "{0} {1}", "hello", "world"); +LL + writeln!(v, "{0} world", "hello"); | error: literal with an empty format string - --> $DIR/write_literal.rs:38:33 + --> $DIR/write_literal.rs:38:28 | -LL | writeln!(&mut v, "{1} {0}", "hello", "world"); - | ^^^^^^^ +LL | writeln!(v, "{1} {0}", "hello", "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{1} {0}", "hello", "world"); -LL + writeln!(&mut v, "{1} hello", "world"); +LL - writeln!(v, "{1} {0}", "hello", "world"); +LL + writeln!(v, "{1} hello", "world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:38:42 + --> $DIR/write_literal.rs:38:37 | -LL | writeln!(&mut v, "{1} {0}", "hello", "world"); - | ^^^^^^^ +LL | writeln!(v, "{1} {0}", "hello", "world"); + | ^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{1} {0}", "hello", "world"); -LL + writeln!(&mut v, "world {0}", "hello"); +LL - writeln!(v, "{1} {0}", "hello", "world"); +LL + writeln!(v, "world {0}", "hello"); | error: literal with an empty format string - --> $DIR/write_literal.rs:41:37 + --> $DIR/write_literal.rs:41:32 | -LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); - | ^^^^^^^^^^^^^ +LL | writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); -LL + writeln!(&mut v, "hello {bar}", bar = "world"); +LL - writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); +LL + writeln!(v, "hello {bar}", bar = "world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:41:52 + --> $DIR/write_literal.rs:41:47 | -LL | writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); - | ^^^^^^^^^^^^^ +LL | writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); + | ^^^^^^^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{foo} {bar}", foo = "hello", bar = "world"); -LL + writeln!(&mut v, "{foo} world", foo = "hello"); +LL - writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); +LL + writeln!(v, "{foo} world", foo = "hello"); | error: literal with an empty format string - --> $DIR/write_literal.rs:42:37 + --> $DIR/write_literal.rs:42:32 | -LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); - | ^^^^^^^^^^^^^ +LL | writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); -LL + writeln!(&mut v, "{bar} hello", bar = "world"); +LL - writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); +LL + writeln!(v, "{bar} hello", bar = "world"); | error: literal with an empty format string - --> $DIR/write_literal.rs:42:52 + --> $DIR/write_literal.rs:42:47 | -LL | writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); - | ^^^^^^^^^^^^^ +LL | writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); + | ^^^^^^^^^^^^^ | help: try this | -LL - writeln!(&mut v, "{bar} {foo}", foo = "hello", bar = "world"); -LL + writeln!(&mut v, "world {foo}", foo = "hello"); +LL - writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); +LL + writeln!(v, "world {foo}", foo = "hello"); | error: aborting due to 11 previous errors diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs index f341e8215e1ca..ba0d7be5eaa68 100644 --- a/tests/ui/write_literal_2.rs +++ b/tests/ui/write_literal_2.rs @@ -6,20 +6,20 @@ use std::io::Write; fn main() { let mut v = Vec::new(); - writeln!(&mut v, "{}", "{hello}"); - writeln!(&mut v, r"{}", r"{hello}"); - writeln!(&mut v, "{}", '\''); - writeln!(&mut v, "{}", '"'); - writeln!(&mut v, r"{}", '"'); // don't lint - writeln!(&mut v, r"{}", '\''); + writeln!(v, "{}", "{hello}"); + writeln!(v, r"{}", r"{hello}"); + writeln!(v, "{}", '\''); + writeln!(v, "{}", '"'); + writeln!(v, r"{}", '"'); // don't lint + writeln!(v, r"{}", '\''); writeln!( - &mut v, + v, "some {}", "hello \ world!" ); writeln!( - &mut v, + v, "some {}\ {} \\ {}", "1", "2", "3", diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr index 73c6b88581329..fc40fbfa9e239 100644 --- a/tests/ui/write_literal_2.stderr +++ b/tests/ui/write_literal_2.stderr @@ -1,62 +1,62 @@ error: literal with an empty format string - --> $DIR/write_literal_2.rs:9:28 + --> $DIR/write_literal_2.rs:9:23 | -LL | writeln!(&mut v, "{}", "{hello}"); - | ^^^^^^^^^ +LL | writeln!(v, "{}", "{hello}"); + | ^^^^^^^^^ | = note: `-D clippy::write-literal` implied by `-D warnings` help: try this | -LL - writeln!(&mut v, "{}", "{hello}"); -LL + writeln!(&mut v, "{{hello}}"); +LL - writeln!(v, "{}", "{hello}"); +LL + writeln!(v, "{{hello}}"); | error: literal with an empty format string - --> $DIR/write_literal_2.rs:10:29 + --> $DIR/write_literal_2.rs:10:24 | -LL | writeln!(&mut v, r"{}", r"{hello}"); - | ^^^^^^^^^^ +LL | writeln!(v, r"{}", r"{hello}"); + | ^^^^^^^^^^ | help: try this | -LL - writeln!(&mut v, r"{}", r"{hello}"); -LL + writeln!(&mut v, r"{{hello}}"); +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{{hello}}"); | error: literal with an empty format string - --> $DIR/write_literal_2.rs:11:28 + --> $DIR/write_literal_2.rs:11:23 | -LL | writeln!(&mut v, "{}", '/''); - | ^^^^ +LL | writeln!(v, "{}", '/''); + | ^^^^ | help: try this | -LL - writeln!(&mut v, "{}", '/''); -LL + writeln!(&mut v, "'"); +LL - writeln!(v, "{}", '/''); +LL + writeln!(v, "'"); | error: literal with an empty format string - --> $DIR/write_literal_2.rs:12:28 + --> $DIR/write_literal_2.rs:12:23 | -LL | writeln!(&mut v, "{}", '"'); - | ^^^ +LL | writeln!(v, "{}", '"'); + | ^^^ | help: try this | -LL - writeln!(&mut v, "{}", '"'); -LL + writeln!(&mut v, "/""); +LL - writeln!(v, "{}", '"'); +LL + writeln!(v, "/""); | error: literal with an empty format string - --> $DIR/write_literal_2.rs:14:29 + --> $DIR/write_literal_2.rs:14:24 | -LL | writeln!(&mut v, r"{}", '/''); - | ^^^^ +LL | writeln!(v, r"{}", '/''); + | ^^^^ | help: try this | -LL - writeln!(&mut v, r"{}", '/''); -LL + writeln!(&mut v, r"'"); +LL - writeln!(v, r"{}", '/''); +LL + writeln!(v, r"'"); | error: literal with an empty format string diff --git a/tests/ui/write_with_newline.rs b/tests/ui/write_with_newline.rs index 1c1b1b58402e8..446d6914d3461 100644 --- a/tests/ui/write_with_newline.rs +++ b/tests/ui/write_with_newline.rs @@ -10,50 +10,50 @@ fn main() { let mut v = Vec::new(); // These should fail - write!(&mut v, "Hello\n"); - write!(&mut v, "Hello {}\n", "world"); - write!(&mut v, "Hello {} {}\n", "world", "#2"); - write!(&mut v, "{}\n", 1265); - write!(&mut v, "\n"); + write!(v, "Hello\n"); + write!(v, "Hello {}\n", "world"); + write!(v, "Hello {} {}\n", "world", "#2"); + write!(v, "{}\n", 1265); + write!(v, "\n"); // These should be fine - write!(&mut v, ""); - write!(&mut v, "Hello"); - writeln!(&mut v, "Hello"); - writeln!(&mut v, "Hello\n"); - writeln!(&mut v, "Hello {}\n", "world"); - write!(&mut v, "Issue\n{}", 1265); - write!(&mut v, "{}", 1265); - write!(&mut v, "\n{}", 1275); - write!(&mut v, "\n\n"); - write!(&mut v, "like eof\n\n"); - write!(&mut v, "Hello {} {}\n\n", "world", "#2"); - writeln!(&mut v, "\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 - writeln!(&mut v, "\nbla\n\n"); // #3126 + write!(v, ""); + write!(v, "Hello"); + writeln!(v, "Hello"); + writeln!(v, "Hello\n"); + writeln!(v, "Hello {}\n", "world"); + write!(v, "Issue\n{}", 1265); + write!(v, "{}", 1265); + write!(v, "\n{}", 1275); + write!(v, "\n\n"); + write!(v, "like eof\n\n"); + write!(v, "Hello {} {}\n\n", "world", "#2"); + writeln!(v, "\ndon't\nwarn\nfor\nmultiple\nnewlines\n"); // #3126 + writeln!(v, "\nbla\n\n"); // #3126 // Escaping - write!(&mut v, "\\n"); // #3514 - write!(&mut v, "\\\n"); // should fail - write!(&mut v, "\\\\n"); + write!(v, "\\n"); // #3514 + write!(v, "\\\n"); // should fail + write!(v, "\\\\n"); // Raw strings - write!(&mut v, r"\n"); // #3778 + write!(v, r"\n"); // #3778 // Literal newlines should also fail write!( - &mut v, + v, " " ); write!( - &mut v, + v, r" " ); // Don't warn on CRLF (#4208) - write!(&mut v, "\r\n"); - write!(&mut v, "foo\r\n"); - write!(&mut v, "\\r\n"); //~ ERROR - write!(&mut v, "foo\rbar\n"); + write!(v, "\r\n"); + write!(v, "foo\r\n"); + write!(v, "\\r\n"); //~ ERROR + write!(v, "foo\rbar\n"); } diff --git a/tests/ui/write_with_newline.stderr b/tests/ui/write_with_newline.stderr index 186459e50b64c..3314a2a6e2420 100644 --- a/tests/ui/write_with_newline.stderr +++ b/tests/ui/write_with_newline.stderr @@ -1,81 +1,81 @@ error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:13:5 | -LL | write!(&mut v, "Hello/n"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "Hello/n"); + | ^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::write-with-newline` implied by `-D warnings` help: use `writeln!()` instead | -LL - write!(&mut v, "Hello/n"); -LL + writeln!(&mut v, "Hello"); +LL - write!(v, "Hello/n"); +LL + writeln!(v, "Hello"); | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:14:5 | -LL | write!(&mut v, "Hello {}/n", "world"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "Hello {}/n", "world"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "Hello {}/n", "world"); -LL + writeln!(&mut v, "Hello {}", "world"); +LL - write!(v, "Hello {}/n", "world"); +LL + writeln!(v, "Hello {}", "world"); | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:15:5 | -LL | write!(&mut v, "Hello {} {}/n", "world", "#2"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "Hello {} {}/n", "world", "#2"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "Hello {} {}/n", "world", "#2"); -LL + writeln!(&mut v, "Hello {} {}", "world", "#2"); +LL - write!(v, "Hello {} {}/n", "world", "#2"); +LL + writeln!(v, "Hello {} {}", "world", "#2"); | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:16:5 | -LL | write!(&mut v, "{}/n", 1265); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "{}/n", 1265); + | ^^^^^^^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "{}/n", 1265); -LL + writeln!(&mut v, "{}", 1265); +LL - write!(v, "{}/n", 1265); +LL + writeln!(v, "{}", 1265); | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:17:5 | -LL | write!(&mut v, "/n"); - | ^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "/n"); + | ^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "/n"); -LL + writeln!(&mut v); +LL - write!(v, "/n"); +LL + writeln!(v); | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:36:5 | -LL | write!(&mut v, "//n"); // should fail - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "//n"); // should fail + | ^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "//n"); // should fail -LL + writeln!(&mut v, "/"); // should fail +LL - write!(v, "//n"); // should fail +LL + writeln!(v, "/"); // should fail | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:43:5 | LL | / write!( -LL | | &mut v, +LL | | v, LL | | " LL | | " LL | | ); @@ -84,7 +84,7 @@ LL | | ); help: use `writeln!()` instead | LL ~ writeln!( -LL | &mut v, +LL | v, LL ~ "" | @@ -92,7 +92,7 @@ error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:48:5 | LL | / write!( -LL | | &mut v, +LL | | v, LL | | r" LL | | " LL | | ); @@ -101,32 +101,32 @@ LL | | ); help: use `writeln!()` instead | LL ~ writeln!( -LL | &mut v, +LL | v, LL ~ r"" | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:57:5 | -LL | write!(&mut v, "/r/n"); //~ ERROR - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "/r/n"); //~ ERROR + | ^^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "/r/n"); //~ ERROR -LL + writeln!(&mut v, "/r"); //~ ERROR +LL - write!(v, "/r/n"); //~ ERROR +LL + writeln!(v, "/r"); //~ ERROR | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:58:5 | -LL | write!(&mut v, "foo/rbar/n"); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | write!(v, "foo/rbar/n"); + | ^^^^^^^^^^^^^^^^^^^^^^^ | help: use `writeln!()` instead | -LL - write!(&mut v, "foo/rbar/n"); -LL + writeln!(&mut v, "foo/rbar"); +LL - write!(v, "foo/rbar/n"); +LL + writeln!(v, "foo/rbar"); | error: aborting due to 10 previous errors diff --git a/tests/ui/writeln_empty_string.fixed b/tests/ui/writeln_empty_string.fixed index c3ac15b03751c..e7d94acd130d6 100644 --- a/tests/ui/writeln_empty_string.fixed +++ b/tests/ui/writeln_empty_string.fixed @@ -8,13 +8,13 @@ fn main() { let mut v = Vec::new(); // These should fail - writeln!(&mut v); + writeln!(v); let mut suggestion = Vec::new(); - writeln!(&mut suggestion); + writeln!(suggestion); // These should be fine - writeln!(&mut v); - writeln!(&mut v, " "); - write!(&mut v, ""); + writeln!(v); + writeln!(v, " "); + write!(v, ""); } diff --git a/tests/ui/writeln_empty_string.rs b/tests/ui/writeln_empty_string.rs index 9a8894b6c0d32..662c62f02116e 100644 --- a/tests/ui/writeln_empty_string.rs +++ b/tests/ui/writeln_empty_string.rs @@ -8,13 +8,13 @@ fn main() { let mut v = Vec::new(); // These should fail - writeln!(&mut v, ""); + writeln!(v, ""); let mut suggestion = Vec::new(); - writeln!(&mut suggestion, ""); + writeln!(suggestion, ""); // These should be fine - writeln!(&mut v); - writeln!(&mut v, " "); - write!(&mut v, ""); + writeln!(v); + writeln!(v, " "); + write!(v, ""); } diff --git a/tests/ui/writeln_empty_string.stderr b/tests/ui/writeln_empty_string.stderr index 99635229b3e13..ac65aadfc0e89 100644 --- a/tests/ui/writeln_empty_string.stderr +++ b/tests/ui/writeln_empty_string.stderr @@ -1,16 +1,16 @@ -error: using `writeln!(&mut v, "")` +error: using `writeln!(v, "")` --> $DIR/writeln_empty_string.rs:11:5 | -LL | writeln!(&mut v, ""); - | ^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut v)` +LL | writeln!(v, ""); + | ^^^^^^^^^^^^^^^ help: replace it with: `writeln!(v)` | = note: `-D clippy::writeln-empty-string` implied by `-D warnings` -error: using `writeln!(&mut suggestion, "")` +error: using `writeln!(suggestion, "")` --> $DIR/writeln_empty_string.rs:14:5 | -LL | writeln!(&mut suggestion, ""); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(&mut suggestion)` +LL | writeln!(suggestion, ""); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `writeln!(suggestion)` error: aborting due to 2 previous errors diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 5b7e61a349d97..83a200ca3c4f4 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -522,6 +522,11 @@

} scrollToLintByURL($scope); + + setTimeout(function () { + var el = document.getElementById('filter-input'); + if (el) { el.focus() } + }, 0); }) .error(function (data) { $scope.error = data; From 23fd95a5e99acf35b471ca9b01230d7578bfac5f Mon Sep 17 00:00:00 2001 From: max <36980911+pr2502@users.noreply.github.com> Date: Sat, 8 Jan 2022 23:10:20 +0100 Subject: [PATCH 10/67] fix underflow in `check_manual_split_once` lint --- clippy_lints/src/methods/str_splitn.rs | 6 +++--- tests/ui/crashes/ice-8250.rs | 6 ++++++ tests/ui/crashes/ice-8250.stderr | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tests/ui/crashes/ice-8250.rs create mode 100644 tests/ui/crashes/ice-8250.stderr diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 70f20da1d6db7..e8a3562c39366 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -45,16 +45,16 @@ pub(super) fn check_manual_split_once( IterUsageKind::Next | IterUsageKind::Second => { let self_deref = { let adjust = cx.typeck_results().expr_adjustments(self_arg); - if adjust.is_empty() { + if adjust.len() < 2 { 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)) + format!("&{}", "*".repeat(adjust.len().saturating_sub(1))) } else { - "*".repeat(adjust.len() - 2) + "*".repeat(adjust.len().saturating_sub(2)) } }; if matches!(usage.kind, IterUsageKind::Next) { diff --git a/tests/ui/crashes/ice-8250.rs b/tests/ui/crashes/ice-8250.rs new file mode 100644 index 0000000000000..d9a5ee1162a49 --- /dev/null +++ b/tests/ui/crashes/ice-8250.rs @@ -0,0 +1,6 @@ +fn _f(s: &str) -> Option<()> { + let _ = s[1..].splitn(2, '.').next()?; + Some(()) +} + +fn main() {} diff --git a/tests/ui/crashes/ice-8250.stderr b/tests/ui/crashes/ice-8250.stderr new file mode 100644 index 0000000000000..04ea445665652 --- /dev/null +++ b/tests/ui/crashes/ice-8250.stderr @@ -0,0 +1,18 @@ +error: manual implementation of `split_once` + --> $DIR/ice-8250.rs:2:13 + | +LL | let _ = s[1..].splitn(2, '.').next()?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `s[1..].split_once('.').map_or(s[1..], |x| x.0)` + | + = note: `-D clippy::manual-split-once` implied by `-D warnings` + +error: unnecessary use of `splitn` + --> $DIR/ice-8250.rs:2:13 + | +LL | let _ = s[1..].splitn(2, '.').next()?; + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `s[1..].split('.')` + | + = note: `-D clippy::needless-splitn` implied by `-D warnings` + +error: aborting due to 2 previous errors + From 515ed80b9d63e20c196a7739118c2f316a097106 Mon Sep 17 00:00:00 2001 From: dswij Date: Tue, 25 Jan 2022 16:18:32 +0800 Subject: [PATCH 11/67] Update docs for `map_flatten` on `Option` --- clippy_lints/src/methods/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index d75ab0c4b1b0a..45f2992df9117 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -566,17 +566,20 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Readability, this can be written more concisely as - /// `_.flat_map(_)` + /// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option` /// /// ### Example /// ```rust /// let vec = vec![vec![1]]; + /// let opt = Some(5); /// /// // Bad /// vec.iter().map(|x| x.iter()).flatten(); + /// opt.map(|x| Some(x * 2)).flatten(); /// /// // Good /// vec.iter().flat_map(|x| x.iter()); + /// opt.and_then(|x| Some(x * 2)); /// ``` #[clippy::version = "1.31.0"] pub MAP_FLATTEN, From cc975929c528d0883d7553c53dc47030c08d01e0 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Wed, 5 Jan 2022 20:36:22 -0600 Subject: [PATCH 12/67] Rename path_to_res to def_path_res --- clippy_lints/src/disallowed_methods.rs | 2 +- clippy_lints/src/disallowed_types.rs | 2 +- clippy_lints/src/missing_enforced_import_rename.rs | 2 +- clippy_lints/src/utils/internal_lints.rs | 10 +++++----- clippy_utils/src/lib.rs | 7 ++++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 73c00d97020be..4c12202c84ab3 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -77,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_crate(&mut self, cx: &LateContext<'_>) { for (index, conf) in self.conf_disallowed.iter().enumerate() { let segs: Vec<_> = conf.path().split("::").collect(); - if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &segs) { + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) { self.disallowed.insert(id, index); } } diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index ea4b49b46fe9f..14f89edce615d 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { ), }; let segs: Vec<_> = path.split("::").collect(); - match clippy_utils::path_to_res(cx, &segs) { + match clippy_utils::def_path_res(cx, &segs) { Res::Def(_, id) => { self.def_ids.insert(id, reason); }, diff --git a/clippy_lints/src/missing_enforced_import_rename.rs b/clippy_lints/src/missing_enforced_import_rename.rs index 566e15ab2a6d6..3d0a23822838e 100644 --- a/clippy_lints/src/missing_enforced_import_rename.rs +++ b/clippy_lints/src/missing_enforced_import_rename.rs @@ -58,7 +58,7 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]); impl LateLintPass<'_> for ImportRename { fn check_crate(&mut self, cx: &LateContext<'_>) { for Rename { path, rename } in &self.conf_renames { - if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &path.split("::").collect::>()) { + if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::>()) { self.renames.insert(id, Symbol::intern(rename)); } } diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index f170ff69154b6..dc0f515bfe5cb 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -4,8 +4,8 @@ use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::snippet; use clippy_utils::ty::match_type; use clippy_utils::{ - higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls, - path_to_res, paths, peel_blocks_with_stmt, SpanlessEq, + def_path_res, higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, + method_calls, paths, peel_blocks_with_stmt, SpanlessEq, }; use if_chain::if_chain; use rustc_ast as ast; @@ -844,7 +844,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem { // Extract the path to the matched type if let Some(segments) = path_to_matched_type(cx, ty_path); let segments: Vec<&str> = segments.iter().map(Symbol::as_str).collect(); - if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id(); + if let Some(ty_did) = def_path_res(cx, &segments[..]).opt_def_id(); // Check if the matched type is a diagnostic item if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did); then { @@ -917,7 +917,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option, path: &[&str]) -> bool { - if path_to_res(cx, path) != Res::Err { + if def_path_res(cx, path) != Res::Err { return true; } @@ -999,7 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { } for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] { - if let Some(def_id) = path_to_res(cx, module).opt_def_id() { + if let Some(def_id) = def_path_res(cx, module).opt_def_id() { for item in cx.tcx.module_children(def_id).iter() { if_chain! { if let Res::Def(DefKind::Const, item_def_id) = item.res; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a2f1f4696513e..28bca902e1907 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -497,8 +497,9 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { path_to_local(expr) == Some(id) } -/// Gets the definition associated to a path. -pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res { +/// Resolves a def path like `std::vec::Vec`. +/// This function is expensive and should be used sparingly. +pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { macro_rules! try_res { ($e:expr) => { match $e { @@ -574,7 +575,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res { /// Convenience function to get the `DefId` of a trait by path. /// It could be a trait or trait alias. pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option { - match path_to_res(cx, path) { + match def_path_res(cx, path) { Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id), _ => None, } From bea09a2329676e48ea9f780c47e27d653417a4e3 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Tue, 4 Jan 2022 17:24:23 -0600 Subject: [PATCH 13/67] Add path_def_id util --- clippy_utils/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 28bca902e1907..8cc4f0449e318 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -497,6 +497,43 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool { path_to_local(expr) == Some(id) } +pub trait MaybePath<'hir> { + fn hir_id(&self) -> HirId; + fn qpath_opt(&self) -> Option<&QPath<'hir>>; +} + +macro_rules! maybe_path { + ($ty:ident, $kind:ident) => { + impl<'hir> MaybePath<'hir> for hir::$ty<'hir> { + fn hir_id(&self) -> HirId { + self.hir_id + } + fn qpath_opt(&self) -> Option<&QPath<'hir>> { + match &self.kind { + hir::$kind::Path(qpath) => Some(qpath), + _ => None, + } + } + } + }; +} +maybe_path!(Expr, ExprKind); +maybe_path!(Pat, PatKind); +maybe_path!(Ty, TyKind); + +/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err` +pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res { + match maybe_path.qpath_opt() { + None => Res::Err, + Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()), + } +} + +/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID +pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option { + path_res(cx, maybe_path).opt_def_id() +} + /// Resolves a def path like `std::vec::Vec`. /// This function is expensive and should be used sparingly. pub fn def_path_res(cx: &LateContext<'_>, path: &[&str]) -> Res { From 20781f195d205dcc3fa244a659dfc5b3547a87d3 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 17 Jan 2022 12:57:45 -0600 Subject: [PATCH 14/67] Rename qpath_generic_tys --- clippy_lints/src/types/rc_buffer.rs | 6 +++--- clippy_lints/src/types/redundant_allocation.rs | 4 ++-- clippy_utils/src/lib.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 31c4abdfc95ea..0a2df7d218835 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{get_qpath_generic_tys, is_ty_param_diagnostic_item}; +use clippy_utils::{is_ty_param_diagnostic_item, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; use rustc_lint::LateContext; @@ -25,7 +25,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ TyKind::Path(qpath) => qpath, _ => return false, }; - let inner_span = match get_qpath_generic_tys(qpath).next() { + let inner_span = match qpath_generic_tys(qpath).next() { Some(ty) => ty.span, None => return false, }; @@ -60,7 +60,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ TyKind::Path(qpath) => qpath, _ => return false, }; - let inner_span = match get_qpath_generic_tys(qpath).next() { + let inner_span = match qpath_generic_tys(qpath).next() { Some(ty) => ty.span, None => return false, }; diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs index ac7bdd6a1ebeb..8638197a5842f 100644 --- a/clippy_lints/src/types/redundant_allocation.rs +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::{get_qpath_generic_tys, is_ty_param_diagnostic_item, is_ty_param_lang_item}; +use clippy_utils::{is_ty_param_diagnostic_item, is_ty_param_lang_item, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::{self as hir, def_id::DefId, LangItem, QPath, TyKind}; use rustc_lint::LateContext; @@ -53,7 +53,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ TyKind::Path(inner_qpath) => inner_qpath, _ => return false, }; - let inner_span = match get_qpath_generic_tys(inner_qpath).next() { + let inner_span = match qpath_generic_tys(inner_qpath).next() { Some(ty) => { // Box> is smaller than Box because of wide pointers if matches!(ty.kind, TyKind::TraitObject(..)) { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8cc4f0449e318..64634c64602f6 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -268,7 +268,7 @@ pub fn is_ty_param_lang_item<'tcx>( qpath: &QPath<'tcx>, item: LangItem, ) -> Option<&'tcx hir::Ty<'tcx>> { - let ty = get_qpath_generic_tys(qpath).next()?; + let ty = qpath_generic_tys(qpath).next()?; if let TyKind::Path(qpath) = &ty.kind { cx.qpath_res(qpath, ty.hir_id) @@ -288,7 +288,7 @@ pub fn is_ty_param_diagnostic_item<'tcx>( qpath: &QPath<'tcx>, item: Symbol, ) -> Option<&'tcx hir::Ty<'tcx>> { - let ty = get_qpath_generic_tys(qpath).next()?; + let ty = qpath_generic_tys(qpath).next()?; if let TyKind::Path(qpath) = &ty.kind { cx.qpath_res(qpath, ty.hir_id) @@ -368,7 +368,7 @@ pub fn get_qpath_generics<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx GenericArgs< } } -pub fn get_qpath_generic_tys<'tcx>(path: &QPath<'tcx>) -> impl Iterator> { +pub fn qpath_generic_tys<'tcx>(path: &QPath<'tcx>) -> impl Iterator> { get_qpath_generics(path) .map_or([].as_ref(), |a| a.args) .iter() From 145d7fc5298cec0995bdc4e71d09d275fcbcdcdb Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 17 Jan 2022 13:39:45 -0600 Subject: [PATCH 15/67] Factor out get_qpath_generics --- clippy_utils/src/lib.rs | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 64634c64602f6..82d5023866684 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -77,9 +77,9 @@ use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ def, lang_items, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, - ExprKind, FnDecl, ForeignItem, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, - Local, MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, - Target, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, + ExprKind, FnDecl, ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, + MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, Target, + TraitItem, TraitItemKind, TraitRef, TyKind, UnOp, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; @@ -360,24 +360,14 @@ pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> { } } -pub fn get_qpath_generics<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx GenericArgs<'tcx>> { - match path { - QPath::Resolved(_, p) => p.segments.last().and_then(|s| s.args), - QPath::TypeRelative(_, s) => s.args, - QPath::LangItem(..) => None, - } -} - -pub fn qpath_generic_tys<'tcx>(path: &QPath<'tcx>) -> impl Iterator> { - get_qpath_generics(path) - .map_or([].as_ref(), |a| a.args) +pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator> { + last_path_segment(qpath) + .args + .map_or(&[][..], |a| a.args) .iter() - .filter_map(|a| { - if let hir::GenericArg::Type(ty) = a { - Some(ty) - } else { - None - } + .filter_map(|a| match a { + hir::GenericArg::Type(ty) => Some(ty), + _ => None, }) } From 66a83d33ea9a39d1fc0f4c2ca058c0ce4884a71a Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Wed, 5 Jan 2022 20:56:29 -0600 Subject: [PATCH 16/67] Factor out some ty param utils --- clippy_lints/src/types/box_collection.rs | 27 ++++++------- clippy_lints/src/types/option_option.rs | 30 +++++++++------ clippy_lints/src/types/rc_buffer.rs | 33 ++++++++++------ clippy_lints/src/types/rc_mutex.rs | 6 ++- .../src/types/redundant_allocation.rs | 19 +++++----- clippy_utils/src/lib.rs | 38 ------------------- 6 files changed, 63 insertions(+), 90 deletions(-) diff --git a/clippy_lints/src/types/box_collection.rs b/clippy_lints/src/types/box_collection.rs index 538c10a5b2045..21a9558ec076a 100644 --- a/clippy_lints/src/types/box_collection.rs +++ b/clippy_lints/src/types/box_collection.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_ty_param_diagnostic_item; +use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_hir::{self as hir, def_id::DefId, QPath}; use rustc_lint::LateContext; -use rustc_span::symbol::sym; +use rustc_span::{sym, Symbol}; use super::BOX_COLLECTION; @@ -11,10 +11,9 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ if Some(def_id) == cx.tcx.lang_items().owned_box(); if let Some(item_type) = get_std_collection(cx, qpath); then { - let generic = if item_type == "String" { - "" - } else { - "<..>" + let generic = match item_type { + sym::String => "", + _ => "<..>", }; span_lint_and_help( cx, @@ -37,14 +36,10 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ } } -fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { - if is_ty_param_diagnostic_item(cx, qpath, sym::Vec).is_some() { - Some("Vec") - } else if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() { - Some("String") - } else if is_ty_param_diagnostic_item(cx, qpath, sym::HashMap).is_some() { - Some("HashMap") - } else { - None - } +fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option { + let param = qpath_generic_tys(qpath).next()?; + let id = path_def_id(cx, param)?; + cx.tcx + .get_diagnostic_name(id) + .filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec)) } diff --git a/clippy_lints/src/types/option_option.rs b/clippy_lints/src/types/option_option.rs index 903e62995c617..8767e3c30a68a 100644 --- a/clippy_lints/src/types/option_option.rs +++ b/clippy_lints/src/types/option_option.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_ty_param_diagnostic_item; +use clippy_utils::{path_def_id, qpath_generic_tys}; +use if_chain::if_chain; use rustc_hir::{self as hir, def_id::DefId, QPath}; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -7,16 +8,21 @@ use rustc_span::symbol::sym; use super::OPTION_OPTION; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { - if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_ty_param_diagnostic_item(cx, qpath, sym::Option).is_some() { - span_lint( - cx, - OPTION_OPTION, - hir_ty.span, - "consider using `Option` instead of `Option>` or a custom \ - enum if you need to distinguish all 3 cases", - ); - true - } else { - false + if_chain! { + if cx.tcx.is_diagnostic_item(sym::Option, def_id); + if let Some(arg) = qpath_generic_tys(qpath).next(); + if path_def_id(cx, arg) == Some(def_id); + then { + span_lint( + cx, + OPTION_OPTION, + hir_ty.span, + "consider using `Option` instead of `Option>` or a custom \ + enum if you need to distinguish all 3 cases", + ); + true + } else { + false + } } } diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 0a2df7d218835..4d72a29e8c747 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_ty_param_diagnostic_item, qpath_generic_tys}; +use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_errors::Applicability; use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; use rustc_lint::LateContext; @@ -20,7 +20,12 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ format!("Rc<{}>", alternate), Applicability::MachineApplicable, ); - } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Vec) { + } else { + let Some(ty) = qpath_generic_tys(qpath).next() else { return false }; + let Some(id) = path_def_id(cx, ty) else { return false }; + if !cx.tcx.is_diagnostic_item(sym::Vec, id) { + return false; + } let qpath = match &ty.kind { TyKind::Path(qpath) => qpath, _ => return false, @@ -55,7 +60,11 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ format!("Arc<{}>", alternate), Applicability::MachineApplicable, ); - } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Vec) { + } else if let Some(ty) = qpath_generic_tys(qpath).next() { + let Some(id) = path_def_id(cx, ty) else { return false }; + if !cx.tcx.is_diagnostic_item(sym::Vec, id) { + return false; + } let qpath = match &ty.kind { TyKind::Path(qpath) => qpath, _ => return false, @@ -85,13 +94,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ } fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { - if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() { - Some("str") - } else if is_ty_param_diagnostic_item(cx, qpath, sym::OsString).is_some() { - Some("std::ffi::OsStr") - } else if is_ty_param_diagnostic_item(cx, qpath, sym::PathBuf).is_some() { - Some("std::path::Path") - } else { - None - } + let ty = qpath_generic_tys(qpath).next()?; + let id = path_def_id(cx, ty)?; + let path = match cx.tcx.get_diagnostic_name(id)? { + sym::String => "str", + sym::OsString => "std::ffi::OsStr", + sym::PathBuf => "std::path::Path", + _ => return None, + }; + Some(path) } diff --git a/clippy_lints/src/types/rc_mutex.rs b/clippy_lints/src/types/rc_mutex.rs index d54608a07bb27..a75972cf3ddbe 100644 --- a/clippy_lints/src/types/rc_mutex.rs +++ b/clippy_lints/src/types/rc_mutex.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_ty_param_diagnostic_item; +use clippy_utils::{path_def_id, qpath_generic_tys}; use if_chain::if_chain; use rustc_hir::{self as hir, def_id::DefId, QPath}; use rustc_lint::LateContext; @@ -10,7 +10,9 @@ use super::RC_MUTEX; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { if_chain! { if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ; - if let Some(_) = is_ty_param_diagnostic_item(cx, qpath, sym::Mutex) ; + if let Some(arg) = qpath_generic_tys(qpath).next(); + if let Some(id) = path_def_id(cx, arg); + if cx.tcx.is_diagnostic_item(sym::Mutex, id); then { span_lint_and_help( cx, diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs index 8638197a5842f..10d2ae2eb1dbb 100644 --- a/clippy_lints/src/types/redundant_allocation.rs +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::{is_ty_param_diagnostic_item, is_ty_param_lang_item, qpath_generic_tys}; +use clippy_utils::{path_def_id, qpath_generic_tys}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, def_id::DefId, LangItem, QPath, TyKind}; +use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -39,14 +39,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ return true; } - let (inner_sym, ty) = if let Some(ty) = is_ty_param_lang_item(cx, qpath, LangItem::OwnedBox) { - ("Box", ty) - } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Rc) { - ("Rc", ty) - } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Arc) { - ("Arc", ty) - } else { - return false; + let Some(ty) = qpath_generic_tys(qpath).next() else { return false }; + let Some(id) = path_def_id(cx, ty) else { return false }; + let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) { + Some(sym::Arc) => ("Arc", ty), + Some(sym::Rc) => ("Rc", ty), + _ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty), + _ => return false, }; let inner_qpath = match &ty.kind { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 82d5023866684..ed73364841c59 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -262,44 +262,6 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } -/// Checks if the first type parameter is a lang item. -pub fn is_ty_param_lang_item<'tcx>( - cx: &LateContext<'_>, - qpath: &QPath<'tcx>, - item: LangItem, -) -> Option<&'tcx hir::Ty<'tcx>> { - let ty = qpath_generic_tys(qpath).next()?; - - if let TyKind::Path(qpath) = &ty.kind { - cx.qpath_res(qpath, ty.hir_id) - .opt_def_id() - .map_or(false, |id| { - cx.tcx.lang_items().require(item).map_or(false, |lang_id| id == lang_id) - }) - .then(|| ty) - } else { - None - } -} - -/// Checks if the first type parameter is a diagnostic item. -pub fn is_ty_param_diagnostic_item<'tcx>( - cx: &LateContext<'_>, - qpath: &QPath<'tcx>, - item: Symbol, -) -> Option<&'tcx hir::Ty<'tcx>> { - let ty = qpath_generic_tys(qpath).next()?; - - if let TyKind::Path(qpath) = &ty.kind { - cx.qpath_res(qpath, ty.hir_id) - .opt_def_id() - .map_or(false, |id| cx.tcx.is_diagnostic_item(item, id)) - .then(|| ty) - } else { - None - } -} - /// Checks if the method call given in `expr` belongs to the given trait. /// This is a deprecated function, consider using [`is_trait_method`]. pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool { From deadc255883a726abee105564bc9a0f65f01e62b Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Tue, 2 Nov 2021 09:43:31 -0500 Subject: [PATCH 17/67] Factor out differing_macro_contexts --- clippy_lints/src/blocks_in_if_conditions.rs | 6 +++--- clippy_lints/src/formatting.rs | 14 ++++++-------- clippy_lints/src/implicit_hasher.rs | 3 +-- clippy_lints/src/methods/option_map_unwrap_or.rs | 3 +-- clippy_lints/src/swap.rs | 4 ++-- clippy_lints/src/unwrap.rs | 7 ++++--- clippy_utils/src/hir_utils.rs | 3 +-- clippy_utils/src/lib.rs | 7 ------- doc/common_tools_writing_lints.md | 8 ++++++-- 9 files changed, 24 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/blocks_in_if_conditions.rs b/clippy_lints/src/blocks_in_if_conditions.rs index c4956bacf4361..4c4dd85d518a6 100644 --- a/clippy_lints/src/blocks_in_if_conditions.rs +++ b/clippy_lints/src/blocks_in_if_conditions.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::get_parent_expr; use clippy_utils::higher; use clippy_utils::source::snippet_block_with_applicability; use clippy_utils::ty::implements_trait; -use clippy_utils::{differing_macro_contexts, get_parent_expr}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; @@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions { if let Some(ex) = &block.expr { // don't dig into the expression here, just suggest that they remove // the block - if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) { + if expr.span.from_expansion() || ex.span.from_expansion() { return; } let mut applicability = Applicability::MachineApplicable; @@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions { } } else { let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span); - if span.from_expansion() || differing_macro_contexts(expr.span, span) { + if span.from_expansion() || expr.span.from_expansion() { return; } // move block higher diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index ae18f8081bcc8..57964b8d48ea9 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note}; -use clippy_utils::differing_macro_contexts; use clippy_utils::source::snippet_opt; use if_chain::if_chain; use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp}; @@ -135,7 +134,7 @@ impl EarlyLintPass for Formatting { /// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint. fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) { if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind { - if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() { + if !lhs.span.from_expansion() && !rhs.span.from_expansion() { let eq_span = lhs.span.between(rhs.span); if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind { if let Some(eq_snippet) = snippet_opt(cx, eq_span) { @@ -165,7 +164,7 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) { fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) { if_chain! { if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind; - if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion(); + if !lhs.span.from_expansion() && !rhs.span.from_expansion(); // span between BinOp LHS and RHS let binop_span = lhs.span.between(rhs.span); // if RHS is an UnOp @@ -206,8 +205,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) { if_chain! { if let ExprKind::If(_, then, Some(else_)) = &expr.kind; if is_block(else_) || is_if(else_); - if !differing_macro_contexts(then.span, else_.span); - if !then.span.from_expansion() && !in_external_macro(cx.sess(), expr.span); + if !then.span.from_expansion() && !else_.span.from_expansion(); + if !in_external_macro(cx.sess(), expr.span); // workaround for rust-lang/rust#43081 if expr.span.lo().0 != 0 && expr.span.hi().0 != 0; @@ -268,7 +267,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) { for element in array { if_chain! { if let ExprKind::Binary(ref op, ref lhs, _) = element.kind; - if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span); + if has_unary_equivalent(op.node) && lhs.span.ctxt() == op.span.ctxt(); let space_span = lhs.span.between(op.span); if let Some(space_snippet) = snippet_opt(cx, space_span); let lint_span = lhs.span.with_lo(lhs.span.hi()); @@ -291,8 +290,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) { fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) { if_chain! { - if !differing_macro_contexts(first.span, second.span); - if !first.span.from_expansion(); + if !first.span.from_expansion() && !second.span.from_expansion(); if let ExprKind::If(cond_expr, ..) = &first.kind; if is_block(second) || is_if(second); diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index eed25e9bc0ea8..3fb7e5dfd6cdc 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -17,7 +17,6 @@ use rustc_typeck::hir_ty_to_ty; use if_chain::if_chain; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; -use clippy_utils::differing_macro_contexts; use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::ty::is_type_diagnostic_item; @@ -123,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { vis.visit_ty(impl_.self_ty); for target in &vis.found { - if differing_macro_contexts(item.span, target.span()) { + if item.span.ctxt() != target.span().ctxt() { return; } diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 9c6f421103185..6c641af59f92b 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::differing_macro_contexts; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_copy; use clippy_utils::ty::is_type_diagnostic_item; @@ -48,7 +47,7 @@ pub(super) fn check<'tcx>( } } - if differing_macro_contexts(unwrap_arg.span, map_span) { + if unwrap_arg.span.ctxt() != map_span.ctxt() { return; } diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 4c10b12437d7b..1885f3ca414df 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value, std_or_core}; +use clippy_utils::{can_mut_borrow_both, eq_expr_value, std_or_core}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind}; @@ -172,7 +172,7 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) { if_chain! { if let StmtKind::Semi(first) = w[0].kind; if let StmtKind::Semi(second) = w[1].kind; - if !differing_macro_contexts(first.span, second.span); + if first.span.ctxt() == second.span.ctxt(); if let ExprKind::Assign(lhs0, rhs0, _) = first.kind; if let ExprKind::Assign(lhs1, rhs1, _) = second.kind; if eq_expr_value(cx, lhs0, rhs1); diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index e984048701341..9b9e25326f966 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,7 +1,7 @@ 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, path_to_local, usage::is_potentially_mutated}; +use clippy_utils::{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, Visitor}; @@ -238,8 +238,9 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> { if let Some(unwrappable) = self.unwrappables.iter() .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); + let span_ctxt = expr.span.ctxt(); + if unwrappable.branch.span.ctxt() == span_ctxt; + if unwrappable.check.span.ctxt() == span_ctxt; then { if call_to_unwrap == unwrappable.safe_to_unwrap { let is_entire_condition = unwrappable.is_entire_condition; diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index ed573ad905619..a2b10c12eb90c 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1,5 +1,4 @@ use crate::consts::{constant_context, constant_simple}; -use crate::differing_macro_contexts; use crate::source::snippet_opt; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHasher; @@ -186,7 +185,7 @@ impl HirEqInterExpr<'_, '_, '_> { #[allow(clippy::similar_names)] pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool { - if !self.inner.allow_side_effects && differing_macro_contexts(left.span, right.span) { + if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() { return false; } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index ed73364841c59..f10f130bc32be 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -134,13 +134,6 @@ macro_rules! extract_msrv_attr { }; } -/// Returns `true` if the two spans come from differing expansions (i.e., one is -/// from a macro and one isn't). -#[must_use] -pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool { - rhs.ctxt() != lhs.ctxt() -} - /// If the given expression is a local binding, find the initializer expression. /// If that initializer expression is another local binding, find its initializer again. /// This process repeats as long as possible (but usually no more than once). Initializer diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index 6c8a3dc418b15..36c454745ba06 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -235,7 +235,11 @@ Use the following functions to deal with macros: assert_eq!(in_external_macro(cx.sess(), match_span), true); ``` -- `differing_macro_contexts()`: returns true if the two given spans are not from the same context +- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it + +One thing `SpanContext` is useful for is to check if two spans are in the same context. For example, +in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some +expression with a different context from `a`. ```rust macro_rules! m { @@ -252,7 +256,7 @@ Use the following functions to deal with macros: // These spans are not from the same context // x.is_some() is from inside the macro // x.unwrap() is from outside the macro - assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true); + assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt()); ``` [TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html From 98c6381a387ac05e27c03ccfc64146bf4934f5c8 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 6 Jan 2022 09:31:38 -0600 Subject: [PATCH 18/67] Factor out single_segment_path --- clippy_lints/src/loops/single_element_loop.rs | 24 +++++++---------- clippy_lints/src/methods/chars_cmp.rs | 15 +++++------ .../src/methods/option_map_or_none.rs | 13 +++++----- clippy_lints/src/ranges.rs | 26 +++++-------------- clippy_utils/src/lib.rs | 8 ------ 5 files changed, 29 insertions(+), 57 deletions(-) diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs index 15f419e4410ca..36ecd83f7d643 100644 --- a/clippy_lints/src/loops/single_element_loop.rs +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -1,10 +1,9 @@ use super::SINGLE_ELEMENT_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::single_segment_path; -use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::source::{indent_of, snippet_with_applicability}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Pat}; use rustc_lint::LateContext; pub(super) fn check<'tcx>( @@ -16,24 +15,21 @@ pub(super) fn check<'tcx>( ) { let arg_expr = match arg.kind { ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg, - ExprKind::MethodCall(method, args, _) if args.len() == 1 && method.ident.name == rustc_span::sym::iter => { - &args[0] - }, + ExprKind::MethodCall(method, [arg], _) if method.ident.name == rustc_span::sym::iter => arg, _ => return, }; if_chain! { - if let PatKind::Binding(.., target, _) = pat.kind; if let ExprKind::Array([arg_expression]) = arg_expr.kind; - if let ExprKind::Path(ref list_item) = arg_expression.kind; - if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); if let ExprKind::Block(block, _) = body.kind; if !block.stmts.is_empty(); - then { - let mut block_str = snippet(cx, block.span, "..").into_owned(); + let mut applicability = Applicability::MachineApplicable; + let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability); + let arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability); + let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned(); block_str.remove(0); block_str.pop(); - + let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)); span_lint_and_sugg( cx, @@ -41,8 +37,8 @@ pub(super) fn check<'tcx>( expr.span, "for loop over a single element", "try", - format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), - Applicability::MachineApplicable + format!("{{\n{}let {} = &{};{}}}", indent, pat_snip, arg_snip, block_str), + applicability, ) } } diff --git a/clippy_lints/src/methods/chars_cmp.rs b/clippy_lints/src/methods/chars_cmp.rs index 514c411876551..2cf2c5641bf10 100644 --- a/clippy_lints/src/methods/chars_cmp.rs +++ b/clippy_lints/src/methods/chars_cmp.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{method_chain_args, single_segment_path}; +use clippy_utils::{method_chain_args, path_def_id}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_lint::Lint; -use rustc_middle::ty; -use rustc_span::sym; +use rustc_middle::ty::{self, DefIdTree}; /// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints. pub(super) fn check( @@ -19,11 +18,9 @@ pub(super) fn check( ) -> bool { if_chain! { if let Some(args) = method_chain_args(info.chain, chain_methods); - if let hir::ExprKind::Call(fun, arg_char) = info.other.kind; - if arg_char.len() == 1; - if let hir::ExprKind::Path(ref qpath) = fun.kind; - if let Some(segment) = single_segment_path(qpath); - if segment.ident.name == sym::Some; + if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind; + if let Some(id) = path_def_id(cx, fun).and_then(|ctor_id| cx.tcx.parent(ctor_id)); + if Some(id) == cx.tcx.lang_items().option_some_variant(); then { let mut applicability = Applicability::MachineApplicable; let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs(); @@ -42,7 +39,7 @@ pub(super) fn check( if info.eq { "" } else { "!" }, snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), suggest, - snippet_with_applicability(cx, arg_char[0].span, "..", &mut applicability)), + snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)), applicability, ); diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 5e5c1038e829e..bdf8cea120739 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_lang_ctor, single_segment_path}; +use clippy_utils::{is_lang_ctor, path_def_id}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; +use rustc_middle::ty::DefIdTree; use rustc_span::symbol::sym; use super::OPTION_MAP_OR_NONE; @@ -76,13 +77,11 @@ pub(super) fn check<'tcx>( if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind; let arg_snippet = snippet(cx, span, ".."); let body = cx.tcx.hir().body(id); - if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value); - if arg_char.len() == 1; - if let hir::ExprKind::Path(ref qpath) = func.kind; - if let Some(segment) = single_segment_path(qpath); - if segment.ident.name == sym::Some; + if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value); + if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id)); + if Some(id) == cx.tcx.lang_items().option_some_variant(); then { - let func_snippet = snippet(cx, arg_char[0].span, ".."); + let func_snippet = snippet(cx, arg_char.span, ".."); let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ `map(..)` instead"; return span_lint_and_sugg( diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 8065ed3ffc55b..e213c208794c1 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -2,19 +2,18 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, single_segment_path}; +use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local}; use clippy_utils::{higher, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath}; +use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; use rustc_span::sym; -use rustc_span::symbol::Ident; use std::cmp::Ordering; declare_clippy_lint! { @@ -220,12 +219,12 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' _ => return, }; // value, name, order (higher/lower), inclusiveness - if let (Some((lval, lname, name_span, lval_span, lord, linc)), Some((rval, rname, _, rval_span, rord, rinc))) = + if let (Some((lval, lid, name_span, lval_span, lord, linc)), Some((rval, rid, _, rval_span, rord, rinc))) = (check_range_bounds(cx, l), check_range_bounds(cx, r)) { // we only lint comparisons on the same name and with different // direction - if lname != rname || lord == rord { + if lid != rid || lord == rord { return; } let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval); @@ -293,7 +292,7 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<' } } -fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, Ident, Span, Span, Ordering, bool)> { +fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, HirId, Span, Span, Ordering, bool)> { if let ExprKind::Binary(ref op, l, r) = ex.kind { let (inclusive, ordering) = match op.node { BinOpKind::Gt => (false, Ordering::Greater), @@ -302,11 +301,11 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, BinOpKind::Le => (true, Ordering::Less), _ => return None, }; - if let Some(id) = match_ident(l) { + if let Some(id) = path_to_local(l) { if let Some((c, _)) = constant(cx, cx.typeck_results(), r) { return Some((c, id, l.span, r.span, ordering, inclusive)); } - } else if let Some(id) = match_ident(r) { + } else if let Some(id) = path_to_local(r) { if let Some((c, _)) = constant(cx, cx.typeck_results(), l) { return Some((c, id, r.span, l.span, ordering.reverse(), inclusive)); } @@ -315,17 +314,6 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, None } -fn match_ident(e: &Expr<'_>) -> Option { - if let ExprKind::Path(ref qpath) = e.kind { - if let Some(seg) = single_segment_path(qpath) { - if seg.args.is_none() { - return Some(seg.ident); - } - } - } - None -} - fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) { if_chain! { if path.ident.as_str() == "zip"; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f10f130bc32be..3df7af601cf7f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -326,14 +326,6 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { - match *path { - QPath::Resolved(_, path) => path.segments.get(0), - QPath::TypeRelative(_, seg) => Some(seg), - QPath::LangItem(..) => None, - } -} - /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the /// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from /// `QPath::Resolved.1.res.opt_def_id()`. From 3771fe4adeafc0825a562b5b542ce1be0350b7dd Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 6 Jan 2022 09:35:25 -0600 Subject: [PATCH 19/67] Factor out expr_path_res --- clippy_lints/src/misc.rs | 7 +++---- clippy_lints/src/ptr.rs | 7 ++++--- clippy_utils/src/lib.rs | 17 ++--------------- clippy_utils/src/ty.rs | 4 ++-- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 3918bdbdf4387..ac82dd306a528 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -20,8 +20,8 @@ use rustc_span::symbol::sym; use clippy_utils::consts::{constant, Constant}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats, - last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq, + get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats, + last_path_segment, match_any_def_paths, path_def_id, paths, unsext, SpanlessEq, }; declare_clippy_lint! { @@ -583,8 +583,7 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: ) }, ExprKind::Call(path, [arg]) => { - if expr_path_res(cx, path) - .opt_def_id() + if path_def_id(cx, path) .and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM])) .is_some() { diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index b5d65542de0bf..26717c0c0fdf6 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -4,7 +4,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the use clippy_utils::source::snippet_opt; use clippy_utils::ty::expr_sig; use clippy_utils::{ - expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths, + expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, is_lint_allowed, match_any_diagnostic_items, + path_def_id, path_to_local, paths, paths, }; use if_chain::if_chain; use rustc_errors::Applicability; @@ -665,8 +666,8 @@ fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutabil fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ExprKind::Call(pathexp, []) = expr.kind { - expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| { - match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some() + path_def_id(cx, pathexp).map_or(false, |id| { + matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut)) }) } else { false diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 3df7af601cf7f..eeadc6d4ddaa1 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -357,15 +357,6 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool { } } -/// If the expression is a path, resolve it. Otherwise, return `Res::Err`. -pub fn expr_path_res(cx: &LateContext<'_>, expr: &Expr<'_>) -> Res { - if let ExprKind::Path(p) = &expr.kind { - cx.qpath_res(p, expr.hir_id) - } else { - Res::Err - } -} - /// Resolves the path to a `DefId` and checks if it matches the given path. pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool { cx.qpath_res(path, hir_id) @@ -377,17 +368,13 @@ pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, /// /// Please use `is_expr_diagnostic_item` if the target is a diagnostic item. pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool { - expr_path_res(cx, expr) - .opt_def_id() - .map_or(false, |id| match_def_path(cx, id, segments)) + path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments)) } /// If the expression is a path, resolves it to a `DefId` and checks if it matches the given /// diagnostic item. pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool { - expr_path_res(cx, expr) - .opt_def_id() - .map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) + path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id)) } /// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index d057da73302a2..aa3ea2d23da78 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -20,7 +20,7 @@ use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::normalize::AtExt; use std::iter; -use crate::{expr_path_res, match_def_path, must_use_attr}; +use crate::{match_def_path, must_use_attr, path_res}; // Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { @@ -443,7 +443,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { - if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = expr_path_res(cx, expr) { + if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) { Some(ExprFnSig::Sig(cx.tcx.fn_sig(id))) } else { let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs(); From ece7fa4f9c4e934a3176de9abcf9ab3b71a6dd7c Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 6 Jan 2022 09:39:46 -0600 Subject: [PATCH 20/67] Factor out match_any_diagnostic_items --- clippy_lints/src/ptr.rs | 5 +--- .../transmute/unsound_collection_transmute.rs | 30 ++++++++++--------- clippy_utils/src/lib.rs | 10 +------ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 26717c0c0fdf6..6decd18b0a3ec 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -3,10 +3,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::expr_sig; -use clippy_utils::{ - expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, is_lint_allowed, match_any_diagnostic_items, - path_def_id, path_to_local, paths, paths, -}; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; diff --git a/clippy_lints/src/transmute/unsound_collection_transmute.rs b/clippy_lints/src/transmute/unsound_collection_transmute.rs index 2ce8d4031d77c..2d67401a15f2d 100644 --- a/clippy_lints/src/transmute/unsound_collection_transmute.rs +++ b/clippy_lints/src/transmute/unsound_collection_transmute.rs @@ -1,29 +1,31 @@ use super::utils::is_layout_incompatible; use super::UNSOUND_COLLECTION_TRANSMUTE; use clippy_utils::diagnostics::span_lint; -use clippy_utils::match_any_diagnostic_items; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty}; -use rustc_span::symbol::{sym, Symbol}; - -// used to check for UNSOUND_COLLECTION_TRANSMUTE -static COLLECTIONS: &[Symbol] = &[ - sym::Vec, - sym::VecDeque, - sym::BinaryHeap, - sym::BTreeSet, - sym::BTreeMap, - sym::HashSet, - sym::HashMap, -]; +use rustc_span::symbol::sym; /// Checks for `unsound_collection_transmute` lint. /// Returns `true` if it's triggered, otherwise returns `false`. pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { match (&from_ty.kind(), &to_ty.kind()) { (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => { - if from_adt.did != to_adt.did || match_any_diagnostic_items(cx, to_adt.did, COLLECTIONS).is_none() { + if from_adt.did != to_adt.did { + return false; + } + if !matches!( + cx.tcx.get_diagnostic_name(to_adt.did), + Some( + sym::BTreeMap + | sym::BTreeSet + | sym::BinaryHeap + | sym::HashMap + | sym::HashSet + | sym::Vec + | sym::VecDeque + ) + ) { return false; } if from_substs diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index eeadc6d4ddaa1..62066987232a6 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1615,7 +1615,7 @@ pub fn match_function_call<'tcx>( /// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if /// any. /// -/// Please use `match_any_diagnostic_items` if the targets are all diagnostic items. +/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items. pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option { let search_path = cx.get_def_path(did); paths @@ -1623,14 +1623,6 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) .position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied())) } -/// Checks if the given `DefId` matches any of provided diagnostic items. Returns the index of -/// matching path, if any. -pub fn match_any_diagnostic_items(cx: &LateContext<'_>, def_id: DefId, diag_items: &[Symbol]) -> Option { - diag_items - .iter() - .position(|item| cx.tcx.is_diagnostic_item(*item, def_id)) -} - /// Checks if the given `DefId` matches the path. pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool { // We should probably move to Symbols in Clippy as well rather than interning every time. From bd583d91a1bf3717f724a38531e142aa4a9ee6cc Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Thu, 6 Jan 2022 12:41:17 -0600 Subject: [PATCH 21/67] Factor out is_qpath_def_path --- clippy_lints/src/infinite_iter.rs | 12 ++-- clippy_lints/src/matches.rs | 62 +++++++++++-------- .../methods/manual_saturating_arithmetic.rs | 8 +-- clippy_utils/src/lib.rs | 10 +-- 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 3008e86ef8b29..b6badef02f58a 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; -use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths}; +use clippy_utils::{get_trait_def_id, higher, match_def_path, path_def_id, paths}; use rustc_hir::{BorrowKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -167,13 +167,9 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { }, ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)), ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e), - ExprKind::Call(path, _) => { - if let ExprKind::Path(ref qpath) = path.kind { - is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into() - } else { - Finite - } - }, + ExprKind::Call(path, _) => path_def_id(cx, path) + .map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT)) + .into(), ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(), _ => Finite, } diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 2579404fb18cc..81ae4dacdf615 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -1765,22 +1765,22 @@ where mod redundant_pattern_match { use super::REDUNDANT_PATTERN_MATCHING; use clippy_utils::diagnostics::span_lint_and_then; - use clippy_utils::higher; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type}; - use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; + use clippy_utils::{higher, match_def_path}; + use clippy_utils::{is_lang_ctor, is_trait_method, paths}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; - use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; + use rustc_hir::LangItem::{OptionNone, PollPending}; use rustc_hir::{ intravisit::{walk_expr, Visitor}, Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp, }; use rustc_lint::LateContext; - use rustc_middle::ty::{self, subst::GenericArgKind, Ty}; + use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; use rustc_span::sym; pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -1956,28 +1956,31 @@ mod redundant_pattern_match { has_else: bool, ) { // also look inside refs - let mut kind = &let_pat.kind; // if we have &None for example, peel it so we can detect "if let None = x" - if let PatKind::Ref(inner, _mutability) = kind { - kind = &inner.kind; - } + let check_pat = match let_pat.kind { + PatKind::Ref(inner, _mutability) => inner, + _ => let_pat, + }; let op_ty = cx.typeck_results().expr_ty(let_expr); // Determine which function should be used, and the type contained by the corresponding // variant. - let (good_method, inner_ty) = match kind { - PatKind::TupleStruct(ref path, [sub_pat], _) => { + let (good_method, inner_ty) = match check_pat.kind { + PatKind::TupleStruct(ref qpath, [sub_pat], _) => { if let PatKind::Wild = sub_pat.kind { - if is_lang_ctor(cx, path, ResultOk) { + let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); + let Some(id) = res.opt_def_id().and_then(|ctor_id| cx.tcx.parent(ctor_id)) else { return }; + let lang_items = cx.tcx.lang_items(); + if Some(id) == lang_items.result_ok_variant() { ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, ResultErr) { + } else if Some(id) == lang_items.result_err_variant() { ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, OptionSome) { + } else if Some(id) == lang_items.option_some_variant() { ("is_some()", op_ty) - } else if is_lang_ctor(cx, path, PollReady) { + } else if Some(id) == lang_items.poll_ready_variant() { ("is_ready()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) { + } else if match_def_path(cx, id, &paths::IPADDR_V4) { ("is_ipv4()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) { + } else if match_def_path(cx, id, &paths::IPADDR_V6) { ("is_ipv6()", op_ty) } else { return; @@ -2177,17 +2180,22 @@ mod redundant_pattern_match { should_be_left: &'a str, should_be_right: &'a str, ) -> Option<&'a str> { - let body_node_pair = if is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_left) - && is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_right) - { - (&(*arms[0].body).kind, &(*arms[1].body).kind) - } else if is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_left) - && is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_right) - { - (&(*arms[1].body).kind, &(*arms[0].body).kind) - } else { - return None; - }; + let left_id = cx + .typeck_results() + .qpath_res(path_left, arms[0].pat.hir_id) + .opt_def_id()?; + let right_id = cx + .typeck_results() + .qpath_res(path_right, arms[1].pat.hir_id) + .opt_def_id()?; + let body_node_pair = + if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[0].body).kind, &(*arms[1].body).kind) + } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[1].body).kind, &(*arms[0].body).kind) + } else { + return None; + }; match body_node_pair { (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index 4307cbf00507a..0fe510beaa07e 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_qpath_def_path; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{match_def_path, path_def_id}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; @@ -93,12 +93,12 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option, segments: &[&str]) -> bool { } } -/// Resolves the path to a `DefId` and checks if it matches the given path. -pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool { - cx.qpath_res(path, hir_id) - .opt_def_id() - .map_or(false, |id| match_def_path(cx, id, segments)) -} - /// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path. /// /// Please use `is_expr_diagnostic_item` if the target is a diagnostic item. @@ -1775,8 +1768,7 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool match expr.kind { ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)), - ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY), - _ => false, + _ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)), } } From b7000b2a5363430af917f7a4df17d1b7a10f5cc4 Mon Sep 17 00:00:00 2001 From: Georgy Komarov Date: Sat, 15 Jan 2022 12:27:24 +0300 Subject: [PATCH 22/67] Add `default_union_representation` lint Closes #8235 --- CHANGELOG.md | 1 + .../src/default_union_representation.rs | 105 ++++++++++++++++++ clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.rs | 2 + tests/ui/default_union_representation.rs | 78 +++++++++++++ tests/ui/default_union_representation.stderr | 48 ++++++++ 7 files changed, 236 insertions(+) create mode 100644 clippy_lints/src/default_union_representation.rs create mode 100644 tests/ui/default_union_representation.rs create mode 100644 tests/ui/default_union_representation.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4da9a382792..60f84c769b73f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2930,6 +2930,7 @@ Released 2018-09-13 [`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const [`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback [`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access +[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation [`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 diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs new file mode 100644 index 0000000000000..9b5da0bd8a660 --- /dev/null +++ b/clippy_lints/src/default_union_representation.rs @@ -0,0 +1,105 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir::{self as hir, HirId, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::layout::LayoutOf; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; +use rustc_typeck::hir_ty_to_ty; + +declare_clippy_lint! { + /// ### What it does + /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute). + /// + /// ### Why is this bad? + /// Unions in Rust have unspecified layout by default, despite many people thinking that they + /// lay out each field at the start of the union (like C does). That is, there are no guarantees + /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly + /// specified layout. These cases may lead to undefined behavior in unsafe blocks. + /// + /// ### Example + /// ```rust + /// union Foo { + /// a: i32, + /// b: u32, + /// } + /// + /// fn main() { + /// let _x: u32 = unsafe { + /// Foo { a: 0_i32 }.b // Undefined behaviour: `b` is allowed to be padding + /// }; + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[repr(C)] + /// union Foo { + /// a: i32, + /// b: u32, + /// } + /// + /// fn main() { + /// let _x: u32 = unsafe { + /// Foo { a: 0_i32 }.b // Now defined behaviour, this is just an i32 -> u32 transmute + /// }; + /// } + /// ``` + #[clippy::version = "1.60.0"] + pub DEFAULT_UNION_REPRESENTATION, + restriction, + "unions without a `#[repr(C)]` attribute" +} +declare_lint_pass!(DefaultUnionRepresentation => [DEFAULT_UNION_REPRESENTATION]); + +impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if is_union_with_two_non_zst_fields(cx, item) && !has_c_repr_attr(cx, item.hir_id()) { + span_lint_and_help( + cx, + DEFAULT_UNION_REPRESENTATION, + item.span, + "this union has the default representation", + None, + &format!( + "consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout", + cx.tcx.def_path_str(item.def_id.to_def_id()) + ), + ); + } + } +} + +/// Returns true if the given item is a union with at least two non-ZST fields. +fn is_union_with_two_non_zst_fields(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if let ItemKind::Union(data, _) = &item.kind { + data.fields().iter().filter(|f| !is_zst(cx, f.ty)).count() >= 2 + } else { + false + } +} + +fn is_zst(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>) -> bool { + if hir_ty.span.from_expansion() { + return false; + } + let ty = hir_ty_to_ty(cx.tcx, hir_ty); + if let Ok(layout) = cx.layout_of(ty) { + layout.is_zst() + } else { + false + } +} + +fn has_c_repr_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { + cx.tcx.hir().attrs(hir_id).iter().any(|attr| { + if attr.has_name(sym::repr) { + if let Some(items) = attr.meta_item_list() { + for item in items { + if item.is_word() && matches!(item.name_or_empty(), sym::C) { + return true; + } + } + } + } + false + }) +} diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 746bdb19c3d92..c768df1554c07 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -92,6 +92,7 @@ store.register_lints(&[ default::DEFAULT_TRAIT_ACCESS, default::FIELD_REASSIGN_WITH_DEFAULT, default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK, + default_union_representation::DEFAULT_UNION_REPRESENTATION, dereference::EXPLICIT_DEREF_METHODS, dereference::NEEDLESS_BORROW, dereference::REF_BINDING_TO_REFERENCE, diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index e7e2798da7da9..5a89fdb05a990 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -12,6 +12,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(create_dir::CREATE_DIR), LintId::of(dbg_macro::DBG_MACRO), LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK), + LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION), LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS), LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE), LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 79e9882fef4c4..1dfc03dc34bb1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -189,6 +189,7 @@ mod create_dir; mod dbg_macro; mod default; mod default_numeric_fallback; +mod default_union_representation; mod dereference; mod derivable_impls; mod derive; @@ -860,6 +861,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv))); store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); + store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/default_union_representation.rs b/tests/ui/default_union_representation.rs new file mode 100644 index 0000000000000..93b2d33da2cd2 --- /dev/null +++ b/tests/ui/default_union_representation.rs @@ -0,0 +1,78 @@ +#![feature(transparent_unions)] +#![warn(clippy::default_union_representation)] + +union NoAttribute { + a: i32, + b: u32, +} + +#[repr(C)] +union ReprC { + a: i32, + b: u32, +} + +#[repr(packed)] +union ReprPacked { + a: i32, + b: u32, +} + +#[repr(C, packed)] +union ReprCPacked { + a: i32, + b: u32, +} + +#[repr(C, align(32))] +union ReprCAlign { + a: i32, + b: u32, +} + +#[repr(align(32))] +union ReprAlign { + a: i32, + b: u32, +} + +union SingleZST { + f0: (), +} +union ZSTsAndField1 { + f0: u32, + f1: (), + f2: (), + f3: (), +} +union ZSTsAndField2 { + f0: (), + f1: (), + f2: u32, + f3: (), +} +union ZSTAndTwoFields { + f0: u32, + f1: u64, + f2: (), +} + +#[repr(C)] +union CZSTAndTwoFields { + f0: u32, + f1: u64, + f2: (), +} + +#[repr(transparent)] +union ReprTransparent { + a: i32, +} + +#[repr(transparent)] +union ReprTransparentZST { + a: i32, + b: (), +} + +fn main() {} diff --git a/tests/ui/default_union_representation.stderr b/tests/ui/default_union_representation.stderr new file mode 100644 index 0000000000000..138884af868c7 --- /dev/null +++ b/tests/ui/default_union_representation.stderr @@ -0,0 +1,48 @@ +error: this union has the default representation + --> $DIR/default_union_representation.rs:4:1 + | +LL | / union NoAttribute { +LL | | a: i32, +LL | | b: u32, +LL | | } + | |_^ + | + = note: `-D clippy::default-union-representation` implied by `-D warnings` + = help: consider annotating `NoAttribute` with `#[repr(C)]` to explicitly specify memory layout + +error: this union has the default representation + --> $DIR/default_union_representation.rs:16:1 + | +LL | / union ReprPacked { +LL | | a: i32, +LL | | b: u32, +LL | | } + | |_^ + | + = help: consider annotating `ReprPacked` with `#[repr(C)]` to explicitly specify memory layout + +error: this union has the default representation + --> $DIR/default_union_representation.rs:34:1 + | +LL | / union ReprAlign { +LL | | a: i32, +LL | | b: u32, +LL | | } + | |_^ + | + = help: consider annotating `ReprAlign` with `#[repr(C)]` to explicitly specify memory layout + +error: this union has the default representation + --> $DIR/default_union_representation.rs:54:1 + | +LL | / union ZSTAndTwoFields { +LL | | f0: u32, +LL | | f1: u64, +LL | | f2: (), +LL | | } + | |_^ + | + = help: consider annotating `ZSTAndTwoFields` with `#[repr(C)]` to explicitly specify memory layout + +error: aborting due to 4 previous errors + From 4119f60c1f422d84fb53bb7d3e73bfdfc2b09add Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Mon, 18 Oct 2021 00:41:57 +0100 Subject: [PATCH 23/67] Create `core::fmt::ArgumentV1` with generics instead of fn pointer --- clippy_utils/src/macros.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index b7a242cf90a43..a75f6b86a9bae 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -339,15 +339,13 @@ impl<'tcx> FormatArgsExpn<'tcx> { expr_visitor_no_bodies(|e| { // if we're still inside of the macro definition... if e.span.ctxt() == expr.span.ctxt() { - // ArgumnetV1::new(, ::fmt) + // ArgumnetV1::new_() if_chain! { - if let ExprKind::Call(callee, [val, fmt_path]) = e.kind; + if let ExprKind::Call(callee, [val]) = e.kind; if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind; - if seg.ident.name == sym::new; if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind; if path.segments.last().unwrap().ident.name == sym::ArgumentV1; - if let ExprKind::Path(QPath::Resolved(_, path)) = fmt_path.kind; - if let [.., fmt_trait, _fmt] = path.segments; + if seg.ident.name.as_str().starts_with("new_"); then { let val_idx = if_chain! { if val.span.ctxt() == expr.span.ctxt(); @@ -361,7 +359,19 @@ impl<'tcx> FormatArgsExpn<'tcx> { formatters.len() } }; - formatters.push((val_idx, fmt_trait.ident.name)); + let fmt_trait = match seg.ident.name.as_str() { + "new_display" => "Display", + "new_debug" => "Debug", + "new_lower_exp" => "LowerExp", + "new_upper_exp" => "UpperExp", + "new_octal" => "Octal", + "new_pointer" => "Pointer", + "new_binary" => "Binary", + "new_lower_hex" => "LowerHex", + "new_upper_hex" => "UpperHex", + _ => unreachable!(), + }; + formatters.push((val_idx, Symbol::intern(fmt_trait))); } } if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind { From bee482b44ca318babda21f285be6021650cfb3ac Mon Sep 17 00:00:00 2001 From: Gary Guo Date: Sat, 8 Jan 2022 01:56:13 +0000 Subject: [PATCH 24/67] Change index_refutable_slice to use FxIndexMap This will prevent unstable order when HirIds are pertubated. --- clippy_lints/src/index_refutable_slice.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 4615122bbf9e5..8819fa1bef841 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -4,7 +4,7 @@ use clippy_utils::higher::IfLet; use clippy_utils::ty::is_copy; use clippy_utils::{is_expn_of, is_lint_allowed, meets_msrv, msrvs, path_to_local}; use if_chain::if_chain; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{self, Visitor}; @@ -92,9 +92,9 @@ impl<'tcx> LateLintPass<'tcx> for IndexRefutableSlice { extract_msrv_attr!(LateContext); } -fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxHashMap { +fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap { let mut removed_pat: FxHashSet = FxHashSet::default(); - let mut slices: FxHashMap = FxHashMap::default(); + let mut slices: FxIndexMap = FxIndexMap::default(); pat.walk_always(|pat| { if let hir::PatKind::Binding(binding, value_hir_id, ident, sub_pat) = pat.kind { // We'll just ignore mut and ref mut for simplicity sake right now @@ -208,10 +208,10 @@ impl SliceLintInformation { fn filter_lintable_slices<'a, 'tcx>( cx: &'a LateContext<'tcx>, - slice_lint_info: FxHashMap, + slice_lint_info: FxIndexMap, max_suggested_slice: u64, scope: &'tcx hir::Expr<'tcx>, -) -> FxHashMap { +) -> FxIndexMap { let mut visitor = SliceIndexLintingVisitor { cx, slice_lint_info, @@ -225,7 +225,7 @@ fn filter_lintable_slices<'a, 'tcx>( struct SliceIndexLintingVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - slice_lint_info: FxHashMap, + slice_lint_info: FxIndexMap, max_suggested_slice: u64, } From e7922f745dd617dbdef1f1c57d635b42e316cd92 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sat, 29 Jan 2022 10:50:18 -0500 Subject: [PATCH 25/67] Format `if_chain` invocations in clippy_utils --- clippy_utils/src/consts.rs | 21 ++++++++++----------- clippy_utils/src/higher.rs | 11 ++++------- clippy_utils/src/lib.rs | 16 ++++++---------- clippy_utils/src/macros.rs | 6 +++++- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 34c5af848a6db..3f604d5166bf7 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -331,17 +331,16 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).collect(); if let ["core", "num", int_impl, "max_value"] = *def_path; then { - let value = match int_impl { - "" => i8::MAX as u128, - "" => i16::MAX as u128, - "" => i32::MAX as u128, - "" => i64::MAX as u128, - "" => i128::MAX as u128, - _ => return None, - }; - Some(Constant::Int(value)) - } - else { + let value = match int_impl { + "" => i8::MAX as u128, + "" => i16::MAX as u128, + "" => i32::MAX as u128, + "" => i64::MAX as u128, + "" => i128::MAX as u128, + _ => return None, + }; + Some(Constant::Int(value)) + } else { None } } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 160a51740cd7c..2095fc966c5dc 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -284,8 +284,7 @@ impl<'a> VecArgs<'a> { return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 { // `vec![elem; size]` case Some(VecArgs::Repeat(&args[0], &args[1])) - } - else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 { + } 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(boxed) = args[0].kind; @@ -296,11 +295,9 @@ impl<'a> VecArgs<'a> { } None - } - else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() { + } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() { Some(VecArgs::Vec(&[])) - } - else { + } else { None }; } @@ -456,7 +453,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - if let ExprKind::Lit(lit) = &arg.kind; if let LitKind::Int(num, _) = lit.node; then { - return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?)) + return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?)); } } return Some(VecInitKind::WithExprCapacity(arg.hir_id)); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a2f1f4696513e..c1ef0031e1f31 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -603,7 +603,9 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) -> if parent_impl != CRATE_DEF_ID; if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl); if let hir::ItemKind::Impl(impl_) = &item.kind; - then { return impl_.of_trait.as_ref(); } + then { + return impl_.of_trait.as_ref(); + } } None } @@ -713,12 +715,7 @@ pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) -> 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 - } + then { true } else { false } } } @@ -1553,8 +1550,7 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc if arms.len() == 2; if arms[0].guard.is_none(); if arms[1].guard.is_none(); - if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || - (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])); + if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0])); then { return Some(expr); } @@ -1644,7 +1640,7 @@ pub fn match_function_call<'tcx>( if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id(); if match_def_path(cx, fun_def_id, path); then { - return Some(args) + return Some(args); } }; None diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index b7a242cf90a43..5bc353bdd8928 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -452,7 +452,11 @@ impl<'tcx> FormatArgsExpn<'tcx> { if let Ok(i) = usize::try_from(position); if let Some(&(j, format_trait)) = self.formatters.get(i); then { - Some(FormatArgsArg { value: self.value_args[j], format_trait, spec: Some(spec) }) + Some(FormatArgsArg { + value: self.value_args[j], + format_trait, + spec: Some(spec), + }) } else { None } From 66bb7263b50f47878784902b5a208b7b26b74bc1 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 29 Jan 2022 09:45:47 -0500 Subject: [PATCH 26/67] Don't lint `ptr_arg` for `&mut _` types in trait items --- clippy_lints/src/ptr.rs | 20 ++++++++++++++------ tests/ui/ptr_arg.rs | 6 ++++++ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index b5d65542de0bf..77bf5f002f7c9 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -153,7 +153,9 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { cx.tcx.fn_sig(item.def_id).skip_binder().inputs(), sig.decl.inputs, &[], - ) { + ) + .filter(|arg| arg.mutability() == Mutability::Not) + { span_lint_and_sugg( cx, PTR_ARG, @@ -170,10 +172,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { let hir = cx.tcx.hir(); let mut parents = hir.parent_iter(body.value.hir_id); - let (item_id, decl) = match parents.next() { + let (item_id, decl, is_trait_item) = match parents.next() { Some((_, Node::Item(i))) => { if let ItemKind::Fn(sig, ..) = &i.kind { - (i.def_id, sig.decl) + (i.def_id, sig.decl, false) } else { return; } @@ -185,14 +187,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { return; } if let ImplItemKind::Fn(sig, _) = &i.kind { - (i.def_id, sig.decl) + (i.def_id, sig.decl, false) } else { return; } }, Some((_, Node::TraitItem(i))) => { if let TraitItemKind::Fn(sig, _) = &i.kind { - (i.def_id, sig.decl) + (i.def_id, sig.decl, true) } else { return; } @@ -202,7 +204,9 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { check_mut_from_ref(cx, decl); let sig = cx.tcx.fn_sig(item_id).skip_binder(); - let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params).collect(); + let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params) + .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) + .collect(); let results = check_ptr_arg_usage(cx, body, &lint_args); for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) { @@ -318,6 +322,10 @@ impl PtrArg<'_> { self.deref_ty.argless_str(), ) } + + fn mutability(&self) -> Mutability { + self.ref_prefix.mutability + } } struct RefPrefix { diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index ed72423780821..00b99da2631c6 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -180,3 +180,9 @@ fn dyn_fn_requires_vec(v: &Vec, f: &dyn Fn(&Vec)) { // No error for types behind an alias (#7699) type A = Vec; fn aliased(a: &A) {} + +// Issue #8366 +pub trait Trait { + fn f(v: &mut Vec); + fn f2(v: &mut Vec) {} +} From 8ccd26462032427741f753424702e78e1050e96b Mon Sep 17 00:00:00 2001 From: tamaron Date: Sun, 30 Jan 2022 12:56:07 +0900 Subject: [PATCH 27/67] modify code --- clippy_lints/src/methods/mod.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 18d4867b7eb51..7847adce0f121 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2402,10 +2402,13 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { implicit_clone::check(cx, name, expr, recv); }, - ("unwrap", []) => match method_call(recv) { - Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false), - Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true), - _ => unwrap_used::check(cx, expr, recv), + ("unwrap", []) => { + match method_call(recv) { + Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false), + Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true), + _ => {}, + } + unwrap_used::check(cx, expr, recv); }, ("unwrap_or", [u_arg]) => match method_call(recv) { Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => { From ba80d45b3ccbae00b55bed1b30ef5839f5d6855e Mon Sep 17 00:00:00 2001 From: tamaron Date: Sun, 30 Jan 2022 12:58:49 +0900 Subject: [PATCH 28/67] update testsuite --- tests/ui/get_unwrap.fixed | 2 +- tests/ui/get_unwrap.rs | 2 +- tests/ui/get_unwrap.stderr | 145 ++++++++++++++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/tests/ui/get_unwrap.fixed b/tests/ui/get_unwrap.fixed index 924c02a4054d4..94b554ad6cb49 100644 --- a/tests/ui/get_unwrap.fixed +++ b/tests/ui/get_unwrap.fixed @@ -1,6 +1,6 @@ // run-rustfix #![allow(unused_mut, clippy::from_iter_instead_of_collect)] -#![deny(clippy::get_unwrap)] +#![deny(clippy::get_unwrap, clippy::unwrap_used)] use std::collections::BTreeMap; use std::collections::HashMap; diff --git a/tests/ui/get_unwrap.rs b/tests/ui/get_unwrap.rs index c0c37bb720663..9da892bb1aa7d 100644 --- a/tests/ui/get_unwrap.rs +++ b/tests/ui/get_unwrap.rs @@ -1,6 +1,6 @@ // run-rustfix #![allow(unused_mut, clippy::from_iter_instead_of_collect)] -#![deny(clippy::get_unwrap)] +#![deny(clippy::get_unwrap, clippy::unwrap_used)] use std::collections::BTreeMap; use std::collections::HashMap; diff --git a/tests/ui/get_unwrap.stderr b/tests/ui/get_unwrap.stderr index 76a098df82aa1..5b02e54b17c6b 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -7,80 +7,221 @@ LL | let _ = boxed_slice.get(1).unwrap(); note: the lint level is defined here --> $DIR/get_unwrap.rs:3:9 | -LL | #![deny(clippy::get_unwrap)] +LL | #![deny(clippy::get_unwrap, clippy::unwrap_used)] | ^^^^^^^^^^^^^^^^^^ +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:34:17 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/get_unwrap.rs:3:29 + | +LL | #![deny(clippy::get_unwrap, clippy::unwrap_used)] + | ^^^^^^^^^^^^^^^^^^^ + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:35:17 | LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:35:17 + | +LL | let _ = some_slice.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:36:17 | LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:36:17 + | +LL | let _ = some_vec.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:37:17 | LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:37:17 + | +LL | let _ = some_vecdeque.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:38:17 | LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:38:17 + | +LL | let _ = some_hashmap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:39:17 | LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:39:17 + | +LL | let _ = some_btreemap.get(&1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:40:17 + | +LL | let _ = false_positive.get(0).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:42:21 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:42:22 + | +LL | let _: u8 = *boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:47:9 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:47:10 + | +LL | *boxed_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:48:9 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:48:10 + | +LL | *some_slice.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:49:9 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:49:10 + | +LL | *some_vec.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:50:9 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:50:10 + | +LL | *some_vecdeque.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:52:10 + | +LL | *some_hashmap.get_mut(&1).unwrap() = 'b'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:53:10 + | +LL | *some_btreemap.get_mut(&1).unwrap() = 'b'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:54:10 + | +LL | *false_positive.get_mut(0).unwrap() = 1; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:59:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:59:17 + | +LL | let _ = some_vec.get(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise --> $DIR/get_unwrap.rs:60:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` -error: aborting due to 13 previous errors +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:60:17 + | +LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message + +error: aborting due to 30 previous errors From 1a2364e534b75377c9e5727b29dca5017596bb1f Mon Sep 17 00:00:00 2001 From: tamaron Date: Sun, 30 Jan 2022 14:59:25 +0900 Subject: [PATCH 29/67] fix code --- clippy_lints/src/methods/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 7847adce0f121..5b4a152afd5da 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2404,8 +2404,12 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio }, ("unwrap", []) => { match method_call(recv) { - Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false), - Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true), + Some(("get", [recv, get_arg], _)) => { + get_unwrap::check(cx, expr, recv, get_arg, false); + }, + Some(("get_mut", [recv, get_arg], _)) => { + get_unwrap::check(cx, expr, recv, get_arg, true); + }, _ => {}, } unwrap_used::check(cx, expr, recv); From df6ef6059bb977cf57223495dde31d346b22d5f9 Mon Sep 17 00:00:00 2001 From: tamaron Date: Sun, 30 Jan 2022 14:59:46 +0900 Subject: [PATCH 30/67] update get_unwrap.rs --- tests/ui/get_unwrap.fixed | 4 ++- tests/ui/get_unwrap.rs | 4 ++- tests/ui/get_unwrap.stderr | 70 ++++++++++++++++++-------------------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/tests/ui/get_unwrap.fixed b/tests/ui/get_unwrap.fixed index 94b554ad6cb49..e9749e85208a3 100644 --- a/tests/ui/get_unwrap.fixed +++ b/tests/ui/get_unwrap.fixed @@ -1,6 +1,8 @@ // run-rustfix + #![allow(unused_mut, clippy::from_iter_instead_of_collect)] -#![deny(clippy::get_unwrap, clippy::unwrap_used)] +#![warn(clippy::unwrap_used)] +#![deny(clippy::get_unwrap)] use std::collections::BTreeMap; use std::collections::HashMap; diff --git a/tests/ui/get_unwrap.rs b/tests/ui/get_unwrap.rs index 9da892bb1aa7d..86c6bf533cd35 100644 --- a/tests/ui/get_unwrap.rs +++ b/tests/ui/get_unwrap.rs @@ -1,6 +1,8 @@ // run-rustfix + #![allow(unused_mut, clippy::from_iter_instead_of_collect)] -#![deny(clippy::get_unwrap, clippy::unwrap_used)] +#![warn(clippy::unwrap_used)] +#![deny(clippy::get_unwrap)] use std::collections::BTreeMap; use std::collections::HashMap; diff --git a/tests/ui/get_unwrap.stderr b/tests/ui/get_unwrap.stderr index 5b02e54b17c6b..a710bd59f8015 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -1,36 +1,32 @@ error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:34:17 + --> $DIR/get_unwrap.rs:36:17 | LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&boxed_slice[1]` | note: the lint level is defined here - --> $DIR/get_unwrap.rs:3:9 + --> $DIR/get_unwrap.rs:5:9 | -LL | #![deny(clippy::get_unwrap, clippy::unwrap_used)] +LL | #![deny(clippy::get_unwrap)] | ^^^^^^^^^^^^^^^^^^ error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:34:17 + --> $DIR/get_unwrap.rs:36:17 | LL | let _ = boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | -note: the lint level is defined here - --> $DIR/get_unwrap.rs:3:29 - | -LL | #![deny(clippy::get_unwrap, clippy::unwrap_used)] - | ^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unwrap-used` implied by `-D warnings` = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:35:17 + --> $DIR/get_unwrap.rs:37:17 | LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_slice[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:35:17 + --> $DIR/get_unwrap.rs:37:17 | LL | let _ = some_slice.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -38,13 +34,13 @@ LL | let _ = some_slice.get(0).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:36:17 + --> $DIR/get_unwrap.rs:38:17 | LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vec[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:36:17 + --> $DIR/get_unwrap.rs:38:17 | LL | let _ = some_vec.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -52,13 +48,13 @@ LL | let _ = some_vec.get(0).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a VecDeque. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:37:17 + --> $DIR/get_unwrap.rs:39:17 | LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_vecdeque[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:37:17 + --> $DIR/get_unwrap.rs:39:17 | LL | let _ = some_vecdeque.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -66,13 +62,13 @@ LL | let _ = some_vecdeque.get(0).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a HashMap. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:38:17 + --> $DIR/get_unwrap.rs:40:17 | LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_hashmap[&1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:38:17 + --> $DIR/get_unwrap.rs:40:17 | LL | let _ = some_hashmap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -80,13 +76,13 @@ LL | let _ = some_hashmap.get(&1).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a BTreeMap. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:39:17 + --> $DIR/get_unwrap.rs:41:17 | LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `&some_btreemap[&1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:39:17 + --> $DIR/get_unwrap.rs:41:17 | LL | let _ = some_btreemap.get(&1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -94,7 +90,7 @@ LL | let _ = some_btreemap.get(&1).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:40:17 + --> $DIR/get_unwrap.rs:42:17 | LL | let _ = false_positive.get(0).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -102,13 +98,13 @@ LL | let _ = false_positive.get(0).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:42:21 + --> $DIR/get_unwrap.rs:44:21 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:42:22 + --> $DIR/get_unwrap.rs:44:22 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -116,13 +112,13 @@ LL | let _: u8 = *boxed_slice.get(1).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:47:9 + --> $DIR/get_unwrap.rs:49:9 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:47:10 + --> $DIR/get_unwrap.rs:49:10 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -130,13 +126,13 @@ LL | *boxed_slice.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:48:9 + --> $DIR/get_unwrap.rs:50:9 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:48:10 + --> $DIR/get_unwrap.rs:50:10 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -144,13 +140,13 @@ LL | *some_slice.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:49:9 + --> $DIR/get_unwrap.rs:51:9 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:49:10 + --> $DIR/get_unwrap.rs:51:10 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -158,13 +154,13 @@ LL | *some_vec.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:50:9 + --> $DIR/get_unwrap.rs:52:9 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:50:10 + --> $DIR/get_unwrap.rs:52:10 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -172,7 +168,7 @@ LL | *some_vecdeque.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:52:10 + --> $DIR/get_unwrap.rs:54:10 | LL | *some_hashmap.get_mut(&1).unwrap() = 'b'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -180,7 +176,7 @@ LL | *some_hashmap.get_mut(&1).unwrap() = 'b'; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:53:10 + --> $DIR/get_unwrap.rs:55:10 | LL | *some_btreemap.get_mut(&1).unwrap() = 'b'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -188,7 +184,7 @@ LL | *some_btreemap.get_mut(&1).unwrap() = 'b'; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:54:10 + --> $DIR/get_unwrap.rs:56:10 | LL | *false_positive.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -196,13 +192,13 @@ LL | *false_positive.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:59:17 + --> $DIR/get_unwrap.rs:61:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:59:17 + --> $DIR/get_unwrap.rs:61:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -210,13 +206,13 @@ LL | let _ = some_vec.get(0..1).unwrap().to_vec(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:60:17 + --> $DIR/get_unwrap.rs:62:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:60:17 + --> $DIR/get_unwrap.rs:62:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 447ed5ce5234e645df298847fd616817d3dfd6e4 Mon Sep 17 00:00:00 2001 From: tamaron Date: Sun, 30 Jan 2022 15:39:47 +0900 Subject: [PATCH 31/67] add attributes --- tests/ui/get_unwrap.fixed | 10 ++++-- tests/ui/get_unwrap.rs | 10 ++++-- tests/ui/get_unwrap.stderr | 62 +++++++++----------------------------- 3 files changed, 29 insertions(+), 53 deletions(-) diff --git a/tests/ui/get_unwrap.fixed b/tests/ui/get_unwrap.fixed index e9749e85208a3..c3a36dcabd1a9 100644 --- a/tests/ui/get_unwrap.fixed +++ b/tests/ui/get_unwrap.fixed @@ -39,6 +39,7 @@ fn main() { let _ = &some_vecdeque[0]; let _ = &some_hashmap[&1]; let _ = &some_btreemap[&1]; + #[allow(clippy::unwrap_used)] let _ = false_positive.get(0).unwrap(); // Test with deref let _: u8 = boxed_slice[1]; @@ -51,9 +52,12 @@ fn main() { some_vec[0] = 1; some_vecdeque[0] = 1; // Check false positives - *some_hashmap.get_mut(&1).unwrap() = 'b'; - *some_btreemap.get_mut(&1).unwrap() = 'b'; - *false_positive.get_mut(0).unwrap() = 1; + #[allow(clippy::unwrap_used)] + { + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } } { diff --git a/tests/ui/get_unwrap.rs b/tests/ui/get_unwrap.rs index 86c6bf533cd35..d77a202aa39c3 100644 --- a/tests/ui/get_unwrap.rs +++ b/tests/ui/get_unwrap.rs @@ -39,6 +39,7 @@ fn main() { let _ = some_vecdeque.get(0).unwrap(); let _ = some_hashmap.get(&1).unwrap(); let _ = some_btreemap.get(&1).unwrap(); + #[allow(clippy::unwrap_used)] let _ = false_positive.get(0).unwrap(); // Test with deref let _: u8 = *boxed_slice.get(1).unwrap(); @@ -51,9 +52,12 @@ fn main() { *some_vec.get_mut(0).unwrap() = 1; *some_vecdeque.get_mut(0).unwrap() = 1; // Check false positives - *some_hashmap.get_mut(&1).unwrap() = 'b'; - *some_btreemap.get_mut(&1).unwrap() = 'b'; - *false_positive.get_mut(0).unwrap() = 1; + #[allow(clippy::unwrap_used)] + { + *some_hashmap.get_mut(&1).unwrap() = 'b'; + *some_btreemap.get_mut(&1).unwrap() = 'b'; + *false_positive.get_mut(0).unwrap() = 1; + } } { diff --git a/tests/ui/get_unwrap.stderr b/tests/ui/get_unwrap.stderr index a710bd59f8015..cb5f44fbd59ee 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -89,22 +89,14 @@ LL | let _ = some_btreemap.get(&1).unwrap(); | = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message -error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:42:17 - | -LL | let _ = false_positive.get(0).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message - error: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:44:21 + --> $DIR/get_unwrap.rs:45:21 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:44:22 + --> $DIR/get_unwrap.rs:45:22 | LL | let _: u8 = *boxed_slice.get(1).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -112,13 +104,13 @@ LL | let _: u8 = *boxed_slice.get(1).unwrap(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:49:9 + --> $DIR/get_unwrap.rs:50:9 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `boxed_slice[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:49:10 + --> $DIR/get_unwrap.rs:50:10 | LL | *boxed_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -126,13 +118,13 @@ LL | *boxed_slice.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:50:9 + --> $DIR/get_unwrap.rs:51:9 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_slice[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:50:10 + --> $DIR/get_unwrap.rs:51:10 | LL | *some_slice.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -140,13 +132,13 @@ LL | *some_slice.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:51:9 + --> $DIR/get_unwrap.rs:52:9 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:51:10 + --> $DIR/get_unwrap.rs:52:10 | LL | *some_vec.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -154,51 +146,27 @@ LL | *some_vec.get_mut(0).unwrap() = 1; = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a VecDeque. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:52:9 + --> $DIR/get_unwrap.rs:53:9 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vecdeque[0]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:52:10 + --> $DIR/get_unwrap.rs:53:10 | LL | *some_vecdeque.get_mut(0).unwrap() = 1; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message -error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:54:10 - | -LL | *some_hashmap.get_mut(&1).unwrap() = 'b'; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message - -error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:55:10 - | -LL | *some_btreemap.get_mut(&1).unwrap() = 'b'; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message - -error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:56:10 - | -LL | *false_positive.get_mut(0).unwrap() = 1; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message - error: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:61:17 + --> $DIR/get_unwrap.rs:65:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:61:17 + --> $DIR/get_unwrap.rs:65:17 | LL | let _ = some_vec.get(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -206,18 +174,18 @@ LL | let _ = some_vec.get(0..1).unwrap().to_vec(); = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message error: called `.get_mut().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:62:17 + --> $DIR/get_unwrap.rs:66:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `some_vec[0..1]` error: used `unwrap()` on `an Option` value - --> $DIR/get_unwrap.rs:62:17 + --> $DIR/get_unwrap.rs:66:17 | LL | let _ = some_vec.get_mut(0..1).unwrap().to_vec(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: if you don't want to handle the `None` case gracefully, consider using `expect()` to provide a better panic message -error: aborting due to 30 previous errors +error: aborting due to 26 previous errors From 5faa7ebb709dfbd8d0b7274ffda05b08d70b8a3d Mon Sep 17 00:00:00 2001 From: dswij Date: Mon, 31 Jan 2022 13:35:14 +0800 Subject: [PATCH 32/67] Fix `chars_next_cmp` suggestion not escaped --- .../src/methods/chars_cmp_with_unwrap.rs | 2 +- tests/ui/starts_ends_with.fixed | 10 +++- tests/ui/starts_ends_with.rs | 10 +++- tests/ui/starts_ends_with.stderr | 50 ++++++++++++++----- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs b/clippy_lints/src/methods/chars_cmp_with_unwrap.rs index 4275857757fee..a7c0e43923e13 100644 --- a/clippy_lints/src/methods/chars_cmp_with_unwrap.rs +++ b/clippy_lints/src/methods/chars_cmp_with_unwrap.rs @@ -32,7 +32,7 @@ pub(super) fn check<'tcx>( if info.eq { "" } else { "!" }, snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability), suggest, - c), + c.escape_default()), applicability, ); diff --git a/tests/ui/starts_ends_with.fixed b/tests/ui/starts_ends_with.fixed index 7dfcf9c91e486..983fac7afe69a 100644 --- a/tests/ui/starts_ends_with.fixed +++ b/tests/ui/starts_ends_with.fixed @@ -7,6 +7,10 @@ fn main() {} fn starts_with() { "".starts_with(' '); !"".starts_with(' '); + + // Ensure that suggestion is escaped correctly + "".starts_with('\n'); + !"".starts_with('\n'); } fn chars_cmp_with_unwrap() { @@ -31,7 +35,7 @@ fn chars_cmp_with_unwrap() { // !s.ends_with('o') // Nothing here } - if !s.ends_with('o') { + if !s.ends_with('\n') { // !s.ends_with('o') // Nothing here } @@ -43,4 +47,8 @@ fn ends_with() { !"".ends_with(' '); "".ends_with(' '); !"".ends_with(' '); + + // Ensure that suggestion is escaped correctly + "".ends_with('\n'); + !"".ends_with('\n'); } diff --git a/tests/ui/starts_ends_with.rs b/tests/ui/starts_ends_with.rs index e48a424635439..e3335dd2e2ef7 100644 --- a/tests/ui/starts_ends_with.rs +++ b/tests/ui/starts_ends_with.rs @@ -7,6 +7,10 @@ fn main() {} fn starts_with() { "".chars().next() == Some(' '); Some(' ') != "".chars().next(); + + // Ensure that suggestion is escaped correctly + "".chars().next() == Some('\n'); + Some('\n') != "".chars().next(); } fn chars_cmp_with_unwrap() { @@ -31,7 +35,7 @@ fn chars_cmp_with_unwrap() { // !s.ends_with('o') // Nothing here } - if s.chars().last().unwrap() != 'o' { + if s.chars().last().unwrap() != '\n' { // !s.ends_with('o') // Nothing here } @@ -43,4 +47,8 @@ fn ends_with() { Some(' ') != "".chars().last(); "".chars().next_back() == Some(' '); Some(' ') != "".chars().next_back(); + + // Ensure that suggestion is escaped correctly + "".chars().last() == Some('\n'); + Some('\n') != "".chars().last(); } diff --git a/tests/ui/starts_ends_with.stderr b/tests/ui/starts_ends_with.stderr index 7c726d0e01026..2dd9f53b8026a 100644 --- a/tests/ui/starts_ends_with.stderr +++ b/tests/ui/starts_ends_with.stderr @@ -13,13 +13,25 @@ LL | Some(' ') != "".chars().next(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".starts_with(' ')` error: you should use the `starts_with` method - --> $DIR/starts_ends_with.rs:14:8 + --> $DIR/starts_ends_with.rs:12:5 + | +LL | "".chars().next() == Some('/n'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".starts_with('/n')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:13:5 + | +LL | Some('/n') != "".chars().next(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".starts_with('/n')` + +error: you should use the `starts_with` method + --> $DIR/starts_ends_with.rs:18:8 | LL | if s.chars().next().unwrap() == 'f' { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.starts_with('f')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:18:8 + --> $DIR/starts_ends_with.rs:22:8 | LL | if s.chars().next_back().unwrap() == 'o' { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` @@ -27,52 +39,64 @@ LL | if s.chars().next_back().unwrap() == 'o' { = note: `-D clippy::chars-last-cmp` implied by `-D warnings` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:22:8 + --> $DIR/starts_ends_with.rs:26:8 | LL | if s.chars().last().unwrap() == 'o' { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `s.ends_with('o')` error: you should use the `starts_with` method - --> $DIR/starts_ends_with.rs:26:8 + --> $DIR/starts_ends_with.rs:30:8 | LL | if s.chars().next().unwrap() != 'f' { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.starts_with('f')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:30:8 + --> $DIR/starts_ends_with.rs:34:8 | LL | if s.chars().next_back().unwrap() != 'o' { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:34:8 + --> $DIR/starts_ends_with.rs:38:8 | -LL | if s.chars().last().unwrap() != 'o' { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('o')` +LL | if s.chars().last().unwrap() != '/n' { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!s.ends_with('/n')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:42:5 + --> $DIR/starts_ends_with.rs:46:5 | LL | "".chars().last() == Some(' '); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:43:5 + --> $DIR/starts_ends_with.rs:47:5 | LL | Some(' ') != "".chars().last(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:44:5 + --> $DIR/starts_ends_with.rs:48:5 | LL | "".chars().next_back() == Some(' '); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with(' ')` error: you should use the `ends_with` method - --> $DIR/starts_ends_with.rs:45:5 + --> $DIR/starts_ends_with.rs:49:5 | LL | Some(' ') != "".chars().next_back(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with(' ')` -error: aborting due to 12 previous errors +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:52:5 + | +LL | "".chars().last() == Some('/n'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `"".ends_with('/n')` + +error: you should use the `ends_with` method + --> $DIR/starts_ends_with.rs:53:5 + | +LL | Some('/n') != "".chars().last(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: like this: `!"".ends_with('/n')` + +error: aborting due to 16 previous errors From 97e5a70f472db51ea319367b5b424a55a77a2187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Thu, 20 Jan 2022 23:00:50 +0100 Subject: [PATCH 33/67] warn if we find multiple clippy configs Fixes #8323 --- clippy_lints/src/utils/conf.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index c9d99617c1e28..36a7e67af6f17 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -322,6 +322,9 @@ pub fn lookup_conf_file() -> io::Result> { let mut current = env::var_os("CLIPPY_CONF_DIR") .or_else(|| env::var_os("CARGO_MANIFEST_DIR")) .map_or_else(|| PathBuf::from("."), PathBuf::from); + + let mut found_config: Option = None; + loop { for config_file_name in &CONFIG_FILE_NAMES { if let Ok(config_file) = current.join(config_file_name).canonicalize() { @@ -329,11 +332,26 @@ pub fn lookup_conf_file() -> io::Result> { Err(e) if e.kind() == io::ErrorKind::NotFound => {}, Err(e) => return Err(e), Ok(md) if md.is_dir() => {}, - Ok(_) => return Ok(Some(config_file)), + Ok(_) => { + // warn if we happen to find two config files + if let Some(ref found_config_) = found_config { + eprintln!( + "Warning: found two config files: {} and {}.\nUsing the first one!", + found_config_.display(), + config_file.display(), + ); + } else { + found_config = Some(config_file); + } + }, } } } + if found_config.is_some() { + return Ok(found_config); + } + // If the current directory has no parent, we're done searching. if !current.pop() { return Ok(None); From 1193abe4c85ecd57f1a5634629874fb153a180ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Sun, 30 Jan 2022 13:32:35 +0100 Subject: [PATCH 34/67] multiple configs: add tests --- clippy_lints/src/utils/conf.rs | 4 ++-- tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml | 8 ++++++++ tests/ui-cargo/multiple_config_files/no_warn/clippy.toml | 1 + tests/ui-cargo/multiple_config_files/no_warn/src/main.rs | 3 +++ tests/ui-cargo/multiple_config_files/warn/.clippy.toml | 1 + tests/ui-cargo/multiple_config_files/warn/Cargo.toml | 8 ++++++++ tests/ui-cargo/multiple_config_files/warn/clippy.toml | 1 + tests/ui-cargo/multiple_config_files/warn/src/main.rs | 3 +++ tests/ui-cargo/multiple_config_files/warn/src/main.stderr | 2 ++ 9 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml create mode 100644 tests/ui-cargo/multiple_config_files/no_warn/clippy.toml create mode 100644 tests/ui-cargo/multiple_config_files/no_warn/src/main.rs create mode 100644 tests/ui-cargo/multiple_config_files/warn/.clippy.toml create mode 100644 tests/ui-cargo/multiple_config_files/warn/Cargo.toml create mode 100644 tests/ui-cargo/multiple_config_files/warn/clippy.toml create mode 100644 tests/ui-cargo/multiple_config_files/warn/src/main.rs create mode 100644 tests/ui-cargo/multiple_config_files/warn/src/main.stderr diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 36a7e67af6f17..680b2eb1da723 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -333,10 +333,10 @@ pub fn lookup_conf_file() -> io::Result> { Err(e) => return Err(e), Ok(md) if md.is_dir() => {}, Ok(_) => { - // warn if we happen to find two config files + // warn if we happen to find two config files #8323 if let Some(ref found_config_) = found_config { eprintln!( - "Warning: found two config files: {} and {}.\nUsing the first one!", + "Using config file `{}`\nWarning: `{}` will be ignored.", found_config_.display(), config_file.display(), ); diff --git a/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml b/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml new file mode 100644 index 0000000000000..79c973cbfd2d5 --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "no_warn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/multiple_config_files/no_warn/clippy.toml b/tests/ui-cargo/multiple_config_files/no_warn/clippy.toml new file mode 100644 index 0000000000000..cda8d17eed44c --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/no_warn/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/tests/ui-cargo/multiple_config_files/no_warn/src/main.rs b/tests/ui-cargo/multiple_config_files/no_warn/src/main.rs new file mode 100644 index 0000000000000..e7a11a969c037 --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/no_warn/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/ui-cargo/multiple_config_files/warn/.clippy.toml b/tests/ui-cargo/multiple_config_files/warn/.clippy.toml new file mode 100644 index 0000000000000..cda8d17eed44c --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/.clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/tests/ui-cargo/multiple_config_files/warn/Cargo.toml b/tests/ui-cargo/multiple_config_files/warn/Cargo.toml new file mode 100644 index 0000000000000..3d5c707579bca --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "warn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/tests/ui-cargo/multiple_config_files/warn/clippy.toml b/tests/ui-cargo/multiple_config_files/warn/clippy.toml new file mode 100644 index 0000000000000..cda8d17eed44c --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.rs b/tests/ui-cargo/multiple_config_files/warn/src/main.rs new file mode 100644 index 0000000000000..e7a11a969c037 --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.stderr b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr new file mode 100644 index 0000000000000..2abb4e3e06e64 --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.stderr @@ -0,0 +1,2 @@ +Using config file `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/.clippy.toml` +Warning: `$SRC_DIR/tests/ui-cargo/multiple_config_files/warn/clippy.toml` will be ignored. From 0e1cbc5cd1d2e2a52b934dda1701d46fdb4e09b8 Mon Sep 17 00:00:00 2001 From: tamaron Date: Tue, 1 Feb 2022 13:43:39 +0900 Subject: [PATCH 35/67] fix code --- clippy_lints/src/loops/utils.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index eac0f03b142a8..9082ab9437445 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -7,7 +7,7 @@ use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor} use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Symbol}; use rustc_typeck::hir_ty_to_ty; @@ -332,17 +332,20 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic } else { // (&x).into_iter() ==> x.iter() // (&mut x).into_iter() ==> x.iter_mut() - match &arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) - if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() => - { - let meth_name = match mutability { + let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); + match &arg_ty.kind() { + ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, inner_ty).is_some() => { + let meth_name = match mutbl { Mutability::Mut => "iter_mut", Mutability::Not => "iter", }; + let caller = match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner, + _ => arg, + }; format!( "{}.{}()", - sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(), + sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(), meth_name, ) }, From b13704a9cd866b9c1ed73c26f697158c16b75873 Mon Sep 17 00:00:00 2001 From: tamaron Date: Tue, 1 Feb 2022 13:44:24 +0900 Subject: [PATCH 36/67] update test suites --- tests/ui/explicit_counter_loop.stderr | 4 ++-- tests/ui/manual_flatten.rs | 2 -- tests/ui/manual_flatten.stderr | 26 +++++++++++++------------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/tests/ui/explicit_counter_loop.stderr b/tests/ui/explicit_counter_loop.stderr index 9edddea651c26..f9f8407d57755 100644 --- a/tests/ui/explicit_counter_loop.stderr +++ b/tests/ui/explicit_counter_loop.stderr @@ -46,13 +46,13 @@ error: the variable `idx_usize` is used as a loop counter --> $DIR/explicit_counter_loop.rs:170:9 | LL | for _item in slice { - | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.into_iter().enumerate()` + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.iter().enumerate()` error: the variable `idx_u32` is used as a loop counter --> $DIR/explicit_counter_loop.rs:182:9 | LL | for _item in slice { - | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.into_iter())` + | ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.iter())` | = note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate` diff --git a/tests/ui/manual_flatten.rs b/tests/ui/manual_flatten.rs index 7db6b730963c9..6c5232ec5f55b 100644 --- a/tests/ui/manual_flatten.rs +++ b/tests/ui/manual_flatten.rs @@ -26,8 +26,6 @@ fn main() { } // Test for loop over an implicit reference - // Note: if `clippy::manual_flatten` is made autofixable, this case will - // lead to a follow-up lint `clippy::into_iter_on_ref` let z = &y; for n in z { if let Ok(n) = n { diff --git a/tests/ui/manual_flatten.stderr b/tests/ui/manual_flatten.stderr index be5f8a1d81884..392e1a3939375 100644 --- a/tests/ui/manual_flatten.stderr +++ b/tests/ui/manual_flatten.stderr @@ -63,10 +63,10 @@ LL | | } | |_________^ error: unnecessary `if let` since only the `Ok` variant of the iterator element is used - --> $DIR/manual_flatten.rs:32:5 + --> $DIR/manual_flatten.rs:30:5 | LL | for n in z { - | ^ - help: try: `z.into_iter().flatten()` + | ^ - help: try: `z.iter().flatten()` | _____| | | LL | | if let Ok(n) = n { @@ -76,7 +76,7 @@ LL | | } | |_____^ | help: ...and remove the `if let` statement in the for loop - --> $DIR/manual_flatten.rs:33:9 + --> $DIR/manual_flatten.rs:31:9 | LL | / if let Ok(n) = n { LL | | println!("{}", n); @@ -84,7 +84,7 @@ LL | | } | |_________^ error: unnecessary `if let` since only the `Some` variant of the iterator element is used - --> $DIR/manual_flatten.rs:41:5 + --> $DIR/manual_flatten.rs:39:5 | LL | for n in z { | ^ - help: try: `z.flatten()` @@ -97,7 +97,7 @@ LL | | } | |_____^ | help: ...and remove the `if let` statement in the for loop - --> $DIR/manual_flatten.rs:42:9 + --> $DIR/manual_flatten.rs:40:9 | LL | / if let Some(m) = n { LL | | println!("{}", m); @@ -105,7 +105,7 @@ LL | | } | |_________^ error: unnecessary `if let` since only the `Some` variant of the iterator element is used - --> $DIR/manual_flatten.rs:74:5 + --> $DIR/manual_flatten.rs:72:5 | LL | for n in &vec_of_ref { | ^ ----------- help: try: `vec_of_ref.iter().copied().flatten()` @@ -118,7 +118,7 @@ LL | | } | |_____^ | help: ...and remove the `if let` statement in the for loop - --> $DIR/manual_flatten.rs:75:9 + --> $DIR/manual_flatten.rs:73:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); @@ -126,10 +126,10 @@ LL | | } | |_________^ error: unnecessary `if let` since only the `Some` variant of the iterator element is used - --> $DIR/manual_flatten.rs:81:5 + --> $DIR/manual_flatten.rs:79:5 | LL | for n in vec_of_ref { - | ^ ---------- help: try: `vec_of_ref.into_iter().copied().flatten()` + | ^ ---------- help: try: `vec_of_ref.iter().copied().flatten()` | _____| | | LL | | if let Some(n) = n { @@ -139,7 +139,7 @@ LL | | } | |_____^ | help: ...and remove the `if let` statement in the for loop - --> $DIR/manual_flatten.rs:82:9 + --> $DIR/manual_flatten.rs:80:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); @@ -147,10 +147,10 @@ LL | | } | |_________^ error: unnecessary `if let` since only the `Some` variant of the iterator element is used - --> $DIR/manual_flatten.rs:88:5 + --> $DIR/manual_flatten.rs:86:5 | LL | for n in slice_of_ref { - | ^ ------------ help: try: `slice_of_ref.into_iter().copied().flatten()` + | ^ ------------ help: try: `slice_of_ref.iter().copied().flatten()` | _____| | | LL | | if let Some(n) = n { @@ -160,7 +160,7 @@ LL | | } | |_____^ | help: ...and remove the `if let` statement in the for loop - --> $DIR/manual_flatten.rs:89:9 + --> $DIR/manual_flatten.rs:87:9 | LL | / if let Some(n) = n { LL | | println!("{:?}", n); From 544ed0b3cf35540d48bb8dd3868e2a7b2006d7d4 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 1 Feb 2022 10:13:32 +0100 Subject: [PATCH 37/67] silence lint in clippy --- tests/ui/non_send_fields_in_send_ty.rs | 1 + tests/ui/non_send_fields_in_send_ty.stderr | 52 +++++++++++----------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/tests/ui/non_send_fields_in_send_ty.rs b/tests/ui/non_send_fields_in_send_ty.rs index 828248d922f89..514fb25c8cfd8 100644 --- a/tests/ui/non_send_fields_in_send_ty.rs +++ b/tests/ui/non_send_fields_in_send_ty.rs @@ -1,4 +1,5 @@ #![warn(clippy::non_send_fields_in_send_ty)] +#![allow(suspicious_auto_trait_impls)] #![feature(extern_types)] use std::cell::UnsafeCell; diff --git a/tests/ui/non_send_fields_in_send_ty.stderr b/tests/ui/non_send_fields_in_send_ty.stderr index 60df4e226e4fa..b6c904a147a5f 100644 --- a/tests/ui/non_send_fields_in_send_ty.stderr +++ b/tests/ui/non_send_fields_in_send_ty.stderr @@ -1,167 +1,167 @@ error: some fields in `RingBuffer` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:16:1 + --> $DIR/non_send_fields_in_send_ty.rs:17:1 | LL | unsafe impl Send for RingBuffer {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::non-send-fields-in-send-ty` implied by `-D warnings` note: it is not safe to send field `data` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:11:5 + --> $DIR/non_send_fields_in_send_ty.rs:12:5 | LL | data: Vec>, | ^^^^^^^^^^^^^^^^^^^^^^^^ = help: add bounds on type parameter `T` that satisfy `Vec>: Send` error: some fields in `MvccRwLock` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:24:1 + --> $DIR/non_send_fields_in_send_ty.rs:25:1 | LL | unsafe impl Send for MvccRwLock {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `lock` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:21:5 + --> $DIR/non_send_fields_in_send_ty.rs:22:5 | LL | lock: Mutex>, | ^^^^^^^^^^^^^^^^^^^ = help: add bounds on type parameter `T` that satisfy `Mutex>: Send` error: some fields in `ArcGuard` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:32:1 + --> $DIR/non_send_fields_in_send_ty.rs:33:1 | LL | unsafe impl Send for ArcGuard {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `head` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:29:5 + --> $DIR/non_send_fields_in_send_ty.rs:30:5 | LL | head: Arc, | ^^^^^^^^^^^^^ = help: add bounds on type parameter `RC` that satisfy `Arc: Send` error: some fields in `DeviceHandle` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:48:1 + --> $DIR/non_send_fields_in_send_ty.rs:49:1 | LL | unsafe impl Send for DeviceHandle {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `context` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:44:5 + --> $DIR/non_send_fields_in_send_ty.rs:45:5 | LL | context: T, | ^^^^^^^^^^ = help: add `T: Send` bound in `Send` impl error: some fields in `NoGeneric` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:55:1 + --> $DIR/non_send_fields_in_send_ty.rs:56:1 | LL | unsafe impl Send for NoGeneric {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `rc_is_not_send` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:52:5 + --> $DIR/non_send_fields_in_send_ty.rs:53:5 | LL | rc_is_not_send: Rc, | ^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: use a thread-safe type that implements `Send` error: some fields in `MultiField` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:63:1 + --> $DIR/non_send_fields_in_send_ty.rs:64:1 | LL | unsafe impl Send for MultiField {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `field1` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:58:5 + --> $DIR/non_send_fields_in_send_ty.rs:59:5 | LL | field1: T, | ^^^^^^^^^ = help: add `T: Send` bound in `Send` impl note: it is not safe to send field `field2` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:59:5 + --> $DIR/non_send_fields_in_send_ty.rs:60:5 | LL | field2: T, | ^^^^^^^^^ = help: add `T: Send` bound in `Send` impl note: it is not safe to send field `field3` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:60:5 + --> $DIR/non_send_fields_in_send_ty.rs:61:5 | LL | field3: T, | ^^^^^^^^^ = help: add `T: Send` bound in `Send` impl error: some fields in `MyOption` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:70:1 + --> $DIR/non_send_fields_in_send_ty.rs:71:1 | LL | unsafe impl Send for MyOption {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `0` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:66:12 + --> $DIR/non_send_fields_in_send_ty.rs:67:12 | LL | MySome(T), | ^ = help: add `T: Send` bound in `Send` impl error: some fields in `MultiParam` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:82:1 + --> $DIR/non_send_fields_in_send_ty.rs:83:1 | LL | unsafe impl Send for MultiParam {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `vec` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:79:5 + --> $DIR/non_send_fields_in_send_ty.rs:80:5 | LL | vec: Vec<(A, B)>, | ^^^^^^^^^^^^^^^^ = help: add bounds on type parameters `A, B` that satisfy `Vec<(A, B)>: Send` error: some fields in `HeuristicTest` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:100:1 + --> $DIR/non_send_fields_in_send_ty.rs:101:1 | LL | unsafe impl Send for HeuristicTest {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `field4` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:95:5 + --> $DIR/non_send_fields_in_send_ty.rs:96:5 | LL | field4: (*const NonSend, Rc), | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = help: use a thread-safe type that implements `Send` error: some fields in `AttrTest3` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:119:1 + --> $DIR/non_send_fields_in_send_ty.rs:120:1 | LL | unsafe impl Send for AttrTest3 {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `0` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:114:11 + --> $DIR/non_send_fields_in_send_ty.rs:115:11 | LL | Enum2(T), | ^ = help: add `T: Send` bound in `Send` impl error: some fields in `Complex` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:127:1 + --> $DIR/non_send_fields_in_send_ty.rs:128:1 | LL | unsafe impl

Send for Complex {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `field1` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:123:5 + --> $DIR/non_send_fields_in_send_ty.rs:124:5 | LL | field1: A, | ^^^^^^^^^ = help: add `P: Send` bound in `Send` impl error: some fields in `Complex>` are not safe to be sent to another thread - --> $DIR/non_send_fields_in_send_ty.rs:130:1 + --> $DIR/non_send_fields_in_send_ty.rs:131:1 | LL | unsafe impl Send for Complex> {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: it is not safe to send field `field2` to another thread - --> $DIR/non_send_fields_in_send_ty.rs:124:5 + --> $DIR/non_send_fields_in_send_ty.rs:125:5 | LL | field2: B, | ^^^^^^^^^ From 99af4c89718ae1822fde5ef3a37ab3b0567c8cd3 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 25 Jan 2022 08:42:52 +0100 Subject: [PATCH 38/67] remove `TyS::same_type` it ignored regions and constants in adts, but didn't do so for references or any other types. This seemed quite weird --- clippy_lints/src/dereference.rs | 4 ++-- clippy_lints/src/implicit_hasher.rs | 4 ++-- clippy_lints/src/len_zero.rs | 4 ++-- clippy_lints/src/loops/explicit_into_iter_loop.rs | 3 +-- clippy_lints/src/loops/explicit_iter_loop.rs | 4 ++-- clippy_lints/src/matches.rs | 4 ++-- clippy_lints/src/methods/filter_map.rs | 3 +-- clippy_lints/src/methods/implicit_clone.rs | 3 +-- clippy_lints/src/methods/mod.rs | 4 ++-- clippy_lints/src/methods/unnecessary_filter_map.rs | 4 ++-- clippy_lints/src/needless_option_as_deref.rs | 3 +-- clippy_lints/src/needless_question_mark.rs | 3 +-- clippy_lints/src/new_without_default.rs | 3 +-- clippy_lints/src/redundant_slicing.rs | 3 +-- clippy_lints/src/size_of_in_element_count.rs | 4 ++-- clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/ty.rs | 2 +- 17 files changed, 25 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c0adab790f0d5..6d0851d804c26 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -12,7 +12,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; -use rustc_middle::ty::{self, Ty, TyCtxt, TyS, TypeckResults}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeckResults}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{symbol::sym, Span}; @@ -448,7 +448,7 @@ fn try_parse_ref_op<'tcx>( // the reference. fn deref_method_same_type(result_ty: Ty<'_>, arg_ty: Ty<'_>) -> bool { match (result_ty.kind(), arg_ty.kind()) { - (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => TyS::same_type(result_ty, arg_ty), + (ty::Ref(_, result_ty, _), ty::Ref(_, arg_ty, _)) => result_ty == arg_ty, // The result type for a deref method is always a reference // Not matching the previous pattern means the argument type is not a reference diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index eed25e9bc0ea8..a2f943b03ef42 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -8,7 +8,7 @@ use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind} use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{Ty, TyS, TypeckResults}; +use rustc_middle::ty::{Ty, TypeckResults}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::symbol::sym; @@ -346,7 +346,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind; if let Some(ty_did) = ty_path.res.opt_def_id(); then { - if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) { + if self.target.ty() != self.maybe_typeck_results.unwrap().expr_ty(e) { return; } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 530b0a90ebd8a..3418d276c5354 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -10,7 +10,7 @@ use rustc_hir::{ ItemKind, Mutability, Node, TraitItemRef, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, AssocKind, FnSig, Ty, TyS}; +use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{ source_map::{Span, Spanned, Symbol}, @@ -265,7 +265,7 @@ impl LenOutput<'_> { (_, &ty::Bool) => true, (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(), (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => { - subs.type_at(0).is_bool() && TyS::same_type(subs.type_at(1), err_ty) + subs.type_at(0).is_bool() && subs.type_at(1) == err_ty }, _ => false, } diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs index 17246cc5426ae..175e2b382e3f6 100644 --- a/clippy_lints/src/loops/explicit_into_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -5,13 +5,12 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::symbol::sym; pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, call_expr: &Expr<'_>) { let self_ty = cx.typeck_results().expr_ty(self_arg); let self_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg); - if !(TyS::same_type(self_ty, self_ty_adjusted) && is_trait_method(cx, call_expr, sym::IntoIterator)) { + if !(self_ty == self_ty_adjusted && is_trait_method(cx, call_expr, sym::IntoIterator)) { return; } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 5ac69d106ceb4..5f5beccd030c1 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -6,7 +6,7 @@ use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) { @@ -22,7 +22,7 @@ pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, m mutbl: Mutability::Not, }, ); - TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) + receiver_ty_adjusted == ref_receiver_ty }, _ => false, }; diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 2579404fb18cc..e61cb4d227363 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -26,7 +26,7 @@ use rustc_hir::{ }; use rustc_hir::{HirIdMap, HirIdSet}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, TyS, VariantDef}; +use rustc_middle::ty::{self, Ty, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; @@ -2262,7 +2262,7 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { }; // the names technically don't have to match; this makes the lint more conservative if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); - if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); + if cx.typeck_results().expr_ty(a) == cx.typeck_results().expr_ty(b); if pat_contains_local(lhs.pat, a_id); if pat_contains_local(rhs.pat, b_id); then { diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index ba1af9f3d62be..30c68186b3aef 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -8,7 +8,6 @@ use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; use std::borrow::Cow; @@ -149,7 +148,7 @@ pub(super) fn check<'tcx>( if_chain! { if path_to_local_id(a_path, filter_param_id); if path_to_local_id(b, map_param_id); - if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + if cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b); then { return true; } diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs index 865e7702b7151..7a255baffd745 100644 --- a/clippy_lints/src/methods/implicit_clone.rs +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -6,7 +6,6 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; use rustc_span::sym; use super::IMPLICIT_CLONE; @@ -19,7 +18,7 @@ pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv let input_type = cx.typeck_results().expr_ty(recv); let (input_type, ref_count) = peel_mid_ty_refs(input_type); if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did)); - if TyS::same_type(return_type, input_type); + if return_type == input_type; then { let mut app = Applicability::MachineApplicable; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 4b43448bf7b98..80e1eb86e6c6c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -78,7 +78,7 @@ use rustc_hir::def::Res; use rustc_hir::{Expr, ExprKind, PrimTy, QPath, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, TraitRef, Ty, TyS}; +use rustc_middle::ty::{self, TraitRef, Ty}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Span}; @@ -2195,7 +2195,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } } - if name == "new" && !TyS::same_type(ret_ty, self_ty) { + if name == "new" && ret_ty != self_ty { span_lint( cx, NEW_RET_NO_SELF, diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 784014f0d8743..ccfce31713f93 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -5,7 +5,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty; use rustc_span::sym; use super::UNNECESSARY_FILTER_MAP; @@ -34,7 +34,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); match cx.typeck_results().expr_ty(&body.value).kind() { ty::Adt(adt, subst) - if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && TyS::same_type(in_ty, subst.type_at(0)) => + if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => { "filter" }, diff --git a/clippy_lints/src/needless_option_as_deref.rs b/clippy_lints/src/needless_option_as_deref.rs index 21d8263390af3..9d3d7d1f24cbc 100644 --- a/clippy_lints/src/needless_option_as_deref.rs +++ b/clippy_lints/src/needless_option_as_deref.rs @@ -4,7 +4,6 @@ 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; @@ -49,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for OptionNeedlessDeref { 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) ); + if outer_ty == typeck.expr_ty(sub_expr); then{ span_lint_and_sugg( cx, diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index d4c823d1c1ab7..8f85b00596c01 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -6,7 +6,6 @@ use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyS; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -128,7 +127,7 @@ fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { if expr.span.ctxt() == inner_expr.span.ctxt(); let expr_ty = cx.typeck_results().expr_ty(expr); let inner_ty = cx.typeck_results().expr_ty(inner_expr); - if TyS::same_type(expr_ty, inner_ty); + if expr_ty == inner_ty; then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index aec95530bba67..f86af7a7bb6ea 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -8,7 +8,6 @@ use rustc_hir as hir; use rustc_hir::HirIdSet; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::TyS; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; @@ -103,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { if cx.access_levels.is_reachable(impl_item.def_id); let self_def_id = cx.tcx.hir().get_parent_item(id); let self_ty = cx.tcx.type_of(self_def_id); - if TyS::same_type(self_ty, return_ty(cx, id)); + if self_ty == return_ty(cx, id); if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default); then { if self.impling_types.is_none() { diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 7c88b42ea3199..cd3aee5565538 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -6,7 +6,6 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::TyS; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -54,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { if addressee.span.ctxt() == ctxt; if let ExprKind::Index(indexed, range) = addressee.kind; if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull); - if TyS::same_type(cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(indexed)); + if cx.typeck_results().expr_ty(expr) == cx.typeck_results().expr_ty(indexed); then { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index c7c57ab426d9d..971729e5c54b3 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -7,7 +7,7 @@ use if_chain::if_chain; use rustc_hir::BinOpKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, TyS, TypeAndMut}; +use rustc_middle::ty::{self, Ty, TypeAndMut}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -138,7 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { // Find a size_of call in the count parameter expression and // check that it's the same type if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr, false); - if TyS::same_type(pointee_ty, ty_used_for_size_of); + if pointee_ty == ty_used_for_size_of; then { span_lint_and_help( cx, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index ed573ad905619..7b5c5af8f79bf 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -102,7 +102,7 @@ impl HirEqInterExpr<'_, '_, '_> { if let Some(typeck) = self.inner.maybe_typeck_results { let l_ty = typeck.pat_ty(l.pat); let r_ty = typeck.pat_ty(r.pat); - if !rustc_middle::ty::TyS::same_type(l_ty, r_ty) { + if l_ty != r_ty { return false; } } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index d057da73302a2..819ff917b6336 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -42,7 +42,7 @@ pub fn can_partially_move_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool /// Walks into `ty` and returns `true` if any inner type is the same as `other_ty` pub fn contains_ty(ty: Ty<'_>, other_ty: Ty<'_>) -> bool { ty.walk().any(|inner| match inner.unpack() { - GenericArgKind::Type(inner_ty) => ty::TyS::same_type(other_ty, inner_ty), + GenericArgKind::Type(inner_ty) => other_ty == inner_ty, GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false, }) } From 8ef87455b07e527dd636423751591910dac2e166 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 1 Feb 2022 10:53:25 -0500 Subject: [PATCH 39/67] Minor cleanup on transmute lints --- clippy_lints/src/transmute/mod.rs | 31 +++++++++---------- .../src/transmute/transmute_float_to_int.rs | 4 +-- .../src/transmute/transmute_int_to_bool.rs | 4 +-- .../src/transmute/transmute_int_to_char.rs | 4 +-- .../src/transmute/transmute_int_to_float.rs | 4 +-- .../src/transmute/transmute_num_to_bytes.rs | 4 +-- .../src/transmute/transmute_ptr_to_ptr.rs | 4 +-- .../src/transmute/transmute_ptr_to_ref.rs | 4 +-- .../src/transmute/transmute_ref_to_ref.rs | 6 ++-- .../transmutes_expressible_as_ptr_casts.rs | 4 +-- .../src/transmute/useless_transmute.rs | 6 ++-- 11 files changed, 37 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 3ad4ec74bf51c..c370941dd9c21 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -372,10 +372,9 @@ declare_lint_pass!(Transmute => [ ]); impl<'tcx> LateLintPass<'tcx> for Transmute { - #[allow(clippy::similar_names, clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { if_chain! { - if let ExprKind::Call(path_expr, args) = e.kind; + if let ExprKind::Call(path_expr, [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 cx.tcx.is_diagnostic_item(sym::transmute, def_id); @@ -385,28 +384,28 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. let const_context = in_constant(cx, e.hir_id); - let from_ty = cx.typeck_results().expr_ty(&args[0]); + let from_ty = cx.typeck_results().expr_ty(arg); let to_ty = cx.typeck_results().expr_ty(e); // If useless_transmute is triggered, the other lints can be skipped. - if useless_transmute::check(cx, e, from_ty, to_ty, args) { + if useless_transmute::check(cx, e, from_ty, to_ty, arg) { return; } - let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty); - linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty); - linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath); - linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args); - linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context); - linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args); - linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args); - linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context); - linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context); - linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context); - linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty); + let linted = wrong_transmute::check(cx, e, from_ty, to_ty) + | crosspointer_transmute::check(cx, e, from_ty, to_ty) + | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath) + | transmute_int_to_char::check(cx, e, from_ty, to_ty, arg) + | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) + | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg) + | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg) + | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context) + | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context) + | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context) + | unsound_collection_transmute::check(cx, e, from_ty, to_ty); if !linted { - transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args); + transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg); } } } diff --git a/clippy_lints/src/transmute/transmute_float_to_int.rs b/clippy_lints/src/transmute/transmute_float_to_int.rs index 3aa3c393ba57c..f4656283b1c13 100644 --- a/clippy_lints/src/transmute/transmute_float_to_int.rs +++ b/clippy_lints/src/transmute/transmute_float_to_int.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, const_context: bool, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { @@ -26,7 +26,7 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), |diag| { - let mut expr = &args[0]; + let mut expr = arg; let mut arg = sugg::Sugg::hir(cx, expr, ".."); if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { diff --git a/clippy_lints/src/transmute/transmute_int_to_bool.rs b/clippy_lints/src/transmute/transmute_int_to_bool.rs index cc0a5643e2a7d..8c50b58ca4b86 100644 --- a/clippy_lints/src/transmute/transmute_int_to_bool.rs +++ b/clippy_lints/src/transmute/transmute_int_to_bool.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { (ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => { @@ -25,7 +25,7 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `bool`", from_ty), |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = sugg::Sugg::hir(cx, arg, ".."); let zero = sugg::Sugg::NonParen(Cow::from("0")); diag.span_suggestion( e.span, diff --git a/clippy_lints/src/transmute/transmute_int_to_char.rs b/clippy_lints/src/transmute/transmute_int_to_char.rs index e83d2e06b9a8d..3eb07b68992a8 100644 --- a/clippy_lints/src/transmute/transmute_int_to_char.rs +++ b/clippy_lints/src/transmute/transmute_int_to_char.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { (ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => { @@ -24,7 +24,7 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `char`", from_ty), |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = sugg::Sugg::hir(cx, arg, ".."); let arg = if let ty::Int(_) = from_ty.kind() { arg.as_ty(ast::UintTy::U32.name_str()) } else { diff --git a/clippy_lints/src/transmute/transmute_int_to_float.rs b/clippy_lints/src/transmute/transmute_int_to_float.rs index 05eee380d6f40..b8703052e6c86 100644 --- a/clippy_lints/src/transmute/transmute_int_to_float.rs +++ b/clippy_lints/src/transmute/transmute_int_to_float.rs @@ -13,7 +13,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, const_context: bool, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { @@ -24,7 +24,7 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = sugg::Sugg::hir(cx, arg, ".."); let arg = if let ty::Int(int_ty) = from_ty.kind() { arg.as_ty(format!( "u{}", diff --git a/clippy_lints/src/transmute/transmute_num_to_bytes.rs b/clippy_lints/src/transmute/transmute_num_to_bytes.rs index 5ba58a7649401..52d193d11e1a0 100644 --- a/clippy_lints/src/transmute/transmute_num_to_bytes.rs +++ b/clippy_lints/src/transmute/transmute_num_to_bytes.rs @@ -13,7 +13,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, const_context: bool, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { @@ -33,7 +33,7 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = sugg::Sugg::hir(cx, arg, ".."); diag.span_suggestion( e.span, "consider using `to_ne_bytes()`", diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs index 7b646bfc0c6d1..d712b33de9e1a 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs @@ -13,7 +13,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { (ty::RawPtr(_), ty::RawPtr(to_ty)) => { @@ -23,7 +23,7 @@ pub(super) fn check<'tcx>( e.span, "transmute from a pointer to a pointer", |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty)); diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); } diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index f14eef9364531..5699f8e92cfca 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, qpath: &'tcx QPath<'_>, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { @@ -28,7 +28,7 @@ pub(super) fn check<'tcx>( from_ty, to_ty ), |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = sugg::Sugg::hir(cx, arg, ".."); let (deref, cast) = if *mutbl == Mutability::Mut { ("&mut *", "*mut") } else { diff --git a/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/clippy_lints/src/transmute/transmute_ref_to_ref.rs index d105e37abf9c0..fdef8bac7f9b0 100644 --- a/clippy_lints/src/transmute/transmute_ref_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, const_context: bool, ) -> bool { let mut triggered = false; @@ -41,7 +41,7 @@ pub(super) fn check<'tcx>( format!( "std::str::from_utf8{}({}).unwrap()", postfix, - snippet(cx, args[0].span, ".."), + snippet(cx, arg.span, ".."), ), Applicability::Unspecified, ); @@ -54,7 +54,7 @@ pub(super) fn check<'tcx>( TRANSMUTE_PTR_TO_PTR, e.span, "transmute from a reference to a reference", - |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let ty_from_and_mut = ty::TypeAndMut { ty: ty_from, mutbl: *from_mutbl diff --git a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs index e2c6d130f3c9c..626d7cd46fc43 100644 --- a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs +++ b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -14,7 +14,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, ) -> bool { if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) { span_lint_and_then( @@ -26,7 +26,7 @@ pub(super) fn check<'tcx>( from_ty, to_ty ), |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let sugg = arg.as_ty(&to_ty.to_string()).to_string(); diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); } diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index 445bcf60fa71a..998f97eb5d8c6 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -13,7 +13,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - args: &'tcx [Expr<'_>], + arg: &'tcx Expr<'_>, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { _ if from_ty == to_ty => { @@ -32,7 +32,7 @@ pub(super) fn check<'tcx>( e.span, "transmute from a reference to a pointer", |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let rty_and_mut = ty::TypeAndMut { ty: rty, mutbl: *rty_mutbl, @@ -57,7 +57,7 @@ pub(super) fn check<'tcx>( e.span, "transmute from an integer to a pointer", |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { diag.span_suggestion( e.span, "try", From e4f45c22e24c0a739f32ef152dfd4287257e20cb Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 1 Feb 2022 15:05:20 -0500 Subject: [PATCH 40/67] Fix ICE in `ptr_arg` --- clippy_lints/src/ptr.rs | 2 +- tests/ui/crashes/ice-8386.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/ui/crashes/ice-8386.rs diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 77bf5f002f7c9..5c13f660c6b73 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -649,7 +649,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: }, _ => { skip_count += 1; - results[arg.idx].skip = true; + results[i].skip = true; None }, } diff --git a/tests/ui/crashes/ice-8386.rs b/tests/ui/crashes/ice-8386.rs new file mode 100644 index 0000000000000..3e38b1408d8ca --- /dev/null +++ b/tests/ui/crashes/ice-8386.rs @@ -0,0 +1,3 @@ +fn f(x: u32, mut arg: &String) {} + +fn main() {} From f5fd9ded000d7968b6641d2b17f4eca53a4a8f67 Mon Sep 17 00:00:00 2001 From: tamaron Date: Wed, 2 Feb 2022 11:25:15 +0900 Subject: [PATCH 41/67] chore --- clippy_lints/src/loops/utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 9082ab9437445..b6c746d3e3971 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -335,7 +335,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); match &arg_ty.kind() { ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, inner_ty).is_some() => { - let meth_name = match mutbl { + let method_name = match mutbl { Mutability::Mut => "iter_mut", Mutability::Not => "iter", }; @@ -346,7 +346,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic format!( "{}.{}()", sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(), - meth_name, + method_name, ) }, _ => format!( From 69872175eec571189c077b594a8737331798173b Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 20 Aug 2021 14:47:12 +0000 Subject: [PATCH 42/67] Lazily resolve type-alias-impl-trait defining uses by using an opaque type obligation to bubble up comparisons between opaque types and other types Also uses proper obligation causes so that the body id works, because out of some reason nll uses body ids for logic instead of just diagnostics. --- clippy_utils/src/qualify_min_const_fn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 7512039a480bb..5407b5e8ed93e 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -32,6 +32,7 @@ pub fn is_min_const_fn<'a, 'tcx>(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: | ty::PredicateKind::Projection(_) | ty::PredicateKind::ConstEvaluatable(..) | ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::OpaqueType(..) | ty::PredicateKind::TypeWellFormedFromEnv(..) => continue, ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate), ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate), From 3edfd5ec3d6d4777233638aadca3d05b69398935 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Wed, 2 Feb 2022 12:44:51 +0100 Subject: [PATCH 43/67] Remove defaultness from ImplItem. --- clippy_lints/src/utils/inspector.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/clippy_lints/src/utils/inspector.rs b/clippy_lints/src/utils/inspector.rs index b58325ac73ee9..8691148313702 100644 --- a/clippy_lints/src/utils/inspector.rs +++ b/clippy_lints/src/utils/inspector.rs @@ -54,9 +54,6 @@ impl<'tcx> LateLintPass<'tcx> for DeepCodeInspector { ), hir::VisibilityKind::Inherited => println!("visibility inherited from outer item"), } - if item.defaultness.is_default() { - println!("default"); - } match item.kind { hir::ImplItemKind::Const(_, body_id) => { println!("associated constant"); From 4bae06d73c8c53e5b0aabc90203f808932d2b021 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 28 Jan 2022 14:42:19 +0000 Subject: [PATCH 44/67] Use source callsite in FormatArgsExpn::inputs_span --- clippy_utils/src/macros.rs | 9 ++++--- tests/ui/expect_fun_call.fixed | 9 +++++++ tests/ui/expect_fun_call.rs | 9 +++++++ tests/ui/expect_fun_call.stderr | 32 ++++++++++++++--------- tests/ui/manual_assert.edition2018.fixed | 7 +++++ tests/ui/manual_assert.edition2018.stderr | 24 +++++++++++------ tests/ui/manual_assert.edition2021.fixed | 7 +++++ tests/ui/manual_assert.edition2021.stderr | 24 +++++++++++------ tests/ui/manual_assert.rs | 9 +++++++ 9 files changed, 98 insertions(+), 32 deletions(-) diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 5bc353bdd8928..76478c74e2136 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -9,7 +9,7 @@ use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::LateContext; use rustc_span::def_id::DefId; -use rustc_span::hygiene::{MacroKind, SyntaxContext}; +use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol}; use std::ops::ControlFlow; @@ -306,6 +306,7 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> } /// A parsed `format_args!` expansion +#[derive(Debug)] pub struct FormatArgsExpn<'tcx> { /// Span of the first argument, the format string pub format_string_span: Span, @@ -465,11 +466,13 @@ impl<'tcx> FormatArgsExpn<'tcx> { .collect() } - /// Span of all inputs + /// Source callsite span of all inputs pub fn inputs_span(&self) -> Span { match *self.value_args { [] => self.format_string_span, - [.., last] => self.format_string_span.to(last.span), + [.., last] => self + .format_string_span + .to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())), } } } diff --git a/tests/ui/expect_fun_call.fixed b/tests/ui/expect_fun_call.fixed index cf923a6a5940c..53e45d28bded9 100644 --- a/tests/ui/expect_fun_call.fixed +++ b/tests/ui/expect_fun_call.fixed @@ -5,6 +5,12 @@ /// Checks implementation of the `EXPECT_FUN_CALL` lint +macro_rules! one { + () => { + 1 + }; +} + fn main() { struct Foo; @@ -31,6 +37,9 @@ fn main() { let with_none_and_as_str: Option = None; with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code)); + let with_none_and_format_with_macro: Option = None; + with_none_and_format_with_macro.unwrap_or_else(|| panic!("Error {}: fake error", one!())); + let with_ok: Result<(), ()> = Ok(()); with_ok.expect("error"); diff --git a/tests/ui/expect_fun_call.rs b/tests/ui/expect_fun_call.rs index e6f252259df70..22e530b80349d 100644 --- a/tests/ui/expect_fun_call.rs +++ b/tests/ui/expect_fun_call.rs @@ -5,6 +5,12 @@ /// Checks implementation of the `EXPECT_FUN_CALL` lint +macro_rules! one { + () => { + 1 + }; +} + fn main() { struct Foo; @@ -31,6 +37,9 @@ fn main() { let with_none_and_as_str: Option = None; with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); + let with_none_and_format_with_macro: Option = None; + with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str()); + let with_ok: Result<(), ()> = Ok(()); with_ok.expect("error"); diff --git a/tests/ui/expect_fun_call.stderr b/tests/ui/expect_fun_call.stderr index ac48a06671cd2..aca15935fca06 100644 --- a/tests/ui/expect_fun_call.stderr +++ b/tests/ui/expect_fun_call.stderr @@ -1,5 +1,5 @@ error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:29:26 + --> $DIR/expect_fun_call.rs:35:26 | LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` @@ -7,70 +7,76 @@ LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code = note: `-D clippy::expect-fun-call` implied by `-D warnings` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:32:26 + --> $DIR/expect_fun_call.rs:38:26 | LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:42:25 + --> $DIR/expect_fun_call.rs:41:37 + | +LL | with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", one!()))` + +error: use of `expect` followed by a function call + --> $DIR/expect_fun_call.rs:51:25 | LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:45:25 + --> $DIR/expect_fun_call.rs:54:25 | LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:57:17 + --> $DIR/expect_fun_call.rs:66:17 | LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:78:21 + --> $DIR/expect_fun_call.rs:87:21 | LL | Some("foo").expect(&get_string()); | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:79:21 + --> $DIR/expect_fun_call.rs:88:21 | LL | Some("foo").expect(get_string().as_ref()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:80:21 + --> $DIR/expect_fun_call.rs:89:21 | LL | Some("foo").expect(get_string().as_str()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:82:21 + --> $DIR/expect_fun_call.rs:91:21 | LL | Some("foo").expect(get_static_str()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:83:21 + --> $DIR/expect_fun_call.rs:92:21 | LL | Some("foo").expect(get_non_static_str(&0)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:87:16 + --> $DIR/expect_fun_call.rs:96:16 | LL | Some(true).expect(&format!("key {}, {}", 1, 2)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))` error: use of `expect` followed by a function call - --> $DIR/expect_fun_call.rs:93:17 + --> $DIR/expect_fun_call.rs:102:17 | LL | opt_ref.expect(&format!("{:?}", opt_ref)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))` -error: aborting due to 12 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui/manual_assert.edition2018.fixed b/tests/ui/manual_assert.edition2018.fixed index 6c2a25c37d8d7..d0bc640db8899 100644 --- a/tests/ui/manual_assert.edition2018.fixed +++ b/tests/ui/manual_assert.edition2018.fixed @@ -6,6 +6,12 @@ #![warn(clippy::manual_assert)] #![allow(clippy::nonminimal_bool)] +macro_rules! one { + () => { + 1 + }; +} + fn main() { let a = vec![1, 2, 3]; let c = Some(2); @@ -42,4 +48,5 @@ fn main() { assert!(!(a.is_empty() && !b.is_empty()), "panic3"); assert!(!(b.is_empty() || a.is_empty()), "panic4"); assert!(!(a.is_empty() || !b.is_empty()), "panic5"); + assert!(!a.is_empty(), "with expansion {}", one!()); } diff --git a/tests/ui/manual_assert.edition2018.stderr b/tests/ui/manual_assert.edition2018.stderr index 77511631e449a..a0f31afd6ebfe 100644 --- a/tests/ui/manual_assert.edition2018.stderr +++ b/tests/ui/manual_assert.edition2018.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:24:5 + --> $DIR/manual_assert.rs:30:5 | LL | / if !a.is_empty() { LL | | panic!("qaqaq{:?}", a); @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::manual-assert` implied by `-D warnings` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:27:5 + --> $DIR/manual_assert.rs:33:5 | LL | / if !a.is_empty() { LL | | panic!("qwqwq"); @@ -17,7 +17,7 @@ LL | | } | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:44:5 + --> $DIR/manual_assert.rs:50:5 | LL | / if b.is_empty() { LL | | panic!("panic1"); @@ -25,7 +25,7 @@ LL | | } | |_____^ help: try: `assert!(!b.is_empty(), "panic1");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:47:5 + --> $DIR/manual_assert.rs:53:5 | LL | / if b.is_empty() && a.is_empty() { LL | | panic!("panic2"); @@ -33,7 +33,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:50:5 + --> $DIR/manual_assert.rs:56:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | panic!("panic3"); @@ -41,7 +41,7 @@ LL | | } | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:53:5 + --> $DIR/manual_assert.rs:59:5 | LL | / if b.is_empty() || a.is_empty() { LL | | panic!("panic4"); @@ -49,12 +49,20 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:56:5 + --> $DIR/manual_assert.rs:62:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | panic!("panic5"); LL | | } | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");` -error: aborting due to 7 previous errors +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:65:5 + | +LL | / if a.is_empty() { +LL | | panic!("with expansion {}", one!()) +LL | | } + | |_____^ help: try: `assert!(!a.is_empty(), "with expansion {}", one!());` + +error: aborting due to 8 previous errors diff --git a/tests/ui/manual_assert.edition2021.fixed b/tests/ui/manual_assert.edition2021.fixed index 6c2a25c37d8d7..d0bc640db8899 100644 --- a/tests/ui/manual_assert.edition2021.fixed +++ b/tests/ui/manual_assert.edition2021.fixed @@ -6,6 +6,12 @@ #![warn(clippy::manual_assert)] #![allow(clippy::nonminimal_bool)] +macro_rules! one { + () => { + 1 + }; +} + fn main() { let a = vec![1, 2, 3]; let c = Some(2); @@ -42,4 +48,5 @@ fn main() { assert!(!(a.is_empty() && !b.is_empty()), "panic3"); assert!(!(b.is_empty() || a.is_empty()), "panic4"); assert!(!(a.is_empty() || !b.is_empty()), "panic5"); + assert!(!a.is_empty(), "with expansion {}", one!()); } diff --git a/tests/ui/manual_assert.edition2021.stderr b/tests/ui/manual_assert.edition2021.stderr index 77511631e449a..a0f31afd6ebfe 100644 --- a/tests/ui/manual_assert.edition2021.stderr +++ b/tests/ui/manual_assert.edition2021.stderr @@ -1,5 +1,5 @@ error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:24:5 + --> $DIR/manual_assert.rs:30:5 | LL | / if !a.is_empty() { LL | | panic!("qaqaq{:?}", a); @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::manual-assert` implied by `-D warnings` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:27:5 + --> $DIR/manual_assert.rs:33:5 | LL | / if !a.is_empty() { LL | | panic!("qwqwq"); @@ -17,7 +17,7 @@ LL | | } | |_____^ help: try: `assert!(a.is_empty(), "qwqwq");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:44:5 + --> $DIR/manual_assert.rs:50:5 | LL | / if b.is_empty() { LL | | panic!("panic1"); @@ -25,7 +25,7 @@ LL | | } | |_____^ help: try: `assert!(!b.is_empty(), "panic1");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:47:5 + --> $DIR/manual_assert.rs:53:5 | LL | / if b.is_empty() && a.is_empty() { LL | | panic!("panic2"); @@ -33,7 +33,7 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() && a.is_empty()), "panic2");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:50:5 + --> $DIR/manual_assert.rs:56:5 | LL | / if a.is_empty() && !b.is_empty() { LL | | panic!("panic3"); @@ -41,7 +41,7 @@ LL | | } | |_____^ help: try: `assert!(!(a.is_empty() && !b.is_empty()), "panic3");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:53:5 + --> $DIR/manual_assert.rs:59:5 | LL | / if b.is_empty() || a.is_empty() { LL | | panic!("panic4"); @@ -49,12 +49,20 @@ LL | | } | |_____^ help: try: `assert!(!(b.is_empty() || a.is_empty()), "panic4");` error: only a `panic!` in `if`-then statement - --> $DIR/manual_assert.rs:56:5 + --> $DIR/manual_assert.rs:62:5 | LL | / if a.is_empty() || !b.is_empty() { LL | | panic!("panic5"); LL | | } | |_____^ help: try: `assert!(!(a.is_empty() || !b.is_empty()), "panic5");` -error: aborting due to 7 previous errors +error: only a `panic!` in `if`-then statement + --> $DIR/manual_assert.rs:65:5 + | +LL | / if a.is_empty() { +LL | | panic!("with expansion {}", one!()) +LL | | } + | |_____^ help: try: `assert!(!a.is_empty(), "with expansion {}", one!());` + +error: aborting due to 8 previous errors diff --git a/tests/ui/manual_assert.rs b/tests/ui/manual_assert.rs index d3e0897488f0c..027747d838631 100644 --- a/tests/ui/manual_assert.rs +++ b/tests/ui/manual_assert.rs @@ -6,6 +6,12 @@ #![warn(clippy::manual_assert)] #![allow(clippy::nonminimal_bool)] +macro_rules! one { + () => { + 1 + }; +} + fn main() { let a = vec![1, 2, 3]; let c = Some(2); @@ -56,4 +62,7 @@ fn main() { if a.is_empty() || !b.is_empty() { panic!("panic5"); } + if a.is_empty() { + panic!("with expansion {}", one!()) + } } From 144b4a59c7a3d18a0dd2c4316061672d00516ea3 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 28 Jan 2022 14:58:14 +0000 Subject: [PATCH 45/67] Add `explicit_write` suggestions for `write!`s with format args --- clippy_lints/src/explicit_write.rs | 41 ++++++++++----------- tests/ui/explicit_write.fixed | 12 +++++++ tests/ui/explicit_write.rs | 12 +++++++ tests/ui/explicit_write.stderr | 42 +++++++++++++++++----- tests/ui/explicit_write_non_rustfix.rs | 8 ----- tests/ui/explicit_write_non_rustfix.stderr | 11 ------ 6 files changed, 75 insertions(+), 51 deletions(-) delete mode 100644 tests/ui/explicit_write_non_rustfix.rs delete mode 100644 tests/ui/explicit_write_non_rustfix.stderr diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index f326fd83d18e7..3e2217c28da3a 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::FormatArgsExpn; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_expn_of, match_function_call, paths}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -79,28 +80,22 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { "print".into(), ) }; - let msg = format!("use of `{}.unwrap()`", used); - if let [write_output] = *format_args.format_string_parts { - let mut write_output = write_output.to_string(); - if write_output.ends_with('\n') { - write_output.pop(); - } - - let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default()); - span_lint_and_sugg( - cx, - EXPLICIT_WRITE, - expr.span, - &msg, - "try this", - sugg, - Applicability::MachineApplicable - ); - } else { - // We don't have a proper suggestion - let help = format!("consider using `{}{}!` instead", prefix, sugg_mac); - span_lint_and_help(cx, EXPLICIT_WRITE, expr.span, &msg, None, &help); - } + let mut applicability = Applicability::MachineApplicable; + let inputs_snippet = snippet_with_applicability( + cx, + format_args.inputs_span(), + "..", + &mut applicability, + ); + span_lint_and_sugg( + cx, + EXPLICIT_WRITE, + expr.span, + &format!("use of `{}.unwrap()`", used), + "try this", + format!("{}{}!({})", prefix, sugg_mac, inputs_snippet), + applicability, + ) } } } diff --git a/tests/ui/explicit_write.fixed b/tests/ui/explicit_write.fixed index 692d2ca675f91..74d0e5290282a 100644 --- a/tests/ui/explicit_write.fixed +++ b/tests/ui/explicit_write.fixed @@ -10,6 +10,12 @@ fn stderr() -> String { String::new() } +macro_rules! one { + () => { + 1 + }; +} + fn main() { // these should warn { @@ -24,6 +30,12 @@ fn main() { // including newlines println!("test\ntest"); eprintln!("test\ntest"); + + let value = 1; + eprintln!("with {}", value); + eprintln!("with {} {}", 2, value); + eprintln!("with {value}"); + eprintln!("macro arg {}", one!()); } // these should not warn, different destination { diff --git a/tests/ui/explicit_write.rs b/tests/ui/explicit_write.rs index 455c5ef55d05c..e7a698d3e012d 100644 --- a/tests/ui/explicit_write.rs +++ b/tests/ui/explicit_write.rs @@ -10,6 +10,12 @@ fn stderr() -> String { String::new() } +macro_rules! one { + () => { + 1 + }; +} + fn main() { // these should warn { @@ -24,6 +30,12 @@ fn main() { // including newlines writeln!(std::io::stdout(), "test\ntest").unwrap(); writeln!(std::io::stderr(), "test\ntest").unwrap(); + + let value = 1; + writeln!(std::io::stderr(), "with {}", value).unwrap(); + writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); + writeln!(std::io::stderr(), "with {value}").unwrap(); + writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); } // these should not warn, different destination { diff --git a/tests/ui/explicit_write.stderr b/tests/ui/explicit_write.stderr index 9feef9c0dc844..29ae0cdece249 100644 --- a/tests/ui/explicit_write.stderr +++ b/tests/ui/explicit_write.stderr @@ -1,5 +1,5 @@ error: use of `write!(stdout(), ...).unwrap()` - --> $DIR/explicit_write.rs:17:9 + --> $DIR/explicit_write.rs:23:9 | LL | write!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` @@ -7,46 +7,70 @@ LL | write!(std::io::stdout(), "test").unwrap(); = note: `-D clippy::explicit-write` implied by `-D warnings` error: use of `write!(stderr(), ...).unwrap()` - --> $DIR/explicit_write.rs:18:9 + --> $DIR/explicit_write.rs:24:9 | LL | write!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> $DIR/explicit_write.rs:19:9 + --> $DIR/explicit_write.rs:25:9 | LL | writeln!(std::io::stdout(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test")` error: use of `writeln!(stderr(), ...).unwrap()` - --> $DIR/explicit_write.rs:20:9 + --> $DIR/explicit_write.rs:26:9 | LL | writeln!(std::io::stderr(), "test").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test")` error: use of `stdout().write_fmt(...).unwrap()` - --> $DIR/explicit_write.rs:21:9 + --> $DIR/explicit_write.rs:27:9 | LL | std::io::stdout().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `print!("test")` error: use of `stderr().write_fmt(...).unwrap()` - --> $DIR/explicit_write.rs:22:9 + --> $DIR/explicit_write.rs:28:9 | LL | std::io::stderr().write_fmt(format_args!("test")).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprint!("test")` error: use of `writeln!(stdout(), ...).unwrap()` - --> $DIR/explicit_write.rs:25:9 + --> $DIR/explicit_write.rs:31:9 | LL | writeln!(std::io::stdout(), "test/ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `println!("test/ntest")` error: use of `writeln!(stderr(), ...).unwrap()` - --> $DIR/explicit_write.rs:26:9 + --> $DIR/explicit_write.rs:32:9 | LL | writeln!(std::io::stderr(), "test/ntest").unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("test/ntest")` -error: aborting due to 8 previous errors +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:35:9 + | +LL | writeln!(std::io::stderr(), "with {}", value).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {}", value)` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:36:9 + | +LL | writeln!(std::io::stderr(), "with {} {}", 2, value).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {} {}", 2, value)` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:37:9 + | +LL | writeln!(std::io::stderr(), "with {value}").unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("with {value}")` + +error: use of `writeln!(stderr(), ...).unwrap()` + --> $DIR/explicit_write.rs:38:9 + | +LL | writeln!(std::io::stderr(), "macro arg {}", one!()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `eprintln!("macro arg {}", one!())` + +error: aborting due to 12 previous errors diff --git a/tests/ui/explicit_write_non_rustfix.rs b/tests/ui/explicit_write_non_rustfix.rs deleted file mode 100644 index f21e8ef935bd0..0000000000000 --- a/tests/ui/explicit_write_non_rustfix.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(unused_imports, clippy::blacklisted_name)] -#![warn(clippy::explicit_write)] - -fn main() { - use std::io::Write; - let bar = "bar"; - writeln!(std::io::stderr(), "foo {}", bar).unwrap(); -} diff --git a/tests/ui/explicit_write_non_rustfix.stderr b/tests/ui/explicit_write_non_rustfix.stderr deleted file mode 100644 index b94ec6403ddce..0000000000000 --- a/tests/ui/explicit_write_non_rustfix.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: use of `writeln!(stderr(), ...).unwrap()` - --> $DIR/explicit_write_non_rustfix.rs:7:5 - | -LL | writeln!(std::io::stderr(), "foo {}", bar).unwrap(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::explicit-write` implied by `-D warnings` - = help: consider using `eprintln!` instead - -error: aborting due to previous error - From 3403b3e7175936f694403767b93803df3e606fc0 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 1 Feb 2022 14:53:12 -0500 Subject: [PATCH 46/67] Add lint `transumte_undefined_repr` --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.rs | 1 + clippy_lints/src/transmute/mod.rs | 31 +- .../src/transmute/transmute_undefined_repr.rs | 291 ++++++++++++++++++ tests/ui/crashes/ice-4968.rs | 1 + tests/ui/transmute_undefined_repr.rs | 44 +++ tests/ui/transmute_undefined_repr.stderr | 44 +++ 10 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 clippy_lints/src/transmute/transmute_undefined_repr.rs create mode 100644 tests/ui/transmute_undefined_repr.rs create mode 100644 tests/ui/transmute_undefined_repr.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 9548366daf9a1..7fb31f6d2b28c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3304,6 +3304,7 @@ Released 2018-09-13 [`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes [`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr [`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref +[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr [`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts [`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null [`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 4721b7f2b472b..d93e34e76b492 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -277,6 +277,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT), LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES), LintId::of(transmute::TRANSMUTE_PTR_TO_REF), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 4217fd3a3ea72..d013daa8e082a 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -58,6 +58,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(swap::ALMOST_SWAPPED), LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index e5e1c052c15ed..a80320a578f0e 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -473,6 +473,7 @@ store.register_lints(&[ transmute::TRANSMUTE_NUM_TO_BYTES, transmute::TRANSMUTE_PTR_TO_PTR, transmute::TRANSMUTE_PTR_TO_REF, + transmute::TRANSMUTE_UNDEFINED_REPR, transmute::UNSOUND_COLLECTION_TRANSMUTE, transmute::USELESS_TRANSMUTE, transmute::WRONG_TRANSMUTE, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9d42185dc0ec9..0b07726519ecd 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -5,6 +5,7 @@ #![feature(control_flow_enum)] #![feature(drain_filter)] #![feature(iter_intersperse)] +#![feature(let_chains)] #![feature(let_else)] #![feature(once_cell)] #![feature(rustc_private)] diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index c370941dd9c21..4c320deecc28b 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -7,6 +7,7 @@ mod transmute_num_to_bytes; mod transmute_ptr_to_ptr; mod transmute_ptr_to_ref; mod transmute_ref_to_ref; +mod transmute_undefined_repr; mod transmutes_expressible_as_ptr_casts; mod unsound_collection_transmute; mod useless_transmute; @@ -355,6 +356,30 @@ declare_clippy_lint! { "transmute between collections of layout-incompatible types" } +declare_clippy_lint! { + /// ### What it does + /// Checks for transmutes either to or from a type which does not have a defined representation. + /// + /// ### Why is this bad? + /// The results of such a transmute are not defined. + /// + /// ### Example + /// ```rust + /// struct Foo(u32, T); + /// let _ = unsafe { core::mem::transmute::, Foo>(Foo(0u32, 0u32)) }; + /// ``` + /// Use instead: + /// ```rust + /// #[repr(C)] + /// struct Foo(u32, T); + /// let _ = unsafe { core::mem::transmute::, Foo>(Foo(0u32, 0u32)) }; + /// ``` + #[clippy::version = "1.60.0"] + pub TRANSMUTE_UNDEFINED_REPR, + correctness, + "transmute to or from a type with an undefined representation" +} + declare_lint_pass!(Transmute => [ CROSSPOINTER_TRANSMUTE, TRANSMUTE_PTR_TO_REF, @@ -369,6 +394,7 @@ declare_lint_pass!(Transmute => [ TRANSMUTE_NUM_TO_BYTES, UNSOUND_COLLECTION_TRANSMUTE, TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + TRANSMUTE_UNDEFINED_REPR, ]); impl<'tcx> LateLintPass<'tcx> for Transmute { @@ -402,7 +428,10 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context) - | unsound_collection_transmute::check(cx, e, from_ty, to_ty); + | ( + unsound_collection_transmute::check(cx, e, from_ty, to_ty) + || transmute_undefined_repr::check(cx, e, from_ty, to_ty) + ); if !linted { transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg); diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs new file mode 100644 index 0000000000000..4b7c493278f21 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -0,0 +1,291 @@ +use super::TRANSMUTE_UNDEFINED_REPR; +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::subst::{GenericArg, Subst}; +use rustc_middle::ty::{self, Ty, TyS, TypeAndMut}; +use rustc_span::Span; + +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty_orig: Ty<'tcx>, + to_ty_orig: Ty<'tcx>, +) -> bool { + let mut from_ty = cx.tcx.erase_regions(from_ty_orig); + let mut to_ty = cx.tcx.erase_regions(to_ty_orig); + + while !TyS::same_type(from_ty, to_ty) { + match reduce_refs(cx, e.span, from_ty, to_ty) { + ReducedTys::FromFatPtr { unsized_ty, .. } => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if !TyS::same_type(from_ty_orig.peel_refs(), unsized_ty) { + diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); + } + }, + ); + return true; + }, + ReducedTys::ToFatPtr { unsized_ty, .. } => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute to `{}` which has an undefined layout", to_ty_orig), + |diag| { + if !TyS::same_type(to_ty_orig.peel_refs(), unsized_ty) { + diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); + } + }, + ); + return true; + }, + ReducedTys::ToPtr { + from_ty: from_sub_ty, + to_ty: to_sub_ty, + } => match reduce_ty(cx, from_sub_ty) { + ReducedTy::UnorderedFields(from_ty) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + }, + ); + return true; + }, + ReducedTy::Ref(from_sub_ty) => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; + }, + _ => break, + }, + ReducedTys::FromPtr { + from_ty: from_sub_ty, + to_ty: to_sub_ty, + } => match reduce_ty(cx, to_sub_ty) { + ReducedTy::UnorderedFields(to_ty) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute to `{}` which has an undefined layout", to_ty_orig), + |diag| { + if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } + }, + ); + return true; + }, + ReducedTy::Ref(to_sub_ty) => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; + }, + _ => break, + }, + ReducedTys::Other { + from_ty: from_sub_ty, + to_ty: to_sub_ty, + } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) { + (ReducedTy::IntArray, _) | (_, ReducedTy::IntArray) => return false, + (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) + if !TyS::same_type(from_ty, to_ty) => + { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!( + "transmute from `{}` to `{}`, both of which have an undefined layout", + from_ty_orig, to_ty_orig + ), + |diag| { + if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def()) + && from_def == to_def + { + diag.note(&format!( + "two instances of the same generic type (`{}`) may have different layouts", + cx.tcx.item_name(from_def.did) + )); + } else { + if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } + } + }, + ); + return true; + }, + ( + ReducedTy::UnorderedFields(from_ty), + ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_), + ) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if !TyS::same_type(from_ty_orig.peel_refs(), from_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + }, + ); + return true; + }, + ( + ReducedTy::Other(_) | ReducedTy::OrderedFields(_) | ReducedTy::Ref(_), + ReducedTy::UnorderedFields(to_ty), + ) => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute into `{}` which has an undefined layout", to_ty_orig), + |diag| { + if !TyS::same_type(to_ty_orig.peel_refs(), to_ty) { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } + }, + ); + return true; + }, + (ReducedTy::Ref(from_sub_ty), ReducedTy::Ref(to_sub_ty)) => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; + }, + ( + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), + ) + | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break, + }, + } + } + + false +} + +enum ReducedTys<'tcx> { + FromFatPtr { unsized_ty: Ty<'tcx> }, + ToFatPtr { unsized_ty: Ty<'tcx> }, + ToPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, + FromPtr { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, + Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, +} + +fn reduce_refs<'tcx>( + cx: &LateContext<'tcx>, + span: Span, + mut from_ty: Ty<'tcx>, + mut to_ty: Ty<'tcx>, +) -> ReducedTys<'tcx> { + loop { + return match (from_ty.kind(), to_ty.kind()) { + ( + ty::Ref(_, from_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: from_sub_ty, .. }), + ty::Ref(_, to_sub_ty, _) | ty::RawPtr(TypeAndMut { ty: to_sub_ty, .. }), + ) => { + from_ty = from_sub_ty; + to_ty = to_sub_ty; + continue; + }, + (ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. }), _) + if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) => + { + ReducedTys::FromFatPtr { unsized_ty } + }, + (_, ty::Ref(_, unsized_ty, _) | ty::RawPtr(TypeAndMut { ty: unsized_ty, .. })) + if !unsized_ty.is_sized(cx.tcx.at(span), cx.param_env) => + { + ReducedTys::ToFatPtr { unsized_ty } + }, + (ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. }), _) => { + ReducedTys::FromPtr { from_ty, to_ty } + }, + (_, ty::Ref(_, to_ty, _) | ty::RawPtr(TypeAndMut { ty: to_ty, .. })) => { + ReducedTys::ToPtr { from_ty, to_ty } + }, + _ => ReducedTys::Other { from_ty, to_ty }, + }; + } +} + +enum ReducedTy<'tcx> { + OrderedFields(Ty<'tcx>), + UnorderedFields(Ty<'tcx>), + Ref(Ty<'tcx>), + Other(Ty<'tcx>), + IntArray, +} + +fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> { + loop { + ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty); + return match *ty.kind() { + ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray, + ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { + ty = sub_ty; + continue; + }, + ty::Tuple(args) => { + let mut iter = args.iter().map(GenericArg::expect_ty); + let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else { + return ReducedTy::OrderedFields(ty); + }; + if iter.all(|ty| is_zero_sized_ty(cx, ty)) { + ty = sized_ty; + continue; + } + ReducedTy::UnorderedFields(ty) + }, + ty::Adt(def, substs) if def.is_struct() => { + if def.repr.inhibit_struct_field_reordering_opt() { + return ReducedTy::OrderedFields(ty); + } + let mut iter = def + .non_enum_variant() + .fields + .iter() + .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs)); + let Some(sized_ty) = iter.find(|ty| !is_zero_sized_ty(cx, ty)) else { + return ReducedTy::OrderedFields(ty); + }; + if iter.all(|ty| is_zero_sized_ty(cx, ty)) { + ty = sized_ty; + continue; + } + ReducedTy::UnorderedFields(ty) + }, + ty::Ref(..) | ty::RawPtr(_) => ReducedTy::Ref(ty), + _ => ReducedTy::Other(ty), + }; + } +} + +fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) + && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) + { + layout.layout.size.bytes() == 0 + } else { + false + } +} diff --git a/tests/ui/crashes/ice-4968.rs b/tests/ui/crashes/ice-4968.rs index 3822f17459854..e0510d942c200 100644 --- a/tests/ui/crashes/ice-4968.rs +++ b/tests/ui/crashes/ice-4968.rs @@ -3,6 +3,7 @@ // Test for https://github.com/rust-lang/rust-clippy/issues/4968 #![warn(clippy::unsound_collection_transmute)] +#![allow(clippy::transmute_undefined_repr)] trait Trait { type Assoc; diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs new file mode 100644 index 0000000000000..71539940fbf27 --- /dev/null +++ b/tests/ui/transmute_undefined_repr.rs @@ -0,0 +1,44 @@ +#![warn(clippy::transmute_undefined_repr)] +#![allow(clippy::unit_arg)] + +fn value() -> T { + unimplemented!() +} + +struct Empty; +struct Ty(T); +struct Ty2(T, U); + +#[repr(C)] +struct Ty2C(T, U); + +fn main() { + unsafe { + let _: () = core::mem::transmute(value::()); + let _: Empty = core::mem::transmute(value::<()>()); + + let _: Ty = core::mem::transmute(value::()); + let _: Ty = core::mem::transmute(value::()); + + let _: Ty2C = core::mem::transmute(value::>()); // Lint, Ty2 is unordered + let _: Ty2 = core::mem::transmute(value::>()); // Lint, Ty2 is unordered + + let _: Ty2 = core::mem::transmute(value::>>()); // Ok, Ty2 types are the same + let _: Ty> = core::mem::transmute(value::>()); // Ok, Ty2 types are the same + + let _: Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances + let _: Ty> = core::mem::transmute(value::>()); // Lint, different Ty2 instances + + let _: Ty<&()> = core::mem::transmute(value::<&()>()); + let _: &() = core::mem::transmute(value::>()); + + let _: &Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances + let _: Ty<&Ty2> = core::mem::transmute(value::<&Ty2>()); // Lint, different Ty2 instances + + let _: Ty = core::mem::transmute(value::<&Ty2>()); // Ok, pointer to usize conversion + let _: &Ty2 = core::mem::transmute(value::>()); // Ok, pointer to usize conversion + + let _: Ty<[u8; 8]> = core::mem::transmute(value::>()); // Ok, transmute to byte array + let _: Ty2 = core::mem::transmute(value::>()); // Ok, transmute from byte array + } +} diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr new file mode 100644 index 0000000000000..040c63c7afa62 --- /dev/null +++ b/tests/ui/transmute_undefined_repr.stderr @@ -0,0 +1,44 @@ +error: transmute from `Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:23:33 + | +LL | let _: Ty2C = core::mem::transmute(value::>()); // Lint, Ty2 is unordered + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings` + +error: transmute into `Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:24:32 + | +LL | let _: Ty2 = core::mem::transmute(value::>()); // Lint, Ty2 is unordered + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `Ty>` to `Ty2`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:29:32 + | +LL | let _: Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Ty2`) may have different layouts + +error: transmute from `Ty2` to `Ty>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:30:36 + | +LL | let _: Ty> = core::mem::transmute(value::>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Ty2`) may have different layouts + +error: transmute to `&Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:35:33 + | +LL | let _: &Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: transmute from `&Ty2` which has an undefined layout + --> $DIR/transmute_undefined_repr.rs:36:37 + | +LL | let _: Ty<&Ty2> = core::mem::transmute(value::<&Ty2>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + From 04dce4aed45540db007a2d1f70c5c02af8e64422 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 30 Jan 2022 16:39:30 +0000 Subject: [PATCH 47/67] Support `cargo dev bless` for tests with revisions --- clippy_dev/src/bless.rs | 65 +++++++--------------- tests/compile-test.rs | 81 +++++++++++++--------------- tests/ui/non_expressive_names.stdout | 0 3 files changed, 56 insertions(+), 90 deletions(-) delete mode 100644 tests/ui/non_expressive_names.stdout diff --git a/clippy_dev/src/bless.rs b/clippy_dev/src/bless.rs index dcc2502e4c59a..b0fb39e816996 100644 --- a/clippy_dev/src/bless.rs +++ b/clippy_dev/src/bless.rs @@ -5,9 +5,7 @@ use std::ffi::OsStr; use std::fs; use std::lazy::SyncLazy; use std::path::{Path, PathBuf}; -use walkdir::WalkDir; - -use crate::clippy_project_root; +use walkdir::{DirEntry, WalkDir}; #[cfg(not(windows))] static CARGO_CLIPPY_EXE: &str = "cargo-clippy"; @@ -24,43 +22,25 @@ static CLIPPY_BUILD_TIME: SyncLazy> = SyncLazy::ne /// /// Panics if the path to a test file is broken pub fn bless(ignore_timestamp: bool) { - let test_suite_dirs = [ - clippy_project_root().join("tests").join("ui"), - clippy_project_root().join("tests").join("ui-internal"), - clippy_project_root().join("tests").join("ui-toml"), - clippy_project_root().join("tests").join("ui-cargo"), - ]; - for test_suite_dir in &test_suite_dirs { - WalkDir::new(test_suite_dir) - .into_iter() - .filter_map(Result::ok) - .filter(|f| f.path().extension() == Some(OsStr::new("rs"))) - .for_each(|f| { - let test_name = f.path().strip_prefix(test_suite_dir).unwrap(); - for &ext in &["stdout", "stderr", "fixed"] { - let test_name_ext = format!("stage-id.{}", ext); - update_reference_file( - f.path().with_extension(ext), - test_name.with_extension(test_name_ext), - ignore_timestamp, - ); - } - }); - } + let extensions = ["stdout", "stderr", "fixed"].map(OsStr::new); + + WalkDir::new(build_dir()) + .into_iter() + .map(Result::unwrap) + .filter(|entry| entry.path().extension().map_or(false, |ext| extensions.contains(&ext))) + .for_each(|entry| update_reference_file(&entry, ignore_timestamp)); } -fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) { - let test_output_path = build_dir().join(test_name); - let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap(); +fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) { + let test_output_path = test_output_entry.path(); - // If compiletest did not write any changes during the test run, - // we don't have to update anything - if !test_output_path.exists() { - return; - } + let reference_file_name = test_output_entry.file_name().to_str().unwrap().replace(".stage-id", ""); + let reference_file_path = Path::new("tests") + .join(test_output_path.strip_prefix(build_dir()).unwrap()) + .with_file_name(reference_file_name); // If the test output was not updated since the last clippy build, it may be outdated - if !ignore_timestamp && !updated_since_clippy_build(&test_output_path).unwrap_or(true) { + if !ignore_timestamp && !updated_since_clippy_build(test_output_entry).unwrap_or(true) { return; } @@ -69,23 +49,14 @@ fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignor if test_output_file != reference_file { // If a test run caused an output file to change, update the reference file - println!("updating {}", &relative_reference_file_path.display()); + println!("updating {}", reference_file_path.display()); fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file"); - - // We need to re-read the file now because it was potentially updated from copying - let reference_file = fs::read(&reference_file_path).unwrap_or_default(); - - if reference_file.is_empty() { - // If we copied over an empty output file, we remove the now empty reference file - println!("removing {}", &relative_reference_file_path.display()); - fs::remove_file(reference_file_path).expect("Could not remove reference file"); - } } } -fn updated_since_clippy_build(path: &Path) -> Option { +fn updated_since_clippy_build(entry: &DirEntry) -> Option { let clippy_build_time = (*CLIPPY_BUILD_TIME)?; - let modified = fs::metadata(path).ok()?.modified().ok()?; + let modified = entry.metadata().ok()?.modified().ok()?; Some(modified >= clippy_build_time) } diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 6505028db9fad..ab7e2540405d3 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -11,6 +11,7 @@ use std::env::{self, remove_var, set_var, var_os}; use std::ffi::{OsStr, OsString}; use std::fs; use std::io; +use std::lazy::SyncLazy; use std::path::{Path, PathBuf}; use test_utils::IS_RUSTC_TEST_SUITE; @@ -64,11 +65,11 @@ extern crate tokio; /// 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 { +static EXTERN_FLAGS: SyncLazy = SyncLazy::new(|| { let current_exe_depinfo = { let mut path = env::current_exe().unwrap(); path.set_extension("d"); - std::fs::read_to_string(path).unwrap() + 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() { @@ -112,16 +113,17 @@ fn extern_flags() -> String { .into_iter() .map(|(name, path)| format!(" --extern {}={}", name, path)) .collect() -} +}); -fn default_config() -> compiletest::Config { +fn base_config(test_dir: &str) -> compiletest::Config { let mut config = compiletest::Config { edition: Some("2021".into()), + mode: TestMode::Ui, ..compiletest::Config::default() }; if let Ok(filters) = env::var("TESTNAME") { - config.filters = filters.split(',').map(std::string::ToString::to_string).collect(); + config.filters = filters.split(',').map(ToString::to_string).collect(); } if let Some(path) = option_env!("RUSTC_LIB_PATH") { @@ -129,7 +131,7 @@ fn default_config() -> compiletest::Config { config.run_lib_path = path.clone(); config.compile_lib_path = path; } - let current_exe_path = std::env::current_exe().unwrap(); + let current_exe_path = env::current_exe().unwrap(); let deps_path = current_exe_path.parent().unwrap(); let profile_path = deps_path.parent().unwrap(); @@ -143,10 +145,11 @@ fn default_config() -> compiletest::Config { "--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}", deps_path.display(), host_libs, - extern_flags(), + &*EXTERN_FLAGS, )); - config.build_base = profile_path.join("test"); + config.src_base = Path::new("tests").join(test_dir); + config.build_base = profile_path.join("test").join(test_dir); config.rustc_path = profile_path.join(if cfg!(windows) { "clippy-driver.exe" } else { @@ -155,38 +158,31 @@ fn default_config() -> compiletest::Config { config } -fn run_ui(cfg: &mut compiletest::Config) { - cfg.mode = TestMode::Ui; - cfg.src_base = Path::new("tests").join("ui"); +fn run_ui() { + let config = base_config("ui"); // use tests/clippy.toml - let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap()); - compiletest::run_tests(cfg); + let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap()); + compiletest::run_tests(&config); } -fn run_ui_test(cfg: &mut compiletest::Config) { - cfg.mode = TestMode::Ui; - cfg.src_base = Path::new("tests").join("ui_test"); - let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap()); - let rustcflags = cfg.target_rustcflags.get_or_insert_with(Default::default); - let len = rustcflags.len(); +fn run_ui_test() { + let mut config = base_config("ui_test"); + let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap()); + let rustcflags = config.target_rustcflags.get_or_insert_with(Default::default); rustcflags.push_str(" --test"); - compiletest::run_tests(cfg); - if let Some(ref mut flags) = &mut cfg.target_rustcflags { - flags.truncate(len); - } + compiletest::run_tests(&config); } -fn run_internal_tests(cfg: &mut compiletest::Config) { +fn run_internal_tests() { // only run internal tests with the internal-tests feature if !RUN_INTERNAL_TESTS { return; } - cfg.mode = TestMode::Ui; - cfg.src_base = Path::new("tests").join("ui-internal"); - compiletest::run_tests(cfg); + let config = base_config("ui-internal"); + compiletest::run_tests(&config); } -fn run_ui_toml(config: &mut compiletest::Config) { +fn run_ui_toml() { fn run_tests(config: &compiletest::Config, mut tests: Vec) -> Result { let mut result = true; let opts = compiletest::test_opts(config); @@ -222,12 +218,12 @@ fn run_ui_toml(config: &mut compiletest::Config) { Ok(result) } - config.mode = TestMode::Ui; - config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap(); + let mut config = base_config("ui-toml"); + config.src_base = config.src_base.canonicalize().unwrap(); - let tests = compiletest::make_tests(config); + let tests = compiletest::make_tests(&config); - let res = run_tests(config, tests); + let res = run_tests(&config, tests); match res { Ok(true) => {}, Ok(false) => panic!("Some tests failed"), @@ -237,7 +233,7 @@ fn run_ui_toml(config: &mut compiletest::Config) { } } -fn run_ui_cargo(config: &mut compiletest::Config) { +fn run_ui_cargo() { fn run_tests( config: &compiletest::Config, filters: &[String], @@ -310,13 +306,13 @@ fn run_ui_cargo(config: &mut compiletest::Config) { return; } - config.mode = TestMode::Ui; - config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap(); + let mut config = base_config("ui-cargo"); + config.src_base = config.src_base.canonicalize().unwrap(); - let tests = compiletest::make_tests(config); + let tests = compiletest::make_tests(&config); let current_dir = env::current_dir().unwrap(); - let res = run_tests(config, &config.filters, tests); + let res = run_tests(&config, &config.filters, tests); env::set_current_dir(current_dir).unwrap(); match res { @@ -331,12 +327,11 @@ fn run_ui_cargo(config: &mut compiletest::Config) { #[test] fn compile_test() { set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); - let mut config = default_config(); - run_ui(&mut config); - run_ui_test(&mut config); - run_ui_toml(&mut config); - run_ui_cargo(&mut config); - run_internal_tests(&mut config); + run_ui(); + run_ui_test(); + run_ui_toml(); + run_ui_cargo(); + run_internal_tests(); } /// Restores an env var on drop diff --git a/tests/ui/non_expressive_names.stdout b/tests/ui/non_expressive_names.stdout deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 68993b1f6cf94701ff420e51aa927c568ae7eef2 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 09:42:10 -0500 Subject: [PATCH 48/67] Small `transmute_float_to_int` cleanup --- .../src/transmute/transmute_float_to_int.rs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_float_to_int.rs b/clippy_lints/src/transmute/transmute_float_to_int.rs index f4656283b1c13..d5ef86dc4e572 100644 --- a/clippy_lints/src/transmute/transmute_float_to_int.rs +++ b/clippy_lints/src/transmute/transmute_float_to_int.rs @@ -15,7 +15,7 @@ pub(super) fn check<'tcx>( e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, - arg: &'tcx Expr<'_>, + mut arg: &'tcx Expr<'_>, const_context: bool, ) -> bool { match (&from_ty.kind(), &to_ty.kind()) { @@ -26,37 +26,36 @@ pub(super) fn check<'tcx>( e.span, &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), |diag| { - let mut expr = arg; - let mut arg = sugg::Sugg::hir(cx, expr, ".."); + let mut sugg = sugg::Sugg::hir(cx, arg, ".."); - if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { - expr = inner_expr; + if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind { + arg = inner_expr; } if_chain! { // if the expression is a float literal and it is unsuffixed then // add a suffix so the suggestion is valid and unambiguous - if let ExprKind::Lit(lit) = &expr.kind; + if let ExprKind::Lit(lit) = &arg.kind; if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; then { - let op = format!("{}{}", arg, float_ty.name_str()).into(); - match arg { - sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), - _ => arg = sugg::Sugg::NonParen(op) + let op = format!("{}{}", sugg, float_ty.name_str()).into(); + match sugg { + sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op), + _ => sugg = sugg::Sugg::NonParen(op) } } } - arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); + sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_par()).into()); // cast the result of `to_bits` if `to_ty` is signed - arg = if let ty::Int(int_ty) = to_ty.kind() { - arg.as_ty(int_ty.name_str().to_string()) + sugg = if let ty::Int(int_ty) = to_ty.kind() { + sugg.as_ty(int_ty.name_str().to_string()) } else { - arg + sugg }; - diag.span_suggestion(e.span, "consider using", arg.to_string(), Applicability::Unspecified); + diag.span_suggestion(e.span, "consider using", sugg.to_string(), Applicability::Unspecified); }, ); true From aae64e93b7ad9de1da74623cb961322f65f8600f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Wed, 2 Feb 2022 21:21:20 +0100 Subject: [PATCH 49/67] ignore test on windows since I don't know why compiletest does not hand paths properly there --- tests/ui-cargo/multiple_config_files/warn/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.rs b/tests/ui-cargo/multiple_config_files/warn/src/main.rs index e7a11a969c037..2d0b4a7948c4a 100644 --- a/tests/ui-cargo/multiple_config_files/warn/src/main.rs +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.rs @@ -1,3 +1,5 @@ +// ignore-windows + fn main() { println!("Hello, world!"); } From f7be9564e5e7803365d6225883c1711dae694de5 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 13:35:57 -0500 Subject: [PATCH 50/67] Move `matches.rs` to `mod.rs` --- clippy_lints/src/{matches.rs => matches/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename clippy_lints/src/{matches.rs => matches/mod.rs} (100%) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches/mod.rs similarity index 100% rename from clippy_lints/src/matches.rs rename to clippy_lints/src/matches/mod.rs From e41a6fc042cdd40f415eba4288d5c5e1161266b1 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 13:42:17 -0500 Subject: [PATCH 51/67] Split out `match_like_matches_macro` --- .../src/matches/match_like_matches.rs | 166 +++++++++++++++++ clippy_lints/src/matches/mod.rs | 172 +----------------- 2 files changed, 175 insertions(+), 163 deletions(-) create mode 100644 clippy_lints/src/matches/match_like_matches.rs diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs new file mode 100644 index 0000000000000..d605b6d73c09d --- /dev/null +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -0,0 +1,166 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{higher, is_wild}; +use rustc_ast::{Attribute, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Guard, MatchSource, Pat}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Spanned; + +use super::MATCH_LIKE_MATCHES_MACRO; + +/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if let Some(higher::IfLet { + let_pat, + let_expr, + if_then, + if_else: Some(if_else), + }) = higher::IfLet::hir(cx, expr) + { + return find_matches_sugg( + cx, + let_expr, + IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), + expr, + true, + ); + } + + if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind { + return find_matches_sugg( + cx, + scrut, + arms.iter().map(|arm| { + ( + cx.tcx.hir().attrs(arm.hir_id), + Some(arm.pat), + arm.body, + arm.guard.as_ref(), + ) + }), + expr, + false, + ); + } + + false +} + +/// Lint a `match` or `if let` for replacement by `matches!` +fn find_matches_sugg<'a, 'b, I>( + cx: &LateContext<'_>, + ex: &Expr<'_>, + mut iter: I, + expr: &Expr<'_>, + is_if_let: bool, +) -> bool +where + 'b: 'a, + I: Clone + + DoubleEndedIterator + + ExactSizeIterator + + Iterator< + Item = ( + &'a [Attribute], + Option<&'a Pat<'b>>, + &'a Expr<'b>, + Option<&'a Guard<'b>>, + ), + >, +{ + if_chain! { + if iter.len() >= 2; + if cx.typeck_results().expr_ty(expr).is_bool(); + if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); + let iter_without_last = iter.clone(); + if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); + if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); + if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); + if b0 != b1; + if first_guard.is_none() || iter.len() == 0; + if first_attrs.is_empty(); + if iter + .all(|arm| { + 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(last_pat) = last_pat_opt { + if !is_wild(last_pat) { + return false; + } + } + + // The suggestion may be incorrect, because some arms can have `cfg` attributes + // evaluated into `false` and so such arms will be stripped before. + let mut applicability = Applicability::MaybeIncorrect; + let pat = { + use itertools::Itertools as _; + iter_without_last + .filter_map(|arm| { + let pat_span = arm.1?.span; + Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) + }) + .join(" | ") + }; + let pat_and_guard = if let Some(Guard::If(g)) = first_guard { + format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) + } else { + pat + }; + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = ex; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { + if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { + ex_new = ex_inner; + } + }; + span_lint_and_sugg( + cx, + MATCH_LIKE_MATCHES_MACRO, + expr.span, + &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }), + "try this", + format!( + "{}matches!({}, {})", + if b0 { "" } else { "!" }, + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), + pat_and_guard, + ), + applicability, + ); + true + } else { + false + } + } +} + +/// Extract a `bool` or `{ bool }` +fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { + match ex { + ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) => Some(*b), + ExprKind::Block( + rustc_hir::Block { + stmts: &[], + expr: Some(exp), + .. + }, + _, + ) if is_if_let => { + if let ExprKind::Lit(Spanned { + node: LitKind::Bool(b), .. + }) = exp.kind + { + Some(b) + } else { + None + } + }, + _ => None, + } +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 67fb918d20c0a..ff07fc6d4d465 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -3,6 +3,7 @@ use clippy_utils::diagnostics::{ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, }; use clippy_utils::macros::{is_panic, root_macro_call}; +use clippy_utils::peel_blocks_with_stmt; 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}; @@ -12,28 +13,28 @@ use clippy_utils::{ path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, strip_pat_refs, }; -use clippy_utils::{higher, peel_blocks_with_stmt}; use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; -use core::iter::{once, ExactSizeIterator}; +use core::iter::once; use if_chain::if_chain; -use rustc_ast::ast::{Attribute, LitKind}; +use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ - self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, - Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, + self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, Local, MatchSource, Mutability, + Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, }; use rustc_hir::{HirIdMap, HirIdSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty, TyS, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::{Span, Spanned}; -use rustc_span::{sym, symbol::kw}; +use rustc_span::{sym, symbol::kw, Span}; use std::cmp::{max, Ordering}; use std::collections::hash_map::Entry; +mod match_like_matches; + declare_clippy_lint! { /// ### What it does /// Checks for matches with a single arm where an `if let` @@ -622,7 +623,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { redundant_pattern_match::check(cx, expr); if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { - if !check_match_like_matches(cx, expr) { + if !match_like_matches::check(cx, expr) { lint_match_arms(cx, expr); } } else { @@ -1382,161 +1383,6 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { } } -/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` -fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { - if let Some(higher::IfLet { - let_pat, - let_expr, - if_then, - if_else: Some(if_else), - }) = higher::IfLet::hir(cx, expr) - { - return find_matches_sugg( - cx, - let_expr, - IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), - expr, - true, - ); - } - - if let ExprKind::Match(scrut, arms, MatchSource::Normal) = expr.kind { - return find_matches_sugg( - cx, - scrut, - arms.iter().map(|arm| { - ( - cx.tcx.hir().attrs(arm.hir_id), - Some(arm.pat), - arm.body, - arm.guard.as_ref(), - ) - }), - expr, - false, - ); - } - - false -} - -/// Lint a `match` or `if let` for replacement by `matches!` -fn find_matches_sugg<'a, 'b, I>( - cx: &LateContext<'_>, - ex: &Expr<'_>, - mut iter: I, - expr: &Expr<'_>, - is_if_let: bool, -) -> bool -where - 'b: 'a, - I: Clone - + DoubleEndedIterator - + ExactSizeIterator - + Iterator< - Item = ( - &'a [Attribute], - Option<&'a Pat<'b>>, - &'a Expr<'b>, - Option<&'a Guard<'b>>, - ), - >, -{ - if_chain! { - if iter.len() >= 2; - if cx.typeck_results().expr_ty(expr).is_bool(); - if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); - let iter_without_last = iter.clone(); - if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); - if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); - if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); - if b0 != b1; - if first_guard.is_none() || iter.len() == 0; - if first_attrs.is_empty(); - if iter - .all(|arm| { - 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(last_pat) = last_pat_opt { - if !is_wild(last_pat) { - return false; - } - } - - // The suggestion may be incorrect, because some arms can have `cfg` attributes - // evaluated into `false` and so such arms will be stripped before. - let mut applicability = Applicability::MaybeIncorrect; - let pat = { - use itertools::Itertools as _; - iter_without_last - .filter_map(|arm| { - let pat_span = arm.1?.span; - Some(snippet_with_applicability(cx, pat_span, "..", &mut applicability)) - }) - .join(" | ") - }; - let pat_and_guard = if let Some(Guard::If(g)) = first_guard { - format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) - } else { - pat - }; - - // strip potential borrows (#6503), but only if the type is a reference - let mut ex_new = ex; - if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { - if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { - ex_new = ex_inner; - } - }; - span_lint_and_sugg( - cx, - MATCH_LIKE_MATCHES_MACRO, - expr.span, - &format!("{} expression looks like `matches!` macro", if is_if_let { "if let .. else" } else { "match" }), - "try this", - format!( - "{}matches!({}, {})", - if b0 { "" } else { "!" }, - snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), - pat_and_guard, - ), - applicability, - ); - true - } else { - false - } - } -} - -/// Extract a `bool` or `{ bool }` -fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { - match ex { - ExprKind::Lit(Spanned { - node: LitKind::Bool(b), .. - }) => Some(*b), - ExprKind::Block( - rustc_hir::Block { - stmts: &[], - expr: Some(exp), - .. - }, - _, - ) if is_if_let => { - if let ExprKind::Lit(Spanned { - node: LitKind::Bool(b), .. - }) = exp.kind - { - Some(b) - } else { - None - } - }, - _ => None, - } -} - #[allow(clippy::too_many_lines)] fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { From 64548250e7c740d0376d638214df65fb2ace35f1 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 13:53:51 -0500 Subject: [PATCH 52/67] Split out `match_same_arms` --- clippy_lints/src/matches/match_same_arms.rs | 112 ++++++++++++++++++ clippy_lints/src/matches/mod.rs | 120 ++------------------ 2 files changed, 119 insertions(+), 113 deletions(-) create mode 100644 clippy_lints/src/matches/match_same_arms.rs diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs new file mode 100644 index 0000000000000..7efbd540fdb4d --- /dev/null +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -0,0 +1,112 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, MatchSource, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use std::collections::hash_map::Entry; + +use super::MATCH_SAME_ARMS; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind { + let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(arm.body); + h.finish() + }; + + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { + let min_index = usize::min(lindex, rindex); + let max_index = usize::max(lindex, rindex); + + let mut local_map: HirIdMap = HirIdMap::default(); + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + if_chain! { + if let Some(a_id) = path_to_local(a); + if let Some(b_id) = path_to_local(b); + let entry = match local_map.entry(a_id) { + Entry::Vacant(entry) => entry, + // check if using the same bindings as before + Entry::Occupied(entry) => return *entry.get() == b_id, + }; + // the names technically don't have to match; this makes the lint more conservative + if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); + if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); + if pat_contains_local(lhs.pat, a_id); + if pat_contains_local(rhs.pat, b_id); + then { + entry.insert(b_id); + true + } else { + false + } + } + }; + // Arms with a guard are ignored, those can’t always be merged together + // This is also the case for arms in-between each there is an arm with a guard + (min_index..=max_index).all(|index| arms[index].guard.is_none()) + && SpanlessEq::new(cx) + .expr_fallback(eq_fallback) + .eq_expr(lhs.body, rhs.body) + // these checks could be removed to allow unused bindings + && bindings_eq(lhs.pat, local_map.keys().copied().collect()) + && bindings_eq(rhs.pat, local_map.values().copied().collect()) + }; + + let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); + for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + j.body.span, + "this `match` has identical arm bodies", + |diag| { + diag.span_note(i.body.span, "same as this"); + + // Note: this does not use `span_suggestion` on purpose: + // there is no clean way + // to remove the other arm. Building a span and suggest to replace it to "" + // makes an even more confusing error message. Also in order not to make up a + // span for the whole pattern, the suggestion is only shown when there is only + // one pattern. The user should know about `|` if they are already using it… + + let lhs = snippet(cx, i.pat.span, ""); + let rhs = snippet(cx, j.pat.span, ""); + + if let PatKind::Wild = j.pat.kind { + // if the last arm is _, then i could be integrated into _ + // note that i.pat cannot be _, because that would mean that we're + // hiding all the subsequent arms, and rust won't compile + diag.span_note( + i.body.span, + &format!( + "`{}` has the same arm body as the `_` wildcard, consider removing it", + lhs + ), + ); + } else { + diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,)) + .help("...or consider changing the match arm bodies"); + } + }, + ); + } + } +} + +fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { + let mut result = false; + pat.walk_short(|p| { + result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); + !result + }); + result +} + +/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa +fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool { + let mut result = true; + pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); + result && ids.is_empty() +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index ff07fc6d4d465..529f759ba0e32 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -3,6 +3,7 @@ use clippy_utils::diagnostics::{ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, }; use clippy_utils::macros::{is_panic, root_macro_call}; +use clippy_utils::paths; use clippy_utils::peel_blocks_with_stmt; use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; @@ -10,10 +11,8 @@ use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, pe use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, - path_to_local, path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, - strip_pat_refs, + path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, strip_pat_refs, }; -use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; use core::iter::once; use if_chain::if_chain; use rustc_ast::ast::LitKind; @@ -21,19 +20,18 @@ use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ - self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, HirId, Local, MatchSource, Mutability, - Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, + self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, + PatKind, PathSegment, QPath, RangeEnd, TyKind, }; -use rustc_hir::{HirIdMap, HirIdSet}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty, TyS, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, symbol::kw, Span}; use std::cmp::{max, Ordering}; -use std::collections::hash_map::Entry; mod match_like_matches; +mod match_same_arms; declare_clippy_lint! { /// ### What it does @@ -624,10 +622,10 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { if !match_like_matches::check(cx, expr) { - lint_match_arms(cx, expr); + match_same_arms::check(cx, expr); } } else { - lint_match_arms(cx, expr); + match_same_arms::check(cx, expr); } if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { @@ -2185,107 +2183,3 @@ fn test_overlapping() { ],) ); } - -/// Implementation of `MATCH_SAME_ARMS`. -fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { - if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind { - let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(arm.body); - h.finish() - }; - - let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { - let min_index = usize::min(lindex, rindex); - let max_index = usize::max(lindex, rindex); - - let mut local_map: HirIdMap = HirIdMap::default(); - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - if_chain! { - if let Some(a_id) = path_to_local(a); - if let Some(b_id) = path_to_local(b); - let entry = match local_map.entry(a_id) { - Entry::Vacant(entry) => entry, - // check if using the same bindings as before - Entry::Occupied(entry) => return *entry.get() == b_id, - }; - // the names technically don't have to match; this makes the lint more conservative - if cx.tcx.hir().name(a_id) == cx.tcx.hir().name(b_id); - if TyS::same_type(cx.typeck_results().expr_ty(a), cx.typeck_results().expr_ty(b)); - if pat_contains_local(lhs.pat, a_id); - if pat_contains_local(rhs.pat, b_id); - then { - entry.insert(b_id); - true - } else { - false - } - } - }; - // Arms with a guard are ignored, those can’t always be merged together - // This is also the case for arms in-between each there is an arm with a guard - (min_index..=max_index).all(|index| arms[index].guard.is_none()) - && SpanlessEq::new(cx) - .expr_fallback(eq_fallback) - .eq_expr(lhs.body, rhs.body) - // these checks could be removed to allow unused bindings - && bindings_eq(lhs.pat, local_map.keys().copied().collect()) - && bindings_eq(rhs.pat, local_map.values().copied().collect()) - }; - - let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); - for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { - span_lint_and_then( - cx, - MATCH_SAME_ARMS, - j.body.span, - "this `match` has identical arm bodies", - |diag| { - diag.span_note(i.body.span, "same as this"); - - // Note: this does not use `span_suggestion` on purpose: - // there is no clean way - // to remove the other arm. Building a span and suggest to replace it to "" - // makes an even more confusing error message. Also in order not to make up a - // span for the whole pattern, the suggestion is only shown when there is only - // one pattern. The user should know about `|` if they are already using it… - - let lhs = snippet(cx, i.pat.span, ""); - let rhs = snippet(cx, j.pat.span, ""); - - if let PatKind::Wild = j.pat.kind { - // if the last arm is _, then i could be integrated into _ - // note that i.pat cannot be _, because that would mean that we're - // hiding all the subsequent arms, and rust won't compile - diag.span_note( - i.body.span, - &format!( - "`{}` has the same arm body as the `_` wildcard, consider removing it", - lhs - ), - ); - } else { - diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,)) - .help("...or consider changing the match arm bodies"); - } - }, - ); - } - } -} - -fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool { - let mut result = false; - pat.walk_short(|p| { - result |= matches!(p.kind, PatKind::Binding(_, binding_id, ..) if binding_id == id); - !result - }); - result -} - -/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa -fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool { - let mut result = true; - pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); - result && ids.is_empty() -} From f2b6ed7cb243b482803e23494a56f9d7ddc19453 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 14:03:45 -0500 Subject: [PATCH 53/67] Split out `redundant_pattern_match` --- clippy_lints/src/matches/mod.rs | 447 +----------------- .../src/matches/redundant_pattern_match.rs | 436 +++++++++++++++++ 2 files changed, 437 insertions(+), 446 deletions(-) create mode 100644 clippy_lints/src/matches/redundant_pattern_match.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 529f759ba0e32..4b09bdb98ecfa 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -32,6 +32,7 @@ use std::cmp::{max, Ordering}; mod match_like_matches; mod match_same_arms; +mod redundant_pattern_match; declare_clippy_lint! { /// ### What it does @@ -1701,452 +1702,6 @@ where None } -mod redundant_pattern_match { - use super::REDUNDANT_PATTERN_MATCHING; - use clippy_utils::diagnostics::span_lint_and_then; - use clippy_utils::source::snippet; - use clippy_utils::sugg::Sugg; - use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type}; - use clippy_utils::{higher, match_def_path}; - use clippy_utils::{is_lang_ctor, is_trait_method, paths}; - use if_chain::if_chain; - use rustc_ast::ast::LitKind; - use rustc_data_structures::fx::FxHashSet; - use rustc_errors::Applicability; - use rustc_hir::LangItem::{OptionNone, PollPending}; - use rustc_hir::{ - intravisit::{walk_expr, Visitor}, - Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp, - }; - use rustc_lint::LateContext; - use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; - use rustc_span::sym; - - pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some(higher::IfLet { - if_else, - let_pat, - let_expr, - .. - }) = higher::IfLet::hir(cx, expr) - { - 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); - } - 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); - } - } - - /// Checks if the drop order for a type matters. Some std types implement drop solely to - /// deallocate memory. For these types, and composites containing them, changing the drop order - /// won't result in any observable side effects. - fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) - } - - fn type_needs_ordered_drop_inner<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - seen: &mut FxHashSet>, - ) -> bool { - if !seen.insert(ty) { - return false; - } - if !ty.needs_drop(cx.tcx, cx.param_env) { - false - } else if !cx - .tcx - .lang_items() - .drop_trait() - .map_or(false, |id| implements_trait(cx, ty, id, &[])) - { - // This type doesn't implement drop, so no side effects here. - // Check if any component type has any. - match ty.kind() { - ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), - ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen), - ty::Adt(adt, subs) => adt - .all_fields() - .map(|f| f.ty(cx.tcx, subs)) - .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), - _ => true, - } - } - // Check for std types which implement drop, but only for memory allocation. - else if is_type_diagnostic_item(cx, ty, sym::Vec) - || is_type_lang_item(cx, ty, LangItem::OwnedBox) - || is_type_diagnostic_item(cx, ty, sym::Rc) - || is_type_diagnostic_item(cx, ty, sym::Arc) - || is_type_diagnostic_item(cx, ty, sym::cstring_type) - || is_type_diagnostic_item(cx, ty, sym::BTreeMap) - || is_type_diagnostic_item(cx, ty, sym::LinkedList) - || match_type(cx, ty, &paths::WEAK_RC) - || match_type(cx, ty, &paths::WEAK_ARC) - { - // Check all of the generic arguments. - if let ty::Adt(_, subs) = ty.kind() { - subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)) - } else { - true - } - } else { - true - } - } - - // Extract the generic arguments out of a type - fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option> { - if_chain! { - if let ty::Adt(_, subs) = ty.kind(); - if let Some(sub) = subs.get(index); - if let GenericArgKind::Type(sub_ty) = sub.unpack(); - then { - Some(sub_ty) - } else { - None - } - } - } - - // Checks if there are any temporaries created in the given expression for which drop order - // matters. - fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { - struct V<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - res: bool, - } - impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - match expr.kind { - // Taking the reference of a value leaves a temporary - // e.g. In `&String::new()` the string is a temporary value. - // Remaining fields are temporary values - // e.g. In `(String::new(), 0).1` the string is a temporary value. - ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => { - if !matches!(expr.kind, ExprKind::Path(_)) { - if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) { - self.res = true; - } else { - self.visit_expr(expr); - } - } - }, - // the base type is alway taken by reference. - // e.g. In `(vec![0])[0]` the vector is a temporary value. - ExprKind::Index(base, index) => { - if !matches!(base.kind, ExprKind::Path(_)) { - if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) { - self.res = true; - } else { - self.visit_expr(base); - } - } - self.visit_expr(index); - }, - // Method calls can take self by reference. - // e.g. In `String::new().len()` the string is a temporary value. - ExprKind::MethodCall(_, [self_arg, args @ ..], _) => { - if !matches!(self_arg.kind, ExprKind::Path(_)) { - let self_by_ref = self - .cx - .typeck_results() - .type_dependent_def_id(expr.hir_id) - .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref()); - if self_by_ref - && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) - { - self.res = true; - } else { - self.visit_expr(self_arg); - } - } - args.iter().for_each(|arg| self.visit_expr(arg)); - }, - // Either explicitly drops values, or changes control flow. - ExprKind::DropTemps(_) - | ExprKind::Ret(_) - | ExprKind::Break(..) - | ExprKind::Yield(..) - | ExprKind::Block(Block { expr: None, .. }, _) - | ExprKind::Loop(..) => (), - - // Only consider the final expression. - ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr), - - _ => walk_expr(self, expr), - } - } - } - - let mut v = V { cx, res: false }; - v.visit_expr(expr); - v.res - } - - fn find_sugg_for_if_let<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - let_pat: &Pat<'_>, - let_expr: &'tcx Expr<'_>, - keyword: &'static str, - has_else: bool, - ) { - // also look inside refs - // if we have &None for example, peel it so we can detect "if let None = x" - let check_pat = match let_pat.kind { - PatKind::Ref(inner, _mutability) => inner, - _ => let_pat, - }; - let op_ty = cx.typeck_results().expr_ty(let_expr); - // Determine which function should be used, and the type contained by the corresponding - // variant. - let (good_method, inner_ty) = match check_pat.kind { - PatKind::TupleStruct(ref qpath, [sub_pat], _) => { - if let PatKind::Wild = sub_pat.kind { - let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); - let Some(id) = res.opt_def_id().and_then(|ctor_id| cx.tcx.parent(ctor_id)) else { return }; - let lang_items = cx.tcx.lang_items(); - if Some(id) == lang_items.result_ok_variant() { - ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) - } else if Some(id) == lang_items.result_err_variant() { - ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) - } else if Some(id) == lang_items.option_some_variant() { - ("is_some()", op_ty) - } else if Some(id) == lang_items.poll_ready_variant() { - ("is_ready()", op_ty) - } else if match_def_path(cx, id, &paths::IPADDR_V4) { - ("is_ipv4()", op_ty) - } else if match_def_path(cx, id, &paths::IPADDR_V6) { - ("is_ipv6()", op_ty) - } else { - return; - } - } else { - return; - } - }, - PatKind::Path(ref path) => { - let method = if is_lang_ctor(cx, path, OptionNone) { - "is_none()" - } else if is_lang_ctor(cx, path, PollPending) { - "is_pending()" - } else { - return; - }; - // `None` and `Pending` don't have an inner type. - (method, cx.tcx.types.unit) - }, - _ => return, - }; - - // If this is the last expression in a block or there is an else clause then the whole - // type needs to be considered, not just the inner type of the branch being matched on. - // Note the last expression in a block is dropped after all local bindings. - let check_ty = if has_else - || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..))))) - { - op_ty - } else { - inner_ty - }; - - // All temporaries created in the scrutinee expression are dropped at the same time as the - // scrutinee would be, so they have to be considered as well. - // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held - // for the duration if body. - let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr); - - // check that `while_let_on_iterator` lint does not trigger - if_chain! { - if keyword == "while"; - if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind; - if method_path.ident.name == sym::next; - if is_trait_method(cx, let_expr, sym::Iterator); - then { - return; - } - } - - let result_expr = match &let_expr.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - ExprKind::Unary(UnOp::Deref, deref) => deref, - _ => let_expr, - }; - - span_lint_and_then( - cx, - REDUNDANT_PATTERN_MATCHING, - let_pat.span, - &format!("redundant pattern matching, consider using `{}`", good_method), - |diag| { - // if/while let ... = ... { ... } - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - let expr_span = expr.span; - - // if/while let ... = ... { ... } - // ^^^ - let op_span = result_expr.span.source_callsite(); - - // if/while let ... = ... { ... } - // ^^^^^^^^^^^^^^^^^^^ - let span = expr_span.until(op_span.shrink_to_hi()); - - let app = if needs_drop { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - - let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") - .maybe_par() - .to_string(); - - diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app); - - if needs_drop { - diag.note("this will change drop order of the result, as well as all temporaries"); - diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important"); - } - }, - ); - } - - fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { - if arms.len() == 2 { - let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); - - let found_good_method = match node_pair { - ( - PatKind::TupleStruct(ref path_left, patterns_left, _), - PatKind::TupleStruct(ref path_right, patterns_right, _), - ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { - if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::RESULT_OK, - &paths::RESULT_ERR, - "is_ok()", - "is_err()", - ) - .or_else(|| { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::IPADDR_V4, - &paths::IPADDR_V6, - "is_ipv4()", - "is_ipv6()", - ) - }) - } else { - None - } - }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) - | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) - if patterns.len() == 1 => - { - if let PatKind::Wild = patterns[0].kind { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::OPTION_SOME, - &paths::OPTION_NONE, - "is_some()", - "is_none()", - ) - .or_else(|| { - find_good_method_for_match( - cx, - arms, - path_left, - path_right, - &paths::POLL_READY, - &paths::POLL_PENDING, - "is_ready()", - "is_pending()", - ) - }) - } else { - None - } - }, - _ => None, - }; - - if let Some(good_method) = found_good_method { - let span = expr.span.to(op.span); - let result_expr = match &op.kind { - ExprKind::AddrOf(_, _, borrowed) => borrowed, - _ => op, - }; - span_lint_and_then( - cx, - REDUNDANT_PATTERN_MATCHING, - expr.span, - &format!("redundant pattern matching, consider using `{}`", good_method), - |diag| { - diag.span_suggestion( - span, - "try this", - format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method), - Applicability::MaybeIncorrect, // snippet - ); - }, - ); - } - } - } - - #[allow(clippy::too_many_arguments)] - fn find_good_method_for_match<'a>( - cx: &LateContext<'_>, - arms: &[Arm<'_>], - path_left: &QPath<'_>, - path_right: &QPath<'_>, - expected_left: &[&str], - expected_right: &[&str], - should_be_left: &'a str, - should_be_right: &'a str, - ) -> Option<&'a str> { - let left_id = cx - .typeck_results() - .qpath_res(path_left, arms[0].pat.hir_id) - .opt_def_id()?; - let right_id = cx - .typeck_results() - .qpath_res(path_right, arms[1].pat.hir_id) - .opt_def_id()?; - let body_node_pair = - if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) { - (&(*arms[0].body).kind, &(*arms[1].body).kind) - } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) { - (&(*arms[1].body).kind, &(*arms[0].body).kind) - } else { - return None; - }; - - match body_node_pair { - (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { - (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), - (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), - _ => None, - }, - _ => None, - } - } -} - #[test] fn test_overlapping() { use rustc_span::source_map::DUMMY_SP; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs new file mode 100644 index 0000000000000..61c5fa0872f6a --- /dev/null +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -0,0 +1,436 @@ +use super::REDUNDANT_PATTERN_MATCHING; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type}; +use clippy_utils::{higher, match_def_path}; +use clippy_utils::{is_lang_ctor, is_trait_method, paths}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_data_structures::fx::FxHashSet; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{OptionNone, PollPending}; +use rustc_hir::{ + intravisit::{walk_expr, Visitor}, + Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, Pat, PatKind, QPath, UnOp, +}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty}; +use rustc_span::sym; + +pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(higher::IfLet { + if_else, + let_pat, + let_expr, + .. + }) = higher::IfLet::hir(cx, expr) + { + 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); + } + 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); + } +} + +/// Checks if the drop order for a type matters. Some std types implement drop solely to +/// deallocate memory. For these types, and composites containing them, changing the drop order +/// won't result in any observable side effects. +fn type_needs_ordered_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) +} + +fn type_needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet>) -> bool { + if !seen.insert(ty) { + return false; + } + if !ty.needs_drop(cx.tcx, cx.param_env) { + false + } else if !cx + .tcx + .lang_items() + .drop_trait() + .map_or(false, |id| implements_trait(cx, ty, id, &[])) + { + // This type doesn't implement drop, so no side effects here. + // Check if any component type has any. + match ty.kind() { + ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), + ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen), + ty::Adt(adt, subs) => adt + .all_fields() + .map(|f| f.ty(cx.tcx, subs)) + .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), + _ => true, + } + } + // Check for std types which implement drop, but only for memory allocation. + else if is_type_diagnostic_item(cx, ty, sym::Vec) + || is_type_lang_item(cx, ty, LangItem::OwnedBox) + || is_type_diagnostic_item(cx, ty, sym::Rc) + || is_type_diagnostic_item(cx, ty, sym::Arc) + || is_type_diagnostic_item(cx, ty, sym::cstring_type) + || is_type_diagnostic_item(cx, ty, sym::BTreeMap) + || is_type_diagnostic_item(cx, ty, sym::LinkedList) + || match_type(cx, ty, &paths::WEAK_RC) + || match_type(cx, ty, &paths::WEAK_ARC) + { + // Check all of the generic arguments. + if let ty::Adt(_, subs) = ty.kind() { + subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)) + } else { + true + } + } else { + true + } +} + +// Extract the generic arguments out of a type +fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option> { + if_chain! { + if let ty::Adt(_, subs) = ty.kind(); + if let Some(sub) = subs.get(index); + if let GenericArgKind::Type(sub_ty) = sub.unpack(); + then { + Some(sub_ty) + } else { + None + } + } +} + +// Checks if there are any temporaries created in the given expression for which drop order +// matters. +fn temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + struct V<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + res: bool, + } + impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + match expr.kind { + // Taking the reference of a value leaves a temporary + // e.g. In `&String::new()` the string is a temporary value. + // Remaining fields are temporary values + // e.g. In `(String::new(), 0).1` the string is a temporary value. + ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => { + if !matches!(expr.kind, ExprKind::Path(_)) { + if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) { + self.res = true; + } else { + self.visit_expr(expr); + } + } + }, + // the base type is alway taken by reference. + // e.g. In `(vec![0])[0]` the vector is a temporary value. + ExprKind::Index(base, index) => { + if !matches!(base.kind, ExprKind::Path(_)) { + if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) { + self.res = true; + } else { + self.visit_expr(base); + } + } + self.visit_expr(index); + }, + // Method calls can take self by reference. + // e.g. In `String::new().len()` the string is a temporary value. + ExprKind::MethodCall(_, [self_arg, args @ ..], _) => { + if !matches!(self_arg.kind, ExprKind::Path(_)) { + let self_by_ref = self + .cx + .typeck_results() + .type_dependent_def_id(expr.hir_id) + .map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref()); + if self_by_ref && type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg)) { + self.res = true; + } else { + self.visit_expr(self_arg); + } + } + args.iter().for_each(|arg| self.visit_expr(arg)); + }, + // Either explicitly drops values, or changes control flow. + ExprKind::DropTemps(_) + | ExprKind::Ret(_) + | ExprKind::Break(..) + | ExprKind::Yield(..) + | ExprKind::Block(Block { expr: None, .. }, _) + | ExprKind::Loop(..) => (), + + // Only consider the final expression. + ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr), + + _ => walk_expr(self, expr), + } + } + } + + let mut v = V { cx, res: false }; + v.visit_expr(expr); + v.res +} + +fn find_sugg_for_if_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + let_pat: &Pat<'_>, + let_expr: &'tcx Expr<'_>, + keyword: &'static str, + has_else: bool, +) { + // also look inside refs + // if we have &None for example, peel it so we can detect "if let None = x" + let check_pat = match let_pat.kind { + PatKind::Ref(inner, _mutability) => inner, + _ => let_pat, + }; + let op_ty = cx.typeck_results().expr_ty(let_expr); + // Determine which function should be used, and the type contained by the corresponding + // variant. + let (good_method, inner_ty) = match check_pat.kind { + PatKind::TupleStruct(ref qpath, [sub_pat], _) => { + if let PatKind::Wild = sub_pat.kind { + let res = cx.typeck_results().qpath_res(qpath, check_pat.hir_id); + let Some(id) = res.opt_def_id().and_then(|ctor_id| cx.tcx.parent(ctor_id)) else { return }; + let lang_items = cx.tcx.lang_items(); + if Some(id) == lang_items.result_ok_variant() { + ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) + } else if Some(id) == lang_items.result_err_variant() { + ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) + } else if Some(id) == lang_items.option_some_variant() { + ("is_some()", op_ty) + } else if Some(id) == lang_items.poll_ready_variant() { + ("is_ready()", op_ty) + } else if match_def_path(cx, id, &paths::IPADDR_V4) { + ("is_ipv4()", op_ty) + } else if match_def_path(cx, id, &paths::IPADDR_V6) { + ("is_ipv6()", op_ty) + } else { + return; + } + } else { + return; + } + }, + PatKind::Path(ref path) => { + let method = if is_lang_ctor(cx, path, OptionNone) { + "is_none()" + } else if is_lang_ctor(cx, path, PollPending) { + "is_pending()" + } else { + return; + }; + // `None` and `Pending` don't have an inner type. + (method, cx.tcx.types.unit) + }, + _ => return, + }; + + // If this is the last expression in a block or there is an else clause then the whole + // type needs to be considered, not just the inner type of the branch being matched on. + // Note the last expression in a block is dropped after all local bindings. + let check_ty = if has_else + || (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..))))) + { + op_ty + } else { + inner_ty + }; + + // All temporaries created in the scrutinee expression are dropped at the same time as the + // scrutinee would be, so they have to be considered as well. + // e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held + // for the duration if body. + let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, let_expr); + + // check that `while_let_on_iterator` lint does not trigger + if_chain! { + if keyword == "while"; + if let ExprKind::MethodCall(method_path, _, _) = let_expr.kind; + if method_path.ident.name == sym::next; + if is_trait_method(cx, let_expr, sym::Iterator); + then { + return; + } + } + + let result_expr = match &let_expr.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + ExprKind::Unary(UnOp::Deref, deref) => deref, + _ => let_expr, + }; + + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + let_pat.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + // if/while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let expr_span = expr.span; + + // if/while let ... = ... { ... } + // ^^^ + let op_span = result_expr.span.source_callsite(); + + // if/while let ... = ... { ... } + // ^^^^^^^^^^^^^^^^^^^ + let span = expr_span.until(op_span.shrink_to_hi()); + + let app = if needs_drop { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + + let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_") + .maybe_par() + .to_string(); + + diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app); + + if needs_drop { + diag.note("this will change drop order of the result, as well as all temporaries"); + diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important"); + } + }, + ); +} + +fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { + if arms.len() == 2 { + let node_pair = (&arms[0].pat.kind, &arms[1].pat.kind); + + let found_good_method = match node_pair { + ( + PatKind::TupleStruct(ref path_left, patterns_left, _), + PatKind::TupleStruct(ref path_right, patterns_right, _), + ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::RESULT_OK, + &paths::RESULT_ERR, + "is_ok()", + "is_err()", + ) + .or_else(|| { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::IPADDR_V4, + &paths::IPADDR_V6, + "is_ipv4()", + "is_ipv6()", + ) + }) + } else { + None + } + }, + (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) + | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) + if patterns.len() == 1 => + { + if let PatKind::Wild = patterns[0].kind { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::OPTION_SOME, + &paths::OPTION_NONE, + "is_some()", + "is_none()", + ) + .or_else(|| { + find_good_method_for_match( + cx, + arms, + path_left, + path_right, + &paths::POLL_READY, + &paths::POLL_PENDING, + "is_ready()", + "is_pending()", + ) + }) + } else { + None + } + }, + _ => None, + }; + + if let Some(good_method) = found_good_method { + let span = expr.span.to(op.span); + let result_expr = match &op.kind { + ExprKind::AddrOf(_, _, borrowed) => borrowed, + _ => op, + }; + span_lint_and_then( + cx, + REDUNDANT_PATTERN_MATCHING, + expr.span, + &format!("redundant pattern matching, consider using `{}`", good_method), + |diag| { + diag.span_suggestion( + span, + "try this", + format!("{}.{}", snippet(cx, result_expr.span, "_"), good_method), + Applicability::MaybeIncorrect, // snippet + ); + }, + ); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn find_good_method_for_match<'a>( + cx: &LateContext<'_>, + arms: &[Arm<'_>], + path_left: &QPath<'_>, + path_right: &QPath<'_>, + expected_left: &[&str], + expected_right: &[&str], + should_be_left: &'a str, + should_be_right: &'a str, +) -> Option<&'a str> { + let left_id = cx + .typeck_results() + .qpath_res(path_left, arms[0].pat.hir_id) + .opt_def_id()?; + let right_id = cx + .typeck_results() + .qpath_res(path_right, arms[1].pat.hir_id) + .opt_def_id()?; + let body_node_pair = if match_def_path(cx, left_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[0].body).kind, &(*arms[1].body).kind) + } else if match_def_path(cx, right_id, expected_left) && match_def_path(cx, right_id, expected_right) { + (&(*arms[1].body).kind, &(*arms[0].body).kind) + } else { + return None; + }; + + match body_node_pair { + (ExprKind::Lit(ref lit_left), ExprKind::Lit(ref lit_right)) => match (&lit_left.node, &lit_right.node) { + (LitKind::Bool(true), LitKind::Bool(false)) => Some(should_be_left), + (LitKind::Bool(false), LitKind::Bool(true)) => Some(should_be_right), + _ => None, + }, + _ => None, + } +} From f23dc16e1df17e7d2c62d11bc3b180aba644624b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 14:18:00 -0500 Subject: [PATCH 54/67] Split out `single_match` --- clippy_lints/src/matches/mod.rs | 272 +---------------------- clippy_lints/src/matches/single_match.rs | 269 ++++++++++++++++++++++ 2 files changed, 277 insertions(+), 264 deletions(-) create mode 100644 clippy_lints/src/matches/single_match.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 4b09bdb98ecfa..5173102a9e55a 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -3,15 +3,14 @@ use clippy_utils::diagnostics::{ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, }; use clippy_utils::macros::{is_panic, root_macro_call}; -use clippy_utils::paths; use clippy_utils::peel_blocks_with_stmt; 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::ty::is_type_diagnostic_item; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - get_parent_expr, is_lang_ctor, is_lint_allowed, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, - path_to_local_id, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, strip_pat_refs, + get_parent_expr, is_lang_ctor, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, path_to_local_id, + peel_blocks, peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs, }; use core::iter::once; use if_chain::if_chain; @@ -20,19 +19,20 @@ use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ - self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, + self as hir, Arm, BindingAnnotation, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, TyS, VariantDef}; +use rustc_middle::ty::{self, Ty, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, symbol::kw, Span}; -use std::cmp::{max, Ordering}; +use std::cmp::Ordering; mod match_like_matches; mod match_same_arms; mod redundant_pattern_match; +mod single_match; declare_clippy_lint! { /// ### What it does @@ -630,7 +630,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { - check_single_match(cx, ex, arms, expr); + single_match::check(cx, ex, arms, expr); check_match_bool(cx, ex, arms, expr); check_overlapping_arms(cx, ex, arms); check_wild_err_arm(cx, ex, arms); @@ -710,262 +710,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -#[rustfmt::skip] -fn check_single_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - if expr.span.from_expansion() { - // Don't lint match expressions present in - // macro_rules! block - return; - } - if let PatKind::Or(..) = arms[0].pat.kind { - // don't lint for or patterns for now, this makes - // the lint noisy in unnecessary situations - return; - } - let els = arms[1].body; - let els = if is_unit_expr(peel_blocks(els)) { - None - } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { - if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { - // single statement/expr "else" block, don't lint - return; - } - // block with 2+ statements or 1 expr and 1+ statement - Some(els) - } else { - // not a block, don't lint - return; - }; - - let ty = cx.typeck_results().expr_ty(ex); - if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { - check_single_match_single_pattern(cx, ex, arms, expr, els); - check_single_match_opt_like(cx, ex, arms, expr, ty, els); - } - } -} - -fn check_single_match_single_pattern( - cx: &LateContext<'_>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - els: Option<&Expr<'_>>, -) { - if is_wild(arms[1].pat) { - report_single_match_single_pattern(cx, ex, arms, expr, els); - } -} - -fn report_single_match_single_pattern( - cx: &LateContext<'_>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - els: Option<&Expr<'_>>, -) { - let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; - let els_str = els.map_or(String::new(), |els| { - format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) - }); - - let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); - let (msg, sugg) = if_chain! { - if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; - let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); - if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait(); - if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait(); - if ty.is_integral() || ty.is_char() || ty.is_str() - || (implements_trait(cx, ty, spe_trait_id, &[]) - && implements_trait(cx, ty, pe_trait_id, &[ty.into()])); - then { - // scrutinee derives PartialEq and the pattern is a constant. - let pat_ref_count = match pat.kind { - // string literals are already a reference. - PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, - _ => pat_ref_count, - }; - // References are only implicitly added to the pattern, so no overflow here. - // e.g. will work: match &Some(_) { Some(_) => () } - // will not: match Some(_) { &Some(_) => () } - let ref_count_diff = ty_ref_count - pat_ref_count; - - // Try to remove address of expressions first. - let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); - let ref_count_diff = ref_count_diff - removed; - - let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; - let sugg = format!( - "if {} == {}{} {}{}", - snippet(cx, ex.span, ".."), - // PartialEq for different reference counts may not exist. - "&".repeat(ref_count_diff), - snippet(cx, arms[0].pat.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), - els_str, - ); - (msg, sugg) - } else { - let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; - let sugg = format!( - "if let {} = {} {}{}", - snippet(cx, arms[0].pat.span, ".."), - snippet(cx, ex.span, ".."), - expr_block(cx, arms[0].body, None, "..", Some(expr.span)), - els_str, - ); - (msg, sugg) - } - }; - - span_lint_and_sugg( - cx, - lint, - expr.span, - msg, - "try this", - sugg, - Applicability::HasPlaceholders, - ); -} - -fn check_single_match_opt_like<'a>( - cx: &LateContext<'a>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - ty: Ty<'a>, - els: Option<&Expr<'_>>, -) { - // list of candidate `Enum`s we know will never get any more members - let candidates = &[ - (&paths::COW, "Borrowed"), - (&paths::COW, "Cow::Borrowed"), - (&paths::COW, "Cow::Owned"), - (&paths::COW, "Owned"), - (&paths::OPTION, "None"), - (&paths::RESULT, "Err"), - (&paths::RESULT, "Ok"), - ]; - - // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive - // match with the second branch, without enum variants in matches. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { - return; - } - - let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } - - let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool { - let (path, ty) = path_info; - for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, ty, ty_path) { - return true; - } - } - false - }; - if paths_and_types.iter().all(in_candidate_enum) { - report_single_match_single_pattern(cx, ex, arms, expr, els); - } -} - -/// Collects paths and their types from the given patterns. Returns true if the given pattern could -/// be simplified, false otherwise. -fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { - match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) => inner.iter().all(|p| { - let p_ty = cx.typeck_results().pat_ty(p); - collect_pat_paths(acc, cx, p, p_ty) - }), - PatKind::TupleStruct(ref path, ..) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push((ident.to_string(), ty)); - true - }, - PatKind::Path(ref path) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - _ => false, - } -} - -/// Returns true if the given arm of pattern matching contains wildcard patterns. -fn contains_only_wilds(pat: &Pat<'_>) -> bool { - match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), - _ => false, - } -} - -/// Returns true if the given patterns forms only exhaustive matches that don't contain enum -/// patterns without a wildcard. -fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { - match (&left.kind, &right.kind) { - (PatKind::Wild, _) | (_, PatKind::Wild) => true, - (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { - // We don't actually know the position and the presence of the `..` (dotdot) operator - // in the arms, so we need to evaluate the correct offsets here in order to iterate in - // both arms at the same time. - let len = max( - left_in.len() + { - if left_pos.is_some() { 1 } else { 0 } - }, - right_in.len() + { - if right_pos.is_some() { 1 } else { 0 } - }, - ); - let mut left_pos = left_pos.unwrap_or(usize::MAX); - let mut right_pos = right_pos.unwrap_or(usize::MAX); - let mut left_dot_space = 0; - let mut right_dot_space = 0; - for i in 0..len { - let mut found_dotdot = false; - if i == left_pos { - left_dot_space += 1; - if left_dot_space < len - left_in.len() { - left_pos += 1; - } - found_dotdot = true; - } - if i == right_pos { - right_dot_space += 1; - if right_dot_space < len - right_in.len() { - right_pos += 1; - } - found_dotdot = true; - } - if found_dotdot { - continue; - } - if !contains_only_wilds(&left_in[i - left_dot_space]) - && !contains_only_wilds(&right_in[i - right_dot_space]) - { - return false; - } - } - true - }, - _ => false, - } -} - fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { // Type of expression is `bool`. if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs new file mode 100644 index 0000000000000..6ba279eaf1224 --- /dev/null +++ b/clippy_lints/src/matches/single_match.rs @@ -0,0 +1,269 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{expr_block, snippet}; +use clippy_utils::ty::{implements_trait, match_type, peel_mid_ty_refs}; +use clippy_utils::{ + is_lint_allowed, is_unit_expr, is_wild, paths, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs, +}; +use core::cmp::max; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, TyS}; + +use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; + +#[rustfmt::skip] +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + if expr.span.from_expansion() { + // Don't lint match expressions present in + // macro_rules! block + return; + } + if let PatKind::Or(..) = arms[0].pat.kind { + // don't lint for or patterns for now, this makes + // the lint noisy in unnecessary situations + return; + } + let els = arms[1].body; + let els = if is_unit_expr(peel_blocks(els)) { + None + } else if let ExprKind::Block(Block { stmts, expr: block_expr, .. }, _) = els.kind { + if stmts.len() == 1 && block_expr.is_none() || stmts.is_empty() && block_expr.is_some() { + // single statement/expr "else" block, don't lint + return; + } + // block with 2+ statements or 1 expr and 1+ statement + Some(els) + } else { + // not a block, don't lint + return; + }; + + let ty = cx.typeck_results().expr_ty(ex); + if *ty.kind() != ty::Bool || is_lint_allowed(cx, MATCH_BOOL, ex.hir_id) { + check_single_pattern(cx, ex, arms, expr, els); + check_opt_like(cx, ex, arms, expr, ty, els); + } + } +} + +fn check_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + if is_wild(arms[1].pat) { + report_single_pattern(cx, ex, arms, expr, els); + } +} + +fn report_single_pattern( + cx: &LateContext<'_>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + els: Option<&Expr<'_>>, +) { + let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH }; + let els_str = els.map_or(String::new(), |els| { + format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) + }); + + let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); + let (msg, sugg) = if_chain! { + if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; + let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); + if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait(); + if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait(); + if ty.is_integral() || ty.is_char() || ty.is_str() + || (implements_trait(cx, ty, spe_trait_id, &[]) + && implements_trait(cx, ty, pe_trait_id, &[ty.into()])); + then { + // scrutinee derives PartialEq and the pattern is a constant. + let pat_ref_count = match pat.kind { + // string literals are already a reference. + PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, + _ => pat_ref_count, + }; + // References are only implicitly added to the pattern, so no overflow here. + // e.g. will work: match &Some(_) { Some(_) => () } + // will not: match Some(_) { &Some(_) => () } + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + let ref_count_diff = ref_count_diff - removed; + + let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; + let sugg = format!( + "if {} == {}{} {}{}", + snippet(cx, ex.span, ".."), + // PartialEq for different reference counts may not exist. + "&".repeat(ref_count_diff), + snippet(cx, arms[0].pat.span, ".."), + expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } else { + let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; + let sugg = format!( + "if let {} = {} {}{}", + snippet(cx, arms[0].pat.span, ".."), + snippet(cx, ex.span, ".."), + expr_block(cx, arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } + }; + + span_lint_and_sugg( + cx, + lint, + expr.span, + msg, + "try this", + sugg, + Applicability::HasPlaceholders, + ); +} + +fn check_opt_like<'a>( + cx: &LateContext<'a>, + ex: &Expr<'_>, + arms: &[Arm<'_>], + expr: &Expr<'_>, + ty: Ty<'a>, + els: Option<&Expr<'_>>, +) { + // list of candidate `Enum`s we know will never get any more members + let candidates = &[ + (&paths::COW, "Borrowed"), + (&paths::COW, "Cow::Borrowed"), + (&paths::COW, "Cow::Owned"), + (&paths::COW, "Owned"), + (&paths::OPTION, "None"), + (&paths::RESULT, "Err"), + (&paths::RESULT, "Ok"), + ]; + + // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive + // match with the second branch, without enum variants in matches. + if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { + return; + } + + let mut paths_and_types = Vec::new(); + if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { + return; + } + + let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool { + let (path, ty) = path_info; + for &(ty_path, pat_path) in candidates { + if path == pat_path && match_type(cx, ty, ty_path) { + return true; + } + } + false + }; + if paths_and_types.iter().all(in_candidate_enum) { + report_single_pattern(cx, ex, arms, expr, els); + } +} + +/// Collects paths and their types from the given patterns. Returns true if the given pattern could +/// be simplified, false otherwise. +fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) => inner.iter().all(|p| { + let p_ty = cx.typeck_results().pat_ty(p); + collect_pat_paths(acc, cx, p, p_ty) + }), + PatKind::TupleStruct(ref path, ..) => { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + }); + acc.push((path, ty)); + true + }, + PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { + acc.push((ident.to_string(), ty)); + true + }, + PatKind::Path(ref path) => { + let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { + s.print_qpath(path, false); + }); + acc.push((path, ty)); + true + }, + _ => false, + } +} + +/// Returns true if the given arm of pattern matching contains wildcard patterns. +fn contains_only_wilds(pat: &Pat<'_>) -> bool { + match pat.kind { + PatKind::Wild => true, + PatKind::Tuple(inner, _) | PatKind::TupleStruct(_, inner, ..) => inner.iter().all(contains_only_wilds), + _ => false, + } +} + +/// Returns true if the given patterns forms only exhaustive matches that don't contain enum +/// patterns without a wildcard. +fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { + match (&left.kind, &right.kind) { + (PatKind::Wild, _) | (_, PatKind::Wild) => true, + (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { + // We don't actually know the position and the presence of the `..` (dotdot) operator + // in the arms, so we need to evaluate the correct offsets here in order to iterate in + // both arms at the same time. + let len = max( + left_in.len() + { + if left_pos.is_some() { 1 } else { 0 } + }, + right_in.len() + { + if right_pos.is_some() { 1 } else { 0 } + }, + ); + let mut left_pos = left_pos.unwrap_or(usize::MAX); + let mut right_pos = right_pos.unwrap_or(usize::MAX); + let mut left_dot_space = 0; + let mut right_dot_space = 0; + for i in 0..len { + let mut found_dotdot = false; + if i == left_pos { + left_dot_space += 1; + if left_dot_space < len - left_in.len() { + left_pos += 1; + } + found_dotdot = true; + } + if i == right_pos { + right_dot_space += 1; + if right_dot_space < len - right_in.len() { + right_pos += 1; + } + found_dotdot = true; + } + if found_dotdot { + continue; + } + if !contains_only_wilds(&left_in[i - left_dot_space]) + && !contains_only_wilds(&right_in[i - right_dot_space]) + { + return false; + } + } + true + }, + _ => false, + } +} From f3dd909e0f2900420915f55a3f6cea6000b18cad Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 14:25:53 -0500 Subject: [PATCH 55/67] Split out `match_bool` --- clippy_lints/src/matches/match_bool.rs | 75 ++++++++++++++++++++++++++ clippy_lints/src/matches/mod.rs | 74 ++----------------------- 2 files changed, 80 insertions(+), 69 deletions(-) create mode 100644 clippy_lints/src/matches/match_bool.rs diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs new file mode 100644 index 0000000000000..90c50b994d2bf --- /dev/null +++ b/clippy_lints/src/matches/match_bool.rs @@ -0,0 +1,75 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_unit_expr; +use clippy_utils::source::{expr_block, snippet}; +use clippy_utils::sugg::Sugg; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MATCH_BOOL; + +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + // Type of expression is `bool`. + if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { + span_lint_and_then( + cx, + MATCH_BOOL, + expr.span, + "you seem to be trying to match on a boolean expression", + move |diag| { + if arms.len() == 2 { + // no guards + let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind { + if let ExprKind::Lit(ref lit) = arm_bool.kind { + match lit.node { + LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), + LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), + _ => None, + } + } else { + None + } + } else { + None + }; + + if let Some((true_expr, false_expr)) = exprs { + let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { + (false, false) => Some(format!( + "if {} {} else {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)), + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )), + (false, true) => Some(format!( + "if {} {}", + snippet(cx, ex.span, "b"), + expr_block(cx, true_expr, None, "..", Some(expr.span)) + )), + (true, false) => { + let test = Sugg::hir(cx, ex, ".."); + Some(format!( + "if {} {}", + !test, + expr_block(cx, false_expr, None, "..", Some(expr.span)) + )) + }, + (true, true) => None, + }; + + if let Some(sugg) = sugg { + diag.span_suggestion( + expr.span, + "consider using an `if`/`else` expression", + sugg, + Applicability::HasPlaceholders, + ); + } + } + } + }, + ); + } +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 5173102a9e55a..4775e07d24bdb 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -4,17 +4,16 @@ use clippy_utils::diagnostics::{ }; use clippy_utils::macros::{is_panic, root_macro_call}; use clippy_utils::peel_blocks_with_stmt; -use clippy_utils::source::{expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - get_parent_expr, is_lang_ctor, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, path_to_local_id, - peel_blocks, peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs, + get_parent_expr, is_lang_ctor, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, + peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs, }; use core::iter::once; use if_chain::if_chain; -use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -29,6 +28,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, symbol::kw, Span}; use std::cmp::Ordering; +mod match_bool; mod match_like_matches; mod match_same_arms; mod redundant_pattern_match; @@ -631,7 +631,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { single_match::check(cx, ex, arms, expr); - check_match_bool(cx, ex, arms, expr); + match_bool::check(cx, ex, arms, expr); check_overlapping_arms(cx, ex, arms); check_wild_err_arm(cx, ex, arms); check_wild_enum_match(cx, ex, arms); @@ -710,70 +710,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - // Type of expression is `bool`. - if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool { - span_lint_and_then( - cx, - MATCH_BOOL, - expr.span, - "you seem to be trying to match on a boolean expression", - move |diag| { - if arms.len() == 2 { - // no guards - let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind { - if let ExprKind::Lit(ref lit) = arm_bool.kind { - match lit.node { - LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), - LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)), - _ => None, - } - } else { - None - } - } else { - None - }; - - if let Some((true_expr, false_expr)) = exprs { - let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { - (false, false) => Some(format!( - "if {} {} else {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)), - expr_block(cx, false_expr, None, "..", Some(expr.span)) - )), - (false, true) => Some(format!( - "if {} {}", - snippet(cx, ex.span, "b"), - expr_block(cx, true_expr, None, "..", Some(expr.span)) - )), - (true, false) => { - let test = Sugg::hir(cx, ex, ".."); - Some(format!( - "if {} {}", - !test, - expr_block(cx, false_expr, None, "..", Some(expr.span)) - )) - }, - (true, true) => None, - }; - - if let Some(sugg) = sugg { - diag.span_suggestion( - expr.span, - "consider using an `if`/`else` expression", - sugg, - Applicability::HasPlaceholders, - ); - } - } - } - }, - ); - } -} - fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); From 2a70439ef080f9b0a2bf08d124035ac120c6e0ee Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 14:36:24 -0500 Subject: [PATCH 56/67] Split out `overlapping_arms` --- clippy_lints/src/matches/mod.rs | 183 +------------------ clippy_lints/src/matches/overlapping_arms.rs | 181 ++++++++++++++++++ 2 files changed, 186 insertions(+), 178 deletions(-) create mode 100644 clippy_lints/src/matches/overlapping_arms.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 4775e07d24bdb..a7fc399bfe229 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,4 +1,3 @@ -use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt}; use clippy_utils::diagnostics::{ multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, }; @@ -19,18 +18,18 @@ use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ self as hir, Arm, BindingAnnotation, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, - PatKind, PathSegment, QPath, RangeEnd, TyKind, + PatKind, PathSegment, QPath, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty, VariantDef}; +use rustc_middle::ty::{self, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{sym, symbol::kw, Span}; -use std::cmp::Ordering; +use rustc_span::{sym, symbol::kw}; mod match_bool; mod match_like_matches; mod match_same_arms; +mod overlapping_arms; mod redundant_pattern_match; mod single_match; @@ -632,7 +631,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { single_match::check(cx, ex, arms, expr); match_bool::check(cx, ex, arms, expr); - check_overlapping_arms(cx, ex, arms); + overlapping_arms::check(cx, ex, arms); check_wild_err_arm(cx, ex, arms); check_wild_enum_match(cx, ex, arms); check_match_as_ref(cx, ex, arms, expr); @@ -710,24 +709,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -fn check_overlapping_arms<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { - if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { - let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); - if !ranges.is_empty() { - if let Some((start, end)) = overlapping(&ranges) { - span_lint_and_note( - cx, - MATCH_OVERLAPPING_ARM, - start.span, - "some ranges overlap", - Some(end.span), - "overlaps with this", - ); - } - } - } -} - fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); if is_type_diagnostic_item(cx, ex_ty, sym::Result) { @@ -1219,59 +1200,6 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<' None } -/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges. -fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { - arms.iter() - .filter_map(|arm| { - if let Arm { pat, guard: None, .. } = *arm { - if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { - let lhs_const = match lhs { - Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, - None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, - }; - let rhs_const = match rhs { - Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, - None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, - }; - - let lhs_val = lhs_const.int_value(cx, ty)?; - let rhs_val = rhs_const.int_value(cx, ty)?; - - let rhs_bound = match range_end { - RangeEnd::Included => EndBound::Included(rhs_val), - RangeEnd::Excluded => EndBound::Excluded(rhs_val), - }; - return Some(SpannedRange { - span: pat.span, - node: (lhs_val, rhs_bound), - }); - } - - if let PatKind::Lit(value) = pat.kind { - let value = constant_full_int(cx, cx.typeck_results(), value)?; - return Some(SpannedRange { - span: pat.span, - node: (value, EndBound::Included(value)), - }); - } - } - None - }) - .collect() -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum EndBound { - Included(T), - Excluded(T), -} - -#[derive(Debug, Eq, PartialEq)] -struct SpannedRange { - pub span: Span, - pub node: (T, EndBound), -} - // Checks if arm has the form `None => None` fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone)) @@ -1317,104 +1245,3 @@ where } ref_count > 1 } - -fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> -where - T: Copy + Ord, -{ - #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] - enum BoundKind { - EndExcluded, - Start, - EndIncluded, - } - - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); - - impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { - fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { - let RangeBound(self_value, self_kind, _) = *self; - (self_value, self_kind).cmp(&(*other_value, *other_kind)) - } - } - - let mut values = Vec::with_capacity(2 * ranges.len()); - - for r @ SpannedRange { node: (start, end), .. } in ranges { - values.push(RangeBound(*start, BoundKind::Start, r)); - values.push(match end { - EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r), - EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r), - }); - } - - values.sort(); - - let mut started = vec![]; - - for RangeBound(_, kind, range) in values { - match kind { - BoundKind::Start => started.push(range), - BoundKind::EndExcluded | BoundKind::EndIncluded => { - let mut overlap = None; - - while let Some(last_started) = started.pop() { - if last_started == range { - break; - } - overlap = Some(last_started); - } - - if let Some(first_overlapping) = overlap { - return Some((range, first_overlapping)); - } - }, - } - } - - None -} - -#[test] -fn test_overlapping() { - use rustc_span::source_map::DUMMY_SP; - - let sp = |s, e| SpannedRange { - span: DUMMY_SP, - node: (s, e), - }; - - assert_eq!(None, overlapping::(&[])); - assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))])); - assert_eq!( - None, - overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))]) - ); - assert_eq!( - None, - overlapping(&[ - sp(1, EndBound::Included(4)), - sp(5, EndBound::Included(6)), - sp(10, EndBound::Included(11)) - ],) - ); - assert_eq!( - Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))), - overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))]) - ); - assert_eq!( - Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))), - overlapping(&[ - sp(1, EndBound::Included(4)), - sp(5, EndBound::Included(6)), - sp(6, EndBound::Included(11)) - ],) - ); -} diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs new file mode 100644 index 0000000000000..7e65812669029 --- /dev/null +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -0,0 +1,181 @@ +use clippy_utils::consts::{constant, constant_full_int, miri_to_const, FullInt}; +use clippy_utils::diagnostics::span_lint_and_note; +use core::cmp::Ordering; +use rustc_hir::{Arm, Expr, PatKind, RangeEnd}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::Span; + +use super::MATCH_OVERLAPPING_ARM; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { + if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { + let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); + if !ranges.is_empty() { + if let Some((start, end)) = overlapping(&ranges) { + span_lint_and_note( + cx, + MATCH_OVERLAPPING_ARM, + start.span, + "some ranges overlap", + Some(end.span), + "overlaps with this", + ); + } + } + } +} + +/// Gets the ranges for each range pattern arm. Applies `ty` bounds for open ranges. +fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec> { + arms.iter() + .filter_map(|arm| { + if let Arm { pat, guard: None, .. } = *arm { + if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { + let lhs_const = match lhs { + Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, + None => miri_to_const(ty.numeric_min_val(cx.tcx)?)?, + }; + let rhs_const = match rhs { + Some(rhs) => constant(cx, cx.typeck_results(), rhs)?.0, + None => miri_to_const(ty.numeric_max_val(cx.tcx)?)?, + }; + + let lhs_val = lhs_const.int_value(cx, ty)?; + let rhs_val = rhs_const.int_value(cx, ty)?; + + let rhs_bound = match range_end { + RangeEnd::Included => EndBound::Included(rhs_val), + RangeEnd::Excluded => EndBound::Excluded(rhs_val), + }; + return Some(SpannedRange { + span: pat.span, + node: (lhs_val, rhs_bound), + }); + } + + if let PatKind::Lit(value) = pat.kind { + let value = constant_full_int(cx, cx.typeck_results(), value)?; + return Some(SpannedRange { + span: pat.span, + node: (value, EndBound::Included(value)), + }); + } + } + None + }) + .collect() +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EndBound { + Included(T), + Excluded(T), +} + +#[derive(Debug, Eq, PartialEq)] +struct SpannedRange { + pub span: Span, + pub node: (T, EndBound), +} + +fn overlapping(ranges: &[SpannedRange]) -> Option<(&SpannedRange, &SpannedRange)> +where + T: Copy + Ord, +{ + #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] + enum BoundKind { + EndExcluded, + Start, + EndIncluded, + } + + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); + + impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { + fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { + let RangeBound(self_value, self_kind, _) = *self; + (self_value, self_kind).cmp(&(*other_value, *other_kind)) + } + } + + let mut values = Vec::with_capacity(2 * ranges.len()); + + for r @ SpannedRange { node: (start, end), .. } in ranges { + values.push(RangeBound(*start, BoundKind::Start, r)); + values.push(match end { + EndBound::Excluded(val) => RangeBound(*val, BoundKind::EndExcluded, r), + EndBound::Included(val) => RangeBound(*val, BoundKind::EndIncluded, r), + }); + } + + values.sort(); + + let mut started = vec![]; + + for RangeBound(_, kind, range) in values { + match kind { + BoundKind::Start => started.push(range), + BoundKind::EndExcluded | BoundKind::EndIncluded => { + let mut overlap = None; + + while let Some(last_started) = started.pop() { + if last_started == range { + break; + } + overlap = Some(last_started); + } + + if let Some(first_overlapping) = overlap { + return Some((range, first_overlapping)); + } + }, + } + } + + None +} + +#[test] +fn test_overlapping() { + use rustc_span::source_map::DUMMY_SP; + + let sp = |s, e| SpannedRange { + span: DUMMY_SP, + node: (s, e), + }; + + assert_eq!(None, overlapping::(&[])); + assert_eq!(None, overlapping(&[sp(1, EndBound::Included(4))])); + assert_eq!( + None, + overlapping(&[sp(1, EndBound::Included(4)), sp(5, EndBound::Included(6))]) + ); + assert_eq!( + None, + overlapping(&[ + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(10, EndBound::Included(11)) + ],) + ); + assert_eq!( + Some((&sp(1, EndBound::Included(4)), &sp(3, EndBound::Included(6)))), + overlapping(&[sp(1, EndBound::Included(4)), sp(3, EndBound::Included(6))]) + ); + assert_eq!( + Some((&sp(5, EndBound::Included(6)), &sp(6, EndBound::Included(11)))), + overlapping(&[ + sp(1, EndBound::Included(4)), + sp(5, EndBound::Included(6)), + sp(6, EndBound::Included(11)) + ],) + ); +} From dc75695e973d608903e1860db45b950b1a4cd470 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 16:08:05 -0500 Subject: [PATCH 57/67] Split out `match_wild_err_arm` --- .../src/matches/match_wild_err_arm.rs | 51 ++++++++++++++++++ clippy_lints/src/matches/mod.rs | 53 ++----------------- 2 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 clippy_lints/src/matches/match_wild_err_arm.rs diff --git a/clippy_lints/src/matches/match_wild_err_arm.rs b/clippy_lints/src/matches/match_wild_err_arm.rs new file mode 100644 index 0000000000000..bc16f17b6196e --- /dev/null +++ b/clippy_lints/src/matches/match_wild_err_arm.rs @@ -0,0 +1,51 @@ +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::macros::{is_panic, root_macro_call}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::visitors::is_local_used; +use clippy_utils::{is_wild, peel_blocks_with_stmt}; +use rustc_hir::{Arm, Expr, PatKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::{kw, sym}; + +use super::MATCH_WILD_ERR_ARM; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { + let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); + if is_type_diagnostic_item(cx, ex_ty, sym::Result) { + for arm in arms { + if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { + let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); + if path_str == "Err" { + let mut matching_wild = inner.iter().any(is_wild); + let mut ident_bind_name = kw::Underscore; + if !matching_wild { + // 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('_') && !is_local_used(cx, arm.body, id) { + ident_bind_name = ident.name; + matching_wild = true; + } + } + } + } + if_chain! { + if matching_wild; + if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span); + if is_panic(cx, macro_call.def_id); + then { + // `Err(_)` or `Err(_e)` arm with `panic!` found + span_lint_and_note(cx, + MATCH_WILD_ERR_ARM, + arm.pat.span, + &format!("`Err({})` matches all errors", ident_bind_name), + None, + "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", + ); + } + } + } + } + } + } +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index a7fc399bfe229..e2a103c58d1ef 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,12 +1,7 @@ -use clippy_utils::diagnostics::{ - multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, -}; -use clippy_utils::macros::{is_panic, root_macro_call}; -use clippy_utils::peel_blocks_with_stmt; +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::is_local_used; use clippy_utils::{ get_parent_expr, is_lang_ctor, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs, @@ -24,11 +19,12 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, VariantDef}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{sym, symbol::kw}; +use rustc_span::sym; mod match_bool; mod match_like_matches; mod match_same_arms; +mod match_wild_err_arm; mod overlapping_arms; mod redundant_pattern_match; mod single_match; @@ -632,7 +628,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { single_match::check(cx, ex, arms, expr); match_bool::check(cx, ex, arms, expr); overlapping_arms::check(cx, ex, arms); - check_wild_err_arm(cx, ex, arms); + match_wild_err_arm::check(cx, ex, arms); check_wild_enum_match(cx, ex, arms); check_match_as_ref(cx, ex, arms, expr); check_wild_in_or_pats(cx, arms); @@ -709,47 +705,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<'tcx>]) { - let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); - if is_type_diagnostic_item(cx, ex_ty, sym::Result) { - for arm in arms { - if let PatKind::TupleStruct(ref path, inner, _) = arm.pat.kind { - let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)); - if path_str == "Err" { - let mut matching_wild = inner.iter().any(is_wild); - let mut ident_bind_name = kw::Underscore; - if !matching_wild { - // 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('_') && !is_local_used(cx, arm.body, id) { - ident_bind_name = ident.name; - matching_wild = true; - } - } - } - } - if_chain! { - if matching_wild; - if let Some(macro_call) = root_macro_call(peel_blocks_with_stmt(arm.body).span); - if is_panic(cx, macro_call.def_id); - then { - // `Err(_)` or `Err(_e)` arm with `panic!` found - span_lint_and_note(cx, - MATCH_WILD_ERR_ARM, - arm.pat.span, - &format!("`Err({})` matches all errors", ident_bind_name), - None, - "match each error separately or use the error output, or use `.except(msg)` if the error case is unreachable", - ); - } - } - } - } - } - } -} - enum CommonPrefixSearcher<'a> { None, Path(&'a [PathSegment<'a>]), From 75923dff5baa8de3acdec3026aac487033fe68fe Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 16:18:05 -0500 Subject: [PATCH 58/67] Split out `wildcard_enum_match_arm` and `match_wildcard_for_single_variants` --- clippy_lints/src/matches/match_wild_enum.rs | 198 +++++++++++++++++++ clippy_lints/src/matches/mod.rs | 200 +------------------- 2 files changed, 203 insertions(+), 195 deletions(-) create mode 100644 clippy_lints/src/matches/match_wild_enum.rs diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs new file mode 100644 index 0000000000000..3515286d5b4af --- /dev/null +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -0,0 +1,198 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns}; +use rustc_errors::Applicability; +use rustc_hir::def::{CtorKind, DefKind, Res}; +use rustc_hir::{Arm, Expr, PatKind, PathSegment, QPath, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, VariantDef}; +use rustc_span::sym; + +use super::{MATCH_WILDCARD_FOR_SINGLE_VARIANTS, WILDCARD_ENUM_MATCH_ARM}; + +#[allow(clippy::too_many_lines)] +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { + let ty = cx.typeck_results().expr_ty(ex).peel_refs(); + let adt_def = match ty.kind() { + ty::Adt(adt_def, _) + if adt_def.is_enum() + && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) => + { + adt_def + }, + _ => return, + }; + + // First pass - check for violation, but don't do much book-keeping because this is hopefully + // the uncommon case, and the book-keeping is slightly expensive. + let mut wildcard_span = None; + let mut wildcard_ident = None; + let mut has_non_wild = false; + for arm in arms { + match peel_hir_pat_refs(arm.pat).0.kind { + PatKind::Wild => wildcard_span = Some(arm.pat.span), + PatKind::Binding(_, _, ident, None) => { + wildcard_span = Some(arm.pat.span); + wildcard_ident = Some(ident); + }, + _ => has_non_wild = true, + } + } + let wildcard_span = match wildcard_span { + Some(x) if has_non_wild => x, + _ => return, + }; + + // Accumulate the variants which should be put in place of the wildcard because they're not + // already covered. + let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x)); + let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect(); + + let mut path_prefix = CommonPrefixSearcher::None; + for arm in arms { + // Guards mean that this case probably isn't exhaustively covered. Technically + // this is incorrect, as we should really check whether each variant is exhaustively + // covered by the set of guards that cover it, but that's really hard to do. + recurse_or_patterns(arm.pat, |pat| { + let path = match &peel_hir_pat_refs(pat).0.kind { + PatKind::Path(path) => { + #[allow(clippy::match_same_arms)] + let id = match cx.qpath_res(path, pat.hir_id) { + Res::Def( + DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst, + _, + ) => return, + Res::Def(_, id) => id, + _ => return, + }; + if arm.guard.is_none() { + missing_variants.retain(|e| e.ctor_def_id != Some(id)); + } + path + }, + PatKind::TupleStruct(path, patterns, ..) => { + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { + if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) { + missing_variants.retain(|e| e.ctor_def_id != Some(id)); + } + } + path + }, + PatKind::Struct(path, patterns, ..) => { + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { + if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) { + missing_variants.retain(|e| e.def_id != id); + } + } + path + }, + _ => return, + }; + match path { + QPath::Resolved(_, path) => path_prefix.with_path(path.segments), + QPath::TypeRelative( + Ty { + kind: TyKind::Path(QPath::Resolved(_, path)), + .. + }, + _, + ) => path_prefix.with_prefix(path.segments), + _ => (), + } + }); + } + + let format_suggestion = |variant: &VariantDef| { + format!( + "{}{}{}{}", + if let Some(ident) = wildcard_ident { + format!("{} @ ", ident.name) + } else { + String::new() + }, + if let CommonPrefixSearcher::Path(path_prefix) = path_prefix { + let mut s = String::new(); + for seg in path_prefix { + s.push_str(seg.ident.as_str()); + s.push_str("::"); + } + s + } else { + let mut s = cx.tcx.def_path_str(adt_def.did); + s.push_str("::"); + s + }, + variant.name, + match variant.ctor_kind { + CtorKind::Fn if variant.fields.len() == 1 => "(_)", + CtorKind::Fn => "(..)", + CtorKind::Const => "", + CtorKind::Fictive => "{ .. }", + } + ) + }; + + match missing_variants.as_slice() { + [] => (), + [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg( + cx, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + wildcard_span, + "wildcard matches only a single variant and will also match any future added variants", + "try this", + format_suggestion(x), + Applicability::MaybeIncorrect, + ), + variants => { + let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); + let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden { + suggestions.push("_".into()); + "wildcard matches known variants and will also match future added variants" + } else { + "wildcard match will also match any future added variants" + }; + + span_lint_and_sugg( + cx, + WILDCARD_ENUM_MATCH_ARM, + wildcard_span, + message, + "try this", + suggestions.join(" | "), + Applicability::MaybeIncorrect, + ); + }, + }; +} + +enum CommonPrefixSearcher<'a> { + None, + Path(&'a [PathSegment<'a>]), + Mixed, +} +impl<'a> CommonPrefixSearcher<'a> { + fn with_path(&mut self, path: &'a [PathSegment<'a>]) { + match path { + [path @ .., _] => self.with_prefix(path), + [] => (), + } + } + + fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) { + match self { + Self::None => *self = Self::Path(path), + Self::Path(self_path) + if path + .iter() + .map(|p| p.ident.name) + .eq(self_path.iter().map(|p| p.ident.name)) => {}, + Self::Path(_) => *self = Self::Mixed, + Self::Mixed => (), + } + } +} + +fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool { + let attrs = cx.tcx.get_attrs(variant_def.def_id); + clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs) +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index e2a103c58d1ef..8c67380ed526a 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,29 +1,26 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{ get_parent_expr, is_lang_ctor, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, - peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs, + strip_pat_refs, }; use core::iter::once; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{ - self as hir, Arm, BindingAnnotation, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, - PatKind, PathSegment, QPath, TyKind, + Arm, BindingAnnotation, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, VariantDef}; +use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::sym; mod match_bool; mod match_like_matches; mod match_same_arms; +mod match_wild_enum; mod match_wild_err_arm; mod overlapping_arms; mod redundant_pattern_match; @@ -629,7 +626,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_bool::check(cx, ex, arms, expr); overlapping_arms::check(cx, ex, arms); match_wild_err_arm::check(cx, ex, arms); - check_wild_enum_match(cx, ex, arms); + match_wild_enum::check(cx, ex, arms); check_match_as_ref(cx, ex, arms, expr); check_wild_in_or_pats(cx, arms); @@ -705,193 +702,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -enum CommonPrefixSearcher<'a> { - None, - Path(&'a [PathSegment<'a>]), - Mixed, -} -impl<'a> CommonPrefixSearcher<'a> { - fn with_path(&mut self, path: &'a [PathSegment<'a>]) { - match path { - [path @ .., _] => self.with_prefix(path), - [] => (), - } - } - - fn with_prefix(&mut self, path: &'a [PathSegment<'a>]) { - match self { - Self::None => *self = Self::Path(path), - Self::Path(self_path) - if path - .iter() - .map(|p| p.ident.name) - .eq(self_path.iter().map(|p| p.ident.name)) => {}, - Self::Path(_) => *self = Self::Mixed, - Self::Mixed => (), - } - } -} - -fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool { - let attrs = cx.tcx.get_attrs(variant_def.def_id); - clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs) -} - -#[allow(clippy::too_many_lines)] -fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { - let ty = cx.typeck_results().expr_ty(ex).peel_refs(); - let adt_def = match ty.kind() { - ty::Adt(adt_def, _) - if adt_def.is_enum() - && !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) => - { - adt_def - }, - _ => return, - }; - - // First pass - check for violation, but don't do much book-keeping because this is hopefully - // the uncommon case, and the book-keeping is slightly expensive. - let mut wildcard_span = None; - let mut wildcard_ident = None; - let mut has_non_wild = false; - for arm in arms { - match peel_hir_pat_refs(arm.pat).0.kind { - PatKind::Wild => wildcard_span = Some(arm.pat.span), - PatKind::Binding(_, _, ident, None) => { - wildcard_span = Some(arm.pat.span); - wildcard_ident = Some(ident); - }, - _ => has_non_wild = true, - } - } - let wildcard_span = match wildcard_span { - Some(x) if has_non_wild => x, - _ => return, - }; - - // Accumulate the variants which should be put in place of the wildcard because they're not - // already covered. - let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x)); - let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect(); - - let mut path_prefix = CommonPrefixSearcher::None; - for arm in arms { - // Guards mean that this case probably isn't exhaustively covered. Technically - // this is incorrect, as we should really check whether each variant is exhaustively - // covered by the set of guards that cover it, but that's really hard to do. - recurse_or_patterns(arm.pat, |pat| { - let path = match &peel_hir_pat_refs(pat).0.kind { - PatKind::Path(path) => { - #[allow(clippy::match_same_arms)] - let id = match cx.qpath_res(path, pat.hir_id) { - Res::Def( - DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst, - _, - ) => return, - Res::Def(_, id) => id, - _ => return, - }; - if arm.guard.is_none() { - missing_variants.retain(|e| e.ctor_def_id != Some(id)); - } - path - }, - PatKind::TupleStruct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) { - missing_variants.retain(|e| e.ctor_def_id != Some(id)); - } - } - path - }, - PatKind::Struct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) { - missing_variants.retain(|e| e.def_id != id); - } - } - path - }, - _ => return, - }; - match path { - QPath::Resolved(_, path) => path_prefix.with_path(path.segments), - QPath::TypeRelative( - hir::Ty { - kind: TyKind::Path(QPath::Resolved(_, path)), - .. - }, - _, - ) => path_prefix.with_prefix(path.segments), - _ => (), - } - }); - } - - let format_suggestion = |variant: &VariantDef| { - format!( - "{}{}{}{}", - if let Some(ident) = wildcard_ident { - format!("{} @ ", ident.name) - } else { - String::new() - }, - if let CommonPrefixSearcher::Path(path_prefix) = path_prefix { - let mut s = String::new(); - for seg in path_prefix { - s.push_str(seg.ident.as_str()); - s.push_str("::"); - } - s - } else { - let mut s = cx.tcx.def_path_str(adt_def.did); - s.push_str("::"); - s - }, - variant.name, - match variant.ctor_kind { - CtorKind::Fn if variant.fields.len() == 1 => "(_)", - CtorKind::Fn => "(..)", - CtorKind::Const => "", - CtorKind::Fictive => "{ .. }", - } - ) - }; - - match missing_variants.as_slice() { - [] => (), - [x] if !adt_def.is_variant_list_non_exhaustive() && !has_hidden => span_lint_and_sugg( - cx, - MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - wildcard_span, - "wildcard matches only a single variant and will also match any future added variants", - "try this", - format_suggestion(x), - Applicability::MaybeIncorrect, - ), - variants => { - let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect(); - let message = if adt_def.is_variant_list_non_exhaustive() || has_hidden { - suggestions.push("_".into()); - "wildcard matches known variants and will also match future added variants" - } else { - "wildcard match will also match any future added variants" - }; - - span_lint_and_sugg( - cx, - WILDCARD_ENUM_MATCH_ARM, - wildcard_span, - message, - "try this", - suggestions.join(" | "), - Applicability::MaybeIncorrect, - ); - }, - }; -} - fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) where 'b: 'a, From bccf06f60184cedc780e3936799d2aa63cf28b7e Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 16:24:14 -0500 Subject: [PATCH 59/67] Split out `match_as_ref` --- clippy_lints/src/matches/match_as_ref.rs | 85 +++++++++++++++++++++++ clippy_lints/src/matches/mod.rs | 87 ++---------------------- 2 files changed, 89 insertions(+), 83 deletions(-) create mode 100644 clippy_lints/src/matches/match_as_ref.rs diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs new file mode 100644 index 0000000000000..d914eba01716b --- /dev/null +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -0,0 +1,85 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{is_lang_ctor, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, LangItem, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::MATCH_AS_REF; + +pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { + let arm_ref: Option = if is_none_arm(cx, &arms[0]) { + is_ref_some_arm(cx, &arms[1]) + } else if is_none_arm(cx, &arms[1]) { + is_ref_some_arm(cx, &arms[0]) + } else { + None + }; + if let Some(rb) = arm_ref { + let suggestion = if rb == BindingAnnotation::Ref { + "as_ref" + } else { + "as_mut" + }; + + let output_ty = cx.typeck_results().expr_ty(expr); + let input_ty = cx.typeck_results().expr_ty(ex); + + let cast = if_chain! { + if let ty::Adt(_, substs) = input_ty.kind(); + let input_ty = substs.type_at(0); + if let ty::Adt(_, substs) = output_ty.kind(); + let output_ty = substs.type_at(0); + if let ty::Ref(_, output_ty, _) = *output_ty.kind(); + if input_ty != output_ty; + then { + ".map(|x| x as _)" + } else { + "" + } + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MATCH_AS_REF, + expr.span, + &format!("use `{}()` instead", suggestion), + "try this", + format!( + "{}.{}(){}", + snippet_with_applicability(cx, ex.span, "_", &mut applicability), + suggestion, + cast, + ), + applicability, + ); + } + } +} + +// Checks if arm has the form `None => None` +fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, LangItem::OptionNone)) +} + +// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) +fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { + if_chain! { + if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; + if is_lang_ctor(cx, qpath, LangItem::OptionSome); + if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; + if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; + if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; + if let ExprKind::Path(ref some_path) = e.kind; + if is_lang_ctor(cx, some_path, LangItem::OptionSome) && args.len() == 1; + if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; + if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; + then { + return Some(rb) + } + } + None +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 8c67380ed526a..b48cdcb7d7169 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -2,21 +2,18 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_an use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - get_parent_expr, is_lang_ctor, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, - strip_pat_refs, + get_parent_expr, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs, }; use core::iter::once; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; -use rustc_hir::{ - Arm, BindingAnnotation, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath, -}; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +mod match_as_ref; mod match_bool; mod match_like_matches; mod match_same_arms; @@ -627,7 +624,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { overlapping_arms::check(cx, ex, arms); match_wild_err_arm::check(cx, ex, arms); match_wild_enum::check(cx, ex, arms); - check_match_as_ref(cx, ex, arms, expr); + match_as_ref::check(cx, ex, arms, expr); check_wild_in_or_pats(cx, arms); if self.infallible_destructuring_match_linted { @@ -738,58 +735,6 @@ where }); } -fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() { - let arm_ref: Option = if is_none_arm(cx, &arms[0]) { - is_ref_some_arm(cx, &arms[1]) - } else if is_none_arm(cx, &arms[1]) { - is_ref_some_arm(cx, &arms[0]) - } else { - None - }; - if let Some(rb) = arm_ref { - let suggestion = if rb == BindingAnnotation::Ref { - "as_ref" - } else { - "as_mut" - }; - - let output_ty = cx.typeck_results().expr_ty(expr); - let input_ty = cx.typeck_results().expr_ty(ex); - - let cast = if_chain! { - if let ty::Adt(_, substs) = input_ty.kind(); - let input_ty = substs.type_at(0); - if let ty::Adt(_, substs) = output_ty.kind(); - let output_ty = substs.type_at(0); - if let ty::Ref(_, output_ty, _) = *output_ty.kind(); - if input_ty != output_ty; - then { - ".map(|x| x as _)" - } else { - "" - } - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - MATCH_AS_REF, - expr.span, - &format!("use `{}()` instead", suggestion), - "try this", - format!( - "{}.{}(){}", - snippet_with_applicability(cx, ex.span, "_", &mut applicability), - suggestion, - cast, - ), - applicability, - ); - } - } -} - fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { for arm in arms { if let PatKind::Or(fields) = arm.pat.kind { @@ -965,30 +910,6 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<' None } -// Checks if arm has the form `None => None` -fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone)) -} - -// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) -fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { - if_chain! { - if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; - if is_lang_ctor(cx, qpath, OptionSome); - if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; - if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; - if let ExprKind::Call(e, args) = peel_blocks(arm.body).kind; - if let ExprKind::Path(ref some_path) = e.kind; - if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1; - if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind; - if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; - then { - return Some(rb) - } - } - None -} - fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool where 'b: 'a, From 1da26c8a70c13a0b897f70209c7c33f993d178f9 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 16:38:34 -0500 Subject: [PATCH 60/67] Split out `match_single_binding` --- .../src/matches/match_single_binding.rs | 166 +++++++++++++++++ clippy_lints/src/matches/mod.rs | 168 +----------------- 2 files changed, 171 insertions(+), 163 deletions(-) create mode 100644 clippy_lints/src/matches/match_single_binding.rs diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs new file mode 100644 index 0000000000000..8ae19e03f1a6a --- /dev/null +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -0,0 +1,166 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, snippet_block, snippet_opt, snippet_with_applicability}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; +use rustc_errors::Applicability; +use rustc_hir::{Arm, Expr, ExprKind, Local, Node, PatKind}; +use rustc_lint::LateContext; + +use super::MATCH_SINGLE_BINDING; + +#[allow(clippy::too_many_lines)] +pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { + if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { + return; + } + + // HACK: + // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here + // to prevent false positives as there is currently no better way to detect if code was excluded by + // a macro. See PR #6435 + if_chain! { + if let Some(match_snippet) = snippet_opt(cx, expr.span); + if let Some(arm_snippet) = snippet_opt(cx, arms[0].span); + if let Some(ex_snippet) = snippet_opt(cx, ex.span); + let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, ""); + if rest_snippet.contains("=>"); + then { + // The code it self contains another thick arrow "=>" + // -> Either another arm or a comment + return; + } + } + + let matched_vars = ex.span; + let bind_names = arms[0].pat.span; + let match_body = peel_blocks(arms[0].body); + let mut snippet_body = if match_body.span.from_expansion() { + Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() + } else { + snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() + }; + + // Do we need to add ';' to suggestion ? + match match_body.kind { + ExprKind::Block(block, _) => { + // macro + expr_ty(body) == () + if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() { + snippet_body.push(';'); + } + }, + _ => { + // expr_ty(body) == () + if cx.typeck_results().expr_ty(match_body).is_unit() { + snippet_body.push(';'); + } + }, + } + + let mut applicability = Applicability::MaybeIncorrect; + match arms[0].pat.kind { + PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { + // If this match is in a local (`let`) stmt + let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) { + ( + parent_let_node.span, + format!( + "let {} = {};\n{}let {} = {};", + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), + snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability), + snippet_body + ), + ) + } else { + // If we are in closure, we need curly braces around suggestion + let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); + let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string()); + if let Some(parent_expr) = get_parent_expr(cx, expr) { + if let ExprKind::Closure(..) = parent_expr.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + } + // If the parent is already an arm, and the body is another match statement, + // we need curly braces around suggestion + let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id); + if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) { + if let ExprKind::Match(..) = arm.body.kind { + cbrace_end = format!("\n{}}}", indent); + // Fix body indent due to the match + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{}", indent); + } + } + ( + expr.span, + format!( + "{}let {} = {};\n{}{}{}", + cbrace_start, + snippet_with_applicability(cx, bind_names, "..", &mut applicability), + snippet_with_applicability(cx, matched_vars, "..", &mut applicability), + indent, + snippet_body, + cbrace_end + ), + ) + }; + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + target_span, + "this match could be written as a `let` statement", + "consider using `let` statement", + sugg, + applicability, + ); + }, + PatKind::Wild => { + if ex.can_have_side_effects() { + let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0)); + let sugg = format!( + "{};\n{}{}", + snippet_with_applicability(cx, ex.span, "..", &mut applicability), + indent, + snippet_body + ); + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its scrutinee and body", + "consider using the scrutinee and body instead", + sugg, + applicability, + ); + } else { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + } + }, + _ => (), + } +} + +/// Returns true if the `ex` match expression is in a local (`let`) statement +fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { + let map = &cx.tcx.hir(); + if_chain! { + if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); + if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); + then { + return Some(parent_let_expr); + } + } + None +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index b48cdcb7d7169..c2bf2aa6c9d1d 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,13 +1,11 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::{ - get_parent_expr, is_refutable, is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs, -}; +use clippy_utils::{is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs}; use core::iter::once; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Node, Pat, PatKind, QPath}; +use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; @@ -17,6 +15,7 @@ mod match_as_ref; mod match_bool; mod match_like_matches; mod match_same_arms; +mod match_single_binding; mod match_wild_enum; mod match_wild_err_arm; mod overlapping_arms; @@ -630,7 +629,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if self.infallible_destructuring_match_linted { self.infallible_destructuring_match_linted = false; } else { - check_match_single_binding(cx, ex, arms, expr); + match_single_binding::check(cx, ex, arms, expr); } } if let ExprKind::Match(ex, arms, _) = expr.kind { @@ -753,163 +752,6 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { } } -#[allow(clippy::too_many_lines)] -fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], expr: &Expr<'_>) { - if expr.span.from_expansion() || arms.len() != 1 || is_refutable(cx, arms[0].pat) { - return; - } - - // HACK: - // This is a hack to deal with arms that are excluded by macros like `#[cfg]`. It is only used here - // to prevent false positives as there is currently no better way to detect if code was excluded by - // a macro. See PR #6435 - if_chain! { - if let Some(match_snippet) = snippet_opt(cx, expr.span); - if let Some(arm_snippet) = snippet_opt(cx, arms[0].span); - if let Some(ex_snippet) = snippet_opt(cx, ex.span); - let rest_snippet = match_snippet.replace(&arm_snippet, "").replace(&ex_snippet, ""); - if rest_snippet.contains("=>"); - then { - // The code it self contains another thick arrow "=>" - // -> Either another arm or a comment - return; - } - } - - let matched_vars = ex.span; - let bind_names = arms[0].pat.span; - let match_body = peel_blocks(arms[0].body); - let mut snippet_body = if match_body.span.from_expansion() { - Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() - } else { - snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string() - }; - - // Do we need to add ';' to suggestion ? - match match_body.kind { - ExprKind::Block(block, _) => { - // macro + expr_ty(body) == () - if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() { - snippet_body.push(';'); - } - }, - _ => { - // expr_ty(body) == () - if cx.typeck_results().expr_ty(match_body).is_unit() { - snippet_body.push(';'); - } - }, - } - - let mut applicability = Applicability::MaybeIncorrect; - match arms[0].pat.kind { - PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => { - // If this match is in a local (`let`) stmt - let (target_span, sugg) = if let Some(parent_let_node) = opt_parent_let(cx, ex) { - ( - parent_let_node.span, - format!( - "let {} = {};\n{}let {} = {};", - snippet_with_applicability(cx, bind_names, "..", &mut applicability), - snippet_with_applicability(cx, matched_vars, "..", &mut applicability), - " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), - snippet_with_applicability(cx, parent_let_node.pat.span, "..", &mut applicability), - snippet_body - ), - ) - } else { - // If we are in closure, we need curly braces around suggestion - let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); - let (mut cbrace_start, mut cbrace_end) = ("".to_string(), "".to_string()); - if let Some(parent_expr) = get_parent_expr(cx, expr) { - if let ExprKind::Closure(..) = parent_expr.kind { - cbrace_end = format!("\n{}}}", indent); - // Fix body indent due to the closure - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{}", indent); - } - } - // If the parent is already an arm, and the body is another match statement, - // we need curly braces around suggestion - let parent_node_id = cx.tcx.hir().get_parent_node(expr.hir_id); - if let Node::Arm(arm) = &cx.tcx.hir().get(parent_node_id) { - if let ExprKind::Match(..) = arm.body.kind { - cbrace_end = format!("\n{}}}", indent); - // Fix body indent due to the match - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{}", indent); - } - } - ( - expr.span, - format!( - "{}let {} = {};\n{}{}{}", - cbrace_start, - snippet_with_applicability(cx, bind_names, "..", &mut applicability), - snippet_with_applicability(cx, matched_vars, "..", &mut applicability), - indent, - snippet_body, - cbrace_end - ), - ) - }; - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - target_span, - "this match could be written as a `let` statement", - "consider using `let` statement", - sugg, - applicability, - ); - }, - PatKind::Wild => { - if ex.can_have_side_effects() { - let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0)); - let sugg = format!( - "{};\n{}{}", - snippet_with_applicability(cx, ex.span, "..", &mut applicability), - indent, - snippet_body - ); - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - expr.span, - "this match could be replaced by its scrutinee and body", - "consider using the scrutinee and body instead", - sugg, - applicability, - ); - } else { - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - expr.span, - "this match could be replaced by its body itself", - "consider using the match body instead", - snippet_body, - Applicability::MachineApplicable, - ); - } - }, - _ => (), - } -} - -/// Returns true if the `ex` match expression is in a local (`let`) statement -fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> { - let map = &cx.tcx.hir(); - if_chain! { - if let Some(Node::Expr(parent_arm_expr)) = map.find(map.get_parent_node(ex.hir_id)); - if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id)); - then { - return Some(parent_let_expr); - } - } - None -} - fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool where 'b: 'a, From fb1093c56189e1386376715f4a61990b90289cca Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 6 Feb 2022 16:44:52 -0500 Subject: [PATCH 61/67] Split out `match_ref_pats` --- clippy_lints/src/matches/match_ref_pats.rs | 66 +++++++++++++++++++++ clippy_lints/src/matches/mod.rs | 69 ++-------------------- 2 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 clippy_lints/src/matches/match_ref_pats.rs diff --git a/clippy_lints/src/matches/match_ref_pats.rs b/clippy_lints/src/matches/match_ref_pats.rs new file mode 100644 index 0000000000000..80f964ba1b72c --- /dev/null +++ b/clippy_lints/src/matches/match_ref_pats.rs @@ -0,0 +1,66 @@ +use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; +use clippy_utils::source::snippet; +use clippy_utils::sugg::Sugg; +use core::iter::once; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; +use rustc_lint::LateContext; + +use super::MATCH_REF_PATS; + +pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) +where + 'b: 'a, + I: Clone + Iterator>, +{ + if !has_multiple_ref_pats(pats.clone()) { + return; + } + + let (first_sugg, msg, title); + let span = ex.span.source_callsite(); + 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"; + } else { + first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); + msg = "instead of prefixing all patterns with `&`, you can dereference the expression"; + title = "you don't need to add `&` to all patterns"; + } + + let remaining_suggs = pats.filter_map(|pat| { + if let PatKind::Ref(refp, _) = pat.kind { + Some((pat.span, snippet(cx, refp.span, "..").to_string())) + } else { + None + } + }); + + span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { + if !expr.span.from_expansion() { + multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs)); + } + }); +} + +fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool +where + 'b: 'a, + I: Iterator>, +{ + let mut ref_count = 0; + for opt in pats.map(|pat| match pat.kind { + PatKind::Ref(..) => Some(true), // &-patterns + PatKind::Wild => Some(false), // an "anything" wildcard is also fine + _ => None, // any other pattern is not fine + }) { + if let Some(inner) = opt { + if inner { + ref_count += 1; + } + } else { + return false; + } + } + ref_count > 1 +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index c2bf2aa6c9d1d..56f1031f9b93a 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,11 +1,9 @@ -use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_with_applicability}; -use clippy_utils::sugg::Sugg; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs}; -use core::iter::once; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Local, MatchSource, Mutability, Pat, PatKind, QPath}; +use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; @@ -14,6 +12,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; mod match_as_ref; mod match_bool; mod match_like_matches; +mod match_ref_pats; mod match_same_arms; mod match_single_binding; mod match_wild_enum; @@ -633,7 +632,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } } if let ExprKind::Match(ex, arms, _) = expr.kind { - check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); + match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } } @@ -698,42 +697,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } -fn check_match_ref_pats<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>) -where - 'b: 'a, - I: Clone + Iterator>, -{ - if !has_multiple_ref_pats(pats.clone()) { - return; - } - - let (first_sugg, msg, title); - let span = ex.span.source_callsite(); - 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"; - } else { - first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string())); - msg = "instead of prefixing all patterns with `&`, you can dereference the expression"; - title = "you don't need to add `&` to all patterns"; - } - - let remaining_suggs = pats.filter_map(|pat| { - if let PatKind::Ref(refp, _) = pat.kind { - Some((pat.span, snippet(cx, refp.span, "..").to_string())) - } else { - None - } - }); - - span_lint_and_then(cx, MATCH_REF_PATS, expr.span, title, |diag| { - if !expr.span.from_expansion() { - multispan_sugg(diag, msg, first_sugg.chain(remaining_suggs)); - } - }); -} - fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { for arm in arms { if let PatKind::Or(fields) = arm.pat.kind { @@ -751,25 +714,3 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { } } } - -fn has_multiple_ref_pats<'a, 'b, I>(pats: I) -> bool -where - 'b: 'a, - I: Iterator>, -{ - let mut ref_count = 0; - for opt in pats.map(|pat| match pat.kind { - PatKind::Ref(..) => Some(true), // &-patterns - PatKind::Wild => Some(false), // an "anything" wildcard is also fine - _ => None, // any other pattern is not fine - }) { - if let Some(inner) = opt { - if inner { - ref_count += 1; - } - } else { - return false; - } - } - ref_count > 1 -} From 64779233238b3dcadae13bdeebac82445c1e8f6d Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 7 Feb 2022 12:28:57 -0500 Subject: [PATCH 62/67] Split out `infalliable_detructuring_match` --- .../matches/infalliable_detructuring_match.rs | 44 +++++++++++++++++++ clippy_lints/src/matches/mod.rs | 40 ++--------------- 2 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 clippy_lints/src/matches/infalliable_detructuring_match.rs diff --git a/clippy_lints/src/matches/infalliable_detructuring_match.rs b/clippy_lints/src/matches/infalliable_detructuring_match.rs new file mode 100644 index 0000000000000..2472acb6f6e8b --- /dev/null +++ b/clippy_lints/src/matches/infalliable_detructuring_match.rs @@ -0,0 +1,44 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::{path_to_local_id, peel_blocks, strip_pat_refs}; +use rustc_errors::Applicability; +use rustc_hir::{ExprKind, Local, MatchSource, PatKind, QPath}; +use rustc_lint::LateContext; + +use super::INFALLIBLE_DESTRUCTURING_MATCH; + +pub(crate) fn check(cx: &LateContext<'_>, local: &Local<'_>) -> bool { + if_chain! { + if !local.span.from_expansion(); + if let Some(expr) = local.init; + if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; + if arms.len() == 1 && arms[0].guard.is_none(); + if let PatKind::TupleStruct( + QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; + if args.len() == 1; + if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; + let body = peel_blocks(arms[0].body); + if path_to_local_id(body, arg); + + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + INFALLIBLE_DESTRUCTURING_MATCH, + local.span, + "you seem to be trying to use `match` to destructure a single infallible pattern. \ + Consider using `let`", + "try this", + format!( + "let {}({}) = {};", + snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), + snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), + snippet_with_applicability(cx, target.span, "..", &mut applicability), + ), + applicability, + ); + return true; + } + } + false +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 56f1031f9b93a..0adfa424ee146 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,14 +1,13 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_wild, meets_msrv, msrvs, path_to_local_id, peel_blocks, strip_pat_refs}; +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::{is_wild, meets_msrv, msrvs}; use if_chain::if_chain; -use rustc_errors::Applicability; use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +mod infalliable_detructuring_match; mod match_as_ref; mod match_bool; mod match_like_matches; @@ -637,38 +636,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { - if_chain! { - if !local.span.from_expansion(); - if let Some(expr) = local.init; - if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind; - if arms.len() == 1 && arms[0].guard.is_none(); - if let PatKind::TupleStruct( - QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind; - if args.len() == 1; - if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; - let body = peel_blocks(arms[0].body); - if path_to_local_id(body, arg); - - then { - let mut applicability = Applicability::MachineApplicable; - self.infallible_destructuring_match_linted = true; - span_lint_and_sugg( - cx, - INFALLIBLE_DESTRUCTURING_MATCH, - local.span, - "you seem to be trying to use `match` to destructure a single infallible pattern. \ - Consider using `let`", - "try this", - format!( - "let {}({}) = {};", - snippet_with_applicability(cx, variant_name.span, "..", &mut applicability), - snippet_with_applicability(cx, local.pat.span, "..", &mut applicability), - snippet_with_applicability(cx, target.span, "..", &mut applicability), - ), - applicability, - ); - } - } + self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local); } fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { From aa3af30dee07a82cd3fcec5732dc575a4571d392 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 7 Feb 2022 12:57:02 -0500 Subject: [PATCH 63/67] Split out `rest_pat_in_fully_bound_struct` --- clippy_lints/src/matches/mod.rs | 26 ++--------------- .../matches/rest_pat_in_fully_bound_struct.rs | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 0adfa424ee146..4429065966dc7 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,9 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::{is_wild, meets_msrv, msrvs}; -use if_chain::if_chain; -use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind, QPath}; +use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -18,6 +16,7 @@ mod match_wild_enum; mod match_wild_err_arm; mod overlapping_arms; mod redundant_pattern_match; +mod rest_pat_in_fully_bound_struct; mod single_match; declare_clippy_lint! { @@ -640,26 +639,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { - if_chain! { - if !pat.span.from_expansion(); - if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; - if let Some(def_id) = path.res.opt_def_id(); - let ty = cx.tcx.type_of(def_id); - if let ty::Adt(def, _) = ty.kind(); - if def.is_struct() || def.is_union(); - if fields.len() == def.non_enum_variant().fields.len(); - - then { - span_lint_and_help( - cx, - REST_PAT_IN_FULLY_BOUND_STRUCTS, - pat.span, - "unnecessary use of `..` pattern in struct binding. All fields were already bound", - None, - "consider removing `..` from this binding", - ); - } - } + rest_pat_in_fully_bound_struct::check(cx, pat); } extract_msrv_attr!(LateContext); diff --git a/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs new file mode 100644 index 0000000000000..5076239a57c4d --- /dev/null +++ b/clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs @@ -0,0 +1,29 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir::{Pat, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::REST_PAT_IN_FULLY_BOUND_STRUCTS; + +pub(crate) fn check(cx: &LateContext<'_>, pat: &Pat<'_>) { + if_chain! { + if !pat.span.from_expansion(); + if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind; + if let Some(def_id) = path.res.opt_def_id(); + let ty = cx.tcx.type_of(def_id); + if let ty::Adt(def, _) = ty.kind(); + if def.is_struct() || def.is_union(); + if fields.len() == def.non_enum_variant().fields.len(); + + then { + span_lint_and_help( + cx, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + pat.span, + "unnecessary use of `..` pattern in struct binding. All fields were already bound", + None, + "consider removing `..` from this binding", + ); + } + } +} From c65894cf1a3bc6b111305071942f77e8654a7a6a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 7 Feb 2022 13:00:19 -0500 Subject: [PATCH 64/67] Split out `wild_in_or_pats` --- clippy_lints/src/matches/mod.rs | 26 ++++----------------- clippy_lints/src/matches/wild_in_or_pats.rs | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 clippy_lints/src/matches/wild_in_or_pats.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 4429065966dc7..b5ee4561f06ec 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,6 +1,5 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{is_wild, meets_msrv, msrvs}; -use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat, PatKind}; +use clippy_utils::{meets_msrv, msrvs}; +use rustc_hir::{Expr, ExprKind, Local, MatchSource, Pat}; use rustc_lint::{LateContext, LateLintPass}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -18,6 +17,7 @@ mod overlapping_arms; mod redundant_pattern_match; mod rest_pat_in_fully_bound_struct; mod single_match; +mod wild_in_or_pats; declare_clippy_lint! { /// ### What it does @@ -621,7 +621,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_wild_err_arm::check(cx, ex, arms); match_wild_enum::check(cx, ex, arms); match_as_ref::check(cx, ex, arms, expr); - check_wild_in_or_pats(cx, arms); + wild_in_or_pats::check(cx, arms); if self.infallible_destructuring_match_linted { self.infallible_destructuring_match_linted = false; @@ -644,21 +644,3 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } - -fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { - for arm in arms { - if let PatKind::Or(fields) = arm.pat.kind { - // look for multiple fields in this arm that contains at least one Wild pattern - if fields.len() > 1 && fields.iter().any(is_wild) { - span_lint_and_help( - cx, - WILDCARD_IN_OR_PATTERNS, - arm.pat.span, - "wildcard pattern covers any other pattern as it will match anyway", - None, - "consider handling `_` separately", - ); - } - } - } -} diff --git a/clippy_lints/src/matches/wild_in_or_pats.rs b/clippy_lints/src/matches/wild_in_or_pats.rs new file mode 100644 index 0000000000000..459513e65bfad --- /dev/null +++ b/clippy_lints/src/matches/wild_in_or_pats.rs @@ -0,0 +1,24 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_wild; +use rustc_hir::{Arm, PatKind}; +use rustc_lint::LateContext; + +use super::WILDCARD_IN_OR_PATTERNS; + +pub(crate) fn check(cx: &LateContext<'_>, arms: &[Arm<'_>]) { + for arm in arms { + if let PatKind::Or(fields) = arm.pat.kind { + // look for multiple fields in this arm that contains at least one Wild pattern + if fields.len() > 1 && fields.iter().any(is_wild) { + span_lint_and_help( + cx, + WILDCARD_IN_OR_PATTERNS, + arm.pat.span, + "wildcard pattern covers any other pattern as it will match anyway", + None, + "consider handling `_` separately", + ); + } + } + } +} From 88fd0905315ec02cadb224d034cd017b6ffcd379 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Tue, 8 Feb 2022 10:45:52 -0600 Subject: [PATCH 65/67] Factor out ui_test suite --- clippy_utils/src/lib.rs | 4 +- tests/compile-test.rs | 9 --- tests/ui/eq_op.rs | 93 +++++++++++++---------- tests/ui/eq_op.stderr | 162 ++++++++++++++++++++-------------------- tests/ui_test/eq_op.rs | 15 ---- 5 files changed, 134 insertions(+), 149 deletions(-) delete mode 100644 tests/ui_test/eq_op.rs diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f76004774add0..42955080c966e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2116,7 +2116,7 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn( /// Checks if the function containing the given `HirId` is a `#[test]` function /// -/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`. +/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { with_test_item_names(tcx, tcx.parent_module(id), |names| { tcx.hir() @@ -2139,7 +2139,7 @@ pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool { /// Checks whether item either has `test` attribute applied, or /// is a module with `test` in its name. /// -/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`. +/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { is_in_test_function(tcx, item.hir_id()) || matches!(item.kind, ItemKind::Mod(..)) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index ab7e2540405d3..a82ff18283931 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -165,14 +165,6 @@ fn run_ui() { compiletest::run_tests(&config); } -fn run_ui_test() { - let mut config = base_config("ui_test"); - let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap()); - let rustcflags = config.target_rustcflags.get_or_insert_with(Default::default); - rustcflags.push_str(" --test"); - compiletest::run_tests(&config); -} - fn run_internal_tests() { // only run internal tests with the internal-tests feature if !RUN_INTERNAL_TESTS { @@ -328,7 +320,6 @@ fn run_ui_cargo() { fn compile_test() { set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); run_ui(); - run_ui_test(); run_ui_toml(); run_ui_cargo(); run_internal_tests(); diff --git a/tests/ui/eq_op.rs b/tests/ui/eq_op.rs index 707b449f82e4f..422f9486503d2 100644 --- a/tests/ui/eq_op.rs +++ b/tests/ui/eq_op.rs @@ -1,58 +1,56 @@ -// does not test any rustfixable lints - -#[rustfmt::skip] -#[warn(clippy::eq_op)] -#[allow(clippy::identity_op, clippy::double_parens)] -#[allow(clippy::no_effect, unused_variables, clippy::unnecessary_operation, clippy::short_circuit_statement)] -#[allow(clippy::nonminimal_bool)] -#[allow(unused)] -#[allow(clippy::unnecessary_cast)] +// compile-flags: --test + +#![warn(clippy::eq_op)] +#![allow(clippy::double_parens, clippy::identity_op, clippy::nonminimal_bool)] + fn main() { // simple values and comparisons - 1 == 1; - "no" == "no"; + let _ = 1 == 1; + let _ = "no" == "no"; // even though I agree that no means no ;-) - false != false; - 1.5 < 1.5; - 1u64 >= 1u64; + let _ = false != false; + let _ = 1.5 < 1.5; + let _ = 1u64 >= 1u64; // casts, methods, parentheses - (1 as u64) & (1 as u64); - 1 ^ ((((((1)))))); + let _ = (1u32 as u64) & (1u32 as u64); + #[rustfmt::skip] + { + let _ = 1 ^ ((((((1)))))); + }; // unary and binary operators - (-(2) < -(2)); - ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); - (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + let _ = (-(2) < -(2)); + let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; // various other things - ([1] != [1]); - ((1, 2) != (1, 2)); - vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros + let _ = ([1] != [1]); + let _ = ((1, 2) != (1, 2)); + let _ = vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros // const folding - 1 + 1 == 2; - 1 - 1 == 0; + let _ = 1 + 1 == 2; + let _ = 1 - 1 == 0; - 1 - 1; - 1 / 1; - true && true; - - true || true; + let _ = 1 - 1; + let _ = 1 / 1; + let _ = true && true; + let _ = true || true; let a: u32 = 0; let b: u32 = 0; - a == b && b == a; - a != b && b != a; - a < b && b > a; - a <= b && b >= a; + let _ = a == b && b == a; + let _ = a != b && b != a; + let _ = a < b && b > a; + let _ = a <= b && b >= a; let mut a = vec![1]; - a == a; - 2*a.len() == 2*a.len(); // ok, functions - a.pop() == a.pop(); // ok, functions + let _ = a == a; + let _ = 2 * a.len() == 2 * a.len(); // ok, functions + let _ = a.pop() == a.pop(); // ok, functions check_ignore_macro(); @@ -63,15 +61,14 @@ fn main() { const D: u32 = A / A; } -#[rustfmt::skip] macro_rules! check_if_named_foo { - ($expression:expr) => ( + ($expression:expr) => { if stringify!($expression) == "foo" { println!("foo!"); } else { println!("not foo."); } - ) + }; } macro_rules! bool_macro { @@ -80,11 +77,10 @@ macro_rules! bool_macro { }; } -#[allow(clippy::short_circuit_statement)] fn check_ignore_macro() { check_if_named_foo!(foo); // checks if the lint ignores macros with `!` operator - !bool_macro!(1) && !bool_macro!(""); + let _ = !bool_macro!(1) && !bool_macro!(""); } struct Nested { @@ -95,3 +91,18 @@ fn check_nested(n1: &Nested, n2: &Nested) -> bool { // `n2.inner.0.0` mistyped as `n1.inner.0.0` (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0 } + +#[test] +fn eq_op_shouldnt_trigger_in_tests() { + let a = 1; + let result = a + 1 == 1 + a; + assert!(result); +} + +#[test] +fn eq_op_macros_shouldnt_trigger_in_tests() { + let a = 1; + let b = 2; + assert_eq!(a, a); + assert_eq!(a + b, b + a); +} diff --git a/tests/ui/eq_op.stderr b/tests/ui/eq_op.stderr index 8ef658af8df42..313ceed2b41fa 100644 --- a/tests/ui/eq_op.stderr +++ b/tests/ui/eq_op.stderr @@ -1,174 +1,172 @@ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:12:5 + --> $DIR/eq_op.rs:8:13 | -LL | 1 == 1; - | ^^^^^^ +LL | let _ = 1 == 1; + | ^^^^^^ | = note: `-D clippy::eq-op` implied by `-D warnings` error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:13:5 + --> $DIR/eq_op.rs:9:13 | -LL | "no" == "no"; - | ^^^^^^^^^^^^ +LL | let _ = "no" == "no"; + | ^^^^^^^^^^^^ error: equal expressions as operands to `!=` - --> $DIR/eq_op.rs:15:5 + --> $DIR/eq_op.rs:11:13 | -LL | false != false; - | ^^^^^^^^^^^^^^ +LL | let _ = false != false; + | ^^^^^^^^^^^^^^ error: equal expressions as operands to `<` - --> $DIR/eq_op.rs:16:5 + --> $DIR/eq_op.rs:12:13 | -LL | 1.5 < 1.5; - | ^^^^^^^^^ +LL | let _ = 1.5 < 1.5; + | ^^^^^^^^^ error: equal expressions as operands to `>=` - --> $DIR/eq_op.rs:17:5 + --> $DIR/eq_op.rs:13:13 | -LL | 1u64 >= 1u64; - | ^^^^^^^^^^^^ +LL | let _ = 1u64 >= 1u64; + | ^^^^^^^^^^^^ error: equal expressions as operands to `&` - --> $DIR/eq_op.rs:20:5 + --> $DIR/eq_op.rs:16:13 | -LL | (1 as u64) & (1 as u64); - | ^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = (1u32 as u64) & (1u32 as u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `^` - --> $DIR/eq_op.rs:21:5 + --> $DIR/eq_op.rs:19:17 | -LL | 1 ^ ((((((1)))))); - | ^^^^^^^^^^^^^^^^^ +LL | let _ = 1 ^ ((((((1)))))); + | ^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `<` - --> $DIR/eq_op.rs:24:5 + --> $DIR/eq_op.rs:23:13 | -LL | (-(2) < -(2)); - | ^^^^^^^^^^^^^ +LL | let _ = (-(2) < -(2)); + | ^^^^^^^^^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:25:5 + --> $DIR/eq_op.rs:24:13 | -LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `&` - --> $DIR/eq_op.rs:25:6 + --> $DIR/eq_op.rs:24:14 | -LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); - | ^^^^^^^^^^^^^^^^^ +LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `&` - --> $DIR/eq_op.rs:25:27 + --> $DIR/eq_op.rs:24:35 | -LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); - | ^^^^^^^^^^^^^^^^^ +LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1)); + | ^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:26:5 + --> $DIR/eq_op.rs:25:13 | -LL | (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `!=` - --> $DIR/eq_op.rs:29:5 + --> $DIR/eq_op.rs:28:13 | -LL | ([1] != [1]); - | ^^^^^^^^^^^^ +LL | let _ = ([1] != [1]); + | ^^^^^^^^^^^^ error: equal expressions as operands to `!=` - --> $DIR/eq_op.rs:30:5 + --> $DIR/eq_op.rs:29:13 | -LL | ((1, 2) != (1, 2)); - | ^^^^^^^^^^^^^^^^^^ +LL | let _ = ((1, 2) != (1, 2)); + | ^^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:34:5 + --> $DIR/eq_op.rs:33:13 | -LL | 1 + 1 == 2; - | ^^^^^^^^^^ +LL | let _ = 1 + 1 == 2; + | ^^^^^^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:35:5 + --> $DIR/eq_op.rs:34:13 | -LL | 1 - 1 == 0; - | ^^^^^^^^^^ +LL | let _ = 1 - 1 == 0; + | ^^^^^^^^^^ error: equal expressions as operands to `-` - --> $DIR/eq_op.rs:35:5 + --> $DIR/eq_op.rs:34:13 | -LL | 1 - 1 == 0; - | ^^^^^ +LL | let _ = 1 - 1 == 0; + | ^^^^^ error: equal expressions as operands to `-` - --> $DIR/eq_op.rs:37:5 + --> $DIR/eq_op.rs:36:13 | -LL | 1 - 1; - | ^^^^^ +LL | let _ = 1 - 1; + | ^^^^^ error: equal expressions as operands to `/` - --> $DIR/eq_op.rs:38:5 + --> $DIR/eq_op.rs:37:13 | -LL | 1 / 1; - | ^^^^^ +LL | let _ = 1 / 1; + | ^^^^^ error: equal expressions as operands to `&&` - --> $DIR/eq_op.rs:39:5 + --> $DIR/eq_op.rs:38:13 | -LL | true && true; - | ^^^^^^^^^^^^ +LL | let _ = true && true; + | ^^^^^^^^^^^^ error: equal expressions as operands to `||` - --> $DIR/eq_op.rs:41:5 + --> $DIR/eq_op.rs:40:13 | -LL | true || true; - | ^^^^^^^^^^^^ +LL | let _ = true || true; + | ^^^^^^^^^^^^ error: equal expressions as operands to `&&` - --> $DIR/eq_op.rs:47:5 + --> $DIR/eq_op.rs:45:13 | -LL | a == b && b == a; - | ^^^^^^^^^^^^^^^^ +LL | let _ = a == b && b == a; + | ^^^^^^^^^^^^^^^^ error: equal expressions as operands to `&&` - --> $DIR/eq_op.rs:48:5 + --> $DIR/eq_op.rs:46:13 | -LL | a != b && b != a; - | ^^^^^^^^^^^^^^^^ +LL | let _ = a != b && b != a; + | ^^^^^^^^^^^^^^^^ error: equal expressions as operands to `&&` - --> $DIR/eq_op.rs:49:5 + --> $DIR/eq_op.rs:47:13 | -LL | a < b && b > a; - | ^^^^^^^^^^^^^^ +LL | let _ = a < b && b > a; + | ^^^^^^^^^^^^^^ error: equal expressions as operands to `&&` - --> $DIR/eq_op.rs:50:5 + --> $DIR/eq_op.rs:48:13 | -LL | a <= b && b >= a; - | ^^^^^^^^^^^^^^^^ +LL | let _ = a <= b && b >= a; + | ^^^^^^^^^^^^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:53:5 + --> $DIR/eq_op.rs:51:13 | -LL | a == a; - | ^^^^^^ +LL | let _ = a == a; + | ^^^^^^ error: equal expressions as operands to `/` - --> $DIR/eq_op.rs:63:20 + --> $DIR/eq_op.rs:61:20 | LL | const D: u32 = A / A; | ^^^^^ error: equal expressions as operands to `==` - --> $DIR/eq_op.rs:96:5 + --> $DIR/eq_op.rs:92:5 | LL | (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `#[deny(clippy::eq_op)]` on by default error: aborting due to 28 previous errors diff --git a/tests/ui_test/eq_op.rs b/tests/ui_test/eq_op.rs deleted file mode 100644 index f2f5f1e588ed4..0000000000000 --- a/tests/ui_test/eq_op.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[warn(clippy::eq_op)] -#[test] -fn eq_op_shouldnt_trigger_in_tests() { - let a = 1; - let result = a + 1 == 1 + a; - assert!(result); -} - -#[test] -fn eq_op_macros_shouldnt_trigger_in_tests() { - let a = 1; - let b = 2; - assert_eq!(a, a); - assert_eq!(a + b, b + a); -} From 49b9f9247b2ae66db936242153962554d386be19 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 10 Feb 2022 18:11:49 +0100 Subject: [PATCH 66/67] Bump nightly version -> 2022-02-10 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index e23dc73ab08c1..f065f0bffc7bf 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-01-27" +channel = "nightly-2022-02-10" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From 04c9842ebecea8959ba3da36ab948f4a43b996e0 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 10 Feb 2022 19:52:01 +0100 Subject: [PATCH 67/67] Clippy: Fix botstrap fallout --- src/tools/clippy/clippy_lints/src/lib.rs | 3 +- .../src/transmute/transmute_undefined_repr.rs | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 9999cd3f8243c..5c45012ef0686 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -5,7 +5,6 @@ #![feature(control_flow_enum)] #![feature(drain_filter)] #![feature(iter_intersperse)] -#![feature(let_chains)] #![feature(let_else)] #![feature(once_cell)] #![feature(rustc_private)] @@ -19,7 +18,7 @@ // warn on rustc internal lints #![warn(rustc::internal)] // Disable this rustc lint for now, as it was also done in rustc -#![allow(rustc::potential_query_instability)] +#![cfg_attr(not(bootstrap), allow(rustc::potential_query_instability))] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs index c91bc3245e417..030d2c2378488 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -111,19 +111,21 @@ pub(super) fn check<'tcx>( from_ty_orig, to_ty_orig ), |diag| { - if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def()) - && from_def == to_def - { - diag.note(&format!( - "two instances of the same generic type (`{}`) may have different layouts", - cx.tcx.item_name(from_def.did) - )); - } else { - if from_ty_orig.peel_refs() != from_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); - } - if to_ty_orig.peel_refs() != to_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + if_chain! { + if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def()); + if from_def == to_def; + then { + diag.note(&format!( + "two instances of the same generic type (`{}`) may have different layouts", + cx.tcx.item_name(from_def.did) + )); + } else { + if from_ty_orig.peel_refs() != from_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + if to_ty_orig.peel_refs() != to_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); + } } } }, @@ -279,11 +281,13 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> } fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty) - && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) - { - layout.layout.size.bytes() == 0 - } else { - false + if_chain! { + if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty); + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + then { + layout.layout.size.bytes() == 0 + } else { + false + } } }