From 611d0398143acef24bc6029810828791a2475b9e Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 10 Feb 2022 18:40:06 +0100 Subject: [PATCH 01/55] Merge commit '57b3c4b90f4346b3990c1be387c3b3ca7b78412c' into clippyup --- CHANGELOG.md | 181 +- clippy_dev/src/bless.rs | 65 +- clippy_lints/src/blocks_in_if_conditions.rs | 6 +- .../src/default_union_representation.rs | 105 + clippy_lints/src/disallowed_methods.rs | 2 +- clippy_lints/src/disallowed_types.rs | 2 +- clippy_lints/src/explicit_write.rs | 41 +- clippy_lints/src/formatting.rs | 14 +- clippy_lints/src/implicit_hasher.rs | 3 +- clippy_lints/src/infinite_iter.rs | 12 +- clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_lints.rs | 2 + clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.rs | 5 + clippy_lints/src/loops/single_element_loop.rs | 24 +- clippy_lints/src/loops/utils.rs | 19 +- clippy_lints/src/matches.rs | 2342 ----------------- .../matches/infalliable_detructuring_match.rs | 44 + clippy_lints/src/matches/match_as_ref.rs | 85 + clippy_lints/src/matches/match_bool.rs | 75 + .../src/matches/match_like_matches.rs | 166 ++ clippy_lints/src/matches/match_ref_pats.rs | 66 + clippy_lints/src/matches/match_same_arms.rs | 111 + .../src/matches/match_single_binding.rs | 166 ++ clippy_lints/src/matches/match_wild_enum.rs | 198 ++ .../src/matches/match_wild_err_arm.rs | 51 + clippy_lints/src/matches/mod.rs | 646 +++++ clippy_lints/src/matches/overlapping_arms.rs | 181 ++ .../src/matches/redundant_pattern_match.rs | 436 +++ .../matches/rest_pat_in_fully_bound_struct.rs | 29 + clippy_lints/src/matches/single_match.rs | 269 ++ clippy_lints/src/matches/wild_in_or_pats.rs | 24 + clippy_lints/src/methods/chars_cmp.rs | 15 +- .../src/methods/chars_cmp_with_unwrap.rs | 2 +- .../methods/manual_saturating_arithmetic.rs | 8 +- clippy_lints/src/methods/mod.rs | 20 +- .../src/methods/option_map_or_none.rs | 13 +- .../src/methods/option_map_unwrap_or.rs | 3 +- clippy_lints/src/methods/str_splitn.rs | 6 +- .../src/methods/unnecessary_filter_map.rs | 4 +- clippy_lints/src/misc.rs | 7 +- .../src/missing_enforced_import_rename.rs | 2 +- clippy_lints/src/ptr.rs | 30 +- clippy_lints/src/ranges.rs | 26 +- clippy_lints/src/swap.rs | 4 +- clippy_lints/src/transmute/mod.rs | 60 +- .../src/transmute/transmute_float_to_int.rs | 29 +- .../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 +- .../src/transmute/transmute_undefined_repr.rs | 289 ++ .../transmutes_expressible_as_ptr_casts.rs | 4 +- .../transmute/unsound_collection_transmute.rs | 30 +- .../src/transmute/useless_transmute.rs | 6 +- clippy_lints/src/types/box_collection.rs | 27 +- clippy_lints/src/types/option_option.rs | 30 +- clippy_lints/src/types/rc_buffer.rs | 37 +- clippy_lints/src/types/rc_mutex.rs | 6 +- .../src/types/redundant_allocation.rs | 21 +- clippy_lints/src/unwrap.rs | 7 +- clippy_lints/src/utils/conf.rs | 20 +- clippy_lints/src/utils/internal_lints.rs | 10 +- .../internal_lints/metadata_collector.rs | 4 +- clippy_utils/src/consts.rs | 21 +- clippy_utils/src/higher.rs | 11 +- clippy_utils/src/hir_utils.rs | 3 +- clippy_utils/src/lib.rs | 184 +- clippy_utils/src/macros.rs | 15 +- clippy_utils/src/ty.rs | 4 +- doc/common_tools_writing_lints.md | 8 +- rust-toolchain | 2 +- tests/compile-test.rs | 78 +- .../multiple_config_files/no_warn/Cargo.toml | 8 + .../multiple_config_files/no_warn/clippy.toml | 1 + .../multiple_config_files/no_warn/src/main.rs | 3 + .../multiple_config_files/warn/.clippy.toml | 1 + .../multiple_config_files/warn/Cargo.toml | 8 + .../multiple_config_files/warn/clippy.toml | 1 + .../multiple_config_files/warn/src/main.rs | 5 + .../warn/src/main.stderr | 2 + tests/ui/crashes/ice-4968.rs | 1 + tests/ui/crashes/ice-8250.rs | 6 + tests/ui/crashes/ice-8250.stderr | 18 + tests/ui/crashes/ice-8386.rs | 3 + tests/ui/default_union_representation.rs | 78 + tests/ui/default_union_representation.stderr | 48 + tests/ui/eq_op.rs | 93 +- tests/ui/eq_op.stderr | 162 +- tests/ui/expect_fun_call.fixed | 9 + tests/ui/expect_fun_call.rs | 9 + tests/ui/expect_fun_call.stderr | 32 +- tests/ui/explicit_counter_loop.stderr | 4 +- 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 - tests/ui/get_unwrap.fixed | 12 +- tests/ui/get_unwrap.rs | 12 +- tests/ui/get_unwrap.stderr | 135 +- 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 + tests/ui/manual_flatten.rs | 2 - tests/ui/manual_flatten.stderr | 26 +- tests/ui/non_expressive_names.stdout | 0 tests/ui/ptr_arg.rs | 6 + tests/ui/single_match.rs | 78 + tests/ui/single_match.stderr | 38 +- tests/ui/single_match_else.stderr | 42 +- tests/ui/starts_ends_with.fixed | 10 +- tests/ui/starts_ends_with.rs | 10 +- tests/ui/starts_ends_with.stderr | 50 +- tests/ui/transmute_undefined_repr.rs | 44 + tests/ui/transmute_undefined_repr.stderr | 44 + tests/ui_test/eq_op.rs | 15 - 123 files changed, 4462 insertions(+), 3159 deletions(-) create mode 100644 clippy_lints/src/default_union_representation.rs delete mode 100644 clippy_lints/src/matches.rs create mode 100644 clippy_lints/src/matches/infalliable_detructuring_match.rs create mode 100644 clippy_lints/src/matches/match_as_ref.rs create mode 100644 clippy_lints/src/matches/match_bool.rs create mode 100644 clippy_lints/src/matches/match_like_matches.rs create mode 100644 clippy_lints/src/matches/match_ref_pats.rs create mode 100644 clippy_lints/src/matches/match_same_arms.rs create mode 100644 clippy_lints/src/matches/match_single_binding.rs create mode 100644 clippy_lints/src/matches/match_wild_enum.rs create mode 100644 clippy_lints/src/matches/match_wild_err_arm.rs create mode 100644 clippy_lints/src/matches/mod.rs create mode 100644 clippy_lints/src/matches/overlapping_arms.rs create mode 100644 clippy_lints/src/matches/redundant_pattern_match.rs create mode 100644 clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs create mode 100644 clippy_lints/src/matches/single_match.rs create mode 100644 clippy_lints/src/matches/wild_in_or_pats.rs create mode 100644 clippy_lints/src/transmute/transmute_undefined_repr.rs 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 create mode 100644 tests/ui/crashes/ice-8250.rs create mode 100644 tests/ui/crashes/ice-8250.stderr create mode 100644 tests/ui/crashes/ice-8386.rs create mode 100644 tests/ui/default_union_representation.rs create mode 100644 tests/ui/default_union_representation.stderr delete mode 100644 tests/ui/explicit_write_non_rustfix.rs delete mode 100644 tests/ui/explicit_write_non_rustfix.stderr delete mode 100644 tests/ui/non_expressive_names.stdout create mode 100644 tests/ui/transmute_undefined_repr.rs create mode 100644 tests/ui/transmute_undefined_repr.stderr delete mode 100644 tests/ui_test/eq_op.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d66e6cf7fb659..c9adf77c0d639 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) @@ -2930,6 +3101,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 @@ -3303,6 +3475,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_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/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/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/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/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/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 a2f943b03ef42..5e4cde553b52e 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/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/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 2d2693832e971..a80320a578f0e 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, @@ -472,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.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 f2a7e925dd395..9999cd3f8243c 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)] @@ -17,6 +18,8 @@ #![warn(rust_2018_idioms, unused_lifetimes)] // 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)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) @@ -189,6 +192,7 @@ mod create_dir; mod dbg_macro; mod default; mod default_numeric_fallback; +mod default_union_representation; mod dereference; mod derivable_impls; mod derive; @@ -859,6 +863,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/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/loops/utils.rs b/clippy_lints/src/loops/utils.rs index eac0f03b142a8..b6c746d3e3971 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,18 +332,21 @@ 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 method_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(), - meth_name, + sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(), + method_name, ) }, _ => format!( diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs deleted file mode 100644 index e61cb4d227363..0000000000000 --- a/clippy_lints/src/matches.rs +++ /dev/null @@ -1,2342 +0,0 @@ -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, -}; -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::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, -}; -use clippy_utils::{higher, peel_blocks_with_stmt}; -use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; -use core::iter::{once, ExactSizeIterator}; -use if_chain::if_chain; -use rustc_ast::ast::{Attribute, 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, -}; -use rustc_hir::{HirIdMap, HirIdSet}; -use rustc_lint::{LateContext, LateLintPass}; -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}; -use rustc_span::{sym, symbol::kw}; -use std::cmp::Ordering; -use std::collections::hash_map::Entry; - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches with a single arm where an `if let` - /// will usually suffice. - /// - /// ### Why is this bad? - /// Just readability – `if let` nests less than a `match`. - /// - /// ### Example - /// ```rust - /// # fn bar(stool: &str) {} - /// # let x = Some("abc"); - /// // Bad - /// match x { - /// Some(ref foo) => bar(foo), - /// _ => (), - /// } - /// - /// // Good - /// if let Some(ref foo) = x { - /// bar(foo); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SINGLE_MATCH, - style, - "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches with two arms where an `if let else` will - /// usually suffice. - /// - /// ### Why is this bad? - /// Just readability – `if let` nests less than a `match`. - /// - /// ### Known problems - /// Personal style preferences may differ. - /// - /// ### Example - /// Using `match`: - /// - /// ```rust - /// # fn bar(foo: &usize) {} - /// # let other_ref: usize = 1; - /// # let x: Option<&usize> = Some(&1); - /// match x { - /// Some(ref foo) => bar(foo), - /// _ => bar(&other_ref), - /// } - /// ``` - /// - /// Using `if let` with `else`: - /// - /// ```rust - /// # fn bar(foo: &usize) {} - /// # let other_ref: usize = 1; - /// # let x: Option<&usize> = Some(&1); - /// if let Some(ref foo) = x { - /// bar(foo); - /// } else { - /// bar(&other_ref); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub SINGLE_MATCH_ELSE, - pedantic, - "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches where all arms match a reference, - /// suggesting to remove the reference and deref the matched expression - /// instead. It also checks for `if let &foo = bar` blocks. - /// - /// ### Why is this bad? - /// It just makes the code less readable. That reference - /// destructuring adds nothing to the code. - /// - /// ### Example - /// ```rust,ignore - /// // Bad - /// match x { - /// &A(ref y) => foo(y), - /// &B => bar(), - /// _ => frob(&x), - /// } - /// - /// // Good - /// match *x { - /// A(ref y) => foo(y), - /// B => bar(), - /// _ => frob(x), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_REF_PATS, - style, - "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches where match expression is a `bool`. It - /// suggests to replace the expression with an `if...else` block. - /// - /// ### Why is this bad? - /// It makes the code less readable. - /// - /// ### Example - /// ```rust - /// # fn foo() {} - /// # fn bar() {} - /// let condition: bool = true; - /// match condition { - /// true => foo(), - /// false => bar(), - /// } - /// ``` - /// Use if/else instead: - /// ```rust - /// # fn foo() {} - /// # fn bar() {} - /// let condition: bool = true; - /// if condition { - /// foo(); - /// } else { - /// bar(); - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_BOOL, - pedantic, - "a `match` on a boolean expression instead of an `if..else` block" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for overlapping match arms. - /// - /// ### Why is this bad? - /// It is likely to be an error and if not, makes the code - /// less obvious. - /// - /// ### Example - /// ```rust - /// let x = 5; - /// match x { - /// 1..=10 => println!("1 ... 10"), - /// 5..=15 => println!("5 ... 15"), - /// _ => (), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_OVERLAPPING_ARM, - style, - "a `match` with overlapping arms" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for arm which matches all errors with `Err(_)` - /// and take drastic actions like `panic!`. - /// - /// ### Why is this bad? - /// It is generally a bad practice, similar to - /// catching all exceptions in java with `catch(Exception)` - /// - /// ### Example - /// ```rust - /// let x: Result = Ok(3); - /// match x { - /// Ok(_) => println!("ok"), - /// Err(_) => panic!("err"), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_WILD_ERR_ARM, - pedantic, - "a `match` with `Err(_)` arm and take drastic actions" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for match which is used to add a reference to an - /// `Option` value. - /// - /// ### Why is this bad? - /// Using `as_ref()` or `as_mut()` instead is shorter. - /// - /// ### Example - /// ```rust - /// let x: Option<()> = None; - /// - /// // Bad - /// let r: Option<&()> = match x { - /// None => None, - /// Some(ref v) => Some(v), - /// }; - /// - /// // Good - /// let r: Option<&()> = x.as_ref(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_AS_REF, - complexity, - "a `match` on an Option value instead of using `as_ref()` or `as_mut`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard enum matches using `_`. - /// - /// ### Why is this bad? - /// New enum variants added by library updates can be missed. - /// - /// ### Known problems - /// Suggested replacements may be incorrect if guards exhaustively cover some - /// variants, and also may not use correct path to enum if it's not present in the current scope. - /// - /// ### Example - /// ```rust - /// # enum Foo { A(usize), B(usize) } - /// # let x = Foo::B(1); - /// // Bad - /// match x { - /// Foo::A(_) => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match x { - /// Foo::A(_) => {}, - /// Foo::B(_) => {}, - /// } - /// ``` - #[clippy::version = "1.34.0"] - pub WILDCARD_ENUM_MATCH_ARM, - restriction, - "a wildcard enum match arm using `_`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard enum matches for a single variant. - /// - /// ### Why is this bad? - /// New enum variants added by library updates can be missed. - /// - /// ### Known problems - /// Suggested replacements may not use correct path to enum - /// if it's not present in the current scope. - /// - /// ### Example - /// ```rust - /// # enum Foo { A, B, C } - /// # let x = Foo::B; - /// // Bad - /// match x { - /// Foo::A => {}, - /// Foo::B => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match x { - /// Foo::A => {}, - /// Foo::B => {}, - /// Foo::C => {}, - /// } - /// ``` - #[clippy::version = "1.45.0"] - pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - pedantic, - "a wildcard enum match for a single variant" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard pattern used with others patterns in same match arm. - /// - /// ### Why is this bad? - /// Wildcard pattern already covers any other pattern as it will match anyway. - /// It makes the code less readable, especially to spot wildcard pattern use in match arm. - /// - /// ### Example - /// ```rust - /// // Bad - /// match "foo" { - /// "a" => {}, - /// "bar" | _ => {}, - /// } - /// - /// // Good - /// match "foo" { - /// "a" => {}, - /// _ => {}, - /// } - /// ``` - #[clippy::version = "1.42.0"] - pub WILDCARD_IN_OR_PATTERNS, - complexity, - "a wildcard pattern used with others patterns in same match arm" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for matches being used to destructure a single-variant enum - /// or tuple struct where a `let` will suffice. - /// - /// ### Why is this bad? - /// Just readability – `let` doesn't nest, whereas a `match` does. - /// - /// ### Example - /// ```rust - /// enum Wrapper { - /// Data(i32), - /// } - /// - /// let wrapper = Wrapper::Data(42); - /// - /// let data = match wrapper { - /// Wrapper::Data(i) => i, - /// }; - /// ``` - /// - /// The correct use would be: - /// ```rust - /// enum Wrapper { - /// Data(i32), - /// } - /// - /// let wrapper = Wrapper::Data(42); - /// let Wrapper::Data(data) = wrapper; - /// ``` - #[clippy::version = "pre 1.29.0"] - pub INFALLIBLE_DESTRUCTURING_MATCH, - style, - "a `match` statement with a single infallible arm instead of a `let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for useless match that binds to only one value. - /// - /// ### Why is this bad? - /// Readability and needless complexity. - /// - /// ### Known problems - /// Suggested replacements may be incorrect when `match` - /// is actually binding temporary value, bringing a 'dropped while borrowed' error. - /// - /// ### Example - /// ```rust - /// # let a = 1; - /// # let b = 2; - /// - /// // Bad - /// match (a, b) { - /// (c, d) => { - /// // useless match - /// } - /// } - /// - /// // Good - /// let (c, d) = (a, b); - /// ``` - #[clippy::version = "1.43.0"] - pub MATCH_SINGLE_BINDING, - complexity, - "a match with a single binding instead of using `let` statement" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. - /// - /// ### Why is this bad? - /// Correctness and readability. It's like having a wildcard pattern after - /// matching all enum variants explicitly. - /// - /// ### Example - /// ```rust - /// # struct A { a: i32 } - /// let a = A { a: 5 }; - /// - /// // Bad - /// match a { - /// A { a: 5, .. } => {}, - /// _ => {}, - /// } - /// - /// // Good - /// match a { - /// A { a: 5 } => {}, - /// _ => {}, - /// } - /// ``` - #[clippy::version = "1.43.0"] - pub REST_PAT_IN_FULLY_BOUND_STRUCTS, - restriction, - "a match on a struct that binds all fields but still uses the wildcard pattern" -} - -declare_clippy_lint! { - /// ### What it does - /// Lint for redundant pattern matching over `Result`, `Option`, - /// `std::task::Poll` or `std::net::IpAddr` - /// - /// ### Why is this bad? - /// It's more concise and clear to just use the proper - /// utility function - /// - /// ### Known problems - /// This will change the drop order for the matched type. Both `if let` and - /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the - /// value before entering the block. For most types this change will not matter, but for a few - /// types this will not be an acceptable change (e.g. locks). See the - /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about - /// drop order. - /// - /// ### Example - /// ```rust - /// # use std::task::Poll; - /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - /// if let Ok(_) = Ok::(42) {} - /// if let Err(_) = Err::(42) {} - /// if let None = None::<()> {} - /// if let Some(_) = Some(42) {} - /// if let Poll::Pending = Poll::Pending::<()> {} - /// if let Poll::Ready(_) = Poll::Ready(42) {} - /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} - /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} - /// match Ok::(42) { - /// Ok(_) => true, - /// Err(_) => false, - /// }; - /// ``` - /// - /// The more idiomatic use would be: - /// - /// ```rust - /// # use std::task::Poll; - /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; - /// if Ok::(42).is_ok() {} - /// if Err::(42).is_err() {} - /// if None::<()>.is_none() {} - /// if Some(42).is_some() {} - /// if Poll::Pending::<()>.is_pending() {} - /// if Poll::Ready(42).is_ready() {} - /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} - /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} - /// Ok::(42).is_ok(); - /// ``` - #[clippy::version = "1.31.0"] - pub REDUNDANT_PATTERN_MATCHING, - style, - "use the proper utility function avoiding an `if let`" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `match` or `if let` expressions producing a - /// `bool` that could be written using `matches!` - /// - /// ### Why is this bad? - /// Readability and needless complexity. - /// - /// ### Known problems - /// This lint falsely triggers, if there are arms with - /// `cfg` attributes that remove an arm evaluating to `false`. - /// - /// ### Example - /// ```rust - /// let x = Some(5); - /// - /// // Bad - /// let a = match x { - /// Some(0) => true, - /// _ => false, - /// }; - /// - /// let a = if let Some(0) = x { - /// true - /// } else { - /// false - /// }; - /// - /// // Good - /// let a = matches!(x, Some(0)); - /// ``` - #[clippy::version = "1.47.0"] - pub MATCH_LIKE_MATCHES_MACRO, - style, - "a match that could be written with the matches! macro" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for `match` with identical arm bodies. - /// - /// ### Why is this bad? - /// This is probably a copy & paste error. If arm bodies - /// are the same on purpose, you can factor them - /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). - /// - /// ### Known problems - /// False positive possible with order dependent `match` - /// (see issue - /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). - /// - /// ### Example - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => bar(), // <= oops - /// } - /// ``` - /// - /// This should probably be - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => baz(), // <= fixed - /// } - /// ``` - /// - /// or if the original code was not a typo: - /// ```rust,ignore - /// match foo { - /// Bar | Baz => bar(), // <= shows the intent better - /// Quz => quz(), - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MATCH_SAME_ARMS, - pedantic, - "`match` with identical arm bodies" -} - -#[derive(Default)] -pub struct Matches { - msrv: Option, - infallible_destructuring_match_linted: bool, -} - -impl Matches { - #[must_use] - pub fn new(msrv: Option) -> Self { - Self { - msrv, - ..Matches::default() - } - } -} - -impl_lint_pass!(Matches => [ - SINGLE_MATCH, - MATCH_REF_PATS, - MATCH_BOOL, - SINGLE_MATCH_ELSE, - MATCH_OVERLAPPING_ARM, - MATCH_WILD_ERR_ARM, - MATCH_AS_REF, - WILDCARD_ENUM_MATCH_ARM, - MATCH_WILDCARD_FOR_SINGLE_VARIANTS, - WILDCARD_IN_OR_PATTERNS, - MATCH_SINGLE_BINDING, - INFALLIBLE_DESTRUCTURING_MATCH, - REST_PAT_IN_FULLY_BOUND_STRUCTS, - REDUNDANT_PATTERN_MATCHING, - MATCH_LIKE_MATCHES_MACRO, - MATCH_SAME_ARMS, -]); - -impl<'tcx> LateLintPass<'tcx> for Matches { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - - redundant_pattern_match::check(cx, expr); - - if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { - if !check_match_like_matches(cx, expr) { - lint_match_arms(cx, expr); - } - } else { - lint_match_arms(cx, expr); - } - - if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { - check_single_match(cx, ex, arms, expr); - check_match_bool(cx, ex, arms, expr); - check_overlapping_arms(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); - check_wild_in_or_pats(cx, arms); - - if self.infallible_destructuring_match_linted { - self.infallible_destructuring_match_linted = false; - } else { - check_match_single_binding(cx, ex, arms, expr); - } - } - if let ExprKind::Match(ex, arms, _) = expr.kind { - check_match_ref_pats(cx, ex, arms.iter().map(|el| el.pat), expr); - } - } - - 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, - ); - } - } - } - - 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", - ); - } - } - } - - 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( - cx: &LateContext<'_>, - ex: &Expr<'_>, - arms: &[Arm<'_>], - expr: &Expr<'_>, - ty: Ty<'_>, - 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"), - ]; - - 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; - } - rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false)) - }, - 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)) - }, - _ => return, - }; - - 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); - } - } -} - -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)); - 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) { - 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>]), - 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, - 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_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 { - // 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", - ); - } - } - } -} - -/// 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) { - 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 -} - -/// 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)) -} - -// 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, - 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 -} - -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 -} - -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 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::{ - 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_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 - 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 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], _) => { - if let PatKind::Wild = sub_pat.kind { - if is_lang_ctor(cx, path, ResultOk) { - ("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, ResultErr) { - ("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty)) - } else if is_lang_ctor(cx, path, OptionSome) { - ("is_some()", op_ty) - } else if is_lang_ctor(cx, path, PollReady) { - ("is_ready()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) { - ("is_ipv4()", op_ty) - } else if is_qpath_def_path(cx, path, sub_pat.hir_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 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; - }; - - 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; - - 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)) - ],) - ); -} - -/// 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 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/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/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/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/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/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/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs new file mode 100644 index 0000000000000..271a386859555 --- /dev/null +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -0,0 +1,111 @@ +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 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 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/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/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/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 new file mode 100644 index 0000000000000..b5ee4561f06ec --- /dev/null +++ b/clippy_lints/src/matches/mod.rs @@ -0,0 +1,646 @@ +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}; + +mod infalliable_detructuring_match; +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; +mod match_wild_err_arm; +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 + /// Checks for matches with a single arm where an `if let` + /// will usually suffice. + /// + /// ### Why is this bad? + /// Just readability – `if let` nests less than a `match`. + /// + /// ### Example + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); + /// // Bad + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => (), + /// } + /// + /// // Good + /// if let Some(ref foo) = x { + /// bar(foo); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SINGLE_MATCH, + style, + "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches with two arms where an `if let else` will + /// usually suffice. + /// + /// ### Why is this bad? + /// Just readability – `if let` nests less than a `match`. + /// + /// ### Known problems + /// Personal style preferences may differ. + /// + /// ### Example + /// Using `match`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// match x { + /// Some(ref foo) => bar(foo), + /// _ => bar(&other_ref), + /// } + /// ``` + /// + /// Using `if let` with `else`: + /// + /// ```rust + /// # fn bar(foo: &usize) {} + /// # let other_ref: usize = 1; + /// # let x: Option<&usize> = Some(&1); + /// if let Some(ref foo) = x { + /// bar(foo); + /// } else { + /// bar(&other_ref); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub SINGLE_MATCH_ELSE, + pedantic, + "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches where all arms match a reference, + /// suggesting to remove the reference and deref the matched expression + /// instead. It also checks for `if let &foo = bar` blocks. + /// + /// ### Why is this bad? + /// It just makes the code less readable. That reference + /// destructuring adds nothing to the code. + /// + /// ### Example + /// ```rust,ignore + /// // Bad + /// match x { + /// &A(ref y) => foo(y), + /// &B => bar(), + /// _ => frob(&x), + /// } + /// + /// // Good + /// match *x { + /// A(ref y) => foo(y), + /// B => bar(), + /// _ => frob(x), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_REF_PATS, + style, + "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches where match expression is a `bool`. It + /// suggests to replace the expression with an `if...else` block. + /// + /// ### Why is this bad? + /// It makes the code less readable. + /// + /// ### Example + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// match condition { + /// true => foo(), + /// false => bar(), + /// } + /// ``` + /// Use if/else instead: + /// ```rust + /// # fn foo() {} + /// # fn bar() {} + /// let condition: bool = true; + /// if condition { + /// foo(); + /// } else { + /// bar(); + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_BOOL, + pedantic, + "a `match` on a boolean expression instead of an `if..else` block" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for overlapping match arms. + /// + /// ### Why is this bad? + /// It is likely to be an error and if not, makes the code + /// less obvious. + /// + /// ### Example + /// ```rust + /// let x = 5; + /// match x { + /// 1..=10 => println!("1 ... 10"), + /// 5..=15 => println!("5 ... 15"), + /// _ => (), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_OVERLAPPING_ARM, + style, + "a `match` with overlapping arms" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for arm which matches all errors with `Err(_)` + /// and take drastic actions like `panic!`. + /// + /// ### Why is this bad? + /// It is generally a bad practice, similar to + /// catching all exceptions in java with `catch(Exception)` + /// + /// ### Example + /// ```rust + /// let x: Result = Ok(3); + /// match x { + /// Ok(_) => println!("ok"), + /// Err(_) => panic!("err"), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_WILD_ERR_ARM, + pedantic, + "a `match` with `Err(_)` arm and take drastic actions" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for match which is used to add a reference to an + /// `Option` value. + /// + /// ### Why is this bad? + /// Using `as_ref()` or `as_mut()` instead is shorter. + /// + /// ### Example + /// ```rust + /// let x: Option<()> = None; + /// + /// // Bad + /// let r: Option<&()> = match x { + /// None => None, + /// Some(ref v) => Some(v), + /// }; + /// + /// // Good + /// let r: Option<&()> = x.as_ref(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_AS_REF, + complexity, + "a `match` on an Option value instead of using `as_ref()` or `as_mut`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard enum matches using `_`. + /// + /// ### Why is this bad? + /// New enum variants added by library updates can be missed. + /// + /// ### Known problems + /// Suggested replacements may be incorrect if guards exhaustively cover some + /// variants, and also may not use correct path to enum if it's not present in the current scope. + /// + /// ### Example + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); + /// // Bad + /// match x { + /// Foo::A(_) => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A(_) => {}, + /// Foo::B(_) => {}, + /// } + /// ``` + #[clippy::version = "1.34.0"] + pub WILDCARD_ENUM_MATCH_ARM, + restriction, + "a wildcard enum match arm using `_`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard enum matches for a single variant. + /// + /// ### Why is this bad? + /// New enum variants added by library updates can be missed. + /// + /// ### Known problems + /// Suggested replacements may not use correct path to enum + /// if it's not present in the current scope. + /// + /// ### Example + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; + /// // Bad + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match x { + /// Foo::A => {}, + /// Foo::B => {}, + /// Foo::C => {}, + /// } + /// ``` + #[clippy::version = "1.45.0"] + pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + pedantic, + "a wildcard enum match for a single variant" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard pattern used with others patterns in same match arm. + /// + /// ### Why is this bad? + /// Wildcard pattern already covers any other pattern as it will match anyway. + /// It makes the code less readable, especially to spot wildcard pattern use in match arm. + /// + /// ### Example + /// ```rust + /// // Bad + /// match "foo" { + /// "a" => {}, + /// "bar" | _ => {}, + /// } + /// + /// // Good + /// match "foo" { + /// "a" => {}, + /// _ => {}, + /// } + /// ``` + #[clippy::version = "1.42.0"] + pub WILDCARD_IN_OR_PATTERNS, + complexity, + "a wildcard pattern used with others patterns in same match arm" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for matches being used to destructure a single-variant enum + /// or tuple struct where a `let` will suffice. + /// + /// ### Why is this bad? + /// Just readability – `let` doesn't nest, whereas a `match` does. + /// + /// ### Example + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// + /// let data = match wrapper { + /// Wrapper::Data(i) => i, + /// }; + /// ``` + /// + /// The correct use would be: + /// ```rust + /// enum Wrapper { + /// Data(i32), + /// } + /// + /// let wrapper = Wrapper::Data(42); + /// let Wrapper::Data(data) = wrapper; + /// ``` + #[clippy::version = "pre 1.29.0"] + pub INFALLIBLE_DESTRUCTURING_MATCH, + style, + "a `match` statement with a single infallible arm instead of a `let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for useless match that binds to only one value. + /// + /// ### Why is this bad? + /// Readability and needless complexity. + /// + /// ### Known problems + /// Suggested replacements may be incorrect when `match` + /// is actually binding temporary value, bringing a 'dropped while borrowed' error. + /// + /// ### Example + /// ```rust + /// # let a = 1; + /// # let b = 2; + /// + /// // Bad + /// match (a, b) { + /// (c, d) => { + /// // useless match + /// } + /// } + /// + /// // Good + /// let (c, d) = (a, b); + /// ``` + #[clippy::version = "1.43.0"] + pub MATCH_SINGLE_BINDING, + complexity, + "a match with a single binding instead of using `let` statement" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. + /// + /// ### Why is this bad? + /// Correctness and readability. It's like having a wildcard pattern after + /// matching all enum variants explicitly. + /// + /// ### Example + /// ```rust + /// # struct A { a: i32 } + /// let a = A { a: 5 }; + /// + /// // Bad + /// match a { + /// A { a: 5, .. } => {}, + /// _ => {}, + /// } + /// + /// // Good + /// match a { + /// A { a: 5 } => {}, + /// _ => {}, + /// } + /// ``` + #[clippy::version = "1.43.0"] + pub REST_PAT_IN_FULLY_BOUND_STRUCTS, + restriction, + "a match on a struct that binds all fields but still uses the wildcard pattern" +} + +declare_clippy_lint! { + /// ### What it does + /// Lint for redundant pattern matching over `Result`, `Option`, + /// `std::task::Poll` or `std::net::IpAddr` + /// + /// ### Why is this bad? + /// It's more concise and clear to just use the proper + /// utility function + /// + /// ### Known problems + /// This will change the drop order for the matched type. Both `if let` and + /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the + /// value before entering the block. For most types this change will not matter, but for a few + /// types this will not be an acceptable change (e.g. locks). See the + /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about + /// drop order. + /// + /// ### Example + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if let Ok(_) = Ok::(42) {} + /// if let Err(_) = Err::(42) {} + /// if let None = None::<()> {} + /// if let Some(_) = Some(42) {} + /// if let Poll::Pending = Poll::Pending::<()> {} + /// if let Poll::Ready(_) = Poll::Ready(42) {} + /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {} + /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {} + /// match Ok::(42) { + /// Ok(_) => true, + /// Err(_) => false, + /// }; + /// ``` + /// + /// The more idiomatic use would be: + /// + /// ```rust + /// # use std::task::Poll; + /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + /// if Ok::(42).is_ok() {} + /// if Err::(42).is_err() {} + /// if None::<()>.is_none() {} + /// if Some(42).is_some() {} + /// if Poll::Pending::<()>.is_pending() {} + /// if Poll::Ready(42).is_ready() {} + /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {} + /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {} + /// Ok::(42).is_ok(); + /// ``` + #[clippy::version = "1.31.0"] + pub REDUNDANT_PATTERN_MATCHING, + style, + "use the proper utility function avoiding an `if let`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `match` or `if let` expressions producing a + /// `bool` that could be written using `matches!` + /// + /// ### Why is this bad? + /// Readability and needless complexity. + /// + /// ### Known problems + /// This lint falsely triggers, if there are arms with + /// `cfg` attributes that remove an arm evaluating to `false`. + /// + /// ### Example + /// ```rust + /// let x = Some(5); + /// + /// // Bad + /// let a = match x { + /// Some(0) => true, + /// _ => false, + /// }; + /// + /// let a = if let Some(0) = x { + /// true + /// } else { + /// false + /// }; + /// + /// // Good + /// let a = matches!(x, Some(0)); + /// ``` + #[clippy::version = "1.47.0"] + pub MATCH_LIKE_MATCHES_MACRO, + style, + "a match that could be written with the matches! macro" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for `match` with identical arm bodies. + /// + /// ### Why is this bad? + /// This is probably a copy & paste error. If arm bodies + /// are the same on purpose, you can factor them + /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). + /// + /// ### Known problems + /// False positive possible with order dependent `match` + /// (see issue + /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). + /// + /// ### Example + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => bar(), // <= oops + /// } + /// ``` + /// + /// This should probably be + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => baz(), // <= fixed + /// } + /// ``` + /// + /// or if the original code was not a typo: + /// ```rust,ignore + /// match foo { + /// Bar | Baz => bar(), // <= shows the intent better + /// Quz => quz(), + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MATCH_SAME_ARMS, + pedantic, + "`match` with identical arm bodies" +} + +#[derive(Default)] +pub struct Matches { + msrv: Option, + infallible_destructuring_match_linted: bool, +} + +impl Matches { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { + msrv, + ..Matches::default() + } + } +} + +impl_lint_pass!(Matches => [ + SINGLE_MATCH, + MATCH_REF_PATS, + MATCH_BOOL, + SINGLE_MATCH_ELSE, + MATCH_OVERLAPPING_ARM, + MATCH_WILD_ERR_ARM, + MATCH_AS_REF, + WILDCARD_ENUM_MATCH_ARM, + MATCH_WILDCARD_FOR_SINGLE_VARIANTS, + WILDCARD_IN_OR_PATTERNS, + MATCH_SINGLE_BINDING, + INFALLIBLE_DESTRUCTURING_MATCH, + REST_PAT_IN_FULLY_BOUND_STRUCTS, + REDUNDANT_PATTERN_MATCHING, + MATCH_LIKE_MATCHES_MACRO, + MATCH_SAME_ARMS, +]); + +impl<'tcx> LateLintPass<'tcx> for Matches { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + redundant_pattern_match::check(cx, expr); + + if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { + if !match_like_matches::check(cx, expr) { + match_same_arms::check(cx, expr); + } + } else { + match_same_arms::check(cx, expr); + } + + if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { + single_match::check(cx, ex, arms, expr); + match_bool::check(cx, ex, arms, expr); + overlapping_arms::check(cx, ex, arms); + match_wild_err_arm::check(cx, ex, arms); + match_wild_enum::check(cx, ex, arms); + match_as_ref::check(cx, ex, arms, expr); + wild_in_or_pats::check(cx, arms); + + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + match_single_binding::check(cx, ex, arms, expr); + } + } + if let ExprKind::Match(ex, arms, _) = expr.kind { + match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); + } + } + + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local); + } + + fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { + rest_pat_in_fully_bound_struct::check(cx, pat); + } + + extract_msrv_attr!(LateContext); +} 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)) + ],) + ); +} 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, + } +} 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", + ); + } + } +} 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, + } +} 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", + ); + } + } + } +} 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/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/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(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], _)) => { 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/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/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index b2f624ed480e4..926c25b4b40a5 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/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index ccfce31713f93..12ad3d8d69038 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -33,9 +33,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< } else if !found_mapping && !mutates_arg { 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) && in_ty == subst.type_at(0) => - { + ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => { "filter" }, _ => return, 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/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/ptr.rs b/clippy_lints/src/ptr.rs index b5d65542de0bf..e0ce1b7db003a 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -3,9 +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, match_any_diagnostic_items, path_to_local, 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; @@ -153,7 +151,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 +170,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 +185,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 +202,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 +320,10 @@ impl PtrArg<'_> { self.deref_ty.argless_str(), ) } + + fn mutability(&self) -> Mutability { + self.ref_prefix.mutability + } } struct RefPrefix { @@ -641,7 +647,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 }, } @@ -665,8 +671,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_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 027ab70014fc0..be7610f365c5b 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_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/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 3ad4ec74bf51c..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,13 +394,13 @@ 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 { - #[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 +410,31 @@ 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) + || transmute_undefined_repr::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..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>, - args: &'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 = &args[0]; - 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 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/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs new file mode 100644 index 0000000000000..c91bc3245e417 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -0,0 +1,289 @@ +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, 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 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 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 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 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 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 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 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)); + } + } + }, + ); + 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 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 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/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/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_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", 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 31c4abdfc95ea..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::{get_qpath_generic_tys, is_ty_param_diagnostic_item}; +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,12 +20,17 @@ 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, }; - 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, }; @@ -55,12 +60,16 @@ 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, }; - 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, }; @@ -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 ac7bdd6a1ebeb..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::{get_qpath_generic_tys, is_ty_param_diagnostic_item, is_ty_param_lang_item}; +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,21 +39,20 @@ 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 { 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_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_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index c9d99617c1e28..680b2eb1da723 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 #8323 + if let Some(ref found_config_) = found_config { + eprintln!( + "Using config file `{}`\nWarning: `{}` will be ignored.", + 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); 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_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 3547f0b4e0ae0..56633490eaa1a 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -612,8 +612,8 @@ fn get_lint_group_and_level_or_lint( } fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option { - for (group_name, lints, _) in &cx.lint_store.get_lint_groups() { - if IGNORED_LINT_GROUPS.contains(group_name) { + for (group_name, lints, _) in cx.lint_store.get_lint_groups() { + if IGNORED_LINT_GROUPS.contains(&group_name) { continue; } 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/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 7b5c5af8f79bf..9654895060f85 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 a2f1f4696513e..42955080c966e 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; @@ -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 @@ -262,44 +255,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 = get_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 = get_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 { @@ -360,35 +315,17 @@ 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 get_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, }) } -pub fn single_segment_path<'tcx>(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()`. @@ -420,37 +357,17 @@ 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) - .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. 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 @@ -497,8 +414,46 @@ 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 { +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 { macro_rules! try_res { ($e:expr) => { match $e { @@ -574,7 +529,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, } @@ -603,7 +558,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 +670,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 +1505,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 +1595,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 @@ -1653,7 +1604,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 @@ -1661,14 +1612,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. @@ -1821,8 +1764,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)), } } @@ -2174,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() @@ -2197,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/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index a75f6b86a9bae..5a76ac23332d3 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, @@ -462,7 +463,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 } @@ -471,11 +476,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/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 819ff917b6336..958e6d1ec4615 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(); 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 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"] diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 6505028db9fad..a82ff18283931 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,23 @@ 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); -} - -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(); - rustcflags.push_str(" --test"); - compiletest::run_tests(cfg); - if let Some(ref mut flags) = &mut cfg.target_rustcflags { - flags.truncate(len); - } + let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap()); + 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 +210,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 +225,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 +298,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 +319,10 @@ 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_toml(); + run_ui_cargo(); + run_internal_tests(); } /// Restores an env var on drop 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..2d0b4a7948c4a --- /dev/null +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.rs @@ -0,0 +1,5 @@ +// ignore-windows + +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. 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/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 + 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() {} 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 + 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/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/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/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 - diff --git a/tests/ui/get_unwrap.fixed b/tests/ui/get_unwrap.fixed index 924c02a4054d4..c3a36dcabd1a9 100644 --- a/tests/ui/get_unwrap.fixed +++ b/tests/ui/get_unwrap.fixed @@ -1,5 +1,7 @@ // run-rustfix + #![allow(unused_mut, clippy::from_iter_instead_of_collect)] +#![warn(clippy::unwrap_used)] #![deny(clippy::get_unwrap)] use std::collections::BTreeMap; @@ -37,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]; @@ -49,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 c0c37bb720663..d77a202aa39c3 100644 --- a/tests/ui/get_unwrap.rs +++ b/tests/ui/get_unwrap.rs @@ -1,5 +1,7 @@ // run-rustfix + #![allow(unused_mut, clippy::from_iter_instead_of_collect)] +#![warn(clippy::unwrap_used)] #![deny(clippy::get_unwrap)] use std::collections::BTreeMap; @@ -37,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(); @@ -49,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 76a098df82aa1..cb5f44fbd59ee 100644 --- a/tests/ui/get_unwrap.stderr +++ b/tests/ui/get_unwrap.stderr @@ -1,86 +1,191 @@ 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)] | ^^^^^^^^^^^^^^^^^^ +error: used `unwrap()` on `an Option` value + --> $DIR/get_unwrap.rs:36:17 + | +LL | let _ = boxed_slice.get(1).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = 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:37: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 + --> $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:38: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 + --> $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:39: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 + --> $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:40: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 + --> $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:41: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: called `.get().unwrap()` on a slice. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:42: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:45: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 + --> $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:50: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 + --> $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:51: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 + --> $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:52: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 + --> $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: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: called `.get().unwrap()` on a Vec. Using `[]` is more clear and more concise - --> $DIR/get_unwrap.rs:59: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:65: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 + --> $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: aborting due to 13 previous errors +error: used `unwrap()` on `an Option` value + --> $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 26 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!()) + } } 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); diff --git a/tests/ui/non_expressive_names.stdout b/tests/ui/non_expressive_names.stdout deleted file mode 100644 index e69de29bb2d1d..0000000000000 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) {} +} diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index b1819e08d53bf..bd37188804636 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -145,6 +145,84 @@ fn if_suggestion() { }; } +// See: issue #8282 +fn ranges() { + enum E { + V, + } + let x = (Some(E::V), Some(42)); + + // 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, _) => {}, + } + + // 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, see above. + match (Some(E::V), Some(E::V), Some(E::V)) { + (.., Some(E::V), _) => {}, + (.., None, _) => {}, + } + + // Don't lint, see above. + match (Some(E::V), Some(E::V), Some(E::V)) { + (Some(E::V), ..) => {}, + (None, ..) => {}, + } + + // Don't lint, see above. + match (Some(E::V), Some(E::V), Some(E::V)) { + (_, Some(E::V), ..) => {}, + (_, None, ..) => {}, + } +} + +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 { diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index c261b5111c8bf..318faf2571758 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:164: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:170: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:176: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 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 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 + 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 61864b634cf0c9bc9d248feb2d9ba3cfa8559623 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 10 Feb 2022 19:52:01 +0100 Subject: [PATCH 02/55] Clippy: Fix botstrap fallout --- clippy_lints/src/lib.rs | 3 +- .../src/transmute/transmute_undefined_repr.rs | 42 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9999cd3f8243c..5c45012ef0686 100644 --- a/clippy_lints/src/lib.rs +++ b/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/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index c91bc3245e417..030d2c2378488 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/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 + } } } From 8173abad573fccb136739ccc79d05c2453c3ee4e Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 11 Feb 2022 07:18:06 +0000 Subject: [PATCH 03/55] Revert "Auto merge of #92007 - oli-obk:lazy_tait2, r=nikomatsakis" This reverts commit e7cc3bddbe0d0e374d05e7003e662bba1742dbae, reversing changes made to 734368a200904ef9c21db86c595dc04263c87be0. --- clippy_utils/src/qualify_min_const_fn.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 5407b5e8ed93e..7512039a480bb 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -32,7 +32,6 @@ 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 41d7c7e059b8ae829c7d6480c53a5473d213e0a4 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 11 Feb 2022 14:45:56 +0000 Subject: [PATCH 04/55] Migrate `dbg_macro` to late pass --- clippy_lints/src/dbg_macro.rs | 97 +++++++++++++++++++---------------- clippy_lints/src/lib.rs | 2 +- tests/ui/dbg_macro.rs | 23 +++++++++ tests/ui/dbg_macro.stderr | 35 ++++++++++++- 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 5a0b60fdfbc01..df1a4128af359 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -1,11 +1,11 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use clippy_utils::source::snippet_opt; -use rustc_ast::ast; -use rustc_ast::tokenstream::TokenStream; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::macros::root_macro_call_first_node; +use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -15,14 +15,6 @@ declare_clippy_lint! { /// `dbg!` macro is intended as a debugging tool. It /// should not be in version control. /// - /// ### Known problems - /// * The lint level is unaffected by crate attributes. The level can still - /// be set for functions, modules and other items. To change the level for - /// the entire crate, please use command line flags. More information and a - /// configuration example can be found in [clippy#6610]. - /// - /// [clippy#6610]: https://github.com/rust-lang/rust-clippy/issues/6610#issuecomment-977120558 - /// /// ### Example /// ```rust,ignore /// // Bad @@ -39,37 +31,52 @@ declare_clippy_lint! { declare_lint_pass!(DbgMacro => [DBG_MACRO]); -impl EarlyLintPass for DbgMacro { - fn check_mac(&mut self, cx: &EarlyContext<'_>, mac: &ast::MacCall) { - if mac.path == sym!(dbg) { - if let Some(sugg) = tts_span(mac.args.inner_tokens()).and_then(|span| snippet_opt(cx, span)) { - span_lint_and_sugg( - cx, - DBG_MACRO, - mac.span(), - "`dbg!` macro is intended as a debugging tool", - "ensure to avoid having uses of it in version control", - sugg, - Applicability::MaybeIncorrect, - ); - } else { - span_lint_and_help( - cx, - DBG_MACRO, - mac.span(), - "`dbg!` macro is intended as a debugging tool", - None, - "ensure to avoid having uses of it in version control", - ); - } +impl LateLintPass<'_> for DbgMacro { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return }; + if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) { + let mut applicability = Applicability::MachineApplicable; + let suggestion = match expr.peel_drop_temps().kind { + // dbg!() + ExprKind::Block(_, _) => String::new(), + // dbg!(1) + ExprKind::Match(val, ..) => { + snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string() + }, + // dbg!(2, 3) + ExprKind::Tup( + [ + Expr { + kind: ExprKind::Match(first, ..), + .. + }, + .., + Expr { + kind: ExprKind::Match(last, ..), + .. + }, + ], + ) => { + let snippet = snippet_with_applicability( + cx, + first.span.source_callsite().to(last.span.source_callsite()), + "..", + &mut applicability, + ); + format!("({snippet})") + }, + _ => return, + }; + + span_lint_and_sugg( + cx, + DBG_MACRO, + macro_call.span, + "`dbg!` macro is intended as a debugging tool", + "ensure to avoid having uses of it in version control", + suggestion, + applicability, + ); } } } - -// Get span enclosing entire the token stream. -fn tts_span(tts: TokenStream) -> Option { - let mut cursor = tts.into_trees(); - let first = cursor.next()?.span(); - let span = cursor.last().map_or(first, |tree| first.to(tree.span())); - Some(span) -} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9999cd3f8243c..3bd7699792ad7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -432,7 +432,6 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se store.register_pre_expansion_pass(|| Box::new(write::Write::default())); store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv })); - store.register_pre_expansion_pass(|| Box::new(dbg_macro::DbgMacro)); } #[doc(hidden)] @@ -864,6 +863,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: 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)); + store.register_late_pass(|| Box::new(dbg_macro::DbgMacro)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/dbg_macro.rs b/tests/ui/dbg_macro.rs index d74e2611ee1fd..9b03c9b47832f 100644 --- a/tests/ui/dbg_macro.rs +++ b/tests/ui/dbg_macro.rs @@ -16,4 +16,27 @@ fn main() { dbg!(42); dbg!(dbg!(dbg!(42))); foo(3) + dbg!(factorial(4)); + dbg!(1, 2, dbg!(3, 4)); + dbg!(1, 2, 3, 4, 5); +} + +mod issue7274 { + trait Thing<'b> { + fn foo(&self); + } + + macro_rules! define_thing { + ($thing:ident, $body:expr) => { + impl<'a> Thing<'a> for $thing { + fn foo<'b>(&self) { + $body + } + } + }; + } + + struct MyThing; + define_thing!(MyThing, { + dbg!(2); + }); } diff --git a/tests/ui/dbg_macro.stderr b/tests/ui/dbg_macro.stderr index 0abe953af2613..8ee1b328720d9 100644 --- a/tests/ui/dbg_macro.stderr +++ b/tests/ui/dbg_macro.stderr @@ -76,5 +76,38 @@ help: ensure to avoid having uses of it in version control LL | foo(3) + factorial(4); | ~~~~~~~~~~~~ -error: aborting due to 7 previous errors +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:19:5 + | +LL | dbg!(1, 2, dbg!(3, 4)); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | (1, 2, dbg!(3, 4)); + | ~~~~~~~~~~~~~~~~~~ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:20:5 + | +LL | dbg!(1, 2, 3, 4, 5); + | ^^^^^^^^^^^^^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | (1, 2, 3, 4, 5); + | ~~~~~~~~~~~~~~~ + +error: `dbg!` macro is intended as a debugging tool + --> $DIR/dbg_macro.rs:40:9 + | +LL | dbg!(2); + | ^^^^^^^ + | +help: ensure to avoid having uses of it in version control + | +LL | 2; + | ~ + +error: aborting due to 10 previous errors From 9f75aff3919122b5657650930c38095731d2dbba Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Fri, 28 Jan 2022 18:14:18 +0100 Subject: [PATCH 05/55] Bless clippy test. --- tests/ui/manual_async_fn.fixed | 1 + tests/ui/manual_async_fn.rs | 1 + tests/ui/manual_async_fn.stderr | 2 +- tests/ui/needless_lifetimes.stderr | 8 +++++++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/ui/manual_async_fn.fixed b/tests/ui/manual_async_fn.fixed index 136cc96be70ca..e9ca66f125d8f 100644 --- a/tests/ui/manual_async_fn.fixed +++ b/tests/ui/manual_async_fn.fixed @@ -80,6 +80,7 @@ fn elided_not_bound(_: &i32) -> impl Future { async { 42 } } +#[allow(clippy::needless_lifetimes)] async fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> i32 { 42 } // should be ignored diff --git a/tests/ui/manual_async_fn.rs b/tests/ui/manual_async_fn.rs index ddc453ffdb750..c3fa846485ba3 100644 --- a/tests/ui/manual_async_fn.rs +++ b/tests/ui/manual_async_fn.rs @@ -98,6 +98,7 @@ fn elided_not_bound(_: &i32) -> impl Future { async { 42 } } +#[allow(clippy::needless_lifetimes)] fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { async { 42 } } diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr index 7435f46074c81..b83abfccd4e12 100644 --- a/tests/ui/manual_async_fn.stderr +++ b/tests/ui/manual_async_fn.stderr @@ -140,7 +140,7 @@ LL | fn elided(_: &i32) -> impl Future + '_ { 42 } | ~~~~~~ error: this function can be simplified using the `async fn` syntax - --> $DIR/manual_async_fn.rs:101:1 + --> $DIR/manual_async_fn.rs:102:1 | LL | fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/needless_lifetimes.stderr b/tests/ui/needless_lifetimes.stderr index ffa152427a977..8df50d79ca573 100644 --- a/tests/ui/needless_lifetimes.stderr +++ b/tests/ui/needless_lifetimes.stderr @@ -18,6 +18,12 @@ error: explicit lifetimes given in parameter types where they could be elided (o 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:37:1 + | +LL | async fn func<'a>(args: &[&'a str]) -> Option<&'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:56:1 | @@ -192,5 +198,5 @@ error: explicit lifetimes given in parameter types where they could be elided (o LL | fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 32 previous errors +error: aborting due to 33 previous errors From 79644069f0e8a0c6e40da6840053432e2d6a3fd7 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Fri, 11 Feb 2022 19:40:07 -0800 Subject: [PATCH 06/55] Downgrade transmute_undefined_repr lint to nursery --- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_correctness.rs | 1 - clippy_lints/src/lib.register_nursery.rs | 1 + clippy_lints/src/transmute/mod.rs | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index d93e34e76b492..4721b7f2b472b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -277,7 +277,6 @@ 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 d013daa8e082a..4217fd3a3ea72 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -58,7 +58,6 @@ 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_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index a735379010026..8d4dde42bbeca 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -26,6 +26,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(strings::STRING_LIT_AS_BYTES), LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::USELESS_TRANSMUTE), LintId::of(use_self::USE_SELF), ]) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 4c320deecc28b..5e94ab6d04820 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -376,7 +376,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.60.0"] pub TRANSMUTE_UNDEFINED_REPR, - correctness, + nursery, "transmute to or from a type with an undefined representation" } From 7c94736953097fb78bd211ae8d493f2fb8bad072 Mon Sep 17 00:00:00 2001 From: Ellen Date: Wed, 9 Feb 2022 11:03:27 +0000 Subject: [PATCH 07/55] change to a struct variant --- clippy_lints/src/trait_bounds.rs | 2 +- clippy_lints/src/use_self.rs | 4 ++-- clippy_utils/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 5257f5302cd90..bca95b7f25638 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; if !bound_predicate.span.from_expansion(); if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind; - if let Some(PathSegment { res: Some(Res::SelfTy(Some(def_id), _)), .. }) = segments.first(); + if let Some(PathSegment { res: Some(Res::SelfTy{ trait_: Some(def_id), alias_to: _ }), .. }) = segments.first(); if let Some( Node::Item( diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index be20282b3b88c..80164c59ba74c 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -204,7 +204,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { ref types_to_skip, }) = self.stack.last(); if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; - if !matches!(path.res, Res::SelfTy(..) | Res::Def(DefKind::TyParam, _)); + if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _)); if !types_to_skip.contains(&hir_ty.hir_id); let ty = if in_body > 0 { cx.typeck_results().node_type(hir_ty.hir_id) @@ -231,7 +231,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { } match expr.kind { ExprKind::Struct(QPath::Resolved(_, path), ..) => match path.res { - Res::SelfTy(..) => (), + Res::SelfTy { .. } => (), Res::Def(DefKind::Variant, _) => lint_path_to_variant(cx, path), _ => span_lint(cx, path.span), }, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 42955080c966e..f775cdd3bc280 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1460,7 +1460,7 @@ pub fn is_self(slf: &Param<'_>) -> bool { pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { if let TyKind::Path(QPath::Resolved(None, path)) = slf.kind { - if let Res::SelfTy(..) = path.res { + if let Res::SelfTy { .. } = path.res { return true; } } From e2dc8ca08029b317771fcdac8979d77505068670 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sat, 12 Feb 2022 11:44:28 +0000 Subject: [PATCH 08/55] Replace a few paths with diagnostic items --- clippy_lints/src/derive.rs | 7 ++++--- clippy_lints/src/duration_subsec.rs | 11 +++++------ clippy_lints/src/infinite_iter.rs | 11 +++++++---- clippy_lints/src/minmax.rs | 15 ++++++--------- clippy_lints/src/types/borrowed_box.rs | 4 ++-- clippy_utils/src/paths.rs | 7 ------- 6 files changed, 24 insertions(+), 31 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 6d3df260ca255..7277e4080c5c0 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then}; use clippy_utils::paths; use clippy_utils::ty::{implements_trait, is_copy}; -use clippy_utils::{get_trait_def_id, is_automatically_derived, is_lint_allowed, match_def_path}; +use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path}; use if_chain::if_chain; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ @@ -12,6 +12,7 @@ use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -196,7 +197,7 @@ fn check_hash_peq<'tcx>( if_chain! { if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); if let Some(def_id) = trait_ref.trait_def_id(); - if match_def_path(cx, def_id, &paths::HASH); + if cx.tcx.is_diagnostic_item(sym::Hash, def_id); then { // Look for the PartialEq implementations for `ty` cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { @@ -247,7 +248,7 @@ fn check_ord_partial_ord<'tcx>( ord_is_automatically_derived: bool, ) { if_chain! { - if let Some(ord_trait_def_id) = get_trait_def_id(cx, &paths::ORD); + if let Some(ord_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Ord); if let Some(partial_ord_trait_def_id) = cx.tcx.lang_items().partial_ord_trait(); if let Some(def_id) = &trait_ref.trait_def_id(); if *def_id == ord_trait_def_id; diff --git a/clippy_lints/src/duration_subsec.rs b/clippy_lints/src/duration_subsec.rs index 24e32c09f44b3..09318f74527c2 100644 --- a/clippy_lints/src/duration_subsec.rs +++ b/clippy_lints/src/duration_subsec.rs @@ -1,15 +1,14 @@ +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::match_type; +use clippy_utils::ty::is_type_diagnostic_item; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Spanned; - -use clippy_utils::consts::{constant, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::paths; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -46,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for DurationSubsec { if_chain! { if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind; if let ExprKind::MethodCall(method_path, args, _) = left.kind; - if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION); + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::Duration); if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right); then { let suggested_fn = match (method_path.ident.as_str(), divisor) { diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index b6badef02f58a..b2b9889f5dc74 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, match_def_path, path_def_id, paths}; +use clippy_utils::{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}; @@ -229,9 +229,12 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { } } if method.ident.name == sym!(last) && args.len() == 1 { - let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR).map_or(false, |id| { - !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[]) - }); + let not_double_ended = cx + .tcx + .get_diagnostic_item(sym::DoubleEndedIterator) + .map_or(false, |id| { + !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[]) + }); if not_double_ended { return is_infinite(cx, &args[0]); } diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index cf9770f5c1fd3..65d1f440b7639 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -1,10 +1,11 @@ use clippy_utils::consts::{constant_simple, Constant}; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{match_def_path, match_trait_method, paths}; +use clippy_utils::{match_trait_method, paths}; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; use std::cmp::Ordering; declare_clippy_lint! { @@ -73,14 +74,10 @@ fn min_max<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(MinMax, Cons cx.typeck_results() .qpath_res(qpath, path.hir_id) .opt_def_id() - .and_then(|def_id| { - if match_def_path(cx, def_id, &paths::CMP_MIN) { - fetch_const(cx, args, MinMax::Min) - } else if match_def_path(cx, def_id, &paths::CMP_MAX) { - fetch_const(cx, args, MinMax::Max) - } else { - None - } + .and_then(|def_id| match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::cmp_min) => fetch_const(cx, args, MinMax::Min), + Some(sym::cmp_max) => fetch_const(cx, args, MinMax::Max), + _ => None, }) } else { None diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index 63ad65b8afd9f..7c06906293b16 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; -use clippy_utils::{match_def_path, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{self as hir, GenericArg, GenericBounds, GenericParamKind}; use rustc_hir::{HirId, Lifetime, MutTy, Mutability, Node, QPath, TyKind}; use rustc_lint::LateContext; +use rustc_span::sym; use super::BORROWED_BOX; @@ -89,7 +89,7 @@ fn is_any_trait(cx: &LateContext<'_>, t: &hir::Ty<'_>) -> bool { if let Some(trait_did) = traits[0].trait_ref.trait_def_id(); // Only Send/Sync can be used as additional traits, so it is enough to // check only the first trait. - if match_def_path(cx, trait_did, &paths::ANY_TRAIT); + if cx.tcx.is_diagnostic_item(sym::Any, trait_did); then { return true; } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 288c56e9fd737..b9c5d28992477 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -4,7 +4,6 @@ //! Whenever possible, please consider diagnostic items over hardcoded paths. //! See for more information. -pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"]; #[cfg(feature = "internal")] pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"]; #[cfg(feature = "internal")] @@ -32,8 +31,6 @@ pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", " pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; -pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"]; -pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"]; pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; @@ -42,9 +39,7 @@ pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"]; pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"]; -pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"]; pub const DROP: [&str; 3] = ["core", "mem", "drop"]; -pub const DURATION: [&str; 3] = ["core", "time", "Duration"]; #[cfg(feature = "internal")] pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"]; #[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros @@ -59,7 +54,6 @@ pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"]; #[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"]; pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; -pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"]; pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"]; pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"]; pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"]; @@ -67,7 +61,6 @@ pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator" pub const FUTURES_IO_ASYNCREADEXT: [&str; 3] = ["futures_util", "io", "AsyncReadExt"]; #[allow(clippy::invalid_paths)] // internal lints do not know about all external crates pub const FUTURES_IO_ASYNCWRITEEXT: [&str; 3] = ["futures_util", "io", "AsyncWriteExt"]; -pub const HASH: [&str; 3] = ["core", "hash", "Hash"]; pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"]; From 662df33e976e2143f1ca839425b995f0eebf9c6e Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 13 Feb 2022 13:22:17 -0500 Subject: [PATCH 09/55] Fix `transmute_undefined_repr` with single field `#[repr(C)]` structs --- clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_nursery.rs | 1 - clippy_lints/src/transmute/mod.rs | 5 +++-- clippy_lints/src/transmute/transmute_undefined_repr.rs | 9 +++++---- tests/ui/transmute_undefined_repr.rs | 4 ++++ 6 files changed, 14 insertions(+), 7 deletions(-) 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_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 8d4dde42bbeca..a735379010026 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -26,7 +26,6 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(strings::STRING_LIT_AS_BYTES), LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY), - LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::USELESS_TRANSMUTE), LintId::of(use_self::USE_SELF), ]) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 5e94ab6d04820..22a8c53a5852e 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -358,7 +358,8 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for transmutes either to or from a type which does not have a defined representation. + /// Checks for transmutes between types which do not have a representation defined relative to + /// each other. /// /// ### Why is this bad? /// The results of such a transmute are not defined. @@ -376,7 +377,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.60.0"] pub TRANSMUTE_UNDEFINED_REPR, - nursery, + correctness, "transmute to or from a type with an undefined representation" } diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index c91bc3245e417..89439862f6915 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -255,9 +255,6 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> 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 @@ -270,7 +267,11 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty = sized_ty; continue; } - ReducedTy::UnorderedFields(ty) + if def.repr.inhibit_struct_field_reordering_opt() { + ReducedTy::OrderedFields(ty) + } else { + ReducedTy::UnorderedFields(ty) + } }, ty::Ref(..) | ty::RawPtr(_) => ReducedTy::Ref(ty), _ => ReducedTy::Other(ty), diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index 71539940fbf27..f0383a03fbde0 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -40,5 +40,9 @@ fn main() { let _: Ty<[u8; 8]> = core::mem::transmute(value::>()); // Ok, transmute to byte array let _: Ty2 = core::mem::transmute(value::>()); // Ok, transmute from byte array + + // issue #8417 + let _: Ty2C, ()> = core::mem::transmute(value::>()); // Ok, Ty2 types are the same + let _: Ty2 = core::mem::transmute(value::, ()>>()); // Ok, Ty2 types are the same } } From eed7e9f61855e05fc03e42ce69da88b3f3a34d5b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 13 Feb 2022 16:54:33 -0500 Subject: [PATCH 10/55] Merge cargo lints --- clippy_lints/src/cargo/common_metadata.rs | 54 +++++ clippy_lints/src/cargo/feature_name.rs | 92 ++++++++ clippy_lints/src/cargo/mod.rs | 221 ++++++++++++++++++ .../src/cargo/multiple_crate_versions.rs | 63 +++++ .../src/cargo/wildcard_dependencies.rs | 27 +++ clippy_lints/src/cargo_common_metadata.rs | 118 ---------- clippy_lints/src/feature_name.rs | 166 ------------- clippy_lints/src/lib.register_cargo.rs | 10 +- clippy_lints/src/lib.register_lints.rs | 10 +- clippy_lints/src/lib.rs | 16 +- clippy_lints/src/multiple_crate_versions.rs | 101 -------- clippy_lints/src/wildcard_dependencies.rs | 57 ----- clippy_utils/src/lib.rs | 18 -- 13 files changed, 474 insertions(+), 479 deletions(-) create mode 100644 clippy_lints/src/cargo/common_metadata.rs create mode 100644 clippy_lints/src/cargo/feature_name.rs create mode 100644 clippy_lints/src/cargo/mod.rs create mode 100644 clippy_lints/src/cargo/multiple_crate_versions.rs create mode 100644 clippy_lints/src/cargo/wildcard_dependencies.rs delete mode 100644 clippy_lints/src/cargo_common_metadata.rs delete mode 100644 clippy_lints/src/feature_name.rs delete mode 100644 clippy_lints/src/multiple_crate_versions.rs delete mode 100644 clippy_lints/src/wildcard_dependencies.rs diff --git a/clippy_lints/src/cargo/common_metadata.rs b/clippy_lints/src/cargo/common_metadata.rs new file mode 100644 index 0000000000000..e0442dda479d7 --- /dev/null +++ b/clippy_lints/src/cargo/common_metadata.rs @@ -0,0 +1,54 @@ +//! lint on missing cargo common metadata + +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::CARGO_COMMON_METADATA; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: bool) { + for package in &metadata.packages { + // only run the lint if publish is `None` (`publish = true` or skipped entirely) + // or if the vector isn't empty (`publish = ["something"]`) + if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish { + if is_empty_str(&package.description) { + missing_warning(cx, package, "package.description"); + } + + if is_empty_str(&package.license) && is_empty_str(&package.license_file) { + missing_warning(cx, package, "either package.license or package.license_file"); + } + + if is_empty_str(&package.repository) { + missing_warning(cx, package, "package.repository"); + } + + if is_empty_str(&package.readme) { + missing_warning(cx, package, "package.readme"); + } + + if is_empty_vec(&package.keywords) { + missing_warning(cx, package, "package.keywords"); + } + + if is_empty_vec(&package.categories) { + missing_warning(cx, package, "package.categories"); + } + } + } +} + +fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { + let message = format!("package `{}` is missing `{}` metadata", package.name, field); + span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); +} + +fn is_empty_str>(value: &Option) -> bool { + value.as_ref().map_or(true, |s| s.as_ref().is_empty()) +} + +fn is_empty_vec(value: &[String]) -> bool { + // This works because empty iterators return true + value.iter().all(String::is_empty) +} diff --git a/clippy_lints/src/cargo/feature_name.rs b/clippy_lints/src/cargo/feature_name.rs new file mode 100644 index 0000000000000..79a469a4258bb --- /dev/null +++ b/clippy_lints/src/cargo/feature_name.rs @@ -0,0 +1,92 @@ +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::{NEGATIVE_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES}; + +static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"]; +static SUFFIXES: [&str; 2] = ["-support", "_support"]; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + for package in &metadata.packages { + let mut features: Vec<&String> = package.features.keys().collect(); + features.sort(); + for feature in features { + let prefix_opt = { + let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str()); + if i > 0 && feature.starts_with(PREFIXES[i - 1]) { + Some(PREFIXES[i - 1]) + } else { + None + } + }; + if let Some(prefix) = prefix_opt { + lint(cx, feature, prefix, true); + } + + let suffix_opt: Option<&str> = { + let i = SUFFIXES.partition_point(|suffix| { + suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less + }); + if i > 0 && feature.ends_with(SUFFIXES[i - 1]) { + Some(SUFFIXES[i - 1]) + } else { + None + } + }; + if let Some(suffix) = suffix_opt { + lint(cx, feature, suffix, false); + } + } + } +} + +fn is_negative_prefix(s: &str) -> bool { + s.starts_with("no") +} + +fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) { + let is_negative = is_prefix && is_negative_prefix(substring); + span_lint_and_help( + cx, + if is_negative { + NEGATIVE_FEATURE_NAMES + } else { + REDUNDANT_FEATURE_NAMES + }, + DUMMY_SP, + &format!( + "the \"{}\" {} in the feature name \"{}\" is {}", + substring, + if is_prefix { "prefix" } else { "suffix" }, + feature, + if is_negative { "negative" } else { "redundant" } + ), + None, + &format!( + "consider renaming the feature to \"{}\"{}", + if is_prefix { + feature.strip_prefix(substring) + } else { + feature.strip_suffix(substring) + } + .unwrap(), + if is_negative { + ", but make sure the feature adds functionality" + } else { + "" + } + ), + ); +} + +#[test] +fn test_prefixes_sorted() { + let mut sorted_prefixes = PREFIXES; + sorted_prefixes.sort_unstable(); + assert_eq!(PREFIXES, sorted_prefixes); + let mut sorted_suffixes = SUFFIXES; + sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev())); + assert_eq!(SUFFIXES, sorted_suffixes); +} diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs new file mode 100644 index 0000000000000..abe95c6663f70 --- /dev/null +++ b/clippy_lints/src/cargo/mod.rs @@ -0,0 +1,221 @@ +use cargo_metadata::MetadataCommand; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_lint_allowed; +use rustc_hir::hir_id::CRATE_HIR_ID; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::DUMMY_SP; + +mod common_metadata; +mod feature_name; +mod multiple_crate_versions; +mod wildcard_dependencies; + +declare_clippy_lint! { + /// ### What it does + /// Checks to see if all common metadata is defined in + /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata + /// + /// ### Why is this bad? + /// It will be more difficult for users to discover the + /// purpose of the crate, and key information related to it. + /// + /// ### Example + /// ```toml + /// # This `Cargo.toml` is missing a description field: + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// repository = "https://github.com/rust-lang/rust-clippy" + /// readme = "README.md" + /// license = "MIT OR Apache-2.0" + /// keywords = ["clippy", "lint", "plugin"] + /// categories = ["development-tools", "development-tools::cargo-plugins"] + /// ``` + /// + /// Should include a description field like: + /// + /// ```toml + /// # This `Cargo.toml` includes all common metadata + /// [package] + /// name = "clippy" + /// version = "0.0.212" + /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" + /// repository = "https://github.com/rust-lang/rust-clippy" + /// readme = "README.md" + /// license = "MIT OR Apache-2.0" + /// keywords = ["clippy", "lint", "plugin"] + /// categories = ["development-tools", "development-tools::cargo-plugins"] + /// ``` + #[clippy::version = "1.32.0"] + pub CARGO_COMMON_METADATA, + cargo, + "common metadata is defined in `Cargo.toml`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` + /// + /// ### Why is this bad? + /// These prefixes and suffixes have no significant meaning. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with feature name redundancy + /// [features] + /// default = ["use-abc", "with-def", "ghi-support"] + /// use-abc = [] // redundant + /// with-def = [] // redundant + /// ghi-support = [] // redundant + /// ``` + /// + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def", "ghi"] + /// abc = [] + /// def = [] + /// ghi = [] + /// ``` + /// + #[clippy::version = "1.57.0"] + pub REDUNDANT_FEATURE_NAMES, + cargo, + "usage of a redundant feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for negative feature names with prefix `no-` or `not-` + /// + /// ### Why is this bad? + /// Features are supposed to be additive, and negatively-named features violate it. + /// + /// ### Example + /// ```toml + /// # The `Cargo.toml` with negative feature names + /// [features] + /// default = [] + /// no-abc = [] + /// not-def = [] + /// + /// ``` + /// Use instead: + /// ```toml + /// [features] + /// default = ["abc", "def"] + /// abc = [] + /// def = [] + /// + /// ``` + #[clippy::version = "1.57.0"] + pub NEGATIVE_FEATURE_NAMES, + cargo, + "usage of a negative feature name" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks to see if multiple versions of a crate are being + /// used. + /// + /// ### Why is this bad? + /// This bloats the size of targets, and can lead to + /// confusing error messages when structs or traits are used interchangeably + /// between different versions of a crate. + /// + /// ### Known problems + /// Because this can be caused purely by the dependencies + /// themselves, it's not always possible to fix this issue. + /// + /// ### Example + /// ```toml + /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. + /// [dependencies] + /// ctrlc = "=3.1.0" + /// ansi_term = "=0.11.0" + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MULTIPLE_CRATE_VERSIONS, + cargo, + "multiple versions of the same crate being used" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for wildcard dependencies in the `Cargo.toml`. + /// + /// ### Why is this bad? + /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), + /// it is highly unlikely that you work with any possible version of your dependency, + /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. + /// + /// ### Example + /// ```toml + /// [dependencies] + /// regex = "*" + /// ``` + #[clippy::version = "1.32.0"] + pub WILDCARD_DEPENDENCIES, + cargo, + "wildcard dependencies being used" +} + +pub struct Cargo { + pub ignore_publish: bool, +} + +impl_lint_pass!(Cargo => [ + CARGO_COMMON_METADATA, + REDUNDANT_FEATURE_NAMES, + NEGATIVE_FEATURE_NAMES, + MULTIPLE_CRATE_VERSIONS, + WILDCARD_DEPENDENCIES +]); + +impl LateLintPass<'_> for Cargo { + fn check_crate(&mut self, cx: &LateContext<'_>) { + static NO_DEPS_LINTS: &[&Lint] = &[ + CARGO_COMMON_METADATA, + REDUNDANT_FEATURE_NAMES, + NEGATIVE_FEATURE_NAMES, + WILDCARD_DEPENDENCIES, + ]; + static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS]; + + if !NO_DEPS_LINTS + .iter() + .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID)) + { + match MetadataCommand::new().no_deps().exec() { + Ok(metadata) => { + common_metadata::check(cx, &metadata, self.ignore_publish); + feature_name::check(cx, &metadata); + wildcard_dependencies::check(cx, &metadata); + }, + Err(e) => { + for lint in NO_DEPS_LINTS { + span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e)); + } + }, + } + } + + if !WITH_DEPS_LINTS + .iter() + .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID)) + { + match MetadataCommand::new().exec() { + Ok(metadata) => { + multiple_crate_versions::check(cx, &metadata); + }, + Err(e) => { + for lint in WITH_DEPS_LINTS { + span_lint(cx, lint, DUMMY_SP, &format!("could not read cargo metadata: {}", e)); + } + }, + } + } + } +} diff --git a/clippy_lints/src/cargo/multiple_crate_versions.rs b/clippy_lints/src/cargo/multiple_crate_versions.rs new file mode 100644 index 0000000000000..76fd0819a39a5 --- /dev/null +++ b/clippy_lints/src/cargo/multiple_crate_versions.rs @@ -0,0 +1,63 @@ +//! lint on multiple versions of a crate being used + +use cargo_metadata::{DependencyKind, Metadata, Node, Package, PackageId}; +use clippy_utils::diagnostics::span_lint; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_hir::def_id::LOCAL_CRATE; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::MULTIPLE_CRATE_VERSIONS; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + let local_name = cx.tcx.crate_name(LOCAL_CRATE); + let mut packages = metadata.packages.clone(); + packages.sort_by(|a, b| a.name.cmp(&b.name)); + + if_chain! { + if let Some(resolve) = &metadata.resolve; + if let Some(local_id) = packages + .iter() + .find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None }); + then { + for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { + let group: Vec<&Package> = group.collect(); + + if group.len() <= 1 { + continue; + } + + if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { + let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); + versions.sort(); + let versions = versions.iter().join(", "); + + span_lint( + cx, + MULTIPLE_CRATE_VERSIONS, + DUMMY_SP, + &format!("multiple versions for dependency `{}`: {}", name, versions), + ); + } + } + } + } +} + +fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { + fn depends_on(node: &Node, dep_id: &PackageId) -> bool { + node.deps.iter().any(|dep| { + dep.pkg == *dep_id + && dep + .dep_kinds + .iter() + .any(|info| matches!(info.kind, DependencyKind::Normal)) + }) + } + + nodes + .iter() + .filter(|node| depends_on(node, dep_id)) + .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) +} diff --git a/clippy_lints/src/cargo/wildcard_dependencies.rs b/clippy_lints/src/cargo/wildcard_dependencies.rs new file mode 100644 index 0000000000000..7fa6acbf557b1 --- /dev/null +++ b/clippy_lints/src/cargo/wildcard_dependencies.rs @@ -0,0 +1,27 @@ +use cargo_metadata::Metadata; +use clippy_utils::diagnostics::span_lint; +use if_chain::if_chain; +use rustc_lint::LateContext; +use rustc_span::source_map::DUMMY_SP; + +use super::WILDCARD_DEPENDENCIES; + +pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata) { + for dep in &metadata.packages[0].dependencies { + // VersionReq::any() does not work + if_chain! { + if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); + if let Some(ref source) = dep.source; + if !source.starts_with("git"); + if dep.req == wildcard_ver; + then { + span_lint( + cx, + WILDCARD_DEPENDENCIES, + DUMMY_SP, + &format!("wildcard dependency for `{}`", dep.name), + ); + } + } + } +} diff --git a/clippy_lints/src/cargo_common_metadata.rs b/clippy_lints/src/cargo_common_metadata.rs deleted file mode 100644 index 23f79fdc68238..0000000000000 --- a/clippy_lints/src/cargo_common_metadata.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! lint on missing cargo common metadata - -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::DUMMY_SP; - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if all common metadata is defined in - /// `Cargo.toml`. See: https://rust-lang-nursery.github.io/api-guidelines/documentation.html#cargotoml-includes-all-common-metadata-c-metadata - /// - /// ### Why is this bad? - /// It will be more difficult for users to discover the - /// purpose of the crate, and key information related to it. - /// - /// ### Example - /// ```toml - /// # This `Cargo.toml` is missing a description field: - /// [package] - /// name = "clippy" - /// version = "0.0.212" - /// repository = "https://github.com/rust-lang/rust-clippy" - /// readme = "README.md" - /// license = "MIT OR Apache-2.0" - /// keywords = ["clippy", "lint", "plugin"] - /// categories = ["development-tools", "development-tools::cargo-plugins"] - /// ``` - /// - /// Should include a description field like: - /// - /// ```toml - /// # This `Cargo.toml` includes all common metadata - /// [package] - /// name = "clippy" - /// version = "0.0.212" - /// description = "A bunch of helpful lints to avoid common pitfalls in Rust" - /// repository = "https://github.com/rust-lang/rust-clippy" - /// readme = "README.md" - /// license = "MIT OR Apache-2.0" - /// keywords = ["clippy", "lint", "plugin"] - /// categories = ["development-tools", "development-tools::cargo-plugins"] - /// ``` - #[clippy::version = "1.32.0"] - pub CARGO_COMMON_METADATA, - cargo, - "common metadata is defined in `Cargo.toml`" -} - -#[derive(Copy, Clone, Debug)] -pub struct CargoCommonMetadata { - ignore_publish: bool, -} - -impl CargoCommonMetadata { - pub fn new(ignore_publish: bool) -> Self { - Self { ignore_publish } - } -} - -impl_lint_pass!(CargoCommonMetadata => [ - CARGO_COMMON_METADATA -]); - -fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, field: &str) { - let message = format!("package `{}` is missing `{}` metadata", package.name, field); - span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, &message); -} - -fn is_empty_str>(value: &Option) -> bool { - value.as_ref().map_or(true, |s| s.as_ref().is_empty()) -} - -fn is_empty_vec(value: &[String]) -> bool { - // This works because empty iterators return true - value.iter().all(String::is_empty) -} - -impl LateLintPass<'_> for CargoCommonMetadata { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, CARGO_COMMON_METADATA, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, CARGO_COMMON_METADATA, false); - - for package in metadata.packages { - // only run the lint if publish is `None` (`publish = true` or skipped entirely) - // or if the vector isn't empty (`publish = ["something"]`) - if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish { - if is_empty_str(&package.description) { - missing_warning(cx, &package, "package.description"); - } - - if is_empty_str(&package.license) && is_empty_str(&package.license_file) { - missing_warning(cx, &package, "either package.license or package.license_file"); - } - - if is_empty_str(&package.repository) { - missing_warning(cx, &package, "package.repository"); - } - - if is_empty_str(&package.readme) { - missing_warning(cx, &package, "package.readme"); - } - - if is_empty_vec(&package.keywords) { - missing_warning(cx, &package, "package.keywords"); - } - - if is_empty_vec(&package.categories) { - missing_warning(cx, &package, "package.categories"); - } - } - } - } -} diff --git a/clippy_lints/src/feature_name.rs b/clippy_lints/src/feature_name.rs deleted file mode 100644 index dc6bef52ddd9f..0000000000000 --- a/clippy_lints/src/feature_name.rs +++ /dev/null @@ -1,166 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -declare_clippy_lint! { - /// ### What it does - /// Checks for feature names with prefix `use-`, `with-` or suffix `-support` - /// - /// ### Why is this bad? - /// These prefixes and suffixes have no significant meaning. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with feature name redundancy - /// [features] - /// default = ["use-abc", "with-def", "ghi-support"] - /// use-abc = [] // redundant - /// with-def = [] // redundant - /// ghi-support = [] // redundant - /// ``` - /// - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def", "ghi"] - /// abc = [] - /// def = [] - /// ghi = [] - /// ``` - /// - #[clippy::version = "1.57.0"] - pub REDUNDANT_FEATURE_NAMES, - cargo, - "usage of a redundant feature name" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for negative feature names with prefix `no-` or `not-` - /// - /// ### Why is this bad? - /// Features are supposed to be additive, and negatively-named features violate it. - /// - /// ### Example - /// ```toml - /// # The `Cargo.toml` with negative feature names - /// [features] - /// default = [] - /// no-abc = [] - /// not-def = [] - /// - /// ``` - /// Use instead: - /// ```toml - /// [features] - /// default = ["abc", "def"] - /// abc = [] - /// def = [] - /// - /// ``` - #[clippy::version = "1.57.0"] - pub NEGATIVE_FEATURE_NAMES, - cargo, - "usage of a negative feature name" -} - -declare_lint_pass!(FeatureName => [REDUNDANT_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES]); - -static PREFIXES: [&str; 8] = ["no-", "no_", "not-", "not_", "use-", "use_", "with-", "with_"]; -static SUFFIXES: [&str; 2] = ["-support", "_support"]; - -fn is_negative_prefix(s: &str) -> bool { - s.starts_with("no") -} - -fn lint(cx: &LateContext<'_>, feature: &str, substring: &str, is_prefix: bool) { - let is_negative = is_prefix && is_negative_prefix(substring); - span_lint_and_help( - cx, - if is_negative { - NEGATIVE_FEATURE_NAMES - } else { - REDUNDANT_FEATURE_NAMES - }, - DUMMY_SP, - &format!( - "the \"{}\" {} in the feature name \"{}\" is {}", - substring, - if is_prefix { "prefix" } else { "suffix" }, - feature, - if is_negative { "negative" } else { "redundant" } - ), - None, - &format!( - "consider renaming the feature to \"{}\"{}", - if is_prefix { - feature.strip_prefix(substring) - } else { - feature.strip_suffix(substring) - } - .unwrap(), - if is_negative { - ", but make sure the feature adds functionality" - } else { - "" - } - ), - ); -} - -impl LateLintPass<'_> for FeatureName { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, REDUNDANT_FEATURE_NAMES, CRATE_HIR_ID) - && is_lint_allowed(cx, NEGATIVE_FEATURE_NAMES, CRATE_HIR_ID) - { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, REDUNDANT_FEATURE_NAMES, false); - - for package in metadata.packages { - let mut features: Vec<&String> = package.features.keys().collect(); - features.sort(); - for feature in features { - let prefix_opt = { - let i = PREFIXES.partition_point(|prefix| prefix < &feature.as_str()); - if i > 0 && feature.starts_with(PREFIXES[i - 1]) { - Some(PREFIXES[i - 1]) - } else { - None - } - }; - if let Some(prefix) = prefix_opt { - lint(cx, feature, prefix, true); - } - - let suffix_opt: Option<&str> = { - let i = SUFFIXES.partition_point(|suffix| { - suffix.bytes().rev().cmp(feature.bytes().rev()) == std::cmp::Ordering::Less - }); - if i > 0 && feature.ends_with(SUFFIXES[i - 1]) { - Some(SUFFIXES[i - 1]) - } else { - None - } - }; - if let Some(suffix) = suffix_opt { - lint(cx, feature, suffix, false); - } - } - } - } -} - -#[test] -fn test_prefixes_sorted() { - let mut sorted_prefixes = PREFIXES; - sorted_prefixes.sort_unstable(); - assert_eq!(PREFIXES, sorted_prefixes); - let mut sorted_suffixes = SUFFIXES; - sorted_suffixes.sort_by(|a, b| a.bytes().rev().cmp(b.bytes().rev())); - assert_eq!(SUFFIXES, sorted_suffixes); -} diff --git a/clippy_lints/src/lib.register_cargo.rs b/clippy_lints/src/lib.register_cargo.rs index 1809f2cc7d462..c890523fe5aeb 100644 --- a/clippy_lints/src/lib.register_cargo.rs +++ b/clippy_lints/src/lib.register_cargo.rs @@ -3,9 +3,9 @@ // Manual edits will be overwritten. store.register_group(true, "clippy::cargo", Some("clippy_cargo"), vec![ - LintId::of(cargo_common_metadata::CARGO_COMMON_METADATA), - LintId::of(feature_name::NEGATIVE_FEATURE_NAMES), - LintId::of(feature_name::REDUNDANT_FEATURE_NAMES), - LintId::of(multiple_crate_versions::MULTIPLE_CRATE_VERSIONS), - LintId::of(wildcard_dependencies::WILDCARD_DEPENDENCIES), + LintId::of(cargo::CARGO_COMMON_METADATA), + LintId::of(cargo::MULTIPLE_CRATE_VERSIONS), + LintId::of(cargo::NEGATIVE_FEATURE_NAMES), + LintId::of(cargo::REDUNDANT_FEATURE_NAMES), + LintId::of(cargo::WILDCARD_DEPENDENCIES), ]) diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index a80320a578f0e..7a62a99f5ffb9 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -61,7 +61,11 @@ store.register_lints(&[ booleans::NONMINIMAL_BOOL, borrow_as_ptr::BORROW_AS_PTR, bytecount::NAIVE_BYTECOUNT, - cargo_common_metadata::CARGO_COMMON_METADATA, + cargo::CARGO_COMMON_METADATA, + cargo::MULTIPLE_CRATE_VERSIONS, + cargo::NEGATIVE_FEATURE_NAMES, + cargo::REDUNDANT_FEATURE_NAMES, + cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, @@ -139,8 +143,6 @@ store.register_lints(&[ exit::EXIT, explicit_write::EXPLICIT_WRITE, fallible_impl_from::FALLIBLE_IMPL_FROM, - feature_name::NEGATIVE_FEATURE_NAMES, - feature_name::REDUNDANT_FEATURE_NAMES, float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS, float_literal::EXCESSIVE_PRECISION, float_literal::LOSSY_FLOAT_LITERAL, @@ -353,7 +355,6 @@ store.register_lints(&[ module_style::MOD_MODULE_FILES, module_style::SELF_NAMED_MODULE_FILES, modulo_arithmetic::MODULO_ARITHMETIC, - multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, mut_key::MUTABLE_KEY_TYPE, mut_mut::MUT_MUT, mut_mutex_lock::MUT_MUTEX_LOCK, @@ -520,7 +521,6 @@ store.register_lints(&[ vec_init_then_push::VEC_INIT_THEN_PUSH, vec_resize_to_zero::VEC_RESIZE_TO_ZERO, verbose_file_reads::VERBOSE_FILE_READS, - wildcard_dependencies::WILDCARD_DEPENDENCIES, wildcard_imports::ENUM_GLOB_USE, wildcard_imports::WILDCARD_IMPORTS, write::PRINTLN_EMPTY_STRING, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3bd7699792ad7..e78f618735930 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -178,7 +178,7 @@ mod bool_assert_comparison; mod booleans; mod borrow_as_ptr; mod bytecount; -mod cargo_common_metadata; +mod cargo; mod case_sensitive_file_extension_comparisons; mod casts; mod checked_conversions; @@ -220,7 +220,6 @@ mod exhaustive_items; mod exit; mod explicit_write; mod fallible_impl_from; -mod feature_name; mod float_equality_without_abs; mod float_literal; mod floating_point_arithmetic; @@ -290,7 +289,6 @@ mod missing_enforced_import_rename; mod missing_inline; mod module_style; mod modulo_arithmetic; -mod multiple_crate_versions; mod mut_key; mod mut_mut; mod mut_mutex_lock; @@ -399,7 +397,6 @@ mod vec; mod vec_init_then_push; mod vec_resize_to_zero; mod verbose_file_reads; -mod wildcard_dependencies; mod wildcard_imports; mod write; mod zero_div_zero; @@ -724,10 +721,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|| Box::new(create_dir::CreateDir)); store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - let cargo_ignore_publish = conf.cargo_ignore_publish; - store.register_late_pass(move || Box::new(cargo_common_metadata::CargoCommonMetadata::new(cargo_ignore_publish))); - store.register_late_pass(|| Box::new(multiple_crate_versions::MultipleCrateVersions)); - store.register_late_pass(|| Box::new(wildcard_dependencies::WildcardDependencies)); let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions; store.register_early_pass(move || { Box::new(literal_representation::LiteralDigitGrouping::new( @@ -842,7 +835,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(&scripts))); store.register_late_pass(|| Box::new(strlen_on_c_strings::StrlenOnCStrings)); store.register_late_pass(move || Box::new(self_named_constructors::SelfNamedConstructors)); - store.register_late_pass(move || Box::new(feature_name::FeatureName)); store.register_late_pass(move || Box::new(iter_not_returning_iterator::IterNotReturningIterator)); store.register_late_pass(move || Box::new(manual_assert::ManualAssert)); let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send; @@ -864,6 +856,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|| Box::new(dbg_macro::DbgMacro)); + let cargo_ignore_publish = conf.cargo_ignore_publish; + store.register_late_pass(move || { + Box::new(cargo::Cargo { + ignore_publish: cargo_ignore_publish, + }) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/multiple_crate_versions.rs b/clippy_lints/src/multiple_crate_versions.rs deleted file mode 100644 index 1f9db39cf8ca6..0000000000000 --- a/clippy_lints/src/multiple_crate_versions.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! lint on multiple versions of a crate being used - -use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_lint_allowed; -use rustc_hir::def_id::LOCAL_CRATE; -use rustc_hir::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -use cargo_metadata::{DependencyKind, Node, Package, PackageId}; -use if_chain::if_chain; -use itertools::Itertools; - -declare_clippy_lint! { - /// ### What it does - /// Checks to see if multiple versions of a crate are being - /// used. - /// - /// ### Why is this bad? - /// This bloats the size of targets, and can lead to - /// confusing error messages when structs or traits are used interchangeably - /// between different versions of a crate. - /// - /// ### Known problems - /// Because this can be caused purely by the dependencies - /// themselves, it's not always possible to fix this issue. - /// - /// ### Example - /// ```toml - /// # This will pull in both winapi v0.3.x and v0.2.x, triggering a warning. - /// [dependencies] - /// ctrlc = "=3.1.0" - /// ansi_term = "=0.11.0" - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MULTIPLE_CRATE_VERSIONS, - cargo, - "multiple versions of the same crate being used" -} - -declare_lint_pass!(MultipleCrateVersions => [MULTIPLE_CRATE_VERSIONS]); - -impl LateLintPass<'_> for MultipleCrateVersions { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, MULTIPLE_CRATE_VERSIONS, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, MULTIPLE_CRATE_VERSIONS, true); - let local_name = cx.tcx.crate_name(LOCAL_CRATE); - let mut packages = metadata.packages; - packages.sort_by(|a, b| a.name.cmp(&b.name)); - - if_chain! { - if let Some(resolve) = &metadata.resolve; - if let Some(local_id) = packages - .iter() - .find_map(|p| if p.name == local_name.as_str() { Some(&p.id) } else { None }); - then { - for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { - let group: Vec<&Package> = group.collect(); - - if group.len() <= 1 { - continue; - } - - if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { - let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); - versions.sort(); - let versions = versions.iter().join(", "); - - span_lint( - cx, - MULTIPLE_CRATE_VERSIONS, - DUMMY_SP, - &format!("multiple versions for dependency `{}`: {}", name, versions), - ); - } - } - } - } - } -} - -fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { - fn depends_on(node: &Node, dep_id: &PackageId) -> bool { - node.deps.iter().any(|dep| { - dep.pkg == *dep_id - && dep - .dep_kinds - .iter() - .any(|info| matches!(info.kind, DependencyKind::Normal)) - }) - } - - nodes - .iter() - .filter(|node| depends_on(node, dep_id)) - .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) -} diff --git a/clippy_lints/src/wildcard_dependencies.rs b/clippy_lints/src/wildcard_dependencies.rs deleted file mode 100644 index 80d7b8a1b6df5..0000000000000 --- a/clippy_lints/src/wildcard_dependencies.rs +++ /dev/null @@ -1,57 +0,0 @@ -use clippy_utils::{diagnostics::span_lint, is_lint_allowed}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::DUMMY_SP; - -use if_chain::if_chain; - -declare_clippy_lint! { - /// ### What it does - /// Checks for wildcard dependencies in the `Cargo.toml`. - /// - /// ### Why is this bad? - /// [As the edition guide says](https://rust-lang-nursery.github.io/edition-guide/rust-2018/cargo-and-crates-io/crates-io-disallows-wildcard-dependencies.html), - /// it is highly unlikely that you work with any possible version of your dependency, - /// and wildcard dependencies would cause unnecessary breakage in the ecosystem. - /// - /// ### Example - /// ```toml - /// [dependencies] - /// regex = "*" - /// ``` - #[clippy::version = "1.32.0"] - pub WILDCARD_DEPENDENCIES, - cargo, - "wildcard dependencies being used" -} - -declare_lint_pass!(WildcardDependencies => [WILDCARD_DEPENDENCIES]); - -impl LateLintPass<'_> for WildcardDependencies { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if is_lint_allowed(cx, WILDCARD_DEPENDENCIES, CRATE_HIR_ID) { - return; - } - - let metadata = unwrap_cargo_metadata!(cx, WILDCARD_DEPENDENCIES, false); - - for dep in &metadata.packages[0].dependencies { - // VersionReq::any() does not work - if_chain! { - if let Ok(wildcard_ver) = semver::VersionReq::parse("*"); - if let Some(ref source) = dep.source; - if !source.starts_with("git"); - if dep.req == wildcard_ver; - then { - span_lint( - cx, - WILDCARD_DEPENDENCIES, - DUMMY_SP, - &format!("wildcard dependency for `{}`", dep.name), - ); - } - } - } - } -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 42955080c966e..73d91550693de 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2042,24 +2042,6 @@ pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir> expr } -#[macro_export] -macro_rules! unwrap_cargo_metadata { - ($cx: ident, $lint: ident, $deps: expr) => {{ - let mut command = cargo_metadata::MetadataCommand::new(); - if !$deps { - command.no_deps(); - } - - match command.exec() { - Ok(metadata) => metadata, - Err(err) => { - span_lint($cx, $lint, DUMMY_SP, &format!("could not read cargo metadata: {}", err)); - return; - }, - } - }}; -} - pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { if let Res::Def(_, def_id) = path.res { From 78c2e0bfe98bc0490bd97a319104e2bd811c3c91 Mon Sep 17 00:00:00 2001 From: nsunderland1 Date: Sun, 13 Feb 2022 17:07:14 -0800 Subject: [PATCH 11/55] Document `pub` requirement for `new_without_default` lint --- clippy_lints/src/new_without_default.rs | 10 +++++----- tests/ui/new_without_default.rs | 16 ++++++++++++++++ tests/ui/new_without_default.stderr | 8 ++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index f86af7a7bb6ea..47121ad84f2c9 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -13,7 +13,7 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does - /// Checks for types with a `fn new() -> Self` method and no + /// Checks for public types with a `pub fn new() -> Self` method and no /// implementation of /// [`Default`](https://doc.rust-lang.org/std/default/trait.Default.html). /// @@ -24,10 +24,10 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// struct Foo(Bar); + /// pub struct Foo(Bar); /// /// impl Foo { - /// fn new() -> Self { + /// pub fn new() -> Self { /// Foo(Bar::new()) /// } /// } @@ -36,7 +36,7 @@ declare_clippy_lint! { /// To fix the lint, add a `Default` implementation that delegates to `new`: /// /// ```ignore - /// struct Foo(Bar); + /// pub struct Foo(Bar); /// /// impl Default for Foo { /// fn default() -> Self { @@ -47,7 +47,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub NEW_WITHOUT_DEFAULT, style, - "`fn new() -> Self` method without `Default` implementation" + "`pub fn new() -> Self` method without `Default` implementation" } #[derive(Clone, Default)] diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index 4b2e7444dcf63..c76e3b424383e 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -90,6 +90,22 @@ impl Private { } // We don't lint private items } +struct PrivateStruct; + +impl PrivateStruct { + pub fn new() -> PrivateStruct { + unimplemented!() + } // We don't lint public items on private structs +} + +pub struct PrivateItem; + +impl PrivateItem { + fn new() -> PrivateItem { + unimplemented!() + } // We don't lint private items on public structs +} + struct Const; impl Const { diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 14ddb66f2324c..19572dfe8b075 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -51,7 +51,7 @@ LL + } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> $DIR/new_without_default.rs:156:5 + --> $DIR/new_without_default.rs:172:5 | LL | / pub fn new() -> Self { LL | | NewNotEqualToDerive { foo: 1 } @@ -68,7 +68,7 @@ LL + } | error: you should consider adding a `Default` implementation for `FooGenerics` - --> $DIR/new_without_default.rs:164:5 + --> $DIR/new_without_default.rs:180:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) @@ -85,7 +85,7 @@ LL + } | error: you should consider adding a `Default` implementation for `BarGenerics` - --> $DIR/new_without_default.rs:171:5 + --> $DIR/new_without_default.rs:187:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) @@ -102,7 +102,7 @@ LL + } | error: you should consider adding a `Default` implementation for `Foo` - --> $DIR/new_without_default.rs:182:9 + --> $DIR/new_without_default.rs:198:9 | LL | / pub fn new() -> Self { LL | | todo!() From b162b11abc4c5275b2bf2f2f4679f0b6d72be2cb Mon Sep 17 00:00:00 2001 From: James McMurray Date: Tue, 28 Dec 2021 19:27:11 +0100 Subject: [PATCH 12/55] Add recursive_format_impl lint The to_string_in_display lint is renamed to recursive_format_impl A check is added for the use of self formatted with Display or Debug inside any format string in the same impl The to_string_in_display check is kept as is - like in the format_in_format_args lint For now only Display and Debug are checked This could also be extended to other Format traits (Binary, etc.) --- CHANGELOG.md | 6 +- clippy_lints/src/format_args.rs | 26 +- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_correctness.rs | 2 +- clippy_lints/src/lib.register_lints.rs | 2 +- clippy_lints/src/lib.rs | 5 +- clippy_lints/src/recursive_format_impl.rs | 190 +++++++++++ clippy_lints/src/to_string_in_display.rs | 123 ------- clippy_utils/src/macros.rs | 26 ++ clippy_utils/src/paths.rs | 1 + tests/ui/recursive_format_impl.rs | 321 +++++++++++++++++++ tests/ui/recursive_format_impl.stderr | 91 ++++++ tests/ui/rename.fixed | 2 + tests/ui/rename.rs | 2 + tests/ui/rename.stderr | 74 +++-- tests/ui/to_string_in_display.rs | 69 ---- tests/ui/to_string_in_display.stderr | 19 -- 17 files changed, 685 insertions(+), 276 deletions(-) create mode 100644 clippy_lints/src/recursive_format_impl.rs delete mode 100644 clippy_lints/src/to_string_in_display.rs create mode 100644 tests/ui/recursive_format_impl.rs create mode 100644 tests/ui/recursive_format_impl.stderr delete mode 100644 tests/ui/to_string_in_display.rs delete mode 100644 tests/ui/to_string_in_display.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index c9adf77c0d639..c3da1d1f69a3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1438,7 +1438,7 @@ Released 2020-11-19 * [`manual_strip`] [#6038](https://github.com/rust-lang/rust-clippy/pull/6038) * [`map_err_ignore`] [#5998](https://github.com/rust-lang/rust-clippy/pull/5998) * [`rc_buffer`] [#6044](https://github.com/rust-lang/rust-clippy/pull/6044) -* [`to_string_in_display`] [#5831](https://github.com/rust-lang/rust-clippy/pull/5831) +* `to_string_in_display` [#5831](https://github.com/rust-lang/rust-clippy/pull/5831) * `single_char_push_str` [#5881](https://github.com/rust-lang/rust-clippy/pull/5881) ### Moves and Deprecations @@ -1481,7 +1481,7 @@ Released 2020-11-19 [#5949](https://github.com/rust-lang/rust-clippy/pull/5949) * [`doc_markdown`]: allow using "GraphQL" without backticks [#5996](https://github.com/rust-lang/rust-clippy/pull/5996) -* [`to_string_in_display`]: avoid linting when calling `to_string()` on anything that is not `self` +* `to_string_in_display`: avoid linting when calling `to_string()` on anything that is not `self` [#5971](https://github.com/rust-lang/rust-clippy/pull/5971) * [`indexing_slicing`] and [`out_of_bounds_indexing`] treat references to arrays as arrays [#6034](https://github.com/rust-lang/rust-clippy/pull/6034) @@ -3385,6 +3385,7 @@ Released 2018-09-13 [`range_zip_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#range_zip_with_len [`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer [`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex +[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation [`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone [`redundant_closure`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure @@ -3459,7 +3460,6 @@ Released 2018-09-13 [`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments [`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment [`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some -[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args [`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo [`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 17b0749a4a990..ac3708636decf 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::macros::{FormatArgsArg, FormatArgsExpn}; +use clippy_utils::is_diag_trait_item; +use clippy_utils::macros::{is_format_macro, FormatArgsArg, FormatArgsExpn}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_diag_trait_item, match_def_path, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -65,21 +65,6 @@ declare_clippy_lint! { declare_lint_pass!(FormatArgs => [FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS]); -const FORMAT_MACRO_PATHS: &[&[&str]] = &[ - &paths::FORMAT_ARGS_MACRO, - &paths::ASSERT_EQ_MACRO, - &paths::ASSERT_MACRO, - &paths::ASSERT_NE_MACRO, - &paths::EPRINT_MACRO, - &paths::EPRINTLN_MACRO, - &paths::PRINT_MACRO, - &paths::PRINTLN_MACRO, - &paths::WRITE_MACRO, - &paths::WRITELN_MACRO, -]; - -const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro]; - impl<'tcx> LateLintPass<'tcx> for FormatArgs { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if_chain! { @@ -87,12 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { let expr_expn_data = expr.span.ctxt().outer_expn_data(); let outermost_expn_data = outermost_expn_data(expr_expn_data); if let Some(macro_def_id) = outermost_expn_data.macro_def_id; - if FORMAT_MACRO_PATHS - .iter() - .any(|path| match_def_path(cx, macro_def_id, path)) - || FORMAT_MACRO_DIAG_ITEMS - .iter() - .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id)); + if is_format_macro(cx, macro_def_id); if let ExpnKind::Macro(_, name) = outermost_expn_data.kind; if let Some(args) = format_args.args(); then { diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index d93e34e76b492..de0ff5369079b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -241,6 +241,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ranges::MANUAL_RANGE_CONTAINS), LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), + LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), @@ -267,7 +268,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT), LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME), - LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY), LintId::of(transmute::CROSSPOINTER_TRANSMUTE), LintId::of(transmute::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS), LintId::of(transmute::TRANSMUTE_BYTES_TO_STR), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index d013daa8e082a..18fe44282ed0e 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -52,12 +52,12 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ranges::REVERSED_EMPTY_RANGES), + LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(regex::INVALID_REGEX), LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(serde_api::SERDE_API_MISUSE), 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), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 7a62a99f5ffb9..97e22e9a1f9c2 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -416,6 +416,7 @@ store.register_lints(&[ ranges::RANGE_PLUS_ONE, ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, + recursive_format_impl::RECURSIVE_FORMAT_IMPL, redundant_clone::REDUNDANT_CLONE, redundant_closure_call::REDUNDANT_CLOSURE_CALL, redundant_else::REDUNDANT_ELSE, @@ -460,7 +461,6 @@ store.register_lints(&[ tabs_in_doc_comments::TABS_IN_DOC_COMMENTS, temporary_assignment::TEMPORARY_ASSIGNMENT, to_digit_is_some::TO_DIGIT_IS_SOME, - to_string_in_display::TO_STRING_IN_DISPLAY, trailing_empty_array::TRAILING_EMPTY_ARRAY, trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS, trait_bounds::TYPE_REPETITION_IN_BOUNDS, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e78f618735930..93b1d178ac259 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -332,6 +332,7 @@ mod ptr_eq; mod ptr_offset_with_cast; mod question_mark; mod ranges; +mod recursive_format_impl; mod redundant_clone; mod redundant_closure_call; mod redundant_else; @@ -364,7 +365,6 @@ mod swap; mod tabs_in_doc_comments; mod temporary_assignment; mod to_digit_is_some; -mod to_string_in_display; mod trailing_empty_array; mod trait_bounds; mod transmute; @@ -704,7 +704,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic)); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - store.register_late_pass(|| Box::new(to_string_in_display::ToStringInDisplay::new())); + store.register_late_pass(|| Box::new(recursive_format_impl::RecursiveFormatImpl::new())); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); @@ -938,6 +938,7 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) { 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"); + ls.register_renamed("clippy::to_string_in_display", "clippy::recursive_format_impl"); // uplifted lints ls.register_renamed("clippy::invalid_ref", "invalid_value"); diff --git a/clippy_lints/src/recursive_format_impl.rs b/clippy_lints/src/recursive_format_impl.rs new file mode 100644 index 0000000000000..5bb9740749b6c --- /dev/null +++ b/clippy_lints/src/recursive_format_impl.rs @@ -0,0 +1,190 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn}; +use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{sym, symbol::kw, Symbol}; + +#[derive(Clone, Copy)] +enum FormatTrait { + Debug, + Display, +} + +impl FormatTrait { + fn name(self) -> Symbol { + match self { + FormatTrait::Debug => sym::Debug, + FormatTrait::Display => sym::Display, + } + } +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself + /// which uses `self` as a parameter. + /// This is typically done indirectly with the `write!` macro or with `to_string()`. + /// + /// ### Why is this bad? + /// This will lead to infinite recursion and a stack overflow. + /// + /// ### Example + /// + /// ```rust + /// use std::fmt; + /// + /// struct Structure(i32); + /// impl fmt::Display for Structure { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "{}", self.to_string()) + /// } + /// } + /// + /// ``` + /// Use instead: + /// ```rust + /// use std::fmt; + /// + /// struct Structure(i32); + /// impl fmt::Display for Structure { + /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + /// write!(f, "{}", self.0) + /// } + /// } + /// ``` + #[clippy::version = "1.48.0"] + pub RECURSIVE_FORMAT_IMPL, + correctness, + "Format trait method called while implementing the same Format trait" +} + +#[derive(Default)] +pub struct RecursiveFormatImpl { + // Whether we are inside Display or Debug trait impl - None for neither + format_trait_impl: Option, +} + +impl RecursiveFormatImpl { + pub fn new() -> Self { + Self { + format_trait_impl: None, + } + } +} + +impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]); + +impl<'tcx> LateLintPass<'tcx> for RecursiveFormatImpl { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if let Some(format_trait_impl) = is_format_trait_impl(cx, item) { + self.format_trait_impl = Some(format_trait_impl); + } + } + + fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // Assume no nested Impl of Debug and Display within eachother + if is_format_trait_impl(cx, item).is_some() { + self.format_trait_impl = None; + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + match self.format_trait_impl { + Some(FormatTrait::Display) => { + check_to_string_in_display(cx, expr); + check_self_in_format_args(cx, expr, FormatTrait::Display); + }, + Some(FormatTrait::Debug) => { + check_self_in_format_args(cx, expr, FormatTrait::Debug); + }, + None => {}, + } + } +} + +fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) { + if_chain! { + // Get the hir_id of the object we are calling the method on + if let ExprKind::MethodCall(path, [ref self_arg, ..], _) = expr.kind; + // Is the method to_string() ? + if path.ident.name == sym!(to_string); + // Is the method a part of the ToString trait? (i.e. not to_string() implemented + // separately) + if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); + if is_diag_trait_item(cx, expr_def_id, sym::ToString); + // Is the method is called on self + if let ExprKind::Path(QPath::Resolved(_, path)) = self_arg.kind; + if let [segment] = path.segments; + if segment.ident.name == kw::SelfLower; + then { + span_lint( + cx, + RECURSIVE_FORMAT_IMPL, + expr.span, + "using `self.to_string` in `fmt::Display` implementation will cause infinite recursion", + ); + } + } +} + +fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) { + // Check each arg in format calls - do we ever use Display on self (directly or via deref)? + if_chain! { + if let Some(outer_macro) = root_macro_call_first_node(cx, expr); + if let macro_def_id = outer_macro.def_id; + if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn); + if is_format_macro(cx, macro_def_id); + if let Some(args) = format_args.args(); + then { + for arg in args { + if arg.format_trait != impl_trait.name() { + continue; + } + check_format_arg_self(cx, expr, &arg, impl_trait); + } + } + } +} + +fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgsArg<'_>, impl_trait: FormatTrait) { + // Handle multiple dereferencing of references e.g. &&self + // Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl) + // Since the argument to fmt is itself a reference: &self + let reference = peel_ref_operators(cx, arg.value); + let map = cx.tcx.hir(); + // Is the reference self? + let symbol_ident = impl_trait.name().to_ident_string(); + if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { + span_lint( + cx, + RECURSIVE_FORMAT_IMPL, + expr.span, + &format!( + "using `self` as `{}` in `impl {}` will cause infinite recursion", + &symbol_ident, &symbol_ident + ), + ); + } +} + +fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option { + if_chain! { + // Are we at an Impl? + if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind; + if let Some(did) = trait_ref.trait_def_id(); + if let Some(name) = cx.tcx.get_diagnostic_name(did); + then { + // Is Impl for Debug or Display? + match name { + sym::Debug => Some(FormatTrait::Debug), + sym::Display => Some(FormatTrait::Display), + _ => None, + } + } else { + None + } + } +} diff --git a/clippy_lints/src/to_string_in_display.rs b/clippy_lints/src/to_string_in_display.rs deleted file mode 100644 index 03060d78fc5af..0000000000000 --- a/clippy_lints/src/to_string_in_display.rs +++ /dev/null @@ -1,123 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_diag_trait_item, match_def_path, path_to_local_id, paths}; -use if_chain::if_chain; -use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::symbol::sym; - -declare_clippy_lint! { - /// ### What it does - /// Checks for uses of `to_string()` in `Display` traits. - /// - /// ### Why is this bad? - /// Usually `to_string` is implemented indirectly - /// via `Display`. Hence using it while implementing `Display` would - /// lead to infinite recursion. - /// - /// ### Example - /// - /// ```rust - /// use std::fmt; - /// - /// struct Structure(i32); - /// impl fmt::Display for Structure { - /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - /// write!(f, "{}", self.to_string()) - /// } - /// } - /// - /// ``` - /// Use instead: - /// ```rust - /// use std::fmt; - /// - /// struct Structure(i32); - /// impl fmt::Display for Structure { - /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - /// write!(f, "{}", self.0) - /// } - /// } - /// ``` - #[clippy::version = "1.48.0"] - pub TO_STRING_IN_DISPLAY, - correctness, - "`to_string` method used while implementing `Display` trait" -} - -#[derive(Default)] -pub struct ToStringInDisplay { - in_display_impl: bool, - self_hir_id: Option, -} - -impl ToStringInDisplay { - pub fn new() -> Self { - Self { - in_display_impl: false, - self_hir_id: None, - } - } -} - -impl_lint_pass!(ToStringInDisplay => [TO_STRING_IN_DISPLAY]); - -impl LateLintPass<'_> for ToStringInDisplay { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_display_impl(cx, item) { - self.in_display_impl = true; - } - } - - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_display_impl(cx, item) { - self.in_display_impl = false; - self.self_hir_id = None; - } - } - - fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { - if_chain! { - if self.in_display_impl; - if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; - let body = cx.tcx.hir().body(*body_id); - if !body.params.is_empty(); - then { - let self_param = &body.params[0]; - self.self_hir_id = Some(self_param.pat.hir_id); - } - } - } - - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if self.in_display_impl; - if let Some(self_hir_id) = self.self_hir_id; - if let ExprKind::MethodCall(path, [ref self_arg, ..], _) = expr.kind; - if path.ident.name == sym!(to_string); - if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); - if is_diag_trait_item(cx, expr_def_id, sym::ToString); - if path_to_local_id(self_arg, self_hir_id); - then { - span_lint( - cx, - TO_STRING_IN_DISPLAY, - expr.span, - "using `to_string` in `fmt::Display` implementation might lead to infinite recursion", - ); - } - } - } -} - -fn is_display_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { - if_chain! { - if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind; - if let Some(did) = trait_ref.trait_def_id(); - then { - match_def_path(cx, did, &paths::DISPLAY_TRAIT) - } else { - false - } - } -} diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 5a76ac23332d3..256f884ae1994 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -1,6 +1,7 @@ #![allow(clippy::similar_names)] // `expr` and `expn` use crate::visitors::expr_visitor_no_bodies; +use crate::{match_def_path, paths}; use arrayvec::ArrayVec; use if_chain::if_chain; @@ -13,6 +14,31 @@ use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol}; use std::ops::ControlFlow; +const FORMAT_MACRO_PATHS: &[&[&str]] = &[ + &paths::FORMAT_ARGS_MACRO, + &paths::ASSERT_EQ_MACRO, + &paths::ASSERT_MACRO, + &paths::ASSERT_NE_MACRO, + &paths::EPRINT_MACRO, + &paths::EPRINTLN_MACRO, + &paths::PRINT_MACRO, + &paths::PRINTLN_MACRO, + &paths::WRITE_MACRO, + &paths::WRITELN_MACRO, +]; + +const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro]; + +/// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`) +pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { + FORMAT_MACRO_PATHS + .iter() + .any(|path| match_def_path(cx, macro_def_id, path)) + || FORMAT_MACRO_DIAG_ITEMS + .iter() + .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id)) +} + /// A macro call, like `vec![1, 2, 3]`. /// /// Use `tcx.item_name(macro_call.def_id)` to get the macro name. diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index b9c5d28992477..48c0e89e4d2b2 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -33,6 +33,7 @@ pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"]; +pub const DEBUG_TRAIT: [&str; 3] = ["core", "fmt", "Debug"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; /// Preferably use the diagnostic item `sym::deref_method` where possible diff --git a/tests/ui/recursive_format_impl.rs b/tests/ui/recursive_format_impl.rs new file mode 100644 index 0000000000000..9241bf7ed7402 --- /dev/null +++ b/tests/ui/recursive_format_impl.rs @@ -0,0 +1,321 @@ +#![warn(clippy::recursive_format_impl)] +#![allow( + clippy::inherent_to_string_shadow_display, + clippy::to_string_in_format_args, + clippy::deref_addrof +)] + +use std::fmt; + +struct A; +impl A { + fn fmt(&self) { + self.to_string(); + } +} + +trait B { + fn fmt(&self) {} +} + +impl B for A { + fn fmt(&self) { + self.to_string(); + } +} + +impl fmt::Display for A { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +fn fmt(a: A) { + a.to_string(); +} + +struct C; + +impl C { + // Doesn't trigger if to_string defined separately + // i.e. not using ToString trait (from Display) + fn to_string(&self) -> String { + String::from("I am C") + } +} + +impl fmt::Display for C { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +enum D { + E(String), + F, +} + +impl std::fmt::Display for D { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::E(string) => write!(f, "E {}", string.to_string()), + Self::F => write!(f, "F"), + } + } +} + +// Check for use of self as Display, in Display impl +// Triggers on direct use of self +struct G {} + +impl std::fmt::Display for G { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +// Triggers on reference to self +struct H {} + +impl std::fmt::Display for H { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &self) + } +} + +impl std::fmt::Debug for H { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", &self) + } +} + +// Triggers on multiple reference to self +struct H2 {} + +impl std::fmt::Display for H2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &&&self) + } +} + +// Doesn't trigger on correct deref +struct I {} + +impl std::ops::Deref for I { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for I { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &**self) + } +} + +impl std::fmt::Debug for I { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", &**self) + } +} + +// Doesn't trigger on multiple correct deref +struct I2 {} + +impl std::ops::Deref for I2 { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for I2 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", **&&&**self) + } +} + +// Doesn't trigger on multiple correct deref +struct I3 {} + +impl std::ops::Deref for I3 { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for I3 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &&**&&&**self) + } +} + +// Does trigger when deref resolves to self +struct J {} + +impl std::ops::Deref for J { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for J { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &*self) + } +} + +impl std::fmt::Debug for J { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", &*self) + } +} + +struct J2 {} + +impl std::ops::Deref for J2 { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for J2 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", *self) + } +} + +struct J3 {} + +impl std::ops::Deref for J3 { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for J3 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", **&&*self) + } +} + +struct J4 {} + +impl std::ops::Deref for J4 { + type Target = str; + + fn deref(&self) -> &Self::Target { + "test" + } +} + +impl std::fmt::Display for J4 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &&**&&*self) + } +} + +// Doesn't trigger on Debug from Display +struct K {} + +impl std::fmt::Debug for K { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "test") + } +} + +impl std::fmt::Display for K { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +// Doesn't trigger on Display from Debug +struct K2 {} + +impl std::fmt::Debug for K2 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +impl std::fmt::Display for K2 { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "test") + } +} + +// Doesn't trigger on struct fields +struct L { + field1: u32, + field2: i32, +} + +impl std::fmt::Display for L { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{},{}", self.field1, self.field2) + } +} + +impl std::fmt::Debug for L { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:?},{:?}", self.field1, self.field2) + } +} + +// Doesn't trigger on nested enum matching +enum Tree { + Leaf, + Node(Vec), +} + +impl std::fmt::Display for Tree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Tree::Leaf => write!(f, "*"), + Tree::Node(children) => { + write!(f, "(")?; + for child in children.iter() { + write!(f, "{},", child)?; + } + write!(f, ")") + }, + } + } +} + +impl std::fmt::Debug for Tree { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Tree::Leaf => write!(f, "*"), + Tree::Node(children) => { + write!(f, "(")?; + for child in children.iter() { + write!(f, "{:?},", child)?; + } + write!(f, ")") + }, + } + } +} + +fn main() { + let a = A; + a.to_string(); + a.fmt(); + fmt(a); + + let c = C; + c.to_string(); +} diff --git a/tests/ui/recursive_format_impl.stderr b/tests/ui/recursive_format_impl.stderr new file mode 100644 index 0000000000000..6171696ed69d4 --- /dev/null +++ b/tests/ui/recursive_format_impl.stderr @@ -0,0 +1,91 @@ +error: using `self.to_string` in `fmt::Display` implementation will cause infinite recursion + --> $DIR/recursive_format_impl.rs:29:25 + | +LL | write!(f, "{}", self.to_string()) + | ^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::recursive-format-impl` implied by `-D warnings` + +error: unnecessary use of `to_string` + --> $DIR/recursive_format_impl.rs:61:50 + | +LL | Self::E(string) => write!(f, "E {}", string.to_string()), + | ^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::unnecessary-to-owned` implied by `-D warnings` + = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:73:9 + | +LL | write!(f, "{}", self) + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:82:9 + | +LL | write!(f, "{}", &self) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Debug` in `impl Debug` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:88:9 + | +LL | write!(f, "{:?}", &self) + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:97:9 + | +LL | write!(f, "{}", &&&self) + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:171:9 + | +LL | write!(f, "{}", &*self) + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Debug` in `impl Debug` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:177:9 + | +LL | write!(f, "{:?}", &*self) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:193:9 + | +LL | write!(f, "{}", *self) + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:209:9 + | +LL | write!(f, "{}", **&&*self) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: using `self` as `Display` in `impl Display` will cause infinite recursion + --> $DIR/recursive_format_impl.rs:225:9 + | +LL | write!(f, "{}", &&**&&*self) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `write` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 11 previous errors + diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index 8bddec576ed14..24a0c81229198 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -20,6 +20,7 @@ #![allow(clippy::match_result_ok)] #![allow(clippy::disallowed_types)] #![allow(clippy::disallowed_methods)] +#![allow(clippy::recursive_format_impl)] // uplifted lints #![allow(invalid_value)] #![allow(array_into_iter)] @@ -55,6 +56,7 @@ #![warn(clippy::disallowed_types)] #![warn(clippy::disallowed_methods)] #![warn(clippy::needless_borrow)] +#![warn(clippy::recursive_format_impl)] // uplifted lints #![warn(invalid_value)] #![warn(array_into_iter)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index d2010d71d2c11..ea64234c680d3 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -20,6 +20,7 @@ #![allow(clippy::match_result_ok)] #![allow(clippy::disallowed_types)] #![allow(clippy::disallowed_methods)] +#![allow(clippy::recursive_format_impl)] // uplifted lints #![allow(invalid_value)] #![allow(array_into_iter)] @@ -55,6 +56,7 @@ #![warn(clippy::disallowed_type)] #![warn(clippy::disallowed_method)] #![warn(clippy::ref_in_deref)] +#![warn(clippy::to_string_in_display)] // 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 45cb8b786f5f3..8b132a7838470 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> $DIR/rename.rs:34:9 + --> $DIR/rename.rs:35:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` @@ -7,196 +7,202 @@ LL | #![warn(clippy::stutter)] = note: `-D renamed-and-removed-lints` implied by `-D warnings` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> $DIR/rename.rs:35:9 + --> $DIR/rename.rs:36:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> $DIR/rename.rs:36:9 + --> $DIR/rename.rs:37:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> $DIR/rename.rs:37:9 + --> $DIR/rename.rs:38:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> $DIR/rename.rs:38:9 + --> $DIR/rename.rs:39:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> $DIR/rename.rs:39:9 + --> $DIR/rename.rs:40:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:40:9 + --> $DIR/rename.rs:41:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_if_conditions` - --> $DIR/rename.rs:41:9 + --> $DIR/rename.rs:42:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_if_conditions` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:42:9 + --> $DIR/rename.rs:43:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:43:9 + --> $DIR/rename.rs:44:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> $DIR/rename.rs:44:9 + --> $DIR/rename.rs:45:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:45:9 + --> $DIR/rename.rs:46:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> $DIR/rename.rs:46:9 + --> $DIR/rename.rs:47:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:47:9 + --> $DIR/rename.rs:48:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> $DIR/rename.rs:48:9 + --> $DIR/rename.rs:49:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::for_loop_over_option` has been renamed to `clippy::for_loops_over_fallibles` - --> $DIR/rename.rs:49:9 + --> $DIR/rename.rs:50:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `clippy::for_loops_over_fallibles` - --> $DIR/rename.rs:50:9 + --> $DIR/rename.rs:51:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::for_loops_over_fallibles` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> $DIR/rename.rs:51:9 + --> $DIR/rename.rs:52:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> $DIR/rename.rs:52:9 + --> $DIR/rename.rs:53:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> $DIR/rename.rs:53:9 + --> $DIR/rename.rs:54:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> $DIR/rename.rs:54:9 + --> $DIR/rename.rs:55:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> $DIR/rename.rs:55:9 + --> $DIR/rename.rs:56:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> $DIR/rename.rs:56:9 + --> $DIR/rename.rs:57:9 | 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 + --> $DIR/rename.rs:58: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` +error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` --> $DIR/rename.rs:59:9 | +LL | #![warn(clippy::to_string_in_display)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` + +error: lint `clippy::invalid_ref` has been renamed to `invalid_value` + --> $DIR/rename.rs:61: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:60:9 + --> $DIR/rename.rs:62: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:61:9 + --> $DIR/rename.rs:63: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:62:9 + --> $DIR/rename.rs:64: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:63:9 + --> $DIR/rename.rs:65: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:64:9 + --> $DIR/rename.rs:66: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:65:9 + --> $DIR/rename.rs:67: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:66:9 + --> $DIR/rename.rs:68: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:67:9 + --> $DIR/rename.rs:69:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` -error: aborting due to 33 previous errors +error: aborting due to 34 previous errors diff --git a/tests/ui/to_string_in_display.rs b/tests/ui/to_string_in_display.rs deleted file mode 100644 index 3ccdcd1117b5a..0000000000000 --- a/tests/ui/to_string_in_display.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![warn(clippy::to_string_in_display)] -#![allow(clippy::inherent_to_string_shadow_display, clippy::to_string_in_format_args)] - -use std::fmt; - -struct A; -impl A { - fn fmt(&self) { - self.to_string(); - } -} - -trait B { - fn fmt(&self) {} -} - -impl B for A { - fn fmt(&self) { - self.to_string(); - } -} - -impl fmt::Display for A { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.to_string()) - } -} - -fn fmt(a: A) { - a.to_string(); -} - -struct C; - -impl C { - fn to_string(&self) -> String { - String::from("I am C") - } -} - -impl fmt::Display for C { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.to_string()) - } -} - -enum D { - E(String), - F, -} - -impl std::fmt::Display for D { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self { - Self::E(string) => write!(f, "E {}", string.to_string()), - Self::F => write!(f, "F"), - } - } -} - -fn main() { - let a = A; - a.to_string(); - a.fmt(); - fmt(a); - - let c = C; - c.to_string(); -} diff --git a/tests/ui/to_string_in_display.stderr b/tests/ui/to_string_in_display.stderr deleted file mode 100644 index 80189ca1f0aee..0000000000000 --- a/tests/ui/to_string_in_display.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: using `to_string` in `fmt::Display` implementation might lead to infinite recursion - --> $DIR/to_string_in_display.rs:25:25 - | -LL | write!(f, "{}", self.to_string()) - | ^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::to-string-in-display` implied by `-D warnings` - -error: unnecessary use of `to_string` - --> $DIR/to_string_in_display.rs:55:50 - | -LL | Self::E(string) => write!(f, "E {}", string.to_string()), - | ^^^^^^^^^^^^^^^^^^ - | - = note: `-D clippy::unnecessary-to-owned` implied by `-D warnings` - = note: this error originates in the macro `$crate::format_args` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 2 previous errors - From 5fa961b9519d68f0378f4f4f2331e4eb940cfca1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 25 Jan 2022 14:13:38 +1100 Subject: [PATCH 13/55] Overhaul `TyS` and `Ty`. Specifically, change `Ty` from this: ``` pub type Ty<'tcx> = &'tcx TyS<'tcx>; ``` to this ``` pub struct Ty<'tcx>(Interned<'tcx, TyS<'tcx>>); ``` There are two benefits to this. - It's now a first class type, so we can define methods on it. This means we can move a lot of methods away from `TyS`, leaving `TyS` as a barely-used type, which is appropriate given that it's not meant to be used directly. - The uniqueness requirement is now explicit, via the `Interned` type. E.g. the pointer-based `Eq` and `Hash` comes from `Interned`, rather than via `TyS`, which wasn't obvious at all. Much of this commit is boring churn. The interesting changes are in these files: - compiler/rustc_middle/src/arena.rs - compiler/rustc_middle/src/mir/visit.rs - compiler/rustc_middle/src/ty/context.rs - compiler/rustc_middle/src/ty/mod.rs Specifically: - Most mentions of `TyS` are removed. It's very much a dumb struct now; `Ty` has all the smarts. - `TyS` now has `crate` visibility instead of `pub`. - `TyS::make_for_test` is removed in favour of the static `BOOL_TY`, which just works better with the new structure. - The `Eq`/`Ord`/`Hash` impls are removed from `TyS`. `Interned`s impls of `Eq`/`Hash` now suffice. `Ord` is now partly on `Interned` (pointer-based, for the `Equal` case) and partly on `TyS` (contents-based, for the other cases). - There are many tedious sigil adjustments, i.e. adding or removing `*` or `&`. They seem to be unavoidable. --- .../case_sensitive_file_extension_comparisons.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 6 +++--- clippy_lints/src/eq_op.rs | 8 ++++---- clippy_lints/src/format_args.rs | 2 +- clippy_lints/src/index_refutable_slice.rs | 2 +- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/large_stack_arrays.rs | 2 +- clippy_lints/src/len_zero.rs | 2 +- clippy_lints/src/loops/explicit_counter_loop.rs | 4 ++-- clippy_lints/src/loops/manual_memcpy.rs | 4 ++-- clippy_lints/src/loops/needless_collect.rs | 6 +++--- clippy_lints/src/loops/utils.rs | 2 +- clippy_lints/src/map_clone.rs | 2 +- .../src/matches/redundant_pattern_match.rs | 2 +- clippy_lints/src/matches/single_match.rs | 6 +++--- .../src/methods/cloned_instead_of_copied.rs | 2 +- .../src/methods/iter_overeager_cloned.rs | 2 +- clippy_lints/src/methods/manual_str_repeat.rs | 6 +++--- clippy_lints/src/methods/mod.rs | 6 +++--- clippy_lints/src/methods/unnecessary_to_owned.rs | 4 ++-- clippy_lints/src/methods/utils.rs | 4 ++-- .../src/methods/wrong_self_convention.rs | 8 ++++---- clippy_lints/src/methods/zst_offset.rs | 2 +- clippy_lints/src/modulo_arithmetic.rs | 4 ++-- clippy_lints/src/mut_key.rs | 2 +- clippy_lints/src/mut_mutex_lock.rs | 2 +- clippy_lints/src/non_send_fields_in_send_ty.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 4 ++-- clippy_lints/src/size_of_in_element_count.rs | 2 +- .../src/transmute/transmute_ptr_to_ref.rs | 2 +- .../src/transmute/transmute_ref_to_ref.rs | 4 ++-- .../src/transmute/transmute_undefined_repr.rs | 16 ++++++++-------- clippy_lints/src/transmute/useless_transmute.rs | 2 +- clippy_lints/src/unit_return_expecting_ord.rs | 3 ++- clippy_utils/src/lib.rs | 4 ++-- clippy_utils/src/qualify_min_const_fn.rs | 2 +- clippy_utils/src/ty.rs | 16 ++++++++-------- doc/common_tools_writing_lints.md | 4 ++-- 38 files changed, 78 insertions(+), 77 deletions(-) diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs index e71f110820c0b..7637666d059ef 100644 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -47,7 +47,7 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: & then { let mut ty = ctx.typeck_results().expr_ty(obj); ty = match ty.kind() { - ty::Ref(_, ty, ..) => ty, + ty::Ref(_, ty, ..) => *ty, _ => ty }; diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index fb201d2c012b1..b80d55dd192a1 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -123,7 +123,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { if let Some(fn_sig) = fn_sig_opt(self.cx, func.hir_id) { for (expr, bound) in iter::zip(*args, fn_sig.skip_binder().inputs()) { // Push found arg type, then visit arg. - self.ty_bounds.push(TyBound::Ty(bound)); + self.ty_bounds.push(TyBound::Ty(*bound)); self.visit_expr(expr); self.ty_bounds.pop(); } @@ -135,7 +135,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id) { let fn_sig = self.cx.tcx.fn_sig(def_id).skip_binder(); for (expr, bound) in iter::zip(*args, fn_sig.inputs()) { - self.ty_bounds.push(TyBound::Ty(bound)); + self.ty_bounds.push(TyBound::Ty(*bound)); self.visit_expr(expr); self.ty_bounds.pop(); } @@ -210,7 +210,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { fn fn_sig_opt<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option> { let node_ty = cx.typeck_results().node_type_opt(hir_id)?; - // We can't use `TyS::fn_sig` because it automatically performs substs, this may result in FNs. + // We can't use `Ty::fn_sig` because it automatically performs substs, this may result in FNs. match node_ty.kind() { ty::FnDef(def_id, _) => Some(cx.tcx.fn_sig(*def_id)), ty::FnPtr(fn_sig) => Some(*fn_sig), diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index 24d7613e6f8ca..ea547793b1ea2 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -7,10 +7,10 @@ use clippy_utils::{ast_utils::is_useless_with_eq_exprs, eq_expr_value, is_in_tes use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{ - def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, Ty, TyKind, + def::Res, def_id::DefId, BinOpKind, BorrowKind, Expr, ExprKind, GenericArg, ItemKind, QPath, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -279,7 +279,7 @@ 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>)> { +fn in_impl<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, bin_op: DefId) -> Option<(&'tcx rustc_hir::Ty<'tcx>, &'tcx rustc_hir::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()); @@ -301,7 +301,7 @@ fn in_impl<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, bin_op: DefId) -> Op } } -fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: &TyS<'_>, hir_ty: &Ty<'_>) -> bool { +fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool { if_chain! { if let ty::Adt(adt_def, _) = middle_ty.kind(); if let Some(local_did) = adt_def.did.as_local(); diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 17b0749a4a990..503aac8ccd026 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -211,7 +211,7 @@ where if overloaded_deref.is_some() { n_needed = n_total; } - ty = target; + ty = *target; } else { return (n_needed, ty); } diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 2a4bcd773c684..6b62748ffef2e 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -118,7 +118,7 @@ fn find_slice_values(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> FxIndexMap LateLintPass<'tcx> for LargeConstArrays { if let ty::Array(element_type, cst) = ty.kind(); if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); - if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; then { diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 1cc2c28c04ad4..b9e246290ff79 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -45,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind(); if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); - if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes()); + if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; then { span_lint_and_help( diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 3418d276c5354..35d10d53112ec 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -294,7 +294,7 @@ impl LenOutput<'_> { /// Checks if the given signature matches the expectations for `is_empty` fn check_is_empty_sig(sig: FnSig<'_>, self_kind: ImplicitSelfKind, len_output: LenOutput<'_>) -> bool { match &**sig.inputs_and_output { - [arg, res] if len_output.matches_is_empty_output(res) => { + [arg, res] if len_output.matches_is_empty_output(*res) => { matches!( (arg.kind(), self_kind), (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index e0150990cfe5f..fc50e8addccec 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr}; use rustc_hir::{Expr, Pat}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, UintTy}; +use rustc_middle::ty::{self, Ty, UintTy}; // To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be // incremented exactly once in the loop body, and initialized to zero @@ -36,7 +36,7 @@ pub(super) fn check<'tcx>( then { let mut applicability = Applicability::MachineApplicable; - let int_name = match ty.map(ty::TyS::kind) { + let int_name = match ty.map(Ty::kind) { // usize or inferred Some(ty::Uint(UintTy::Usize)) | None => { span_lint_and_sugg( diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index ef0221639aa94..f6ef87264c0a6 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -335,8 +335,8 @@ struct Start<'hir> { fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { match ty.kind() { ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did) => Some(subs.type_at(0)), - ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, subty), - ty::Slice(ty) | ty::Array(ty, _) => Some(ty), + ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, *subty), + ty::Slice(ty) | ty::Array(ty, _) => Some(*ty), _ => None, } } diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index f57dcc2f5c453..06190850bb003 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -12,7 +12,7 @@ use rustc_hir::{Block, Expr, ExprKind, HirId, HirIdSet, Local, Mutability, Node, use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::subst::GenericArgKind; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_span::sym; use rustc_span::{MultiSpan, Span}; @@ -334,8 +334,8 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>( } } -fn get_captured_ids(cx: &LateContext<'_>, ty: &'_ TyS<'_>) -> HirIdSet { - fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: &'_ TyS<'_>, set: &mut HirIdSet) { +fn get_captured_ids(cx: &LateContext<'_>, ty: Ty<'_>) -> HirIdSet { + fn get_captured_ids_recursive(cx: &LateContext<'_>, ty: Ty<'_>, set: &mut HirIdSet) { match ty.kind() { ty::Adt(_, generics) => { for generic in *generics { diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index b6c746d3e3971..772d251b620a8 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -334,7 +334,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic // (&mut x).into_iter() ==> x.iter_mut() 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() => { + ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, *inner_ty).is_some() => { let method_name = match mutbl { Mutability::Mut => "iter_mut", Mutability::Not => "iter", diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index 3f8eeb736fbd2..e233300e26ab8 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -100,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { let obj_ty = cx.typeck_results().expr_ty(obj); if let ty::Ref(_, ty, mutability) = obj_ty.kind() { if matches!(mutability, Mutability::Not) { - let copy = is_copy(cx, ty); + let copy = is_copy(cx, *ty); self.lint_explicit_closure(cx, e.span, args[0].span, copy); } } else { diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 61c5fa0872f6a..e195fddefaba3 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -59,7 +59,7 @@ fn type_needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, see // 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::Array(ty, _) => type_needs_ordered_drop_inner(cx, *ty, seen), ty::Adt(adt, subs) => adt .all_fields() .map(|f| f.ty(cx.tcx, subs)) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 6ba279eaf1224..0c4cb45d147ca 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -8,7 +8,7 @@ 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 rustc_middle::ty::{self, Ty}; use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; @@ -162,10 +162,10 @@ fn check_opt_like<'a>( return; } - let in_candidate_enum = |path_info: &(String, &TyS<'_>)| -> bool { + let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool { let (path, ty) = path_info; for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, ty, ty_path) { + if path == pat_path && match_type(cx, *ty, ty_path) { return true; } } diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints/src/methods/cloned_instead_of_copied.rs index 6fe69b8f01f9b..67a585edc2550 100644 --- a/clippy_lints/src/methods/cloned_instead_of_copied.rs +++ b/clippy_lints/src/methods/cloned_instead_of_copied.rs @@ -30,7 +30,7 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, }; match inner_ty.kind() { // &T where T: Copy - ty::Ref(_, ty, _) if is_copy(cx, ty) => {}, + ty::Ref(_, ty, _) if is_copy(cx, *ty) => {}, _ => return, }; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index ca33bfc643da8..b93f1399eaeed 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -26,7 +26,7 @@ pub(super) fn check<'tcx>( }; match inner_ty.kind() { - ty::Ref(_, ty, _) if !is_copy(cx, ty) => {}, + ty::Ref(_, ty, _) if !is_copy(cx, *ty) => {}, _ => return, }; diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index d74c910b67671..68a75667914aa 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints/src/methods/manual_str_repeat.rs @@ -8,7 +8,7 @@ use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, TyS}; +use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; use std::borrow::Cow; @@ -37,8 +37,8 @@ fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option { } else { let ty = cx.typeck_results().expr_ty(e); if is_type_diagnostic_item(cx, ty, sym::String) - || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, TyS::is_str)) - || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, TyS::is_str)) + || (is_type_lang_item(cx, ty, LangItem::OwnedBox) && get_ty_param(ty).map_or(false, Ty::is_str)) + || (match_type(cx, ty, &paths::COW) && get_ty_param(ty).map_or(false, Ty::is_str)) { Some(RepeatKind::String) } else { diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index c2202cb1e5770..3021a40fae142 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2106,7 +2106,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let method_sig = cx.tcx.fn_sig(impl_item.def_id); let method_sig = cx.tcx.erase_late_bound_regions(method_sig); - let first_arg_ty = &method_sig.inputs().iter().next(); + let first_arg_ty = method_sig.inputs().iter().next(); // check conventions w.r.t. conversion method names and predicates if let Some(first_arg_ty) = first_arg_ty; @@ -2119,7 +2119,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if name == method_config.method_name && sig.decl.inputs.len() == method_config.param_count && method_config.output_type.matches(&sig.decl.output) && - method_config.self_kind.matches(cx, self_ty, first_arg_ty) && + method_config.self_kind.matches(cx, self_ty, *first_arg_ty) && fn_header_equals(method_config.fn_header, sig.header) && method_config.lifetime_param_cond(impl_item) { @@ -2151,7 +2151,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { cx, name, self_ty, - first_arg_ty, + *first_arg_ty, first_arg.pat.span, implements_trait, false diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index b67bfb6597b04..7916fb8e3b45c 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -105,7 +105,7 @@ fn check_addr_of_expr( if is_copy(cx, receiver_ty) || is_cow_into_owned(cx, method_name, method_def_id); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - let (target_ty, n_target_refs) = peel_mid_ty_refs(target_ty); + let (target_ty, n_target_refs) = peel_mid_ty_refs(*target_ty); let (receiver_ty, n_receiver_refs) = peel_mid_ty_refs(receiver_ty); if receiver_ty == target_ty && n_target_refs >= n_receiver_refs { span_lint_and_sugg( @@ -228,7 +228,7 @@ fn check_other_call_arg<'tcx>( let fn_sig = cx.tcx.fn_sig(callee_def_id).skip_binder(); if let Some(i) = call_args.iter().position(|arg| arg.hir_id == maybe_arg.hir_id); if let Some(input) = fn_sig.inputs().get(i); - let (input, n_refs) = peel_mid_ty_refs(input); + let (input, n_refs) = peel_mid_ty_refs(*input); if let (trait_predicates, projection_predicates) = get_input_traits_and_projections(cx, callee_def_id, input); if let Some(sized_def_id) = cx.tcx.lang_items().sized_trait(); if let [trait_predicate] = trait_predicates diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index c4cf994aacaa9..63c3273bd6816 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -19,7 +19,7 @@ pub(super) fn derefs_to_slice<'tcx>( ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec), ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(), - ty::Ref(_, inner, _) => may_slice(cx, inner), + ty::Ref(_, inner, _) => may_slice(cx, *inner), _ => false, } } @@ -35,7 +35,7 @@ pub(super) fn derefs_to_slice<'tcx>( ty::Slice(_) => Some(expr), ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr), ty::Ref(_, inner, _) => { - if may_slice(cx, inner) { + if may_slice(cx, *inner) { Some(expr) } else { None diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index a2e09e5ecec1f..aecfea9c141cf 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -2,7 +2,7 @@ use crate::methods::SelfKind; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::is_copy; use rustc_lint::LateContext; -use rustc_middle::ty::TyS; +use rustc_middle::ty::Ty; use rustc_span::source_map::Span; use std::fmt; @@ -41,7 +41,7 @@ impl Convention { fn check<'tcx>( &self, cx: &LateContext<'tcx>, - self_ty: &'tcx TyS<'tcx>, + self_ty: Ty<'tcx>, other: &str, implements_trait: bool, is_trait_item: bool, @@ -84,8 +84,8 @@ impl fmt::Display for Convention { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, item_name: &str, - self_ty: &'tcx TyS<'tcx>, - first_arg_ty: &'tcx TyS<'tcx>, + self_ty: Ty<'tcx>, + first_arg_ty: Ty<'tcx>, first_arg_span: Span, implements_trait: bool, is_trait_item: bool, diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs index 866cf616679c2..e9f268da69156 100644 --- a/clippy_lints/src/methods/zst_offset.rs +++ b/clippy_lints/src/methods/zst_offset.rs @@ -9,7 +9,7 @@ use super::ZST_OFFSET; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { if_chain! { if let ty::RawPtr(ty::TypeAndMut { ty, .. }) = cx.typeck_results().expr_ty(recv).kind(); - if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(*ty)); if layout.is_zst(); then { span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); diff --git a/clippy_lints/src/modulo_arithmetic.rs b/clippy_lints/src/modulo_arithmetic.rs index d182a7d52497d..195b2e5c2ee0a 100644 --- a/clippy_lints/src/modulo_arithmetic.rs +++ b/clippy_lints/src/modulo_arithmetic.rs @@ -4,7 +4,7 @@ use clippy_utils::sext; use if_chain::if_chain; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use std::fmt::Display; @@ -77,7 +77,7 @@ fn floating_point_operand_info>(f: &T) -> Op } } -fn might_have_negative_value(t: &ty::TyS<'_>) -> bool { +fn might_have_negative_value(t: Ty<'_>) -> bool { t.is_signed() || t.is_floating_point() } diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 1bdd805f65854..b4e29101b3961 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -113,7 +113,7 @@ fn check_sig<'tcx>(cx: &LateContext<'tcx>, item_hir_id: hir::HirId, decl: &hir:: let fn_def_id = cx.tcx.hir().local_def_id(item_hir_id); let fn_sig = cx.tcx.fn_sig(fn_def_id); for (hir_ty, ty) in iter::zip(decl.inputs, fn_sig.inputs().skip_binder()) { - check_ty(cx, hir_ty.span, ty); + check_ty(cx, hir_ty.span, *ty); } check_ty(cx, decl.output.span(), cx.tcx.erase_late_bound_regions(fn_sig.output())); } diff --git a/clippy_lints/src/mut_mutex_lock.rs b/clippy_lints/src/mut_mutex_lock.rs index 7871be41d6294..b7f981faa2d42 100644 --- a/clippy_lints/src/mut_mutex_lock.rs +++ b/clippy_lints/src/mut_mutex_lock.rs @@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for MutMutexLock { if path.ident.name == sym!(lock); let ty = cx.typeck_results().expr_ty(self_arg); if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); - if is_type_diagnostic_item(cx, inner_ty, sym::Mutex); + if is_type_diagnostic_item(cx, *inner_ty, sym::Mutex); then { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index ab1559c85d8b1..f4de999a9281a 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -205,7 +205,7 @@ fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'t ty::Tuple(_) => ty .tuple_fields() .all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)), - ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait), + ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, *ty, send_trait), ty::Adt(_, substs) => { if contains_pointer_like(cx, ty) { // descends only if ADT contains any raw pointers diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 3092ab8392a72..d59249d7f13d3 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -167,8 +167,8 @@ impl<'tcx> PassByRefOrValue { if_chain! { if !output_lts.contains(input_lt); - if is_copy(cx, ty); - if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if is_copy(cx, *ty); + if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes()); if size <= self.ref_min_size; if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind; then { diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index 971729e5c54b3..f3515ea3c2dde 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -116,7 +116,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) = cx.typeck_results().expr_ty(ptr_self).kind(); then { - return Some((pointee_ty, count)); + return Some((*pointee_ty, count)); } }; None diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index 5699f8e92cfca..f3653199b3758 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -38,7 +38,7 @@ pub(super) fn check<'tcx>( let arg = if from_ptr_ty.ty == *to_ref_ty { arg } else { - arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty))) + arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, *to_ref_ty))) }; diag.span_suggestion( diff --git a/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/clippy_lints/src/transmute/transmute_ref_to_ref.rs index fdef8bac7f9b0..7570bc2a7a8f0 100644 --- a/clippy_lints/src/transmute/transmute_ref_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -56,10 +56,10 @@ pub(super) fn check<'tcx>( "transmute from a reference to a reference", |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let ty_from_and_mut = ty::TypeAndMut { - ty: ty_from, + ty: *ty_from, mutbl: *from_mutbl }; - let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: *to_mutbl }; + let ty_to_and_mut = ty::TypeAndMut { ty: *ty_to, mutbl: *to_mutbl }; let sugg_paren = arg .as_ty(cx.tcx.mk_ptr(ty_from_and_mut)) .as_ty(cx.tcx.mk_ptr(ty_to_and_mut)); diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 030d2c2378488..9ed5952a109a5 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -200,27 +200,27 @@ fn reduce_refs<'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, .. }), + &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, .. }), _) + (&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, .. })) + (_, &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, .. }), _) => { + (&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, .. })) => { + (_, &ty::Ref(_, to_ty, _) | &ty::RawPtr(TypeAndMut { ty: to_ty, .. })) => { ReducedTys::ToPtr { from_ty, to_ty } }, _ => ReducedTys::Other { from_ty, to_ty }, @@ -247,7 +247,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> }, 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 { + 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)) { @@ -265,7 +265,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .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 { + 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)) { diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index 998f97eb5d8c6..3cc3d40a143dc 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -34,7 +34,7 @@ pub(super) fn check<'tcx>( |diag| { if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { let rty_and_mut = ty::TypeAndMut { - ty: rty, + ty: *rty, mutbl: *rty_mutbl, }; diff --git a/clippy_lints/src/unit_return_expecting_ord.rs b/clippy_lints/src/unit_return_expecting_ord.rs index eee1229e1ef03..7c39a08a336b6 100644 --- a/clippy_lints/src/unit_return_expecting_ord.rs +++ b/clippy_lints/src/unit_return_expecting_ord.rs @@ -84,7 +84,8 @@ fn get_args_to_check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Ve let partial_ord_preds = get_trait_predicates_for_trait_id(cx, generics, cx.tcx.lang_items().partial_ord_trait()); // Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error - // The trait `rustc::ty::TypeFoldable<'_>` is not implemented for `&[&rustc::ty::TyS<'_>]` + // The trait `rustc::ty::TypeFoldable<'_>` is not implemented for + // `&[rustc_middle::ty::Ty<'_>]` let inputs_output = cx.tcx.erase_late_bound_regions(fn_sig.inputs_and_output()); inputs_output .iter() diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f775cdd3bc280..4bb401273c400 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1911,10 +1911,10 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option is_recursively_primitive_type(element_type), + rustc_ty::Slice(element_type) => is_recursively_primitive_type(*element_type), rustc_ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), &rustc_ty::Slice(_)) => { if let rustc_ty::Slice(element_type) = inner_ty.kind() { - is_recursively_primitive_type(element_type) + is_recursively_primitive_type(*element_type) } else { unreachable!() } diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 7512039a480bb..c039fec955db9 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -149,7 +149,7 @@ fn check_rvalue<'tcx>( Rvalue::Cast(CastKind::Misc, operand, cast_ty) => { use rustc_middle::ty::cast::CastTy; let cast_in = CastTy::from_ty(operand.ty(body, tcx)).expect("bad input type for cast"); - let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast"); + let cast_out = CastTy::from_ty(*cast_ty).expect("bad output type for cast"); match (cast_in, cast_out) { (CastTy::Ptr(_) | CastTy::FnPtr, CastTy::Int(_)) => { Err((span, "casting pointers to ints is unstable in const fn".into())) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 958e6d1ec4615..b44899e6bd587 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -103,7 +103,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option< ]; let ty_to_check = match probably_ref_ty.kind() { - ty::Ref(_, ty_to_check, _) => ty_to_check, + ty::Ref(_, ty_to_check, _) => *ty_to_check, _ => probably_ref_ty, }; @@ -209,7 +209,7 @@ fn is_normalizable_helper<'tcx>( ty: Ty<'tcx>, cache: &mut FxHashMap, bool>, ) -> bool { - if let Some(&cached_result) = cache.get(ty) { + if let Some(&cached_result) = cache.get(&ty) { return cached_result; } // prevent recursive loops, false-negative is better than endless loop leading to stack overflow @@ -252,7 +252,7 @@ pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { match ty.kind() { ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, - ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), + ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(*inner_type), ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type), _ => false, } @@ -318,7 +318,7 @@ pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool { pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) { if let ty::Ref(_, ty, _) = ty.kind() { - peel(ty, count + 1) + peel(*ty, count + 1) } else { (ty, count) } @@ -331,8 +331,8 @@ pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { fn f(ty: Ty<'_>, count: usize, mutability: Mutability) -> (Ty<'_>, usize, Mutability) { match ty.kind() { - ty::Ref(_, ty, Mutability::Mut) => f(ty, count + 1, mutability), - ty::Ref(_, ty, Mutability::Not) => f(ty, count + 1, Mutability::Not), + ty::Ref(_, ty, Mutability::Mut) => f(*ty, count + 1, mutability), + ty::Ref(_, ty, Mutability::Not) => f(*ty, count + 1, Mutability::Not), _ => (ty, count, mutability), } } @@ -360,7 +360,7 @@ pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { fn inner(ty: Ty<'_>, depth: usize) -> (Ty<'_>, usize) { match ty.kind() { - ty::Ref(_, ty, _) => inner(ty, depth + 1), + ty::Ref(_, ty, _) => inner(*ty, depth + 1), _ => (ty, depth), } } @@ -394,7 +394,7 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { /// Checks if a given type looks safe to be uninitialized. pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { match ty.kind() { - ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component), + ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, *component), ty::Tuple(types) => types.types().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did), _ => false, diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md index 36c454745ba06..828bf4cbef948 100644 --- a/doc/common_tools_writing_lints.md +++ b/doc/common_tools_writing_lints.md @@ -26,7 +26,7 @@ Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for ex - does it implement a trait? This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct, -that gives you access to the underlying structure [`TyS`][TyS]. +that gives you access to the underlying structure [`Ty`][Ty]. Example of use: ```rust @@ -259,7 +259,7 @@ expression with a different context from `a`. 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 +[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html [TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty From d071ce1d5774a6bd93a623df1a3cabf940b12b46 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Fri, 28 Jan 2022 11:25:15 +1100 Subject: [PATCH 14/55] Overhaul `RegionKind` and `Region`. Specifically, change `Region` from this: ``` pub type Region<'tcx> = &'tcx RegionKind; ``` to this: ``` pub struct Region<'tcx>(&'tcx Interned); ``` This now matches `Ty` and `Predicate` more closely. Things to note - Regions have always been interned, but we haven't been using pointer-based `Eq` and `Hash`. This is now happening. - I chose to impl `Deref` for `Region` because it makes pattern matching a lot nicer, and `Region` can be viewed as just a smart wrapper for `RegionKind`. - Various methods are moved from `RegionKind` to `Region`. - There is a lot of tedious sigil changes. - A couple of types like `HighlightBuilder`, `RegionHighlightMode` now have a `'tcx` lifetime because they hold a `Ty<'tcx>`, so they can call `mk_region`. - A couple of test outputs change slightly, I'm not sure why, but the new outputs are a little better. --- clippy_lints/src/methods/expect_fun_call.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index d813edab687e8..c3cb02329a11c 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -73,7 +73,7 @@ pub(super) fn check<'tcx>( match cx.qpath_res(p, fun.hir_id) { hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( cx.tcx.fn_sig(def_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) + ty::Ref(re, ..) if re.is_static(), ), _ => false, } @@ -87,7 +87,7 @@ pub(super) fn check<'tcx>( .map_or(false, |method_id| { matches!( cx.tcx.fn_sig(method_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) + ty::Ref(re, ..) if re.is_static() ) }) }, From 64ae3ae0068a549ea35f6e19a62bb778ebaafe20 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 2 Feb 2022 14:24:45 +1100 Subject: [PATCH 15/55] Overhaul `Const`. Specifically, rename the `Const` struct as `ConstS` and re-introduce `Const` as this: ``` pub struct Const<'tcx>(&'tcx Interned); ``` This now matches `Ty` and `Predicate` more closely, including using pointer-based `eq` and `hash`. Notable changes: - `mk_const` now takes a `ConstS`. - `Const` was copy, despite being 48 bytes. Now `ConstS` is not, so need a we need separate arena for it, because we can't use the `Dropless` one any more. - Many `&'tcx Const<'tcx>`/`&Const<'tcx>` to `Const<'tcx>` changes - Many `ct.ty` to `ct.ty()` and `ct.val` to `ct.val()` changes. - Lots of tedious sigil fiddling. --- clippy_lints/src/large_const_arrays.rs | 2 +- clippy_lints/src/large_stack_arrays.rs | 2 +- clippy_lints/src/non_copy_const.rs | 6 +++--- clippy_utils/src/consts.rs | 14 +++++++------- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 663977a57a07c..27db638813613 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -53,7 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { if let ItemKind::Const(hir_ty, _) = &item.kind; let ty = hir_ty_to_ty(cx.tcx, hir_ty); if let ty::Array(element_type, cst) = ty.kind(); - if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val(); if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index b9e246290ff79..57b0d709acd4d 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -43,7 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { if_chain! { if let ExprKind::Repeat(_, _) = expr.kind; if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind(); - if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val; + if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val(); if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx); if let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()); if self.maximum_allowed_size < element_count * element_size; diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 21ac6548b0179..3ba99403f06d0 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -136,14 +136,14 @@ fn is_value_unfrozen_raw<'tcx>( result: Result, ErrorHandled>, ty: Ty<'tcx>, ) -> bool { - fn inner<'tcx>(cx: &LateContext<'tcx>, val: &'tcx Const<'tcx>) -> bool { - match val.ty.kind() { + fn inner<'tcx>(cx: &LateContext<'tcx>, val: Const<'tcx>) -> bool { + match val.ty().kind() { // the fact that we have to dig into every structs to search enums // leads us to the point checking `UnsafeCell` directly is the only option. ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true, ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => { let val = cx.tcx.destructure_const(cx.param_env.and(val)); - val.fields.iter().any(|field| inner(cx, field)) + val.fields.iter().any(|field| inner(cx, *field)) }, _ => false, } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 3f604d5166bf7..d40583c47dd70 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -567,11 +567,11 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { } } -pub fn miri_to_const(result: &ty::Const<'_>) -> Option { +pub fn miri_to_const(result: ty::Const<'_>) -> Option { use rustc_middle::mir::interpret::ConstValue; - match result.val { + match result.val() { ty::ConstKind::Value(ConstValue::Scalar(Scalar::Int(int))) => { - match result.ty.kind() { + match result.ty().kind() { ty::Bool => Some(Constant::Bool(int == ScalarInt::TRUE)), ty::Uint(_) | ty::Int(_) => Some(Constant::Int(int.assert_bits(int.size()))), ty::Float(FloatTy::F32) => Some(Constant::F32(f32::from_bits( @@ -590,7 +590,7 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option { _ => None, } }, - ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty.kind() { + ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty().kind() { ty::Ref(_, tam, _) => match tam.kind() { ty::Str => String::from_utf8( data.inspect_with_uninit_and_ptr_outside_interpreter(start..end) @@ -602,9 +602,9 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option { }, _ => None, }, - ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty.kind() { + ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty().kind() { ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match miri_to_const(len) { + ty::Float(FloatTy::F32) => match miri_to_const(*len) { Some(Constant::Int(len)) => alloc .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) .to_owned() @@ -618,7 +618,7 @@ pub fn miri_to_const(result: &ty::Const<'_>) -> Option { .map(Constant::Vec), _ => None, }, - ty::Float(FloatTy::F64) => match miri_to_const(len) { + ty::Float(FloatTy::F64) => match miri_to_const(*len) { Some(Constant::Int(len)) => alloc .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) .to_owned() From 465cd90866dc934bf64d00d4c9e4aadd6ed89bb5 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 15 Feb 2022 10:54:38 +0100 Subject: [PATCH 16/55] Move transmute_undefined_repr back to nursery There's still open discussion if this lint is ready to be enabled by default. We want to give us more time to figure this out and prevent this lint from getting to stable as an enabled-by-default lint. --- clippy_lints/src/lib.register_all.rs | 1 - clippy_lints/src/lib.register_correctness.rs | 1 - clippy_lints/src/lib.register_nursery.rs | 1 + clippy_lints/src/transmute/mod.rs | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index d93e34e76b492..4721b7f2b472b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -277,7 +277,6 @@ 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 d013daa8e082a..4217fd3a3ea72 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -58,7 +58,6 @@ 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_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index a735379010026..8d4dde42bbeca 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -26,6 +26,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(strings::STRING_LIT_AS_BYTES), LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS), LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY), + LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR), LintId::of(transmute::USELESS_TRANSMUTE), LintId::of(use_self::USE_SELF), ]) diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 4c320deecc28b..5e94ab6d04820 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -376,7 +376,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.60.0"] pub TRANSMUTE_UNDEFINED_REPR, - correctness, + nursery, "transmute to or from a type with an undefined representation" } From dcdff601fe49240df4e73a763ff2e15cd3304cdd Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Wed, 16 Feb 2022 00:38:04 +0000 Subject: [PATCH 17/55] Correctly mark the span of captured arguments in `format_args!()` It should only include the identifier, or misspelling suggestions will be wrong. --- clippy_lints/src/write.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index b0044695ea8a8..1fa6301ebd73d 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -453,7 +453,7 @@ impl SimpleFormatArgs { } } }, - ArgumentNamed(n) => { + ArgumentNamed(n, _) => { if let Some(x) = self.named.iter_mut().find(|x| x.0 == n) { match x.1.as_slice() { // A non-empty format string has been seen already. From a135b52102651b0c3f10dbf869a9e7cfae8adbce Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Feb 2022 23:14:39 -0500 Subject: [PATCH 18/55] Don't lint `needless_borrow` in method receiver positions --- clippy_lints/src/dereference.rs | 2 +- tests/ui/needless_borrow.fixed | 6 +++--- tests/ui/needless_borrow.rs | 6 +++--- tests/ui/needless_borrow.stderr | 20 +------------------- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 6d0851d804c26..fe3911983421b 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -528,7 +528,7 @@ fn is_auto_reborrow_position(parent: Option>) -> bool { 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::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id, ExprKind::Field(..) => true, ExprKind::Call(f, _) => f.hir_id == child_id, _ => false, diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index b856f1375d303..efeb5cf5b2b25 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -64,9 +64,9 @@ fn main() { *x = 5; let s = String::new(); - let _ = s.len(); - let _ = s.capacity(); - let _ = s.capacity(); + // let _ = (&s).len(); + // let _ = (&s).capacity(); + // let _ = (&&s).capacity(); let x = (1, 2); let _ = x.0; diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 0bfe222a3dc17..3e416a0eb84aa 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -64,9 +64,9 @@ fn main() { *x = 5; let s = String::new(); - let _ = (&s).len(); - let _ = (&s).capacity(); - let _ = (&&s).capacity(); + // let _ = (&s).len(); + // let _ = (&s).capacity(); + // let _ = (&&s).capacity(); let x = (1, 2); let _ = (&x).0; diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index b90e8448db0a3..05591ce4117b2 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -84,24 +84,6 @@ error: this expression creates a reference which is immediately dereferenced by 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 | @@ -114,5 +96,5 @@ error: this expression borrows a value the compiler would automatically borrow LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` -error: aborting due to 19 previous errors +error: aborting due to 16 previous errors From faeeef3b9cd841b54ece2ff887d608d5bb54f41c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 3 Jan 2022 15:25:00 -0500 Subject: [PATCH 19/55] Improve `redundant_slicing` lint * Lint when slicing triggers auto-deref * Lint when slicing returns the same type as dereferencing --- clippy_lints/src/redundant_slicing.rs | 67 ++++++++++++++++++++------- tests/ui/redundant_slicing.fixed | 40 ++++++++++++++++ tests/ui/redundant_slicing.rs | 16 +++++-- tests/ui/redundant_slicing.stderr | 42 +++++++++++++---- 4 files changed, 134 insertions(+), 31 deletions(-) create mode 100644 tests/ui/redundant_slicing.fixed diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index cd3aee5565538..b492f944dc324 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; -use clippy_utils::ty::is_type_lang_item; +use clippy_utils::ty::{is_type_lang_item, peel_mid_ty_refs}; 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::subst::GenericArg; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { @@ -53,25 +54,55 @@ 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 cx.typeck_results().expr_ty(expr) == cx.typeck_results().expr_ty(indexed); then { + let (expr_ty, expr_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(expr)); + let (indexed_ty, indexed_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(indexed)); let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; - let (reborrow_str, help_str) = if mutability == Mutability::Mut { - // The slice was used to reborrow the mutable reference. - ("&mut *", "reborrow the original value instead") - } else if matches!( - get_parent_expr(cx, expr), - Some(Expr { - kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _), - .. - }) - ) { - // The slice was used to make a temporary reference. - ("&*", "reborrow the original value instead") + let (help, sugg) = if expr_ty == indexed_ty { + if expr_ref_count > indexed_ref_count { + return; + } + + let (reborrow_str, help_str) = if mutability == Mutability::Mut { + // The slice was used to reborrow the mutable reference. + ("&mut *", "reborrow the original value instead") + } else if matches!( + get_parent_expr(cx, expr), + Some(Expr { + kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _), + .. + }) + ) { + // The slice was used to make a temporary reference. + ("&*", "reborrow the original value instead") + } else if expr_ref_count != indexed_ref_count { + ("", "dereference the original value instead") + } else { + ("", "use the original value instead") + }; + + let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; + (help_str, format!("{}{}{}", reborrow_str, "*".repeat(indexed_ref_count - expr_ref_count), snip)) + } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { + if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( + cx.param_env, + cx.tcx.mk_projection(target_id, cx.tcx.mk_substs([GenericArg::from(indexed_ty)].into_iter())), + ) { + if deref_ty == expr_ty { + let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; + ( + "dereference the original value instead", + format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip), + ) + } else { + return; + } + } else { + return; + } } else { - ("", "use the original value instead") + return; }; span_lint_and_sugg( @@ -79,8 +110,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { REDUNDANT_SLICING, expr.span, "redundant slicing of the whole range", - help_str, - format!("{}{}", reborrow_str, snip), + help, + sugg, app, ); } diff --git a/tests/ui/redundant_slicing.fixed b/tests/ui/redundant_slicing.fixed new file mode 100644 index 0000000000000..9716a48be94ba --- /dev/null +++ b/tests/ui/redundant_slicing.fixed @@ -0,0 +1,40 @@ +// run-rustfix + +#![allow(unused)] +#![warn(clippy::redundant_slicing)] + +fn main() { + let slice: &[u32] = &[0]; + let _ = slice; // Redundant slice + + let v = vec![0]; + let _ = &*v; // Deref instead of slice + let _ = (&*v); // Outer borrow is redundant + + static S: &[u8] = &[0, 1, 2]; + let err = &mut &*S; // Should reborrow instead of slice + + let mut vec = vec![0]; + let mut_slice = &mut *vec; // Deref instead of slice + let _ = &mut *mut_slice; // Should reborrow instead of slice + + let ref_vec = &vec; + let _ = &**ref_vec; // Deref instead of slice + + macro_rules! m { + ($e:expr) => { + $e + }; + } + let _ = slice; + + macro_rules! m2 { + ($e:expr) => { + &$e[..] + }; + } + let _ = m2!(slice); // Don't lint in a macro + + let slice_ref = &slice; + let _ = *slice_ref; // Deref instead of slice +} diff --git a/tests/ui/redundant_slicing.rs b/tests/ui/redundant_slicing.rs index 554b6ba36ae0d..a6902f7619bde 100644 --- a/tests/ui/redundant_slicing.rs +++ b/tests/ui/redundant_slicing.rs @@ -1,21 +1,26 @@ +// run-rustfix + #![allow(unused)] #![warn(clippy::redundant_slicing)] fn main() { let slice: &[u32] = &[0]; - let _ = &slice[..]; + let _ = &slice[..]; // Redundant slice let v = vec![0]; - let _ = &v[..]; // Changes the type - let _ = &(&v[..])[..]; // Outer borrow is redundant + let _ = &v[..]; // Deref instead of slice + let _ = &(&*v)[..]; // Outer borrow is redundant static S: &[u8] = &[0, 1, 2]; let err = &mut &S[..]; // Should reborrow instead of slice let mut vec = vec![0]; - let mut_slice = &mut *vec; + let mut_slice = &mut vec[..]; // Deref instead of slice let _ = &mut mut_slice[..]; // Should reborrow instead of slice + let ref_vec = &vec; + let _ = &ref_vec[..]; // Deref instead of slice + macro_rules! m { ($e:expr) => { $e @@ -29,4 +34,7 @@ fn main() { }; } let _ = m2!(slice); // Don't lint in a macro + + let slice_ref = &slice; + let _ = &slice_ref[..]; // Deref instead of slice } diff --git a/tests/ui/redundant_slicing.stderr b/tests/ui/redundant_slicing.stderr index bbd10eafbbe78..c2a5e9e191401 100644 --- a/tests/ui/redundant_slicing.stderr +++ b/tests/ui/redundant_slicing.stderr @@ -1,34 +1,58 @@ error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:6:13 + --> $DIR/redundant_slicing.rs:8:13 | -LL | let _ = &slice[..]; +LL | let _ = &slice[..]; // Redundant slice | ^^^^^^^^^^ help: use the original value instead: `slice` | = note: `-D clippy::redundant-slicing` implied by `-D warnings` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:10:13 + --> $DIR/redundant_slicing.rs:11:13 | -LL | let _ = &(&v[..])[..]; // Outer borrow is redundant - | ^^^^^^^^^^^^^ help: use the original value instead: `(&v[..])` +LL | let _ = &v[..]; // Deref instead of slice + | ^^^^^^ help: dereference the original value instead: `&*v` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:13:20 + --> $DIR/redundant_slicing.rs:12:13 + | +LL | let _ = &(&*v)[..]; // Outer borrow is redundant + | ^^^^^^^^^^ help: use the original value instead: `(&*v)` + +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:15:20 | LL | let err = &mut &S[..]; // Should reborrow instead of slice | ^^^^^^ help: reborrow the original value instead: `&*S` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:17:13 + --> $DIR/redundant_slicing.rs:18:21 + | +LL | let mut_slice = &mut vec[..]; // Deref instead of slice + | ^^^^^^^^^^^^ help: dereference the original value instead: `&mut *vec` + +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:19:13 | LL | let _ = &mut mut_slice[..]; // Should reborrow instead of slice | ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:24:13 + --> $DIR/redundant_slicing.rs:22:13 + | +LL | let _ = &ref_vec[..]; // Deref instead of slice + | ^^^^^^^^^^^^ help: dereference the original value instead: `&**ref_vec` + +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:29:13 | LL | let _ = &m!(slice)[..]; | ^^^^^^^^^^^^^^ help: use the original value instead: `slice` -error: aborting due to 5 previous errors +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:39:13 + | +LL | let _ = &slice_ref[..]; // Deref instead of slice + | ^^^^^^^^^^^^^^ help: dereference the original value instead: `*slice_ref` + +error: aborting due to 9 previous errors From 4bdc97c4a66eccfc3925cd691121965b08017908 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 3 Jan 2022 20:35:32 -0500 Subject: [PATCH 20/55] Account for auto-borrows and precedence in `redundant_slicing` lint --- clippy_lints/src/redundant_slicing.rs | 38 +++++++++++++++++++++------ tests/ui/redundant_slicing.fixed | 6 +++++ tests/ui/redundant_slicing.rs | 6 +++++ tests/ui/redundant_slicing.stderr | 26 +++++++++++------- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index b492f944dc324..1cc0990b283dd 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -3,9 +3,11 @@ use clippy_utils::get_parent_expr; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{is_type_lang_item, peel_mid_ty_refs}; use if_chain::if_chain; +use rustc_ast::util::parser::PREC_PREFIX; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::subst::GenericArg; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -57,33 +59,51 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { then { let (expr_ty, expr_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(expr)); let (indexed_ty, indexed_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(indexed)); + let parent_expr = get_parent_expr(cx, expr); + let needs_parens_for_prefix = parent_expr.map_or(false, |parent| { + parent.precedence().order() > PREC_PREFIX + }); let mut app = Applicability::MachineApplicable; let (help, sugg) = if expr_ty == indexed_ty { if expr_ref_count > indexed_ref_count { + // Indexing takes self by reference and can't return a reference to that + // reference as it's a local variable. The only way this could happen is if + // `self` contains a reference to the `Self` type. If this occurs then the + // lint no longer applies as it's essentially a field access, which is not + // redundant. return; } + let deref_count = indexed_ref_count - expr_ref_count; let (reborrow_str, help_str) = if mutability == Mutability::Mut { // The slice was used to reborrow the mutable reference. ("&mut *", "reborrow the original value instead") } else if matches!( - get_parent_expr(cx, expr), + parent_expr, Some(Expr { kind: ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _), .. }) - ) { + ) || cx.typeck_results().expr_adjustments(expr).first().map_or(false, |a| { + matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. }))) + }) { // The slice was used to make a temporary reference. ("&*", "reborrow the original value instead") - } else if expr_ref_count != indexed_ref_count { + } else if deref_count != 0 { ("", "dereference the original value instead") } else { ("", "use the original value instead") }; let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; - (help_str, format!("{}{}{}", reborrow_str, "*".repeat(indexed_ref_count - expr_ref_count), snip)) + let sugg = if (deref_count != 0 || !reborrow_str.is_empty()) && needs_parens_for_prefix { + format!("({}{}{})", reborrow_str, "*".repeat(deref_count), snip) + } else { + format!("{}{}{}", reborrow_str, "*".repeat(deref_count), snip) + }; + + (help_str, sugg) } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.param_env, @@ -91,10 +111,12 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { ) { if deref_ty == expr_ty { let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; - ( - "dereference the original value instead", - format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip), - ) + let sugg = if needs_parens_for_prefix { + format!("(&{}{}*{})", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip) + } else { + format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip) + }; + ("dereference the original value instead", sugg) } else { return; } diff --git a/tests/ui/redundant_slicing.fixed b/tests/ui/redundant_slicing.fixed index 9716a48be94ba..98b2adec44048 100644 --- a/tests/ui/redundant_slicing.fixed +++ b/tests/ui/redundant_slicing.fixed @@ -3,6 +3,8 @@ #![allow(unused)] #![warn(clippy::redundant_slicing)] +use std::io::Read; + fn main() { let slice: &[u32] = &[0]; let _ = slice; // Redundant slice @@ -37,4 +39,8 @@ fn main() { let slice_ref = &slice; let _ = *slice_ref; // Deref instead of slice + + // Issue #7972 + let bytes: &[u8] = &[]; + let _ = (&*bytes).read_to_end(&mut vec![]).unwrap(); } diff --git a/tests/ui/redundant_slicing.rs b/tests/ui/redundant_slicing.rs index a6902f7619bde..b84a304ec674c 100644 --- a/tests/ui/redundant_slicing.rs +++ b/tests/ui/redundant_slicing.rs @@ -3,6 +3,8 @@ #![allow(unused)] #![warn(clippy::redundant_slicing)] +use std::io::Read; + fn main() { let slice: &[u32] = &[0]; let _ = &slice[..]; // Redundant slice @@ -37,4 +39,8 @@ fn main() { let slice_ref = &slice; let _ = &slice_ref[..]; // Deref instead of slice + + // Issue #7972 + let bytes: &[u8] = &[]; + let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); } diff --git a/tests/ui/redundant_slicing.stderr b/tests/ui/redundant_slicing.stderr index c2a5e9e191401..bce3511179078 100644 --- a/tests/ui/redundant_slicing.stderr +++ b/tests/ui/redundant_slicing.stderr @@ -1,5 +1,5 @@ error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:8:13 + --> $DIR/redundant_slicing.rs:10:13 | LL | let _ = &slice[..]; // Redundant slice | ^^^^^^^^^^ help: use the original value instead: `slice` @@ -7,52 +7,58 @@ LL | let _ = &slice[..]; // Redundant slice = note: `-D clippy::redundant-slicing` implied by `-D warnings` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:11:13 + --> $DIR/redundant_slicing.rs:13:13 | LL | let _ = &v[..]; // Deref instead of slice | ^^^^^^ help: dereference the original value instead: `&*v` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:12:13 + --> $DIR/redundant_slicing.rs:14:13 | LL | let _ = &(&*v)[..]; // Outer borrow is redundant | ^^^^^^^^^^ help: use the original value instead: `(&*v)` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:15:20 + --> $DIR/redundant_slicing.rs:17:20 | LL | let err = &mut &S[..]; // Should reborrow instead of slice | ^^^^^^ help: reborrow the original value instead: `&*S` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:18:21 + --> $DIR/redundant_slicing.rs:20:21 | LL | let mut_slice = &mut vec[..]; // Deref instead of slice | ^^^^^^^^^^^^ help: dereference the original value instead: `&mut *vec` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:19:13 + --> $DIR/redundant_slicing.rs:21:13 | LL | let _ = &mut mut_slice[..]; // Should reborrow instead of slice | ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:22:13 + --> $DIR/redundant_slicing.rs:24:13 | LL | let _ = &ref_vec[..]; // Deref instead of slice | ^^^^^^^^^^^^ help: dereference the original value instead: `&**ref_vec` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:29:13 + --> $DIR/redundant_slicing.rs:31:13 | LL | let _ = &m!(slice)[..]; | ^^^^^^^^^^^^^^ help: use the original value instead: `slice` error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:39:13 + --> $DIR/redundant_slicing.rs:41:13 | LL | let _ = &slice_ref[..]; // Deref instead of slice | ^^^^^^^^^^^^^^ help: dereference the original value instead: `*slice_ref` -error: aborting due to 9 previous errors +error: redundant slicing of the whole range + --> $DIR/redundant_slicing.rs:45:13 + | +LL | let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); + | ^^^^^^^^^^^^ help: reborrow the original value instead: `(&*bytes)` + +error: aborting due to 10 previous errors From 113ac6e15e16efb193985be9c5329a72d55df30b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 11 Jan 2022 12:41:15 -0500 Subject: [PATCH 21/55] Split off new lint `deref_by_slicing` from `redundant_slicing` --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/redundant_slicing.rs | 36 ++++++++++++++++---- tests/ui/deref_by_slicing.fixed | 16 +++++++++ tests/ui/deref_by_slicing.rs | 16 +++++++++ tests/ui/deref_by_slicing.stderr | 34 ++++++++++++++++++ tests/ui/redundant_slicing.fixed | 8 ++--- tests/ui/redundant_slicing.rs | 8 ++--- tests/ui/redundant_slicing.stderr | 20 +---------- 10 files changed, 108 insertions(+), 33 deletions(-) create mode 100644 tests/ui/deref_by_slicing.fixed create mode 100644 tests/ui/deref_by_slicing.rs create mode 100644 tests/ui/deref_by_slicing.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index c9adf77c0d639..ef7139d63bf08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3105,6 +3105,7 @@ Released 2018-09-13 [`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr [`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver [`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof +[`deref_by_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_by_slicing [`derivable_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#derivable_impls [`derive_hash_xor_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_hash_xor_eq [`derive_ord_xor_partial_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index a80320a578f0e..2451a5aab090f 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -420,6 +420,7 @@ store.register_lints(&[ redundant_else::REDUNDANT_ELSE, redundant_field_names::REDUNDANT_FIELD_NAMES, redundant_pub_crate::REDUNDANT_PUB_CRATE, + redundant_slicing::DEREF_BY_SLICING, redundant_slicing::REDUNDANT_SLICING, redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, ref_option_ref::REF_OPTION_REF, diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 5a89fdb05a990..f89f35b885c15 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -51,6 +51,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(panic_unimplemented::UNIMPLEMENTED), LintId::of(panic_unimplemented::UNREACHABLE), LintId::of(pattern_type_mismatch::PATTERN_TYPE_MISMATCH), + LintId::of(redundant_slicing::DEREF_BY_SLICING), LintId::of(same_name_method::SAME_NAME_METHOD), LintId::of(shadow::SHADOW_REUSE), LintId::of(shadow::SHADOW_SAME), diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 1cc0990b283dd..0de4541b6413a 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -42,7 +42,31 @@ declare_clippy_lint! { "redundant slicing of the whole range of a type" } -declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING]); +declare_clippy_lint! { + /// ### What it does + /// Checks for slicing expression which are equivalent to dereferencing the + /// value. + /// + /// ### Why is this bad? + /// Some people may prefer to dereference rather than slice. + /// + /// ### Example + /// ```rust + /// let vec = vec![1, 2, 3]; + /// let slice = &vec[..]; + /// ``` + /// Use instead: + /// ```rust + /// let vec = vec![1, 2, 3]; + /// let slice = &*vec; + /// ``` + #[clippy::version = "1.60.0"] + pub DEREF_BY_SLICING, + restriction, + "slicing instead of dereferencing" +} + +declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING, DEREF_BY_SLICING]); impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -65,7 +89,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { }); let mut app = Applicability::MachineApplicable; - let (help, sugg) = if expr_ty == indexed_ty { + let (lint, msg, help, sugg) = if expr_ty == indexed_ty { if expr_ref_count > indexed_ref_count { // Indexing takes self by reference and can't return a reference to that // reference as it's a local variable. The only way this could happen is if @@ -103,7 +127,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { format!("{}{}{}", reborrow_str, "*".repeat(deref_count), snip) }; - (help_str, sugg) + (REDUNDANT_SLICING, "redundant slicing of the whole range", help_str, sugg) } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.param_env, @@ -116,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } else { format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip) }; - ("dereference the original value instead", sugg) + (DEREF_BY_SLICING, "slicing when dereferencing would work", "dereference the original value instead", sugg) } else { return; } @@ -129,9 +153,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { span_lint_and_sugg( cx, - REDUNDANT_SLICING, + lint, expr.span, - "redundant slicing of the whole range", + msg, help, sugg, app, diff --git a/tests/ui/deref_by_slicing.fixed b/tests/ui/deref_by_slicing.fixed new file mode 100644 index 0000000000000..edb19de07001a --- /dev/null +++ b/tests/ui/deref_by_slicing.fixed @@ -0,0 +1,16 @@ +// run-rustfix + +#![warn(clippy::deref_by_slicing)] + +fn main() { + let mut vec = vec![0]; + let _ = &*vec; + let _ = &mut *vec; + + let ref_vec = &mut vec; + let _ = &**ref_vec; + let _ = &mut **ref_vec; + + let s = String::new(); + let _ = &*s; +} diff --git a/tests/ui/deref_by_slicing.rs b/tests/ui/deref_by_slicing.rs new file mode 100644 index 0000000000000..6d489a44e7650 --- /dev/null +++ b/tests/ui/deref_by_slicing.rs @@ -0,0 +1,16 @@ +// run-rustfix + +#![warn(clippy::deref_by_slicing)] + +fn main() { + let mut vec = vec![0]; + let _ = &vec[..]; + let _ = &mut vec[..]; + + let ref_vec = &mut vec; + let _ = &ref_vec[..]; + let _ = &mut ref_vec[..]; + + let s = String::new(); + let _ = &s[..]; +} diff --git a/tests/ui/deref_by_slicing.stderr b/tests/ui/deref_by_slicing.stderr new file mode 100644 index 0000000000000..89e4ca718b540 --- /dev/null +++ b/tests/ui/deref_by_slicing.stderr @@ -0,0 +1,34 @@ +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:7:13 + | +LL | let _ = &vec[..]; + | ^^^^^^^^ help: dereference the original value instead: `&*vec` + | + = note: `-D clippy::deref-by-slicing` implied by `-D warnings` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:8:13 + | +LL | let _ = &mut vec[..]; + | ^^^^^^^^^^^^ help: dereference the original value instead: `&mut *vec` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:11:13 + | +LL | let _ = &ref_vec[..]; + | ^^^^^^^^^^^^ help: dereference the original value instead: `&**ref_vec` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:12:13 + | +LL | let _ = &mut ref_vec[..]; + | ^^^^^^^^^^^^^^^^ help: dereference the original value instead: `&mut **ref_vec` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:15:13 + | +LL | let _ = &s[..]; + | ^^^^^^ help: dereference the original value instead: `&*s` + +error: aborting due to 5 previous errors + diff --git a/tests/ui/redundant_slicing.fixed b/tests/ui/redundant_slicing.fixed index 98b2adec44048..da3d953375a17 100644 --- a/tests/ui/redundant_slicing.fixed +++ b/tests/ui/redundant_slicing.fixed @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused)] +#![allow(unused, clippy::deref_by_slicing)] #![warn(clippy::redundant_slicing)] use std::io::Read; @@ -10,18 +10,18 @@ fn main() { let _ = slice; // Redundant slice let v = vec![0]; - let _ = &*v; // Deref instead of slice + let _ = &v[..]; // Ok, results in `&[_]` let _ = (&*v); // Outer borrow is redundant static S: &[u8] = &[0, 1, 2]; let err = &mut &*S; // Should reborrow instead of slice let mut vec = vec![0]; - let mut_slice = &mut *vec; // Deref instead of slice + let mut_slice = &mut vec[..]; // Ok, results in `&mut [_]` let _ = &mut *mut_slice; // Should reborrow instead of slice let ref_vec = &vec; - let _ = &**ref_vec; // Deref instead of slice + let _ = &ref_vec[..]; // Ok, results in `&[_]` macro_rules! m { ($e:expr) => { diff --git a/tests/ui/redundant_slicing.rs b/tests/ui/redundant_slicing.rs index b84a304ec674c..125b1d4591c34 100644 --- a/tests/ui/redundant_slicing.rs +++ b/tests/ui/redundant_slicing.rs @@ -1,6 +1,6 @@ // run-rustfix -#![allow(unused)] +#![allow(unused, clippy::deref_by_slicing)] #![warn(clippy::redundant_slicing)] use std::io::Read; @@ -10,18 +10,18 @@ fn main() { let _ = &slice[..]; // Redundant slice let v = vec![0]; - let _ = &v[..]; // Deref instead of slice + let _ = &v[..]; // Ok, results in `&[_]` let _ = &(&*v)[..]; // Outer borrow is redundant static S: &[u8] = &[0, 1, 2]; let err = &mut &S[..]; // Should reborrow instead of slice let mut vec = vec![0]; - let mut_slice = &mut vec[..]; // Deref instead of slice + let mut_slice = &mut vec[..]; // Ok, results in `&mut [_]` let _ = &mut mut_slice[..]; // Should reborrow instead of slice let ref_vec = &vec; - let _ = &ref_vec[..]; // Deref instead of slice + let _ = &ref_vec[..]; // Ok, results in `&[_]` macro_rules! m { ($e:expr) => { diff --git a/tests/ui/redundant_slicing.stderr b/tests/ui/redundant_slicing.stderr index bce3511179078..b1c1c28894a6d 100644 --- a/tests/ui/redundant_slicing.stderr +++ b/tests/ui/redundant_slicing.stderr @@ -6,12 +6,6 @@ LL | let _ = &slice[..]; // Redundant slice | = note: `-D clippy::redundant-slicing` implied by `-D warnings` -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:13:13 - | -LL | let _ = &v[..]; // Deref instead of slice - | ^^^^^^ help: dereference the original value instead: `&*v` - error: redundant slicing of the whole range --> $DIR/redundant_slicing.rs:14:13 | @@ -24,24 +18,12 @@ error: redundant slicing of the whole range LL | let err = &mut &S[..]; // Should reborrow instead of slice | ^^^^^^ help: reborrow the original value instead: `&*S` -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:20:21 - | -LL | let mut_slice = &mut vec[..]; // Deref instead of slice - | ^^^^^^^^^^^^ help: dereference the original value instead: `&mut *vec` - error: redundant slicing of the whole range --> $DIR/redundant_slicing.rs:21:13 | LL | let _ = &mut mut_slice[..]; // Should reborrow instead of slice | ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice` -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:24:13 - | -LL | let _ = &ref_vec[..]; // Deref instead of slice - | ^^^^^^^^^^^^ help: dereference the original value instead: `&**ref_vec` - error: redundant slicing of the whole range --> $DIR/redundant_slicing.rs:31:13 | @@ -60,5 +42,5 @@ error: redundant slicing of the whole range LL | let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); | ^^^^^^^^^^^^ help: reborrow the original value instead: `(&*bytes)` -error: aborting due to 10 previous errors +error: aborting due to 7 previous errors From 7724d6773d3fe4ac1e57e4e13d67fedbf7cae0ba Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 13 Feb 2022 18:43:19 -0500 Subject: [PATCH 22/55] Move some cases from `redundant_slicing` to `deref_by_slicing` --- clippy_lints/src/redundant_slicing.rs | 23 +++++++++------- tests/ui/deref_by_slicing.fixed | 15 ++++++++++- tests/ui/deref_by_slicing.rs | 15 ++++++++++- tests/ui/deref_by_slicing.stderr | 38 ++++++++++++++++++++++----- tests/ui/redundant_slicing.fixed | 8 +++--- tests/ui/redundant_slicing.rs | 8 +++--- tests/ui/redundant_slicing.stderr | 26 +----------------- 7 files changed, 81 insertions(+), 52 deletions(-) diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 0de4541b6413a..25a9072ef6e0c 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_ast::util::parser::PREC_PREFIX; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, LangItem, Mutability}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::subst::GenericArg; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -44,7 +44,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for slicing expression which are equivalent to dereferencing the + /// Checks for slicing expressions which are equivalent to dereferencing the /// value. /// /// ### Why is this bad? @@ -68,6 +68,9 @@ declare_clippy_lint! { declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING, DEREF_BY_SLICING]); +static REDUNDANT_SLICING_LINT: (&Lint, &str) = (REDUNDANT_SLICING, "redundant slicing of the whole range"); +static DEREF_BY_SLICING_LINT: (&Lint, &str) = (DEREF_BY_SLICING, "slicing when dereferencing would work"); + impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if expr.span.from_expansion() { @@ -89,7 +92,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { }); let mut app = Applicability::MachineApplicable; - let (lint, msg, help, sugg) = if expr_ty == indexed_ty { + let ((lint, msg), help, sugg) = if expr_ty == indexed_ty { if expr_ref_count > indexed_ref_count { // Indexing takes self by reference and can't return a reference to that // reference as it's a local variable. The only way this could happen is if @@ -100,9 +103,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } let deref_count = indexed_ref_count - expr_ref_count; - let (reborrow_str, help_str) = if mutability == Mutability::Mut { + let (lint, reborrow_str, help_str) = if mutability == Mutability::Mut { // The slice was used to reborrow the mutable reference. - ("&mut *", "reborrow the original value instead") + (DEREF_BY_SLICING_LINT, "&mut *", "reborrow the original value instead") } else if matches!( parent_expr, Some(Expr { @@ -113,11 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Mut { .. }))) }) { // The slice was used to make a temporary reference. - ("&*", "reborrow the original value instead") + (DEREF_BY_SLICING_LINT, "&*", "reborrow the original value instead") } else if deref_count != 0 { - ("", "dereference the original value instead") + (DEREF_BY_SLICING_LINT, "", "dereference the original value instead") } else { - ("", "use the original value instead") + (REDUNDANT_SLICING_LINT, "", "use the original value instead") }; let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; @@ -127,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { format!("{}{}{}", reborrow_str, "*".repeat(deref_count), snip) }; - (REDUNDANT_SLICING, "redundant slicing of the whole range", help_str, sugg) + (lint, help_str, sugg) } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.param_env, @@ -140,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { } else { format!("&{}{}*{}", mutability.prefix_str(), "*".repeat(indexed_ref_count), snip) }; - (DEREF_BY_SLICING, "slicing when dereferencing would work", "dereference the original value instead", sugg) + (DEREF_BY_SLICING_LINT, "dereference the original value instead", sugg) } else { return; } diff --git a/tests/ui/deref_by_slicing.fixed b/tests/ui/deref_by_slicing.fixed index edb19de07001a..b26276218b78c 100644 --- a/tests/ui/deref_by_slicing.fixed +++ b/tests/ui/deref_by_slicing.fixed @@ -2,6 +2,8 @@ #![warn(clippy::deref_by_slicing)] +use std::io::Read; + fn main() { let mut vec = vec![0]; let _ = &*vec; @@ -9,8 +11,19 @@ fn main() { let ref_vec = &mut vec; let _ = &**ref_vec; - let _ = &mut **ref_vec; + let mut_slice = &mut **ref_vec; + let _ = &mut *mut_slice; // Err, re-borrows slice let s = String::new(); let _ = &*s; + + static S: &[u8] = &[0, 1, 2]; + let _ = &mut &*S; // Err, re-borrows slice + + let slice: &[u32] = &[0u32, 1u32]; + let slice_ref = &slice; + let _ = *slice_ref; // Err, derefs slice + + let bytes: &[u8] = &[]; + let _ = (&*bytes).read_to_end(&mut vec![]).unwrap(); // Err, re-borrows slice } diff --git a/tests/ui/deref_by_slicing.rs b/tests/ui/deref_by_slicing.rs index 6d489a44e7650..6aa1408ba1769 100644 --- a/tests/ui/deref_by_slicing.rs +++ b/tests/ui/deref_by_slicing.rs @@ -2,6 +2,8 @@ #![warn(clippy::deref_by_slicing)] +use std::io::Read; + fn main() { let mut vec = vec![0]; let _ = &vec[..]; @@ -9,8 +11,19 @@ fn main() { let ref_vec = &mut vec; let _ = &ref_vec[..]; - let _ = &mut ref_vec[..]; + let mut_slice = &mut ref_vec[..]; + let _ = &mut mut_slice[..]; // Err, re-borrows slice let s = String::new(); let _ = &s[..]; + + static S: &[u8] = &[0, 1, 2]; + let _ = &mut &S[..]; // Err, re-borrows slice + + let slice: &[u32] = &[0u32, 1u32]; + let slice_ref = &slice; + let _ = &slice_ref[..]; // Err, derefs slice + + let bytes: &[u8] = &[]; + let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); // Err, re-borrows slice } diff --git a/tests/ui/deref_by_slicing.stderr b/tests/ui/deref_by_slicing.stderr index 89e4ca718b540..ffd76de378df1 100644 --- a/tests/ui/deref_by_slicing.stderr +++ b/tests/ui/deref_by_slicing.stderr @@ -1,5 +1,5 @@ error: slicing when dereferencing would work - --> $DIR/deref_by_slicing.rs:7:13 + --> $DIR/deref_by_slicing.rs:9:13 | LL | let _ = &vec[..]; | ^^^^^^^^ help: dereference the original value instead: `&*vec` @@ -7,28 +7,52 @@ LL | let _ = &vec[..]; = note: `-D clippy::deref-by-slicing` implied by `-D warnings` error: slicing when dereferencing would work - --> $DIR/deref_by_slicing.rs:8:13 + --> $DIR/deref_by_slicing.rs:10:13 | LL | let _ = &mut vec[..]; | ^^^^^^^^^^^^ help: dereference the original value instead: `&mut *vec` error: slicing when dereferencing would work - --> $DIR/deref_by_slicing.rs:11:13 + --> $DIR/deref_by_slicing.rs:13:13 | LL | let _ = &ref_vec[..]; | ^^^^^^^^^^^^ help: dereference the original value instead: `&**ref_vec` error: slicing when dereferencing would work - --> $DIR/deref_by_slicing.rs:12:13 + --> $DIR/deref_by_slicing.rs:14:21 | -LL | let _ = &mut ref_vec[..]; - | ^^^^^^^^^^^^^^^^ help: dereference the original value instead: `&mut **ref_vec` +LL | let mut_slice = &mut ref_vec[..]; + | ^^^^^^^^^^^^^^^^ help: dereference the original value instead: `&mut **ref_vec` error: slicing when dereferencing would work --> $DIR/deref_by_slicing.rs:15:13 | +LL | let _ = &mut mut_slice[..]; // Err, re-borrows slice + | ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:18:13 + | LL | let _ = &s[..]; | ^^^^^^ help: dereference the original value instead: `&*s` -error: aborting due to 5 previous errors +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:21:18 + | +LL | let _ = &mut &S[..]; // Err, re-borrows slice + | ^^^^^^ help: reborrow the original value instead: `&*S` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:25:13 + | +LL | let _ = &slice_ref[..]; // Err, derefs slice + | ^^^^^^^^^^^^^^ help: dereference the original value instead: `*slice_ref` + +error: slicing when dereferencing would work + --> $DIR/deref_by_slicing.rs:28:13 + | +LL | let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); // Err, re-borrows slice + | ^^^^^^^^^^^^ help: reborrow the original value instead: `(&*bytes)` + +error: aborting due to 9 previous errors diff --git a/tests/ui/redundant_slicing.fixed b/tests/ui/redundant_slicing.fixed index da3d953375a17..8dd8d3092378e 100644 --- a/tests/ui/redundant_slicing.fixed +++ b/tests/ui/redundant_slicing.fixed @@ -14,11 +14,11 @@ fn main() { let _ = (&*v); // Outer borrow is redundant static S: &[u8] = &[0, 1, 2]; - let err = &mut &*S; // Should reborrow instead of slice + let _ = &mut &S[..]; // Ok, re-borrows slice let mut vec = vec![0]; let mut_slice = &mut vec[..]; // Ok, results in `&mut [_]` - let _ = &mut *mut_slice; // Should reborrow instead of slice + let _ = &mut mut_slice[..]; // Ok, re-borrows slice let ref_vec = &vec; let _ = &ref_vec[..]; // Ok, results in `&[_]` @@ -38,9 +38,9 @@ fn main() { let _ = m2!(slice); // Don't lint in a macro let slice_ref = &slice; - let _ = *slice_ref; // Deref instead of slice + let _ = &slice_ref[..]; // Ok, derefs slice // Issue #7972 let bytes: &[u8] = &[]; - let _ = (&*bytes).read_to_end(&mut vec![]).unwrap(); + let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); // Ok, re-borrows slice } diff --git a/tests/ui/redundant_slicing.rs b/tests/ui/redundant_slicing.rs index 125b1d4591c34..51c16dd8d65a2 100644 --- a/tests/ui/redundant_slicing.rs +++ b/tests/ui/redundant_slicing.rs @@ -14,11 +14,11 @@ fn main() { let _ = &(&*v)[..]; // Outer borrow is redundant static S: &[u8] = &[0, 1, 2]; - let err = &mut &S[..]; // Should reborrow instead of slice + let _ = &mut &S[..]; // Ok, re-borrows slice let mut vec = vec![0]; let mut_slice = &mut vec[..]; // Ok, results in `&mut [_]` - let _ = &mut mut_slice[..]; // Should reborrow instead of slice + let _ = &mut mut_slice[..]; // Ok, re-borrows slice let ref_vec = &vec; let _ = &ref_vec[..]; // Ok, results in `&[_]` @@ -38,9 +38,9 @@ fn main() { let _ = m2!(slice); // Don't lint in a macro let slice_ref = &slice; - let _ = &slice_ref[..]; // Deref instead of slice + let _ = &slice_ref[..]; // Ok, derefs slice // Issue #7972 let bytes: &[u8] = &[]; - let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); + let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); // Ok, re-borrows slice } diff --git a/tests/ui/redundant_slicing.stderr b/tests/ui/redundant_slicing.stderr index b1c1c28894a6d..82367143c07fa 100644 --- a/tests/ui/redundant_slicing.stderr +++ b/tests/ui/redundant_slicing.stderr @@ -12,35 +12,11 @@ error: redundant slicing of the whole range LL | let _ = &(&*v)[..]; // Outer borrow is redundant | ^^^^^^^^^^ help: use the original value instead: `(&*v)` -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:17:20 - | -LL | let err = &mut &S[..]; // Should reborrow instead of slice - | ^^^^^^ help: reborrow the original value instead: `&*S` - -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:21:13 - | -LL | let _ = &mut mut_slice[..]; // Should reborrow instead of slice - | ^^^^^^^^^^^^^^^^^^ help: reborrow the original value instead: `&mut *mut_slice` - error: redundant slicing of the whole range --> $DIR/redundant_slicing.rs:31:13 | LL | let _ = &m!(slice)[..]; | ^^^^^^^^^^^^^^ help: use the original value instead: `slice` -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:41:13 - | -LL | let _ = &slice_ref[..]; // Deref instead of slice - | ^^^^^^^^^^^^^^ help: dereference the original value instead: `*slice_ref` - -error: redundant slicing of the whole range - --> $DIR/redundant_slicing.rs:45:13 - | -LL | let _ = (&bytes[..]).read_to_end(&mut vec![]).unwrap(); - | ^^^^^^^^^^^^ help: reborrow the original value instead: `(&*bytes)` - -error: aborting due to 7 previous errors +error: aborting due to 3 previous errors From 504f3af70ea4cb2ce13f9272babb4e59b61fd88e Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Mon, 14 Feb 2022 13:22:36 +0100 Subject: [PATCH 23/55] Don't lint Default::default if it is the udpate syntax base An Update Syntax looks like this: Foo { a: 3, ..Default::default() } Don't lint `Default::default` here --- clippy_lints/src/default.rs | 16 +++++++++++++++- tests/ui/default_trait_access.fixed | 15 +++++++++++++-- tests/ui/default_trait_access.rs | 15 +++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 3070588483c21..06e6bf986c2a9 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::ty::{has_drop, is_copy}; -use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths}; +use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, match_def_path, paths}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -88,6 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { if let ExprKind::Path(ref qpath) = path.kind; if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD); + if !is_update_syntax_base(cx, expr); // Detect and ignore ::default() because these calls do explicitly name the type. if let QPath::Resolved(None, _path) = qpath; let expr_ty = cx.typeck_results().expr_ty(expr); @@ -290,3 +291,16 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op } } } + +/// Returns whether `expr` is the update syntax base: `Foo { a: 1, .. base }` +fn is_update_syntax_base<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let ExprKind::Struct(_, _, Some(base)) = parent.kind; + then { + base.hir_id == expr.hir_id + } else { + false + } + } +} diff --git a/tests/ui/default_trait_access.fixed b/tests/ui/default_trait_access.fixed index 9114d8754dcc8..264dd4efaeb86 100644 --- a/tests/ui/default_trait_access.fixed +++ b/tests/ui/default_trait_access.fixed @@ -46,9 +46,14 @@ fn main() { let s19 = ::default(); + let s20 = UpdateSyntax { + s: "foo", + ..Default::default() + }; + println!( - "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}], [{:?}]", - s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, + "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", + s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, ); } @@ -86,3 +91,9 @@ struct ArrayDerivedDefault { #[derive(Debug, Default)] struct TupleStructDerivedDefault(String); + +#[derive(Debug, Default)] +struct UpdateSyntax { + pub s: &'static str, + pub u: u64, +} diff --git a/tests/ui/default_trait_access.rs b/tests/ui/default_trait_access.rs index 8a5f0d6a74976..a0930fab8e7c8 100644 --- a/tests/ui/default_trait_access.rs +++ b/tests/ui/default_trait_access.rs @@ -46,9 +46,14 @@ fn main() { let s19 = ::default(); + let s20 = UpdateSyntax { + s: "foo", + ..Default::default() + }; + println!( - "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}], [{:?}]", - s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, + "[{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}] [{:?}]", + s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, s20, ); } @@ -86,3 +91,9 @@ struct ArrayDerivedDefault { #[derive(Debug, Default)] struct TupleStructDerivedDefault(String); + +#[derive(Debug, Default)] +struct UpdateSyntax { + pub s: &'static str, + pub u: u64, +} From 9af2be8e9657af077e573ebc3bf6ff24493f0bb9 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Feb 2022 23:14:39 -0500 Subject: [PATCH 24/55] Don't lint `needless_borrow` in method receiver positions --- clippy_lints/src/dereference.rs | 2 +- tests/ui/needless_borrow.fixed | 6 +++--- tests/ui/needless_borrow.rs | 6 +++--- tests/ui/needless_borrow.stderr | 20 +------------------- 4 files changed, 8 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 6d0851d804c26..fe3911983421b 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -528,7 +528,7 @@ fn is_auto_reborrow_position(parent: Option>) -> bool { 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::MethodCall(_, [self_arg, ..], _) => self_arg.hir_id == child_id, ExprKind::Field(..) => true, ExprKind::Call(f, _) => f.hir_id == child_id, _ => false, diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index b856f1375d303..efeb5cf5b2b25 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -64,9 +64,9 @@ fn main() { *x = 5; let s = String::new(); - let _ = s.len(); - let _ = s.capacity(); - let _ = s.capacity(); + // let _ = (&s).len(); + // let _ = (&s).capacity(); + // let _ = (&&s).capacity(); let x = (1, 2); let _ = x.0; diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 0bfe222a3dc17..3e416a0eb84aa 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -64,9 +64,9 @@ fn main() { *x = 5; let s = String::new(); - let _ = (&s).len(); - let _ = (&s).capacity(); - let _ = (&&s).capacity(); + // let _ = (&s).len(); + // let _ = (&s).capacity(); + // let _ = (&&s).capacity(); let x = (1, 2); let _ = (&x).0; diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index b90e8448db0a3..05591ce4117b2 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -84,24 +84,6 @@ error: this expression creates a reference which is immediately dereferenced by 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 | @@ -114,5 +96,5 @@ error: this expression borrows a value the compiler would automatically borrow LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` -error: aborting due to 19 previous errors +error: aborting due to 16 previous errors From 9bfcbf4f027fea5096e6a721b1b73fbf057e14cf Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 17 Feb 2022 10:48:24 -0500 Subject: [PATCH 25/55] Remove some redundant checks in various matches lints --- .../src/matches/match_like_matches.rs | 45 +++--- clippy_lints/src/matches/match_same_arms.rs | 152 +++++++++--------- clippy_lints/src/matches/mod.rs | 48 +++--- .../src/matches/redundant_pattern_match.rs | 12 +- 4 files changed, 128 insertions(+), 129 deletions(-) diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index d605b6d73c09d..2e1f7646eb400 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -3,7 +3,7 @@ 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_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::source_map::Spanned; @@ -11,7 +11,7 @@ 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 { +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::IfLet { let_pat, let_expr, @@ -19,7 +19,7 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool if_else: Some(if_else), }) = higher::IfLet::hir(cx, expr) { - return find_matches_sugg( + find_matches_sugg( cx, let_expr, IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]), @@ -27,25 +27,28 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool 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 +pub(super) fn check_match<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + scrutinee: &'tcx Expr<'_>, + arms: &'tcx [Arm<'tcx>], +) -> bool { + find_matches_sugg( + cx, + scrutinee, + arms.iter().map(|arm| { + ( + cx.tcx.hir().attrs(arm.hir_id), + Some(arm.pat), + arm.body, + arm.guard.as_ref(), + ) + }), + e, + false, + ) } /// Lint a `match` or `if let` for replacement by `matches!` diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 271a386859555..d11dda57e6fd9 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,96 +1,94 @@ 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_hir::{Arm, Expr, HirId, HirIdMap, HirIdSet, Pat, PatKind}; use rustc_lint::LateContext; 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() - }; +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { + 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 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 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 - } + 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 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()) + } }; + // 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"); + 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… + // 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, ""); + 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"); - } - }, - ); - } + 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"); + } + }, + ); } } diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index b5ee4561f06ec..614a824010f96 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -604,33 +604,35 @@ impl<'tcx> LateLintPass<'tcx> for Matches { return; } - redundant_pattern_match::check(cx, expr); + if let ExprKind::Match(ex, arms, source) = expr.kind { + if source == MatchSource::Normal { + if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) + && match_like_matches::check_match(cx, expr, ex, arms)) + { + match_same_arms::check(cx, arms); + } - if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { - if !match_like_matches::check(cx, expr) { - match_same_arms::check(cx, expr); - } - } else { - match_same_arms::check(cx, expr); - } - - if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind { - single_match::check(cx, ex, arms, expr); - match_bool::check(cx, ex, arms, expr); - overlapping_arms::check(cx, ex, arms); - match_wild_err_arm::check(cx, ex, arms); - match_wild_enum::check(cx, ex, arms); - match_as_ref::check(cx, ex, arms, expr); - wild_in_or_pats::check(cx, arms); + redundant_pattern_match::check_match(cx, expr, ex, arms); + single_match::check(cx, ex, arms, expr); + match_bool::check(cx, ex, arms, expr); + overlapping_arms::check(cx, ex, arms); + match_wild_err_arm::check(cx, ex, arms); + match_wild_enum::check(cx, ex, arms); + match_as_ref::check(cx, ex, arms, expr); + wild_in_or_pats::check(cx, arms); - if self.infallible_destructuring_match_linted { - self.infallible_destructuring_match_linted = false; - } else { - match_single_binding::check(cx, ex, arms, expr); + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + match_single_binding::check(cx, ex, arms, expr); + } } - } - if let ExprKind::Match(ex, arms, _) = expr.kind { match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); + } else { + if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { + match_like_matches::check(cx, expr); + } + redundant_pattern_match::check(cx, expr); } } diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 61c5fa0872f6a..c491b2775d847 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -12,13 +12,13 @@ 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, + Arm, Block, Expr, ExprKind, LangItem, 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<'_>) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::IfLet { if_else, let_pat, @@ -27,11 +27,7 @@ pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx 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) { + } else 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); } } @@ -304,7 +300,7 @@ fn find_sugg_for_if_let<'tcx>( ); } -fn find_sugg_for_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op: &Expr<'_>, arms: &[Arm<'_>]) { +pub(super) fn check_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); From 4abaa0239ad2f437b1a872976e0dc0a91c8f3789 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 17 Feb 2022 16:00:04 +0000 Subject: [PATCH 26/55] Revert "Auto merge of #91403 - cjgillot:inherit-async, r=oli-obk" This reverts commit 3cfa4def7c87d571bd46d92fed608edf8fad236e, reversing changes made to 5d8767cb229b097fedb1dd4bd9420d463c37774f. --- tests/ui/manual_async_fn.fixed | 1 - tests/ui/manual_async_fn.rs | 1 - tests/ui/manual_async_fn.stderr | 2 +- tests/ui/needless_lifetimes.stderr | 8 +------- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/ui/manual_async_fn.fixed b/tests/ui/manual_async_fn.fixed index e9ca66f125d8f..136cc96be70ca 100644 --- a/tests/ui/manual_async_fn.fixed +++ b/tests/ui/manual_async_fn.fixed @@ -80,7 +80,6 @@ fn elided_not_bound(_: &i32) -> impl Future { async { 42 } } -#[allow(clippy::needless_lifetimes)] async fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> i32 { 42 } // should be ignored diff --git a/tests/ui/manual_async_fn.rs b/tests/ui/manual_async_fn.rs index c3fa846485ba3..ddc453ffdb750 100644 --- a/tests/ui/manual_async_fn.rs +++ b/tests/ui/manual_async_fn.rs @@ -98,7 +98,6 @@ fn elided_not_bound(_: &i32) -> impl Future { async { 42 } } -#[allow(clippy::needless_lifetimes)] fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { async { 42 } } diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr index b83abfccd4e12..7435f46074c81 100644 --- a/tests/ui/manual_async_fn.stderr +++ b/tests/ui/manual_async_fn.stderr @@ -140,7 +140,7 @@ LL | fn elided(_: &i32) -> impl Future + '_ { 42 } | ~~~~~~ error: this function can be simplified using the `async fn` syntax - --> $DIR/manual_async_fn.rs:102:1 + --> $DIR/manual_async_fn.rs:101:1 | LL | fn explicit<'a, 'b>(_: &'a i32, _: &'b i32) -> impl Future + 'a + 'b { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/needless_lifetimes.stderr b/tests/ui/needless_lifetimes.stderr index 8df50d79ca573..ffa152427a977 100644 --- a/tests/ui/needless_lifetimes.stderr +++ b/tests/ui/needless_lifetimes.stderr @@ -18,12 +18,6 @@ error: explicit lifetimes given in parameter types where they could be elided (o 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:37:1 - | -LL | async fn func<'a>(args: &[&'a str]) -> Option<&'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:56:1 | @@ -198,5 +192,5 @@ error: explicit lifetimes given in parameter types where they could be elided (o LL | fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 33 previous errors +error: aborting due to 32 previous errors From cdf9a28006b1216653eac1bdc45a83e436fac9ba Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sat, 12 Feb 2022 10:23:07 +0100 Subject: [PATCH 27/55] Improve lint message of await_holding_* Improves the message of the lints await_holding_lock and await_holding_refcell_ref. Now also actually tests RwLock. --- clippy_lints/src/await_holding_invalid.rs | 31 +++++++---- tests/ui/await_holding_lock.rs | 26 ++++++++- tests/ui/await_holding_lock.stderr | 64 +++++++++++++++++------ tests/ui/await_holding_refcell_ref.stderr | 30 ++++++----- 4 files changed, 114 insertions(+), 37 deletions(-) diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 1cc3418d4748c..f7bc8395c822d 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{match_def_path, paths}; use rustc_hir::def_id::DefId; use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind}; @@ -118,23 +118,36 @@ fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorType for ty_cause in ty_causes { if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() { if is_mutex_guard(cx, adt.did) { - span_lint_and_note( + span_lint_and_then( cx, AWAIT_HOLDING_LOCK, ty_cause.span, - "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await", - ty_cause.scope_span.or(Some(span)), - "these are all the await points this lock is held through", + "this `MutexGuard` is held across an `await` point", + |diag| { + diag.help( + "consider using an async-aware `Mutex` type or ensuring the \ + `MutexGuard` is dropped before calling await", + ); + diag.span_note( + ty_cause.scope_span.unwrap_or(span), + "these are all the `await` points this lock is held through", + ); + }, ); } if is_refcell_ref(cx, adt.did) { - span_lint_and_note( + span_lint_and_then( cx, AWAIT_HOLDING_REFCELL_REF, ty_cause.span, - "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await", - ty_cause.scope_span.or(Some(span)), - "these are all the await points this ref is held through", + "this `RefCell` reference is held across an `await` point", + |diag| { + diag.help("ensure the reference is dropped before calling `await`"); + diag.span_note( + ty_cause.scope_span.unwrap_or(span), + "these are all the `await` points this reference is held through", + ); + }, ); } } diff --git a/tests/ui/await_holding_lock.rs b/tests/ui/await_holding_lock.rs index dd6640a387a23..f03356d83df01 100644 --- a/tests/ui/await_holding_lock.rs +++ b/tests/ui/await_holding_lock.rs @@ -1,6 +1,6 @@ #![warn(clippy::await_holding_lock)] -use std::sync::Mutex; +use std::sync::{Mutex, RwLock}; async fn bad(x: &Mutex) -> u32 { let guard = x.lock().unwrap(); @@ -17,6 +17,30 @@ async fn good(x: &Mutex) -> u32 { 47 } +pub async fn bad_rw(x: &RwLock) -> u32 { + let guard = x.read().unwrap(); + baz().await +} + +pub async fn bad_rw_write(x: &RwLock) -> u32 { + let mut guard = x.write().unwrap(); + baz().await +} + +pub async fn good_rw(x: &RwLock) -> u32 { + { + let guard = x.read().unwrap(); + let y = *guard + 1; + } + { + let mut guard = x.write().unwrap(); + *guard += 1; + } + baz().await; + let guard = x.read().unwrap(); + 47 +} + async fn baz() -> u32 { 42 } diff --git a/tests/ui/await_holding_lock.stderr b/tests/ui/await_holding_lock.stderr index ddfb104cdfbd0..3a501acbb67a6 100644 --- a/tests/ui/await_holding_lock.stderr +++ b/tests/ui/await_holding_lock.stderr @@ -1,11 +1,12 @@ -error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await +error: this `MutexGuard` is held across an `await` point --> $DIR/await_holding_lock.rs:6:9 | LL | let guard = x.lock().unwrap(); | ^^^^^ | = note: `-D clippy::await-holding-lock` implied by `-D warnings` -note: these are all the await points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through --> $DIR/await_holding_lock.rs:6:5 | LL | / let guard = x.lock().unwrap(); @@ -13,14 +14,45 @@ LL | | baz().await LL | | } | |_^ -error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await - --> $DIR/await_holding_lock.rs:27:9 +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:21:9 + | +LL | let guard = x.read().unwrap(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:21:5 + | +LL | / let guard = x.read().unwrap(); +LL | | baz().await +LL | | } + | |_^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:26:9 + | +LL | let mut guard = x.write().unwrap(); + | ^^^^^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:26:5 + | +LL | / let mut guard = x.write().unwrap(); +LL | | baz().await +LL | | } + | |_^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:51:9 | LL | let guard = x.lock().unwrap(); | ^^^^^ | -note: these are all the await points this lock is held through - --> $DIR/await_holding_lock.rs:27:5 + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:51:5 | LL | / let guard = x.lock().unwrap(); LL | | @@ -31,33 +63,35 @@ LL | | first + second + third LL | | } | |_^ -error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await - --> $DIR/await_holding_lock.rs:40:13 +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:64:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ | -note: these are all the await points this lock is held through - --> $DIR/await_holding_lock.rs:40:9 + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:64:9 | LL | / let guard = x.lock().unwrap(); LL | | baz().await LL | | }; | |_____^ -error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await - --> $DIR/await_holding_lock.rs:52:13 +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:76:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ | -note: these are all the await points this lock is held through - --> $DIR/await_holding_lock.rs:52:9 + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:76:9 | LL | / let guard = x.lock().unwrap(); LL | | baz().await LL | | } | |_____^ -error: aborting due to 4 previous errors +error: aborting due to 6 previous errors diff --git a/tests/ui/await_holding_refcell_ref.stderr b/tests/ui/await_holding_refcell_ref.stderr index 67cc0032be2f4..4339fca735dd4 100644 --- a/tests/ui/await_holding_refcell_ref.stderr +++ b/tests/ui/await_holding_refcell_ref.stderr @@ -1,11 +1,12 @@ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:6:9 | LL | let b = x.borrow(); | ^ | = note: `-D clippy::await-holding-refcell-ref` implied by `-D warnings` -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:6:5 | LL | / let b = x.borrow(); @@ -13,13 +14,14 @@ LL | | baz().await LL | | } | |_^ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:11:9 | LL | let b = x.borrow_mut(); | ^ | -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:11:5 | LL | / let b = x.borrow_mut(); @@ -27,13 +29,14 @@ LL | | baz().await LL | | } | |_^ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:32:9 | LL | let b = x.borrow_mut(); | ^ | -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:32:5 | LL | / let b = x.borrow_mut(); @@ -45,13 +48,14 @@ LL | | first + second + third LL | | } | |_^ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:44:9 | LL | let b = x.borrow_mut(); | ^ | -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:44:5 | LL | / let b = x.borrow_mut(); @@ -63,13 +67,14 @@ LL | | first + second + third LL | | } | |_^ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:59:13 | LL | let b = x.borrow_mut(); | ^ | -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:59:9 | LL | / let b = x.borrow_mut(); @@ -77,13 +82,14 @@ LL | | baz().await LL | | }; | |_____^ -error: this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await +error: this `RefCell` reference is held across an `await` point --> $DIR/await_holding_refcell_ref.rs:71:13 | LL | let b = x.borrow_mut(); | ^ | -note: these are all the await points this ref is held through + = help: ensure the reference is dropped before calling `await` +note: these are all the `await` points this reference is held through --> $DIR/await_holding_refcell_ref.rs:71:9 | LL | / let b = x.borrow_mut(); From c4944fb60d102a4a4e0cb5f3f86379bea2338ed0 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sat, 12 Feb 2022 10:32:44 +0100 Subject: [PATCH 28/55] Actually lint parking_lot in await_holding_lock This adapts the paths for the parking_lot mutex guards, so that parking_lot mutexes and RwLocks actually get linted. This is now also tested. --- clippy_utils/src/paths.rs | 6 +- tests/ui/await_holding_lock.rs | 204 +++++++++++++++++++++-------- tests/ui/await_holding_lock.stderr | 180 +++++++++++++++++++------ 3 files changed, 287 insertions(+), 103 deletions(-) diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 48c0e89e4d2b2..b54bd3a4fef0b 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -105,9 +105,9 @@ pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"]; pub const PARKING_LOT_RAWMUTEX: [&str; 3] = ["parking_lot", "raw_mutex", "RawMutex"]; pub const PARKING_LOT_RAWRWLOCK: [&str; 3] = ["parking_lot", "raw_rwlock", "RawRwLock"]; -pub const PARKING_LOT_MUTEX_GUARD: [&str; 2] = ["parking_lot", "MutexGuard"]; -pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 2] = ["parking_lot", "RwLockReadGuard"]; -pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 2] = ["parking_lot", "RwLockWriteGuard"]; +pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard"]; +pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"]; +pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"]; pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"]; pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"]; pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"]; diff --git a/tests/ui/await_holding_lock.rs b/tests/ui/await_holding_lock.rs index f03356d83df01..1a57db91ab59e 100644 --- a/tests/ui/await_holding_lock.rs +++ b/tests/ui/await_holding_lock.rs @@ -1,88 +1,178 @@ #![warn(clippy::await_holding_lock)] -use std::sync::{Mutex, RwLock}; +// When adding or modifying a test, please do the same for parking_lot::Mutex. +mod std_mutex { + use std::sync::{Mutex, RwLock}; -async fn bad(x: &Mutex) -> u32 { - let guard = x.lock().unwrap(); - baz().await -} + pub async fn bad(x: &Mutex) -> u32 { + let guard = x.lock().unwrap(); + baz().await + } -async fn good(x: &Mutex) -> u32 { - { + pub async fn good(x: &Mutex) -> u32 { + { + let guard = x.lock().unwrap(); + let y = *guard + 1; + } + baz().await; let guard = x.lock().unwrap(); - let y = *guard + 1; + 47 } - baz().await; - let guard = x.lock().unwrap(); - 47 -} -pub async fn bad_rw(x: &RwLock) -> u32 { - let guard = x.read().unwrap(); - baz().await -} + pub async fn bad_rw(x: &RwLock) -> u32 { + let guard = x.read().unwrap(); + baz().await + } -pub async fn bad_rw_write(x: &RwLock) -> u32 { - let mut guard = x.write().unwrap(); - baz().await -} + pub async fn bad_rw_write(x: &RwLock) -> u32 { + let mut guard = x.write().unwrap(); + baz().await + } -pub async fn good_rw(x: &RwLock) -> u32 { - { + pub async fn good_rw(x: &RwLock) -> u32 { + { + let guard = x.read().unwrap(); + let y = *guard + 1; + } + { + let mut guard = x.write().unwrap(); + *guard += 1; + } + baz().await; let guard = x.read().unwrap(); - let y = *guard + 1; + 47 } - { - let mut guard = x.write().unwrap(); - *guard += 1; + + pub async fn baz() -> u32 { + 42 } - baz().await; - let guard = x.read().unwrap(); - 47 -} -async fn baz() -> u32 { - 42 -} + pub async fn also_bad(x: &Mutex) -> u32 { + let first = baz().await; + + let guard = x.lock().unwrap(); -async fn also_bad(x: &Mutex) -> u32 { - let first = baz().await; + let second = baz().await; - let guard = x.lock().unwrap(); + let third = baz().await; - let second = baz().await; + first + second + third + } + + pub async fn not_good(x: &Mutex) -> u32 { + let first = baz().await; + + let second = { + let guard = x.lock().unwrap(); + baz().await + }; - let third = baz().await; + let third = baz().await; - first + second + third + first + second + third + } + + #[allow(clippy::manual_async_fn)] + pub fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { + async move { + let guard = x.lock().unwrap(); + baz().await + } + } } -async fn not_good(x: &Mutex) -> u32 { - let first = baz().await; +// When adding or modifying a test, please do the same for std::Mutex. +mod parking_lot_mutex { + use parking_lot::{Mutex, RwLock}; - let second = { - let guard = x.lock().unwrap(); + pub async fn bad(x: &Mutex) -> u32 { + let guard = x.lock(); baz().await - }; + } - let third = baz().await; + pub async fn good(x: &Mutex) -> u32 { + { + let guard = x.lock(); + let y = *guard + 1; + } + baz().await; + let guard = x.lock(); + 47 + } - first + second + third -} + pub async fn bad_rw(x: &RwLock) -> u32 { + let guard = x.read(); + baz().await + } -#[allow(clippy::manual_async_fn)] -fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { - async move { - let guard = x.lock().unwrap(); + pub async fn bad_rw_write(x: &RwLock) -> u32 { + let mut guard = x.write(); baz().await } + + pub async fn good_rw(x: &RwLock) -> u32 { + { + let guard = x.read(); + let y = *guard + 1; + } + { + let mut guard = x.write(); + *guard += 1; + } + baz().await; + let guard = x.read(); + 47 + } + + pub async fn baz() -> u32 { + 42 + } + + pub async fn also_bad(x: &Mutex) -> u32 { + let first = baz().await; + + let guard = x.lock(); + + let second = baz().await; + + let third = baz().await; + + first + second + third + } + + pub async fn not_good(x: &Mutex) -> u32 { + let first = baz().await; + + let second = { + let guard = x.lock(); + baz().await + }; + + let third = baz().await; + + first + second + third + } + + #[allow(clippy::manual_async_fn)] + pub fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { + async move { + let guard = x.lock(); + baz().await + } + } } fn main() { - let m = Mutex::new(100); - good(&m); - bad(&m); - also_bad(&m); - not_good(&m); - block_bad(&m); + let m = std::sync::Mutex::new(100); + std_mutex::good(&m); + std_mutex::bad(&m); + std_mutex::also_bad(&m); + std_mutex::not_good(&m); + std_mutex::block_bad(&m); + + let m = parking_lot::Mutex::new(100); + parking_lot_mutex::good(&m); + parking_lot_mutex::bad(&m); + parking_lot_mutex::also_bad(&m); + parking_lot_mutex::not_good(&m); } diff --git a/tests/ui/await_holding_lock.stderr b/tests/ui/await_holding_lock.stderr index 3a501acbb67a6..a6c1dd228e46f 100644 --- a/tests/ui/await_holding_lock.stderr +++ b/tests/ui/await_holding_lock.stderr @@ -1,97 +1,191 @@ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:6:9 + --> $DIR/await_holding_lock.rs:8:13 | -LL | let guard = x.lock().unwrap(); - | ^^^^^ +LL | let guard = x.lock().unwrap(); + | ^^^^^ | = note: `-D clippy::await-holding-lock` implied by `-D warnings` = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:6:5 + --> $DIR/await_holding_lock.rs:8:9 | -LL | / let guard = x.lock().unwrap(); -LL | | baz().await -LL | | } - | |_^ +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:21:9 + --> $DIR/await_holding_lock.rs:23:13 | -LL | let guard = x.read().unwrap(); - | ^^^^^ +LL | let guard = x.read().unwrap(); + | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:21:5 + --> $DIR/await_holding_lock.rs:23:9 | -LL | / let guard = x.read().unwrap(); -LL | | baz().await -LL | | } - | |_^ +LL | / let guard = x.read().unwrap(); +LL | | baz().await +LL | | } + | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:26:9 + --> $DIR/await_holding_lock.rs:28:13 | -LL | let mut guard = x.write().unwrap(); - | ^^^^^^^^^ +LL | let mut guard = x.write().unwrap(); + | ^^^^^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:26:5 + --> $DIR/await_holding_lock.rs:28:9 | -LL | / let mut guard = x.write().unwrap(); -LL | | baz().await -LL | | } - | |_^ +LL | / let mut guard = x.write().unwrap(); +LL | | baz().await +LL | | } + | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:51:9 + --> $DIR/await_holding_lock.rs:53:13 | -LL | let guard = x.lock().unwrap(); - | ^^^^^ +LL | let guard = x.lock().unwrap(); + | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:51:5 + --> $DIR/await_holding_lock.rs:53:9 | -LL | / let guard = x.lock().unwrap(); +LL | / let guard = x.lock().unwrap(); LL | | -LL | | let second = baz().await; +LL | | let second = baz().await; LL | | ... | -LL | | first + second + third -LL | | } - | |_^ +LL | | first + second + third +LL | | } + | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:64:13 + --> $DIR/await_holding_lock.rs:66:17 | -LL | let guard = x.lock().unwrap(); +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:66:13 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | }; + | |_________^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:78:17 + | +LL | let guard = x.lock().unwrap(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:78:13 + | +LL | / let guard = x.lock().unwrap(); +LL | | baz().await +LL | | } + | |_________^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:89:13 + | +LL | let guard = x.lock(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:64:9 + --> $DIR/await_holding_lock.rs:89:9 | -LL | / let guard = x.lock().unwrap(); +LL | / let guard = x.lock(); LL | | baz().await -LL | | }; +LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:76:13 + --> $DIR/await_holding_lock.rs:104:13 | -LL | let guard = x.lock().unwrap(); +LL | let guard = x.read(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:76:9 + --> $DIR/await_holding_lock.rs:104:9 | -LL | / let guard = x.lock().unwrap(); +LL | / let guard = x.read(); LL | | baz().await LL | | } | |_____^ -error: aborting due to 6 previous errors +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:109:13 + | +LL | let mut guard = x.write(); + | ^^^^^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:109:9 + | +LL | / let mut guard = x.write(); +LL | | baz().await +LL | | } + | |_____^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:134:13 + | +LL | let guard = x.lock(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:134:9 + | +LL | / let guard = x.lock(); +LL | | +LL | | let second = baz().await; +LL | | +... | +LL | | first + second + third +LL | | } + | |_____^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:147:17 + | +LL | let guard = x.lock(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:147:13 + | +LL | / let guard = x.lock(); +LL | | baz().await +LL | | }; + | |_________^ + +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:159:17 + | +LL | let guard = x.lock(); + | ^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:159:13 + | +LL | / let guard = x.lock(); +LL | | baz().await +LL | | } + | |_________^ + +error: aborting due to 12 previous errors From c5709419b1b31c8d4c5e369c7b9adbf592c599ca Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sat, 12 Feb 2022 11:30:03 +0100 Subject: [PATCH 29/55] Add test for drop-before-await FP --- tests/ui/await_holding_lock.rs | 30 +++++++++---- tests/ui/await_holding_lock.stderr | 67 +++++++++++++++++++----------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/tests/ui/await_holding_lock.rs b/tests/ui/await_holding_lock.rs index 1a57db91ab59e..57e5b55045b95 100644 --- a/tests/ui/await_holding_lock.rs +++ b/tests/ui/await_holding_lock.rs @@ -2,6 +2,7 @@ // When adding or modifying a test, please do the same for parking_lot::Mutex. mod std_mutex { + use super::baz; use std::sync::{Mutex, RwLock}; pub async fn bad(x: &Mutex) -> u32 { @@ -43,10 +44,6 @@ mod std_mutex { 47 } - pub async fn baz() -> u32 { - 42 - } - pub async fn also_bad(x: &Mutex) -> u32 { let first = baz().await; @@ -83,6 +80,7 @@ mod std_mutex { // When adding or modifying a test, please do the same for std::Mutex. mod parking_lot_mutex { + use super::baz; use parking_lot::{Mutex, RwLock}; pub async fn bad(x: &Mutex) -> u32 { @@ -124,10 +122,6 @@ mod parking_lot_mutex { 47 } - pub async fn baz() -> u32 { - 42 - } - pub async fn also_bad(x: &Mutex) -> u32 { let first = baz().await; @@ -162,6 +156,26 @@ mod parking_lot_mutex { } } +async fn baz() -> u32 { + 42 +} + +async fn no_await(x: std::sync::Mutex) { + let mut guard = x.lock().unwrap(); + *guard += 1; +} + +// FIXME: FP, because the `MutexGuard` is dropped before crossing the await point. This is +// something the needs to be fixed in rustc. There's already drop-tracking, but this is currently +// disabled, see rust-lang/rust#93751. This case isn't picked up by drop-tracking though. If the +// `*guard += 1` is removed it is picked up. +async fn dropped_before_await(x: std::sync::Mutex) { + let mut guard = x.lock().unwrap(); + *guard += 1; + drop(guard); + baz().await; +} + fn main() { let m = std::sync::Mutex::new(100); std_mutex::good(&m); diff --git a/tests/ui/await_holding_lock.stderr b/tests/ui/await_holding_lock.stderr index a6c1dd228e46f..976da8d924247 100644 --- a/tests/ui/await_holding_lock.stderr +++ b/tests/ui/await_holding_lock.stderr @@ -1,5 +1,5 @@ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:8:13 + --> $DIR/await_holding_lock.rs:9:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ @@ -7,7 +7,7 @@ LL | let guard = x.lock().unwrap(); = note: `-D clippy::await-holding-lock` implied by `-D warnings` = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:8:9 + --> $DIR/await_holding_lock.rs:9:9 | LL | / let guard = x.lock().unwrap(); LL | | baz().await @@ -15,14 +15,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:23:13 + --> $DIR/await_holding_lock.rs:24:13 | LL | let guard = x.read().unwrap(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:23:9 + --> $DIR/await_holding_lock.rs:24:9 | LL | / let guard = x.read().unwrap(); LL | | baz().await @@ -30,14 +30,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:28:13 + --> $DIR/await_holding_lock.rs:29:13 | LL | let mut guard = x.write().unwrap(); | ^^^^^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:28:9 + --> $DIR/await_holding_lock.rs:29:9 | LL | / let mut guard = x.write().unwrap(); LL | | baz().await @@ -45,14 +45,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:53:13 + --> $DIR/await_holding_lock.rs:50:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:53:9 + --> $DIR/await_holding_lock.rs:50:9 | LL | / let guard = x.lock().unwrap(); LL | | @@ -64,14 +64,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:66:17 + --> $DIR/await_holding_lock.rs:63:17 | LL | let guard = x.lock().unwrap(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:66:13 + --> $DIR/await_holding_lock.rs:63:13 | LL | / let guard = x.lock().unwrap(); LL | | baz().await @@ -79,14 +79,14 @@ LL | | }; | |_________^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:78:17 + --> $DIR/await_holding_lock.rs:75:17 | LL | let guard = x.lock().unwrap(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:78:13 + --> $DIR/await_holding_lock.rs:75:13 | LL | / let guard = x.lock().unwrap(); LL | | baz().await @@ -94,14 +94,14 @@ LL | | } | |_________^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:89:13 + --> $DIR/await_holding_lock.rs:87:13 | LL | let guard = x.lock(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:89:9 + --> $DIR/await_holding_lock.rs:87:9 | LL | / let guard = x.lock(); LL | | baz().await @@ -109,14 +109,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:104:13 + --> $DIR/await_holding_lock.rs:102:13 | LL | let guard = x.read(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:104:9 + --> $DIR/await_holding_lock.rs:102:9 | LL | / let guard = x.read(); LL | | baz().await @@ -124,14 +124,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:109:13 + --> $DIR/await_holding_lock.rs:107:13 | LL | let mut guard = x.write(); | ^^^^^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:109:9 + --> $DIR/await_holding_lock.rs:107:9 | LL | / let mut guard = x.write(); LL | | baz().await @@ -139,14 +139,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:134:13 + --> $DIR/await_holding_lock.rs:128:13 | LL | let guard = x.lock(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:134:9 + --> $DIR/await_holding_lock.rs:128:9 | LL | / let guard = x.lock(); LL | | @@ -158,14 +158,14 @@ LL | | } | |_____^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:147:17 + --> $DIR/await_holding_lock.rs:141:17 | LL | let guard = x.lock(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:147:13 + --> $DIR/await_holding_lock.rs:141:13 | LL | / let guard = x.lock(); LL | | baz().await @@ -173,19 +173,36 @@ LL | | }; | |_________^ error: this `MutexGuard` is held across an `await` point - --> $DIR/await_holding_lock.rs:159:17 + --> $DIR/await_holding_lock.rs:153:17 | LL | let guard = x.lock(); | ^^^^^ | = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await note: these are all the `await` points this lock is held through - --> $DIR/await_holding_lock.rs:159:13 + --> $DIR/await_holding_lock.rs:153:13 | LL | / let guard = x.lock(); LL | | baz().await LL | | } | |_________^ -error: aborting due to 12 previous errors +error: this `MutexGuard` is held across an `await` point + --> $DIR/await_holding_lock.rs:173:9 + | +LL | let mut guard = x.lock().unwrap(); + | ^^^^^^^^^ + | + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await +note: these are all the `await` points this lock is held through + --> $DIR/await_holding_lock.rs:173:5 + | +LL | / let mut guard = x.lock().unwrap(); +LL | | *guard += 1; +LL | | drop(guard); +LL | | baz().await; +LL | | } + | |_^ + +error: aborting due to 13 previous errors From ea0ce7bb76bb01741fc3ba2ca32a80c75a380d0f Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 17 Feb 2022 17:57:39 +0100 Subject: [PATCH 30/55] Move await_holding_* lints to suspicious and improve doc Even though the FP for that the lints were moved to pedantic isn't fixed yet, running the lintcheck tool over the most popular 279 crates didn't trigger this lint once. I would say that this lint is valuable enough, despite the known FP, to be warn-by-default. Especially since a pretty nice workaround exists. --- clippy_lints/src/await_holding_invalid.rs | 88 ++++++++++++++------- clippy_lints/src/lib.register_all.rs | 2 + clippy_lints/src/lib.register_pedantic.rs | 2 - clippy_lints/src/lib.register_suspicious.rs | 2 + 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index f7bc8395c822d..f0979840ff8d8 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -9,8 +9,7 @@ use rustc_span::Span; declare_clippy_lint! { /// ### What it does - /// Checks for calls to await while holding a - /// non-async-aware MutexGuard. + /// Checks for calls to await while holding a non-async-aware MutexGuard. /// /// ### Why is this bad? /// The Mutex types found in std::sync and parking_lot @@ -22,41 +21,57 @@ declare_clippy_lint! { /// either by introducing a scope or an explicit call to Drop::drop. /// /// ### Known problems - /// Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). + /// Will report false positive for explicitly dropped guards + /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is + /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard. /// /// ### Example - /// ```rust,ignore - /// use std::sync::Mutex; - /// + /// ```rust + /// # use std::sync::Mutex; + /// # async fn baz() {} /// async fn foo(x: &Mutex) { - /// let guard = x.lock().unwrap(); + /// let mut guard = x.lock().unwrap(); /// *guard += 1; - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &Mutex) { + /// let mut guard = x.lock().unwrap(); + /// *guard += 1; + /// drop(guard); // explicit drop + /// baz().await; /// } /// ``` /// /// Use instead: - /// ```rust,ignore - /// use std::sync::Mutex; - /// + /// ```rust + /// # use std::sync::Mutex; + /// # async fn baz() {} /// async fn foo(x: &Mutex) { /// { - /// let guard = x.lock().unwrap(); + /// let mut guard = x.lock().unwrap(); /// *guard += 1; /// } - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &Mutex) { + /// { + /// let mut guard = x.lock().unwrap(); + /// *guard += 1; + /// } // guard dropped here at end of scope + /// baz().await; /// } /// ``` #[clippy::version = "1.45.0"] pub AWAIT_HOLDING_LOCK, - pedantic, - "Inside an async function, holding a MutexGuard while calling await" + suspicious, + "inside an async function, holding a `MutexGuard` while calling `await`" } declare_clippy_lint! { /// ### What it does - /// Checks for calls to await while holding a - /// `RefCell` `Ref` or `RefMut`. + /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`. /// /// ### Why is this bad? /// `RefCell` refs only check for exclusive mutable access @@ -64,35 +79,52 @@ declare_clippy_lint! { /// risks panics from a mutable ref shared while other refs are outstanding. /// /// ### Known problems - /// Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). + /// Will report false positive for explicitly dropped refs + /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is + /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref. /// /// ### Example - /// ```rust,ignore - /// use std::cell::RefCell; - /// + /// ```rust + /// # use std::cell::RefCell; + /// # async fn baz() {} /// async fn foo(x: &RefCell) { /// let mut y = x.borrow_mut(); /// *y += 1; - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &RefCell) { + /// let mut y = x.borrow_mut(); + /// *y += 1; + /// drop(y); // explicit drop + /// baz().await; /// } /// ``` /// /// Use instead: - /// ```rust,ignore - /// use std::cell::RefCell; - /// + /// ```rust + /// # use std::cell::RefCell; + /// # async fn baz() {} /// async fn foo(x: &RefCell) { /// { /// let mut y = x.borrow_mut(); /// *y += 1; /// } - /// bar.await; + /// baz().await; + /// } + /// + /// async fn bar(x: &RefCell) { + /// { + /// let mut y = x.borrow_mut(); + /// *y += 1; + /// } // y dropped here at end of scope + /// baz().await; /// } /// ``` #[clippy::version = "1.49.0"] pub AWAIT_HOLDING_REFCELL_REF, - pedantic, - "Inside an async function, holding a RefCell ref while calling await" + suspicious, + "inside an async function, holding a `RefCell` ref while calling `await`" } declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]); diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index de0ff5369079b..a94f6b528b47b 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -14,6 +14,8 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(attrs::DEPRECATED_SEMVER), LintId::of(attrs::MISMATCHED_TARGET_OS), LintId::of(attrs::USELESS_ATTRIBUTE), + LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(bit_mask::BAD_BIT_MASK), LintId::of(bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(blacklisted_name::BLACKLISTED_NAME), diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 1292675f4a96c..00d305131810d 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -4,8 +4,6 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(attrs::INLINE_ALWAYS), - LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), - LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(bit_mask::VERBOSE_BIT_MASK), LintId::of(borrow_as_ptr::BORROW_AS_PTR), LintId::of(bytecount::NAIVE_BYTECOUNT), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 10f8ae4b7f7fc..da56f80080499 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -5,6 +5,8 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![ LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), + LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), From 8ce2d46cacc8b91f1d252d52cb38f397ee990435 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 17 Feb 2022 13:43:59 -0500 Subject: [PATCH 31/55] Check for `cfg` attrubutes before linting `match` expressions --- clippy_lints/src/matches/mod.rs | 119 +++++++++++++++---- tests/ui/match_as_ref.fixed | 10 +- tests/ui/match_as_ref.rs | 10 +- tests/ui/match_bool.rs | 8 ++ tests/ui/match_expr_like_matches_macro.fixed | 15 +++ tests/ui/match_expr_like_matches_macro.rs | 15 +++ tests/ui/match_same_arms2.rs | 10 +- tests/ui/single_match.rs | 8 ++ 8 files changed, 171 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 614a824010f96..14dd7cb1dacbb 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,8 +1,11 @@ +use clippy_utils::source::{snippet_opt, walk_span_to_context}; use clippy_utils::{meets_msrv, msrvs}; -use rustc_hir::{Expr, ExprKind, Local, MatchSource, Pat}; +use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat}; +use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{Span, SpanData, SyntaxContext}; mod infalliable_detructuring_match; mod match_as_ref; @@ -605,29 +608,33 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } if let ExprKind::Match(ex, arms, source) = expr.kind { - if source == MatchSource::Normal { - if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) - && match_like_matches::check_match(cx, expr, ex, arms)) - { - match_same_arms::check(cx, arms); - } + if !contains_cfg_arm(cx, expr, ex, arms) { + if source == MatchSource::Normal { + if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) + && match_like_matches::check_match(cx, expr, ex, arms)) + { + match_same_arms::check(cx, arms); + } + + redundant_pattern_match::check_match(cx, expr, ex, arms); + single_match::check(cx, ex, arms, expr); + match_bool::check(cx, ex, arms, expr); + overlapping_arms::check(cx, ex, arms); + match_wild_enum::check(cx, ex, arms); + match_as_ref::check(cx, ex, arms, expr); - redundant_pattern_match::check_match(cx, expr, ex, arms); - single_match::check(cx, ex, arms, expr); - match_bool::check(cx, ex, arms, expr); - overlapping_arms::check(cx, ex, arms); - match_wild_err_arm::check(cx, ex, arms); - match_wild_enum::check(cx, ex, arms); - match_as_ref::check(cx, ex, arms, expr); - wild_in_or_pats::check(cx, arms); - - if self.infallible_destructuring_match_linted { - self.infallible_destructuring_match_linted = false; - } else { - match_single_binding::check(cx, ex, arms, expr); + if self.infallible_destructuring_match_linted { + self.infallible_destructuring_match_linted = false; + } else { + match_single_binding::check(cx, ex, arms, expr); + } } + match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } - match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); + + // These don't depend on a relationship between multiple arms + match_wild_err_arm::check(cx, ex, arms); + wild_in_or_pats::check(cx, arms); } else { if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) { match_like_matches::check(cx, expr); @@ -646,3 +653,73 @@ impl<'tcx> LateLintPass<'tcx> for Matches { extract_msrv_attr!(LateContext); } + +/// Checks if there are any arms with a `#[cfg(..)]` attribute. +fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool { + let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else { + // Shouldn't happen, but treat this as though a `cfg` attribute were found + return true; + }; + + let start = scrutinee_span.hi(); + let mut arm_spans = arms.iter().map(|arm| { + let data = arm.span.data(); + (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi)) + }); + let end = e.span.hi(); + + let found = arm_spans.try_fold(start, |start, range| { + let Some((end, next_start)) = range else { + // Shouldn't happen, but treat this as though a `cfg` attribute were found + return Err(()); + }; + let span = SpanData { + lo: start, + hi: end, + ctxt: SyntaxContext::root(), + parent: None, + } + .span(); + (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(()) + }); + match found { + Ok(start) => { + let span = SpanData { + lo: start, + hi: end, + ctxt: SyntaxContext::root(), + parent: None, + } + .span(); + span_contains_cfg(cx, span) + }, + Err(()) => true, + } +} + +fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { + let Some(snip) = snippet_opt(cx, s) else { + // Assume true. This would require either an invalid span, or one which crosses file boundaries. + return true; + }; + let mut pos = 0usize; + let mut iter = tokenize(&snip).map(|t| { + let start = pos; + pos += t.len; + (t.kind, start..pos) + }); + while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) { + let mut iter = iter.by_ref().skip_while(|(t, _)| { + matches!( + t, + TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } + ) + }); + if matches!(iter.next(), Some((TokenKind::OpenBracket, _))) + && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg") + { + return true; + } + } + false +} diff --git a/tests/ui/match_as_ref.fixed b/tests/ui/match_as_ref.fixed index c61eb9216643e..ddfa1e741ada4 100644 --- a/tests/ui/match_as_ref.fixed +++ b/tests/ui/match_as_ref.fixed @@ -32,4 +32,12 @@ mod issue4437 { } } -fn main() {} +fn main() { + // Don't lint + let _ = match Some(0) { + #[cfg(feature = "foo")] + Some(ref x) if *x > 50 => None, + Some(ref x) => Some(x), + None => None, + }; +} diff --git a/tests/ui/match_as_ref.rs b/tests/ui/match_as_ref.rs index 2fbd0b255faae..025d475ae13db 100644 --- a/tests/ui/match_as_ref.rs +++ b/tests/ui/match_as_ref.rs @@ -41,4 +41,12 @@ mod issue4437 { } } -fn main() {} +fn main() { + // Don't lint + let _ = match Some(0) { + #[cfg(feature = "foo")] + Some(ref x) if *x > 50 => None, + Some(ref x) => Some(x), + None => None, + }; +} diff --git a/tests/ui/match_bool.rs b/tests/ui/match_bool.rs index 9ed55ca7ae7f9..bcc999a49428d 100644 --- a/tests/ui/match_bool.rs +++ b/tests/ui/match_bool.rs @@ -50,6 +50,14 @@ fn match_bool() { 11..=20 => 2, _ => 3, }; + + // Don't lint + let _ = match test { + #[cfg(feature = "foo")] + true if option == 5 => 10, + true => 0, + false => 1, + }; } fn main() {} diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed index c611f76bf9605..36f233f334607 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_expr_like_matches_macro.fixed @@ -146,4 +146,19 @@ fn main() { let _res = matches!(&val, &Some(ref _a)); fun(val); } + + { + enum E { + A, + B, + C, + } + + let _ = match E::A { + E::B => true, + #[cfg(feature = "foo")] + E::A => true, + _ => false, + }; + } } diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_expr_like_matches_macro.rs index 2deeb84e74138..750f69fa5088f 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_expr_like_matches_macro.rs @@ -181,4 +181,19 @@ fn main() { }; fun(val); } + + { + enum E { + A, + B, + C, + } + + let _ = match E::A { + E::B => true, + #[cfg(feature = "foo")] + E::A => true, + _ => false, + }; + } } diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index da4e3020d5b83..67e1d518483c2 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -166,4 +166,12 @@ fn match_expr_like_matches_macro_priority() { }; } -fn main() {} +fn main() { + let _ = match Some(0) { + Some(0) => 0, + Some(1) => 1, + #[cfg(feature = "foo")] + Some(2) => 2, + _ => 1, + }; +} diff --git a/tests/ui/single_match.rs b/tests/ui/single_match.rs index bd37188804636..dd148edf5292d 100644 --- a/tests/ui/single_match.rs +++ b/tests/ui/single_match.rs @@ -234,4 +234,12 @@ macro_rules! single_match { fn main() { single_match!(5); + + // Don't lint + let _ = match Some(0) { + #[cfg(feature = "foo")] + Some(10) => 11, + Some(x) => x, + _ => 0, + }; } From aaeeed6a598c098c222104dd07996ab17d78aa54 Mon Sep 17 00:00:00 2001 From: Rodrigo Mantini Date: Thu, 17 Feb 2022 01:08:53 +0100 Subject: [PATCH 32/55] trigger ptr_as_ptr inside macros --- clippy_lints/src/casts/mod.rs | 5 ++++- tests/ui/auxiliary/macro_rules.rs | 7 +++++++ tests/ui/ptr_as_ptr.fixed | 15 +++++++++++++++ tests/ui/ptr_as_ptr.rs | 15 +++++++++++++++ tests/ui/ptr_as_ptr.stderr | 27 +++++++++++++++++++-------- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index aee1e50b94a2a..51d47b35454c2 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -419,6 +419,10 @@ impl_lint_pass!(Casts => [ impl<'tcx> LateLintPass<'tcx> for Casts { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !in_external_macro(cx.sess(), expr.span) { + ptr_as_ptr::check(cx, expr, &self.msrv); + } + if expr.span.from_expansion() { return; } @@ -455,7 +459,6 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_ref_to_mut::check(cx, expr); cast_ptr_alignment::check(cx, expr); char_lit_as_u8::check(cx, expr); - ptr_as_ptr::check(cx, expr, &self.msrv); } extract_msrv_attr!(LateContext); diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 0251fada9e85a..9f283337c7e13 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -120,3 +120,10 @@ macro_rules! mut_mut { let mut_mut_ty: &mut &mut u32 = &mut &mut 1u32; }; } + +#[macro_export] +macro_rules! ptr_as_ptr_cast { + ($ptr: ident) => { + $ptr as *const i32 + }; +} diff --git a/tests/ui/ptr_as_ptr.fixed b/tests/ui/ptr_as_ptr.fixed index 8346a9454f4ee..bea6be66a8e02 100644 --- a/tests/ui/ptr_as_ptr.fixed +++ b/tests/ui/ptr_as_ptr.fixed @@ -1,8 +1,17 @@ // run-rustfix +// aux-build:macro_rules.rs #![warn(clippy::ptr_as_ptr)] #![feature(custom_inner_attributes)] +extern crate macro_rules; + +macro_rules! cast_it { + ($ptr: ident) => { + $ptr.cast::() + }; +} + fn main() { let ptr: *const u32 = &42_u32; let mut_ptr: *mut u32 = &mut 42_u32; @@ -28,6 +37,12 @@ fn main() { // Ensure the lint doesn't produce unnecessary turbofish for inferred types. let _: *const i32 = ptr.cast(); let _: *mut i32 = mut_ptr.cast(); + + // Make sure the lint is triggered inside a macro + let _ = cast_it!(ptr); + + // Do not lint inside macros from external crates + let _ = macro_rules::ptr_as_ptr_cast!(ptr); } fn _msrv_1_37() { diff --git a/tests/ui/ptr_as_ptr.rs b/tests/ui/ptr_as_ptr.rs index b68d4bc0aaca1..ca2616b0069a0 100644 --- a/tests/ui/ptr_as_ptr.rs +++ b/tests/ui/ptr_as_ptr.rs @@ -1,8 +1,17 @@ // run-rustfix +// aux-build:macro_rules.rs #![warn(clippy::ptr_as_ptr)] #![feature(custom_inner_attributes)] +extern crate macro_rules; + +macro_rules! cast_it { + ($ptr: ident) => { + $ptr as *const i32 + }; +} + fn main() { let ptr: *const u32 = &42_u32; let mut_ptr: *mut u32 = &mut 42_u32; @@ -28,6 +37,12 @@ fn main() { // Ensure the lint doesn't produce unnecessary turbofish for inferred types. let _: *const i32 = ptr as *const _; let _: *mut i32 = mut_ptr as _; + + // Make sure the lint is triggered inside a macro + let _ = cast_it!(ptr); + + // Do not lint inside macros from external crates + let _ = macro_rules::ptr_as_ptr_cast!(ptr); } fn _msrv_1_37() { diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index 854906dc111df..c58c55cfd83a1 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -1,5 +1,5 @@ error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:10:13 + --> $DIR/ptr_as_ptr.rs:19:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` @@ -7,40 +7,51 @@ LL | let _ = ptr as *const i32; = note: `-D clippy::ptr-as-ptr` implied by `-D warnings` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:11:13 + --> $DIR/ptr_as_ptr.rs:20:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:16:17 + --> $DIR/ptr_as_ptr.rs:25:17 | LL | let _ = *ptr_ptr as *const i32; | ^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `(*ptr_ptr).cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:29:25 + --> $DIR/ptr_as_ptr.rs:38:25 | LL | let _: *const i32 = ptr as *const _; | ^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:30:23 + --> $DIR/ptr_as_ptr.rs:39:23 | LL | let _: *mut i32 = mut_ptr as _; | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:48:13 + --> $DIR/ptr_as_ptr.rs:11:9 + | +LL | $ptr as *const i32 + | ^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `$ptr.cast::()` +... +LL | let _ = cast_it!(ptr); + | ------------- in this macro invocation + | + = note: this error originates in the macro `cast_it` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `as` casting between raw pointers without changing its mutability + --> $DIR/ptr_as_ptr.rs:63:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` error: `as` casting between raw pointers without changing its mutability - --> $DIR/ptr_as_ptr.rs:49:13 + --> $DIR/ptr_as_ptr.rs:64:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` -error: aborting due to 7 previous errors +error: aborting due to 8 previous errors From 8912d659cdc9e1fd787136fe1b52a285f2fc2106 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 17 Feb 2022 14:16:21 -0500 Subject: [PATCH 33/55] Remove hack testing for `cfg` attribute in `match_single_binding` --- .../src/matches/match_single_binding.rs | 19 +------------------ tests/ui/match_single_binding.fixed | 8 +++----- tests/ui/match_single_binding.rs | 3 ++- tests/ui/match_single_binding.stderr | 11 ++++++++++- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 8ae19e03f1a6a..39fe54648fbc7 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{indent_of, snippet_block, snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{indent_of, snippet_block, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; use rustc_errors::Applicability; @@ -14,23 +14,6 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e 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); diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index b4ec525ada09a..b8dc8179f7d7d 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -106,10 +106,8 @@ fn main() { 0 => println!("Array index start"), _ => println!("Not an array index start"), } - // False negative + + // Lint let x = 1; - match x { - // => - _ => println!("Not an array index start"), - } + println!("Not an array index start"); } diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index e04c4018b98dd..fe63dcd63f2bb 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -118,7 +118,8 @@ fn main() { 0 => println!("Array index start"), _ => println!("Not an array index start"), } - // False negative + + // Lint let x = 1; match x { // => diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index 291fa77dc2ee1..d939291f53c40 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -167,5 +167,14 @@ LL + unwrapped LL ~ }) | -error: aborting due to 11 previous errors +error: this match could be replaced by its body itself + --> $DIR/match_single_binding.rs:124:5 + | +LL | / match x { +LL | | // => +LL | | _ => println!("Not an array index start"), +LL | | } + | |_____^ help: consider using the match body instead: `println!("Not an array index start");` + +error: aborting due to 12 previous errors From 8a466454ab280f0dd405b713c1a3610de234d9ce Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 31 Jan 2022 15:45:49 -0500 Subject: [PATCH 34/55] Lint enum-to-int casts with `cast_possible_truncation` --- .../src/casts/cast_possible_truncation.rs | 47 +++++-- clippy_lints/src/casts/mod.rs | 3 +- clippy_lints/src/casts/utils.rs | 58 ++++++++- tests/ui/cast.rs | 115 ++++++++++++++++++ tests/ui/cast.stderr | 74 +++++++---- 5 files changed, 258 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index ea74d5acbda05..2384e4a37483f 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -2,6 +2,7 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::expr_or_init; use clippy_utils::ty::is_isize_or_usize; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, FloatTy, Ty}; @@ -75,8 +76,8 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b } pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - let msg = match (cast_from.is_integral(), cast_to.is_integral()) { - (true, true) => { + let msg = match (cast_from.kind(), cast_to.is_integral()) { + (ty::Int(_) | ty::Uint(_), true) => { let from_nbits = apply_reductions( cx, utils::int_ty_to_nbits(cast_from, cx.tcx), @@ -108,19 +109,43 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, ) }, - (false, true) => { - format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to) - }, - - (_, _) => { - if matches!(cast_from.kind(), &ty::Float(FloatTy::F64)) - && matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) + (ty::Adt(def, _), true) if def.is_enum() => { + if let ExprKind::Path(p) = &cast_expr.kind + && let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id) { - "casting `f64` to `f32` may truncate the value".to_string() + return + } + + let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + let suffix = if is_isize_or_usize(cast_to) { + if from_nbits > 32 { + " on targets with 32-bit wide pointers" + } else { + return; + } + } else if to_nbits < from_nbits { + "" } else { return; - } + }; + + format!( + "casting `{}` to `{}` may truncate the value{}", + cast_from, cast_to, suffix, + ) + }, + + (ty::Float(_), true) => { + format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to) }, + + (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => { + "casting `f64` to `f32` may truncate the value".to_string() + }, + + _ => return, }; span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg); diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 51d47b35454c2..b8c9f7db994d9 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -445,13 +445,12 @@ impl<'tcx> LateLintPass<'tcx> for Casts { fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); if cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { + cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to); if cast_from.is_numeric() { - cast_possible_truncation::check(cx, expr, cast_expr, cast_from, cast_to); cast_possible_wrap::check(cx, expr, cast_from, cast_to); cast_precision_loss::check(cx, expr, cast_from, cast_to); cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); } - cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv); } } diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 00fd0b3473b44..d41e745d73359 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,4 +1,6 @@ -use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy}; +use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; +use rustc_target::abi::Size; /// Returns the size in bits of an integral type. /// Will return 0 if the type is not an int or uint variant @@ -23,3 +25,57 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { _ => 0, } } + +pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 { + let mut explicit = 0i128; + let (start, end) = adt + .variants + .iter() + .fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr { + VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) { + Some(x) => (start, end.max(x)), + None => (i128::MIN, end), + }, + VariantDiscr::Explicit(id) => { + let ty = tcx.type_of(id); + if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let value = match (value.size().bytes(), ty.kind()) { + (1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), + (1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8), + (2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), + (2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16), + (4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), + (4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32), + (8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), + (8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64), + (16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128, + (16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) { + Ok(x) => x, + // Requires 128 bits + Err(_) => return (i128::MIN, end), + }, + // Shouldn't happen if compilation was successful + _ => return (start, end), + }; + explicit = value; + (start.min(value), end.max(value)) + } else { + // Shouldn't happen if compilation was successful + (start, end) + } + }, + }); + + if start >= end { + 0 + } else { + let neg_bits = if start < 0 { + 128 - (-(start + 1)).leading_zeros() + 1 + } else { + 0 + }; + let pos_bits = if end > 0 { 128 - end.leading_zeros() } else { 0 }; + neg_bits.max(pos_bits).into() + } +} diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index ebc1ed5587fe3..371bf292f7477 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -1,3 +1,6 @@ +#![feature(repr128)] +#![allow(incomplete_features)] + #[warn( clippy::cast_precision_loss, clippy::cast_possible_truncation, @@ -115,4 +118,116 @@ fn main() { }) as u8; 999999u64.clamp(0, 255) as u8; 999999u64.clamp(0, 256) as u8; // should still be linted + + #[derive(Clone, Copy)] + enum E1 { + A, + B, + C, + } + impl E1 { + fn test(self) { + let _ = self as u8; // Don't lint. `0..=2` fits in u8 + } + } + + #[derive(Clone, Copy)] + enum E2 { + A = 255, + B, + } + impl E2 { + fn test(self) { + let _ = self as u8; + let _ = self as i16; // Don't lint. `255..=256` fits in i16 + } + } + + #[derive(Clone, Copy)] + enum E3 { + A = -1, + B, + C = 50, + } + impl E3 { + fn test(self) { + let _ = self as i8; // Don't lint. `-1..=50` fits in i8 + } + } + + #[derive(Clone, Copy)] + enum E4 { + A = -128, + B, + } + impl E4 { + fn test(self) { + let _ = self as i8; // Don't lint. `-128..=-127` fits in i8 + } + } + + #[derive(Clone, Copy)] + enum E5 { + A = -129, + B = 127, + } + impl E5 { + fn test(self) { + let _ = self as i8; + let _ = self as i16; // Don't lint. `-129..=127` fits in i16 + } + } + + #[derive(Clone, Copy)] + #[repr(u32)] + enum E6 { + A = u16::MAX as u32, + B, + } + impl E6 { + fn test(self) { + let _ = self as i16; + let _ = Self::A as u16; // Don't lint. `2^16-1` fits in u16 + let _ = self as u32; // Don't lint. `2^16-1..=2^16` fits in u32 + } + } + + #[derive(Clone, Copy)] + #[repr(u64)] + enum E7 { + A = u32::MAX as u64, + B, + } + impl E7 { + fn test(self) { + let _ = self as usize; + let _ = self as u64; // Don't lint. `2^32-1..=2^32` fits in u64 + } + } + + #[derive(Clone, Copy)] + #[repr(i128)] + enum E8 { + A = i128::MIN, + B, + C = 0, + D = i128::MAX, + } + impl E8 { + fn test(self) { + let _ = self as i128; // Don't lint. `-(2^127)..=2^127-1` fits it i128 + } + } + + #[derive(Clone, Copy)] + #[repr(u128)] + enum E9 { + A, + B = u128::MAX, + } + impl E9 { + fn test(self) { + let _ = self as u128; // Don't lint. `0..=2^128-1` fits in u128 + } + } } diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index edf8790cf33d8..0fc66f7398119 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -1,5 +1,5 @@ error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> $DIR/cast.rs:11:5 + --> $DIR/cast.rs:14:5 | LL | x0 as f32; | ^^^^^^^^^ @@ -7,37 +7,37 @@ LL | x0 as f32; = note: `-D clippy::cast-precision-loss` implied by `-D warnings` error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> $DIR/cast.rs:13:5 + --> $DIR/cast.rs:16:5 | LL | x1 as f32; | ^^^^^^^^^ error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> $DIR/cast.rs:14:5 + --> $DIR/cast.rs:17:5 | LL | x1 as f64; | ^^^^^^^^^ error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> $DIR/cast.rs:16:5 + --> $DIR/cast.rs:19:5 | LL | x2 as f32; | ^^^^^^^^^ error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> $DIR/cast.rs:18:5 + --> $DIR/cast.rs:21:5 | LL | x3 as f32; | ^^^^^^^^^ error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> $DIR/cast.rs:19:5 + --> $DIR/cast.rs:22:5 | LL | x3 as f64; | ^^^^^^^^^ error: casting `f32` to `i32` may truncate the value - --> $DIR/cast.rs:21:5 + --> $DIR/cast.rs:24:5 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -45,13 +45,13 @@ LL | 1f32 as i32; = note: `-D clippy::cast-possible-truncation` implied by `-D warnings` error: casting `f32` to `u32` may truncate the value - --> $DIR/cast.rs:22:5 + --> $DIR/cast.rs:25:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ error: casting `f32` to `u32` may lose the sign of the value - --> $DIR/cast.rs:22:5 + --> $DIR/cast.rs:25:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ @@ -59,43 +59,43 @@ LL | 1f32 as u32; = note: `-D clippy::cast-sign-loss` implied by `-D warnings` error: casting `f64` to `f32` may truncate the value - --> $DIR/cast.rs:23:5 + --> $DIR/cast.rs:26:5 | LL | 1f64 as f32; | ^^^^^^^^^^^ error: casting `i32` to `i8` may truncate the value - --> $DIR/cast.rs:24:5 + --> $DIR/cast.rs:27:5 | LL | 1i32 as i8; | ^^^^^^^^^^ error: casting `i32` to `u8` may truncate the value - --> $DIR/cast.rs:25:5 + --> $DIR/cast.rs:28:5 | LL | 1i32 as u8; | ^^^^^^^^^^ error: casting `f64` to `isize` may truncate the value - --> $DIR/cast.rs:26:5 + --> $DIR/cast.rs:29:5 | LL | 1f64 as isize; | ^^^^^^^^^^^^^ error: casting `f64` to `usize` may truncate the value - --> $DIR/cast.rs:27:5 + --> $DIR/cast.rs:30:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `f64` to `usize` may lose the sign of the value - --> $DIR/cast.rs:27:5 + --> $DIR/cast.rs:30:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `u8` to `i8` may wrap around the value - --> $DIR/cast.rs:29:5 + --> $DIR/cast.rs:32:5 | LL | 1u8 as i8; | ^^^^^^^^^ @@ -103,52 +103,76 @@ LL | 1u8 as i8; = note: `-D clippy::cast-possible-wrap` implied by `-D warnings` error: casting `u16` to `i16` may wrap around the value - --> $DIR/cast.rs:30:5 + --> $DIR/cast.rs:33:5 | LL | 1u16 as i16; | ^^^^^^^^^^^ error: casting `u32` to `i32` may wrap around the value - --> $DIR/cast.rs:31:5 + --> $DIR/cast.rs:34:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ error: casting `u64` to `i64` may wrap around the value - --> $DIR/cast.rs:32:5 + --> $DIR/cast.rs:35:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ error: casting `usize` to `isize` may wrap around the value - --> $DIR/cast.rs:33:5 + --> $DIR/cast.rs:36:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> $DIR/cast.rs:36:5 + --> $DIR/cast.rs:39:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ error: casting `isize` to `usize` may lose the sign of the value - --> $DIR/cast.rs:38:5 + --> $DIR/cast.rs:41:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ error: casting `i64` to `i8` may truncate the value - --> $DIR/cast.rs:105:5 + --> $DIR/cast.rs:108:5 | LL | (-99999999999i64).min(1) as i8; // should be linted because signed | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `u64` to `u8` may truncate the value - --> $DIR/cast.rs:117:5 + --> $DIR/cast.rs:120:5 | LL | 999999u64.clamp(0, 256) as u8; // should still be linted | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 24 previous errors +error: casting `main::E2` to `u8` may truncate the value + --> $DIR/cast.rs:141:21 + | +LL | let _ = self as u8; + | ^^^^^^^^^^ + +error: casting `main::E5` to `i8` may truncate the value + --> $DIR/cast.rs:176:21 + | +LL | let _ = self as i8; + | ^^^^^^^^^^ + +error: casting `main::E6` to `i16` may truncate the value + --> $DIR/cast.rs:189:21 + | +LL | let _ = self as i16; + | ^^^^^^^^^^^ + +error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers + --> $DIR/cast.rs:203:21 + | +LL | let _ = self as usize; + | ^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors From 90bb7a34764b460681dca1efb333ff378478dbd8 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Mon, 31 Jan 2022 21:22:47 -0500 Subject: [PATCH 35/55] New lint `cast_enum_truncation` --- CHANGELOG.md | 1 + .../src/casts/cast_possible_truncation.rs | 65 +++++++++---- clippy_lints/src/casts/mod.rs | 20 ++++ clippy_lints/src/casts/utils.rs | 96 +++++++++++++------ clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_suspicious.rs | 1 + clippy_lints/src/lib.rs | 1 + tests/ui/cast.rs | 21 ++++ tests/ui/cast.stderr | 28 +++++- 10 files changed, 183 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f41dcce0e000f..1b52a6fcd05e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3068,6 +3068,7 @@ Released 2018-09-13 [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata [`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons +[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation [`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless [`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation [`cast_possible_wrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_wrap diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 2384e4a37483f..6c938062c1972 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -2,12 +2,14 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::expr_or_init; use clippy_utils::ty::is_isize_or_usize; +use rustc_ast::ast; +use rustc_attr::IntType; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, FloatTy, Ty}; +use rustc_middle::ty::{self, FloatTy, Ty, VariantDiscr}; -use super::{utils, CAST_POSSIBLE_TRUNCATION}; +use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; fn constant_int(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { if let Some((Constant::Int(c), _)) = constant(cx, cx.typeck_results(), expr) { @@ -110,27 +112,54 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, }, (ty::Adt(def, _), true) if def.is_enum() => { - if let ExprKind::Path(p) = &cast_expr.kind - && let Res::Def(DefKind::Ctor(..), _) = cx.qpath_res(p, cast_expr.hir_id) + let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind + && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id) { - return - } - - let from_nbits = utils::enum_ty_to_nbits(def, cx.tcx); + let i = def.variant_index_with_ctor_id(id); + let variant = &def.variants[i]; + let nbits: u64 = match variant.discr { + VariantDiscr::Explicit(id) => utils::read_explicit_enum_value(cx.tcx, id).unwrap().nbits(), + VariantDiscr::Relative(x) => { + match def.variants[(i.as_usize() - x as usize).into()].discr { + VariantDiscr::Explicit(id) => { + utils::read_explicit_enum_value(cx.tcx, id).unwrap().add(x).nbits() + } + VariantDiscr::Relative(_) => (32 - x.leading_zeros()).into(), + } + } + }; + (nbits, Some(variant)) + } else { + (utils::enum_ty_to_nbits(def, cx.tcx), None) + }; let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - let suffix = if is_isize_or_usize(cast_to) { - if from_nbits > 32 { - " on targets with 32-bit wide pointers" - } else { - return; - } - } else if to_nbits < from_nbits { - "" - } else { - return; + let cast_from_ptr_size = def.repr.int.map_or(true, |ty| { + matches!( + ty, + IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize) + ) + }); + let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { + (false, false) if from_nbits > to_nbits => "", + (true, false) if from_nbits > to_nbits => "", + (false, true) if from_nbits > 64 => "", + (false, true) if from_nbits > 32 => " on targets with 32-bit wide pointers", + _ => return, }; + if let Some(variant) = variant { + span_lint( + cx, + CAST_ENUM_TRUNCATION, + expr.span, + &format!( + "casting `{}::{}` to `{}` will truncate the value{}", + cast_from, variant.name, cast_to, suffix, + ), + ); + return; + } format!( "casting `{}` to `{}` may truncate the value{}", cast_from, cast_to, suffix, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index b8c9f7db994d9..f2077c569c041 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -390,6 +390,25 @@ declare_clippy_lint! { "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum type to an integral type which will definitely truncate the + /// value. + /// + /// ### Why is this bad? + /// The resulting integral value will not match the value of the variant it came from. + /// + /// ### Example + /// ```rust + /// enum E { X = 256 }; + /// let _ = E::X as u8; + /// ``` + #[clippy::version = "1.60.0"] + pub CAST_ENUM_TRUNCATION, + suspicious, + "casts from an enum type to an integral type which will truncate the value" +} + pub struct Casts { msrv: Option, } @@ -415,6 +434,7 @@ impl_lint_pass!(Casts => [ FN_TO_NUMERIC_CAST_WITH_TRUNCATION, CHAR_LIT_AS_U8, PTR_AS_PTR, + CAST_ENUM_TRUNCATION, ]); impl<'tcx> LateLintPass<'tcx> for Casts { diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index d41e745d73359..fab0c194b3759 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,5 +1,6 @@ use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; +use rustc_span::def_id::DefId; use rustc_target::abi::Size; /// Returns the size in bits of an integral type. @@ -26,48 +27,83 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { } } +pub(super) enum EnumValue { + Unsigned(u128), + Signed(i128), +} +impl EnumValue { + pub(super) fn add(self, n: u32) -> Self { + match self { + Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)), + Self::Signed(x) => Self::Signed(x + i128::from(n)), + } + } + + pub(super) fn nbits(self) -> u64 { + match self { + Self::Unsigned(x) => 128 - x.leading_zeros(), + Self::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1, + Self::Signed(x) => 128 - x.leading_zeros(), + } + .into() + } +} + +#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] +pub(super) fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option { + if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { + match tcx.type_of(id).kind() { + ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { + 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), + 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), + 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), + 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), + 16 => value.assert_bits(Size::from_bytes(16)) as i128, + _ => return None, + })), + ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() { + 1 => value.assert_bits(Size::from_bytes(1)), + 2 => value.assert_bits(Size::from_bytes(2)), + 4 => value.assert_bits(Size::from_bytes(4)), + 8 => value.assert_bits(Size::from_bytes(8)), + 16 => value.assert_bits(Size::from_bytes(16)), + _ => return None, + })), + _ => None, + } + } else { + None + } +} + pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 { let mut explicit = 0i128; let (start, end) = adt .variants .iter() - .fold((i128::MAX, i128::MIN), |(start, end), variant| match variant.discr { + .fold((0, i128::MIN), |(start, end), variant| match variant.discr { VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) { Some(x) => (start, end.max(x)), None => (i128::MIN, end), }, - VariantDiscr::Explicit(id) => { - let ty = tcx.type_of(id); - if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - let value = match (value.size().bytes(), ty.kind()) { - (1, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), - (1, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(1)) as u8), - (2, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), - (2, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(2)) as u16), - (4, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), - (4, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(4)) as u32), - (8, ty::Int(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), - (8, ty::Uint(_)) => i128::from(value.assert_bits(Size::from_bytes(8)) as u64), - (16, ty::Int(_)) => value.assert_bits(Size::from_bytes(16)) as i128, - (16, ty::Uint(_)) => match i128::try_from(value.assert_bits(Size::from_bytes(16))) { - Ok(x) => x, - // Requires 128 bits - Err(_) => return (i128::MIN, end), - }, - // Shouldn't happen if compilation was successful - _ => return (start, end), - }; - explicit = value; - (start.min(value), end.max(value)) - } else { - // Shouldn't happen if compilation was successful - (start, end) - } + VariantDiscr::Explicit(id) => match read_explicit_enum_value(tcx, id) { + Some(EnumValue::Signed(x)) => { + explicit = x; + (start.min(x), end.max(x)) + }, + Some(EnumValue::Unsigned(x)) => match i128::try_from(x) { + Ok(x) => { + explicit = x; + (start, end.max(x)) + }, + Err(_) => (i128::MIN, end), + }, + None => (start, end), }, }); - if start >= end { + if start > end { + // No variants. 0 } else { let neg_bits = if start < 0 { diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index a94f6b528b47b..c6f8470cd7db7 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(booleans::LOGIC_BUG), LintId::of(booleans::NONMINIMAL_BOOL), + LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CHAR_LIT_AS_U8), LintId::of(casts::FN_TO_NUMERIC_CAST), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index df40ca4631413..75ef1b0a9d511 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -67,6 +67,7 @@ store.register_lints(&[ cargo::REDUNDANT_FEATURE_NAMES, cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::CAST_ENUM_TRUNCATION, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, casts::CAST_POSSIBLE_WRAP, diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index da56f80080499..6a8859e19d71d 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -7,6 +7,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), + LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 93b1d178ac259..a21a87899aa16 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -25,6 +25,7 @@ // (Currently there is no way to opt into sysroot crates without `extern crate`.) extern crate rustc_ast; extern crate rustc_ast_pretty; +extern crate rustc_attr; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors; diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index 371bf292f7477..2e31ad3172ee8 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -139,7 +139,9 @@ fn main() { impl E2 { fn test(self) { let _ = self as u8; + let _ = Self::B as u8; let _ = self as i16; // Don't lint. `255..=256` fits in i16 + let _ = Self::A as u8; // Don't lint. } } @@ -174,7 +176,9 @@ fn main() { impl E5 { fn test(self) { let _ = self as i8; + let _ = Self::A as i8; let _ = self as i16; // Don't lint. `-129..=127` fits in i16 + let _ = Self::B as u8; // Don't lint. } } @@ -189,6 +193,7 @@ fn main() { let _ = self as i16; let _ = Self::A as u16; // Don't lint. `2^16-1` fits in u16 let _ = self as u32; // Don't lint. `2^16-1..=2^16` fits in u32 + let _ = Self::A as u16; // Don't lint. } } @@ -201,6 +206,7 @@ fn main() { impl E7 { fn test(self) { let _ = self as usize; + let _ = Self::A as usize; // Don't lint. let _ = self as u64; // Don't lint. `2^32-1..=2^32` fits in u64 } } @@ -227,7 +233,22 @@ fn main() { } impl E9 { fn test(self) { + let _ = Self::A as u8; // Don't lint. let _ = self as u128; // Don't lint. `0..=2^128-1` fits in u128 } } + + #[derive(Clone, Copy)] + #[repr(usize)] + enum E10 { + A, + B = u32::MAX as usize, + } + impl E10 { + fn test(self) { + let _ = self as u16; + let _ = Self::B as u32; // Don't lint. + let _ = self as u64; // Don't lint. + } + } } diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 0fc66f7398119..7a68c0984f140 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -156,23 +156,43 @@ error: casting `main::E2` to `u8` may truncate the value LL | let _ = self as u8; | ^^^^^^^^^^ +error: casting `main::E2::B` to `u8` will truncate the value + --> $DIR/cast.rs:142:21 + | +LL | let _ = Self::B as u8; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-enum-truncation` implied by `-D warnings` + error: casting `main::E5` to `i8` may truncate the value - --> $DIR/cast.rs:176:21 + --> $DIR/cast.rs:178:21 | LL | let _ = self as i8; | ^^^^^^^^^^ +error: casting `main::E5::A` to `i8` will truncate the value + --> $DIR/cast.rs:179:21 + | +LL | let _ = Self::A as i8; + | ^^^^^^^^^^^^^ + error: casting `main::E6` to `i16` may truncate the value - --> $DIR/cast.rs:189:21 + --> $DIR/cast.rs:193:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> $DIR/cast.rs:203:21 + --> $DIR/cast.rs:208:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ -error: aborting due to 28 previous errors +error: casting `main::E10` to `u16` may truncate the value + --> $DIR/cast.rs:249:21 + | +LL | let _ = self as u16; + | ^^^^^^^^^^^ + +error: aborting due to 31 previous errors From 88ecdd0804be7c5643eafa4e1960f5e0176903b3 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 18 Feb 2022 09:10:48 -0500 Subject: [PATCH 36/55] Extract some util functions --- .../src/casts/cast_possible_truncation.rs | 16 +---- clippy_lints/src/casts/utils.rs | 56 +++--------------- clippy_utils/src/ty.rs | 59 ++++++++++++++++++- 3 files changed, 68 insertions(+), 63 deletions(-) diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 6c938062c1972..9b189ea1ef8fb 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -1,13 +1,13 @@ use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint; use clippy_utils::expr_or_init; -use clippy_utils::ty::is_isize_or_usize; +use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize}; use rustc_ast::ast; use rustc_attr::IntType; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, FloatTy, Ty, VariantDiscr}; +use rustc_middle::ty::{self, FloatTy, Ty}; use super::{utils, CAST_ENUM_TRUNCATION, CAST_POSSIBLE_TRUNCATION}; @@ -117,17 +117,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, { let i = def.variant_index_with_ctor_id(id); let variant = &def.variants[i]; - let nbits: u64 = match variant.discr { - VariantDiscr::Explicit(id) => utils::read_explicit_enum_value(cx.tcx, id).unwrap().nbits(), - VariantDiscr::Relative(x) => { - match def.variants[(i.as_usize() - x as usize).into()].discr { - VariantDiscr::Explicit(id) => { - utils::read_explicit_enum_value(cx.tcx, id).unwrap().add(x).nbits() - } - VariantDiscr::Relative(_) => (32 - x.leading_zeros()).into(), - } - } - }; + let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, def, i)); (nbits, Some(variant)) } else { (utils::enum_ty_to_nbits(def, cx.tcx), None) diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index fab0c194b3759..bbed766c47a85 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,7 +1,5 @@ -use rustc_middle::mir::interpret::{ConstValue, Scalar}; +use clippy_utils::ty::{read_explicit_enum_value, EnumValue}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; -use rustc_span::def_id::DefId; -use rustc_target::abi::Size; /// Returns the size in bits of an integral type. /// Will return 0 if the type is not an int or uint variant @@ -27,53 +25,13 @@ pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { } } -pub(super) enum EnumValue { - Unsigned(u128), - Signed(i128), -} -impl EnumValue { - pub(super) fn add(self, n: u32) -> Self { - match self { - Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)), - Self::Signed(x) => Self::Signed(x + i128::from(n)), - } - } - - pub(super) fn nbits(self) -> u64 { - match self { - Self::Unsigned(x) => 128 - x.leading_zeros(), - Self::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1, - Self::Signed(x) => 128 - x.leading_zeros(), - } - .into() - } -} - -#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] -pub(super) fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option { - if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { - match tcx.type_of(id).kind() { - ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { - 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), - 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), - 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), - 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), - 16 => value.assert_bits(Size::from_bytes(16)) as i128, - _ => return None, - })), - ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() { - 1 => value.assert_bits(Size::from_bytes(1)), - 2 => value.assert_bits(Size::from_bytes(2)), - 4 => value.assert_bits(Size::from_bytes(4)), - 8 => value.assert_bits(Size::from_bytes(8)), - 16 => value.assert_bits(Size::from_bytes(16)), - _ => return None, - })), - _ => None, - } - } else { - None +pub(super) fn enum_value_nbits(value: EnumValue) -> u64 { + match value { + EnumValue::Unsigned(x) => 128 - x.leading_zeros(), + EnumValue::Signed(x) if x < 0 => 128 - (-(x + 1)).leading_zeros() + 1, + EnumValue::Signed(x) => 128 - x.leading_zeros(), } + .into() } pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 { diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 958e6d1ec4615..7d74b69906d9d 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -10,12 +10,14 @@ use rustc_hir::def_id::DefId; use rustc_hir::{Expr, TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; +use rustc_middle::mir::interpret::{ConstValue, Scalar}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst}; use rustc_middle::ty::{ - self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, + self, AdtDef, Binder, FnSig, IntTy, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; +use rustc_target::abi::{Size, VariantIdx}; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::normalize::AtExt; use std::iter; @@ -515,3 +517,58 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option for EnumValue { + type Output = Self; + fn add(self, n: u32) -> Self::Output { + match self { + Self::Unsigned(x) => Self::Unsigned(x + u128::from(n)), + Self::Signed(x) => Self::Signed(x + i128::from(n)), + } + } +} + +/// Attempts to read the given constant as though it were an an enum value. +#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] +pub fn read_explicit_enum_value(tcx: TyCtxt<'_>, id: DefId) -> Option { + if let Ok(ConstValue::Scalar(Scalar::Int(value))) = tcx.const_eval_poly(id) { + match tcx.type_of(id).kind() { + ty::Int(_) => Some(EnumValue::Signed(match value.size().bytes() { + 1 => i128::from(value.assert_bits(Size::from_bytes(1)) as u8 as i8), + 2 => i128::from(value.assert_bits(Size::from_bytes(2)) as u16 as i16), + 4 => i128::from(value.assert_bits(Size::from_bytes(4)) as u32 as i32), + 8 => i128::from(value.assert_bits(Size::from_bytes(8)) as u64 as i64), + 16 => value.assert_bits(Size::from_bytes(16)) as i128, + _ => return None, + })), + ty::Uint(_) => Some(EnumValue::Unsigned(match value.size().bytes() { + 1 => value.assert_bits(Size::from_bytes(1)), + 2 => value.assert_bits(Size::from_bytes(2)), + 4 => value.assert_bits(Size::from_bytes(4)), + 8 => value.assert_bits(Size::from_bytes(8)), + 16 => value.assert_bits(Size::from_bytes(16)), + _ => return None, + })), + _ => None, + } + } else { + None + } +} + +/// Gets the value of the given variant. +pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) -> EnumValue { + let variant = &adt.variants[i]; + match variant.discr { + VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(), + VariantDiscr::Relative(x) => match adt.variants[(i.as_usize() - x as usize).into()].discr { + VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x, + VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()), + }, + } +} From c02dff716785721c4c992d03ee816d764fb43402 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Feb 2022 11:29:02 -0500 Subject: [PATCH 37/55] Fix `transmute_undefined_repr` when converting between a pointer and a type containing a pointer --- .../src/transmute/transmute_undefined_repr.rs | 3 ++- tests/ui/transmute_undefined_repr.rs | 14 ++++++++++ tests/ui/transmute_undefined_repr.stderr | 26 ++++++++++++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 89439862f6915..74ea7a3b89392 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -273,7 +273,8 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ReducedTy::UnorderedFields(ty) } }, - ty::Ref(..) | ty::RawPtr(_) => ReducedTy::Ref(ty), + ty::Ref(_, ty, _) => ReducedTy::Ref(ty), + ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), _ => ReducedTy::Other(ty), }; } diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index f0383a03fbde0..0916da819b133 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -44,5 +44,19 @@ fn main() { // issue #8417 let _: Ty2C, ()> = core::mem::transmute(value::>()); // Ok, Ty2 types are the same let _: Ty2 = core::mem::transmute(value::, ()>>()); // Ok, Ty2 types are the same + + // Ty2 types are the same + let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Ok + // Ty2 types are the same + let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Ok + let _: *mut Ty2 = core::mem::transmute(value::>>()); // Ok, Ty2 types are the same + let _: Box> = core::mem::transmute(value::<*mut Ty2>()); // Ok, Ty2 types are the same + + // Different Ty2 instances + let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Lint + // Different Ty2 instances + let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Lint + + } } diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 040c63c7afa62..98035305ec0c3 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -28,17 +28,37 @@ LL | let _: Ty> = core::mem::transmute(value::` which has an undefined layout +error: transmute from `Ty<&Ty2>` to `&Ty2`, both of which have an undefined layout --> $DIR/transmute_undefined_repr.rs:35:33 | 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` which has an undefined layout +error: transmute from `&Ty2` to `Ty<&Ty2>`, both of which have an undefined layout --> $DIR/transmute_undefined_repr.rs:36:37 | LL | let _: Ty<&Ty2> = core::mem::transmute(value::<&Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Ty2`) may have different layouts + +error: transmute from `std::boxed::Box>` to `&mut Ty2`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:56:45 + | +LL | let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Ty2`) may have different layouts + +error: transmute from `&mut Ty2` to `std::boxed::Box>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:58:37 + | +LL | let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Ty2`) may have different layouts -error: aborting due to 6 previous errors +error: aborting due to 8 previous errors From 01732b60192ed7f99571090b5b87f61ab8c98e75 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Feb 2022 11:39:19 -0500 Subject: [PATCH 38/55] Add some comments to `transmute_undefined_repr` --- .../src/transmute/transmute_undefined_repr.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 74ea7a3b89392..5b08ad5840a46 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -189,6 +189,7 @@ enum ReducedTys<'tcx> { Other { from_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, } +/// Remove references so long as both types are references. fn reduce_refs<'tcx>( cx: &LateContext<'tcx>, span: Span, @@ -227,13 +228,21 @@ fn reduce_refs<'tcx>( } enum ReducedTy<'tcx> { + /// The type is a struct containing either zero sized fields, or multiple sized fields with a + /// defined order. OrderedFields(Ty<'tcx>), + /// The type is a struct containing multiple non-zero sized fields with no defined order. UnorderedFields(Ty<'tcx>), + /// The type is a reference to the contained type. Ref(Ty<'tcx>), - Other(Ty<'tcx>), + /// The type is an array of a primitive integer type. These can be used as storage for a value + /// of another type. IntArray, + /// Any other type. + Other(Ty<'tcx>), } +/// Reduce structs containing a single non-zero sized field to it's contained type. 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); From 447a24588a865142aeaf9153952485d1cd6b9c67 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 15 Feb 2022 14:51:57 -0500 Subject: [PATCH 39/55] Allow various type erasure patterns in `transmute_undefined_repr` --- clippy_lints/src/casts/cast_ptr_alignment.rs | 18 +---- .../src/transmute/transmute_undefined_repr.rs | 17 ++++- clippy_utils/src/lib.rs | 1 + clippy_utils/src/ty.rs | 14 ++++ tests/ui/transmute_undefined_repr.rs | 74 +++++++++++-------- tests/ui/transmute_undefined_repr.stderr | 46 ++++++------ 6 files changed, 96 insertions(+), 74 deletions(-) diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 079b7ff0675b4..a4ef1344ab951 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::is_hir_ty_cfg_dependant; +use clippy_utils::ty::is_c_void; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind, GenericArg}; use rustc_lint::LateContext; use rustc_middle::ty::layout::LayoutOf; use rustc_middle::ty::{self, Ty}; -use rustc_span::symbol::sym; use super::CAST_PTR_ALIGNMENT; @@ -62,19 +62,3 @@ fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_f } } } - -/// Check if the given type is either `core::ffi::c_void` or -/// one of the platform specific `libc::::c_void` of libc. -fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - if let ty::Adt(adt, _) = ty.kind() { - let names = cx.get_def_path(adt.did); - - if names.is_empty() { - return false; - } - if names[0] == sym::libc || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) { - return true; - } - } - false -} diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 5b08ad5840a46..74bdace408c2a 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -1,5 +1,6 @@ use super::TRANSMUTE_UNDEFINED_REPR; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_c_void; use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty::subst::{GenericArg, Subst}; @@ -100,7 +101,8 @@ pub(super) fn check<'tcx>( 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::IntArray | ReducedTy::TypeErasure, _) + | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false, (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { span_lint_and_then( cx, @@ -228,8 +230,10 @@ fn reduce_refs<'tcx>( } enum ReducedTy<'tcx> { - /// The type is a struct containing either zero sized fields, or multiple sized fields with a - /// defined order. + /// The type can be used for type erasure. + TypeErasure, + /// The type is a struct containing either zero non-zero sized fields, or multiple non-zero + /// sized fields with a defined order. OrderedFields(Ty<'tcx>), /// The type is a struct containing multiple non-zero sized fields with no defined order. UnorderedFields(Ty<'tcx>), @@ -252,6 +256,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty = sub_ty; continue; }, + ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure, 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 { @@ -270,7 +275,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .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); + return ReducedTy::TypeErasure; }; if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; @@ -282,6 +287,10 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ReducedTy::UnorderedFields(ty) } }, + ty::Adt(def, _) if def.is_enum() && (def.variants.is_empty() || is_c_void(cx, ty)) => { + ReducedTy::TypeErasure + }, + ty::Foreign(_) => ReducedTy::TypeErasure, ty::Ref(_, ty, _) => ReducedTy::Ref(ty), ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), _ => ReducedTy::Other(ty), diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 73d91550693de..5b37a1de8ed86 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,7 @@ #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(let_else)] +#![feature(let_chains)] #![feature(once_cell)] #![feature(rustc_private)] #![recursion_limit = "512"] diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 7d74b69906d9d..6c9ba64525a74 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -572,3 +572,17 @@ pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) - }, } } + +/// Check if the given type is either `core::ffi::c_void`, `std::os::raw::c_void`, or one of the +/// platform specific `libc::::c_void` types in libc. +pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, _) = ty.kind() + && let &[krate, .., name] = &*cx.get_def_path(adt.did) + && let sym::libc | sym::core | sym::std = krate + && name.as_str() == "c_void" + { + true + } else { + false + } +} diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index 0916da819b133..c1ee0d98a9212 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -1,6 +1,9 @@ #![warn(clippy::transmute_undefined_repr)] #![allow(clippy::unit_arg)] +use core::ffi::c_void; +use core::mem::transmute; + fn value() -> T { unimplemented!() } @@ -14,49 +17,60 @@ struct Ty2C(T, U); fn main() { unsafe { - let _: () = core::mem::transmute(value::()); - let _: Empty = core::mem::transmute(value::<()>()); + let _: () = transmute(value::()); + let _: Empty = transmute(value::<()>()); - let _: Ty = core::mem::transmute(value::()); - let _: Ty = core::mem::transmute(value::()); + let _: Ty = transmute(value::()); + let _: Ty = transmute(value::()); - let _: Ty2C = core::mem::transmute(value::>()); // Lint, Ty2 is unordered - let _: Ty2 = core::mem::transmute(value::>()); // Lint, Ty2 is unordered + let _: Ty2C = transmute(value::>()); // Lint, Ty2 is unordered + let _: Ty2 = 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 = transmute(value::>>()); // Ok, Ty2 types are the same + let _: Ty> = 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 _: Ty2 = transmute(value::>>()); // Lint, different Ty2 instances + let _: Ty> = transmute(value::>()); // Lint, different Ty2 instances - let _: Ty<&()> = core::mem::transmute(value::<&()>()); - let _: &() = core::mem::transmute(value::>()); + let _: Ty<&()> = transmute(value::<&()>()); + let _: &() = 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 _: &Ty2 = transmute(value::>>()); // Lint, different Ty2 instances + let _: Ty<&Ty2> = 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 = transmute(value::<&Ty2>()); // Ok, pointer to usize conversion + let _: &Ty2 = 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 + let _: Ty<[u8; 8]> = transmute(value::>()); // Ok, transmute to byte array + let _: Ty2 = transmute(value::>()); // Ok, transmute from byte array // issue #8417 - let _: Ty2C, ()> = core::mem::transmute(value::>()); // Ok, Ty2 types are the same - let _: Ty2 = core::mem::transmute(value::, ()>>()); // Ok, Ty2 types are the same + let _: Ty2C, ()> = transmute(value::>()); // Ok, Ty2 types are the same + let _: Ty2 = transmute(value::, ()>>()); // Ok, Ty2 types are the same + + let _: &'static mut Ty2 = transmute(value::>>()); // Ok, Ty2 types are the same + let _: Box> = transmute(value::<&'static mut Ty2>()); // Ok, Ty2 types are the same + let _: *mut Ty2 = transmute(value::>>()); // Ok, Ty2 types are the same + let _: Box> = transmute(value::<*mut Ty2>()); // Ok, Ty2 types are the same + + let _: &'static mut Ty2 = transmute(value::>>()); // Lint, different Ty2 instances + let _: Box> = transmute(value::<&'static mut Ty2>()); // Lint, different Ty2 instances - // Ty2 types are the same - let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Ok - // Ty2 types are the same - let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Ok - let _: *mut Ty2 = core::mem::transmute(value::>>()); // Ok, Ty2 types are the same - let _: Box> = core::mem::transmute(value::<*mut Ty2>()); // Ok, Ty2 types are the same + let _: *const () = transmute(value::>>()); // Ok, type erasure + let _: Ty<&Ty2> = transmute(value::<*const ()>()); // Ok, reverse type erasure - // Different Ty2 instances - let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Lint - // Different Ty2 instances - let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Lint + let _: *const c_void = transmute(value::>>()); // Ok, type erasure + let _: Ty<&Ty2> = transmute(value::<*const c_void>()); // Ok, reverse type erasure + enum Erase {} + let _: *const Erase = transmute(value::>>()); // Ok, type erasure + let _: Ty<&Ty2> = transmute(value::<*const Erase>()); // Ok, reverse type erasure + struct Erase2( + [u8; 0], + core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, + ); + let _: *const Erase2 = transmute(value::>>()); // Ok, type erasure + let _: Ty<&Ty2> = transmute(value::<*const Erase2>()); // Ok, reverse type erasure } } diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 98035305ec0c3..42d544fc954c5 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -1,62 +1,62 @@ error: transmute from `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:23:33 + --> $DIR/transmute_undefined_repr.rs:26:33 | -LL | let _: Ty2C = core::mem::transmute(value::>()); // Lint, Ty2 is unordered - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Ty2C = 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 + --> $DIR/transmute_undefined_repr.rs:27:32 | -LL | let _: Ty2 = core::mem::transmute(value::>()); // Lint, Ty2 is unordered - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Ty2 = 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 + --> $DIR/transmute_undefined_repr.rs:32:32 | -LL | let _: Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Ty2 = 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 + --> $DIR/transmute_undefined_repr.rs:33:36 | -LL | let _: Ty> = core::mem::transmute(value::>()); // Lint, different Ty2 instances - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Ty> = transmute(value::>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty<&Ty2>` to `&Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:35:33 + --> $DIR/transmute_undefined_repr.rs:38:33 | -LL | let _: &Ty2 = core::mem::transmute(value::>>()); // Lint, different Ty2 instances - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: &Ty2 = 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<&Ty2>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:36:37 + --> $DIR/transmute_undefined_repr.rs:39:37 | -LL | let _: Ty<&Ty2> = core::mem::transmute(value::<&Ty2>()); // Lint, different Ty2 instances - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `std::boxed::Box>` to `&mut Ty2`, both of which have an undefined layout --> $DIR/transmute_undefined_repr.rs:56:45 | -LL | let _: &'static mut Ty2 = core::mem::transmute(value::>>()); // Lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: &'static mut Ty2 = transmute(value::>>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&mut Ty2` to `std::boxed::Box>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:58:37 + --> $DIR/transmute_undefined_repr.rs:57:37 | -LL | let _: Box> = core::mem::transmute(value::<&'static mut Ty2>()); // Lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let _: Box> = transmute(value::<&'static mut Ty2>()); // Lint, different Ty2 instances + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts From 7c07022c987a9f90218f70defe48b2ddb0824a84 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Feb 2022 10:45:49 -0500 Subject: [PATCH 40/55] Allow transmuting fat pointers to some types in `transmute_undefined_repr` --- .../src/transmute/transmute_undefined_repr.rs | 66 ++++++++++--------- tests/ui/transmute_undefined_repr.rs | 16 ++++- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 74bdace408c2a..2630bc4e9ab34 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -19,33 +19,39 @@ pub(super) fn check<'tcx>( while 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 from_ty_orig.peel_refs() != unsized_ty { - diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); - } - }, - ); - return true; + ReducedTys::FromFatPtr { unsized_ty, to_ty } => match reduce_ty(cx, to_ty) { + ReducedTy::IntArray | ReducedTy::TypeErasure => break, + _ => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute from `{}` which has an undefined layout", from_ty_orig), + |diag| { + if 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 to_ty_orig.peel_refs() != unsized_ty { - diag.note(&format!("the contained type `&{}` has an undefined layout", unsized_ty)); - } - }, - ); - return true; + ReducedTys::ToFatPtr { unsized_ty, from_ty } => match reduce_ty(cx, from_ty) { + ReducedTy::IntArray | ReducedTy::TypeErasure => break, + _ => { + span_lint_and_then( + cx, + TRANSMUTE_UNDEFINED_REPR, + e.span, + &format!("transmute to `{}` which has an undefined layout", to_ty_orig), + |diag| { + if 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, @@ -184,8 +190,8 @@ pub(super) fn check<'tcx>( } enum ReducedTys<'tcx> { - FromFatPtr { unsized_ty: Ty<'tcx> }, - ToFatPtr { unsized_ty: Ty<'tcx> }, + FromFatPtr { unsized_ty: Ty<'tcx>, to_ty: Ty<'tcx> }, + ToFatPtr { unsized_ty: Ty<'tcx>, from_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> }, @@ -211,12 +217,12 @@ fn reduce_refs<'tcx>( (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 } + ReducedTys::FromFatPtr { unsized_ty, to_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 } + ReducedTys::ToFatPtr { unsized_ty, from_ty } }, (ty::Ref(_, from_ty, _) | ty::RawPtr(TypeAndMut { ty: from_ty, .. }), _) => { ReducedTys::FromPtr { from_ty, to_ty } diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index c1ee0d98a9212..84dd1ada8c3dd 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -1,8 +1,8 @@ #![warn(clippy::transmute_undefined_repr)] -#![allow(clippy::unit_arg)] +#![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)] use core::ffi::c_void; -use core::mem::transmute; +use core::mem::{size_of, transmute}; fn value() -> T { unimplemented!() @@ -72,5 +72,17 @@ fn main() { ); let _: *const Erase2 = transmute(value::>>()); // Ok, type erasure let _: Ty<&Ty2> = transmute(value::<*const Erase2>()); // Ok, reverse type erasure + + let _: *const () = transmute(value::<&&[u8]>()); // Ok, type erasure + let _: &&[u8] = transmute(value::<*const ()>()); // Ok, reverse type erasure + + let _: *mut c_void = transmute(value::<&mut &[u8]>()); // Ok, type erasure + let _: &mut &[u8] = transmute(value::<*mut c_void>()); // Ok, reverse type erasure + + let _: [u8; size_of::<&[u8]>()] = transmute(value::<&[u8]>()); // Ok, transmute to byte array + let _: &[u8] = transmute(value::<[u8; size_of::<&[u8]>()]>()); // Ok, transmute from byte array + + let _: [usize; 2] = transmute(value::<&[u8]>()); // Ok, transmute to int array + let _: &[u8] = transmute(value::<[usize; 2]>()); // Ok, transmute from int array } } From d28d19d74cfc94921efa06dffb1d3752d507f9dd Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 16 Feb 2022 14:59:04 -0500 Subject: [PATCH 41/55] Fix `transmute_undefined_repr` when converting between a fat pointer and a type containing a fat pointer --- .../src/transmute/transmute_undefined_repr.rs | 20 +++++++++++++++++-- tests/ui/transmute_undefined_repr.rs | 3 +++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 2630bc4e9ab34..b6cc1676b0086 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -19,8 +19,16 @@ pub(super) fn check<'tcx>( while from_ty != to_ty { match reduce_refs(cx, e.span, from_ty, to_ty) { - ReducedTys::FromFatPtr { unsized_ty, to_ty } => match reduce_ty(cx, to_ty) { + ReducedTys::FromFatPtr { + unsized_ty, + to_ty: to_sub_ty, + } => match reduce_ty(cx, to_sub_ty) { ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::Ref(to_sub_ty) => { + from_ty = unsized_ty; + to_ty = to_sub_ty; + continue; + }, _ => { span_lint_and_then( cx, @@ -36,8 +44,16 @@ pub(super) fn check<'tcx>( return true; }, }, - ReducedTys::ToFatPtr { unsized_ty, from_ty } => match reduce_ty(cx, from_ty) { + ReducedTys::ToFatPtr { + unsized_ty, + from_ty: from_sub_ty, + } => match reduce_ty(cx, from_sub_ty) { ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::Ref(from_sub_ty) => { + from_ty = from_sub_ty; + to_ty = unsized_ty; + continue; + }, _ => { span_lint_and_then( cx, diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index 84dd1ada8c3dd..b163d6056343d 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -84,5 +84,8 @@ fn main() { let _: [usize; 2] = transmute(value::<&[u8]>()); // Ok, transmute to int array let _: &[u8] = transmute(value::<[usize; 2]>()); // Ok, transmute from int array + + let _: *const [u8] = transmute(value::>()); // Ok + let _: Box<[u8]> = transmute(value::<*mut [u8]>()); // Ok } } From 78345b4d099063e8f34dc4c7bd2a31670df838f7 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 18 Feb 2022 15:41:46 -0500 Subject: [PATCH 42/55] Clarify `cfg` detection process in `matches.rs` --- clippy_lints/src/matches/mod.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 14dd7cb1dacbb..92179eb6f0e60 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -668,9 +668,27 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar }); let end = e.span.hi(); + // Walk through all the non-code space before each match arm. The space trailing the final arm is + // handled after the `try_fold` e.g. + // + // match foo { + // _________^- everything between the scrutinee and arm1 + //| arm1 => (), + //|---^___________^ everything before arm2 + //| #[cfg(feature = "enabled")] + //| arm2 => some_code(), + //|---^____________________^ everything before arm3 + //| // some comment about arm3 + //| arm3 => some_code(), + //|---^____________________^ everything after arm3 + //| #[cfg(feature = "disabled")] + //| arm4 = some_code(), + //|}; + //|^ let found = arm_spans.try_fold(start, |start, range| { let Some((end, next_start)) = range else { - // Shouldn't happen, but treat this as though a `cfg` attribute were found + // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were + // found. return Err(()); }; let span = SpanData { @@ -697,6 +715,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar } } +/// Checks if the given span contains a `#[cfg(..)]` attribute fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { let Some(snip) = snippet_opt(cx, s) else { // Assume true. This would require either an invalid span, or one which crosses file boundaries. @@ -708,6 +727,8 @@ fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { pos += t.len; (t.kind, start..pos) }); + + // Search for the token sequence [`#`, `[`, `cfg`] while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) { let mut iter = iter.by_ref().skip_while(|(t, _)| { matches!( From a89c7958f5df4d45bfcf084cf371661bb2eaa96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Kr=C3=BCger?= Date: Sun, 20 Feb 2022 13:45:37 +0100 Subject: [PATCH 43/55] tests: default to more threads for ui-tests Benchmarks (tested on i5-7200U, 2 core 4 threads) ``` master branch: cargo test // prime caches cargo --color=always test 70,39s user 21,91s system 180% cpu 51,035 total cargo --color=always test 70,77s user 22,13s system 180% cpu 51,579 total cargo --color=always test 70,97s user 22,12s system 180% cpu 51,673 total cargo --color=always nextest run 78,74s user 22,27s system 220% cpu 45,829 total cargo --color=always nextest run 78,46s user 21,92s system 224% cpu 44,674 total cargo --color=always nextest run 78,31s user 22,21s system 228% cpu 43,909 total Patched (ui_speedup branch) cargo test // prime cache cargo --color=always test 97,51s user 32,02s system 288% cpu 44,905 total cargo --color=always test 99,19s user 31,91s system 276% cpu 47,436 total cargo --color=always test 98,47s user 31,84s system 284% cpu 45,744 total cargo --color=always nextest run 102,18s user 30,80s system 350% cpu 37,902 total cargo --color=always nextest run 99,75s user 29,86s system 350% cpu 36,935 total cargo --color=always nextest run 100,36s user 29,93s system 351% cpu 37,061 total ``` --- Cargo.toml | 1 + tests/compile-test.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index e445889a58f77..ef5b0a0cd7830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ syn = { version = "1.0", features = ["full"] } futures = "0.3" parking_lot = "0.11.2" tokio = { version = "1", features = ["io-util"] } +num_cpus = "1.13" [build-dependencies] rustc_tools_util = { version = "0.2", path = "rustc_tools_util" } diff --git a/tests/compile-test.rs b/tests/compile-test.rs index a82ff18283931..6bc74bc1e9aad 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -162,6 +162,11 @@ fn run_ui() { let config = base_config("ui"); // use tests/clippy.toml let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap()); + let _threads = VarGuard::set( + "RUST_TEST_THREADS", + // if RUST_TEST_THREADS is set, adhere to it, otherwise override it + env::var("RUST_TEST_THREADS").unwrap_or_else(|_| num_cpus::get().to_string()), + ); compiletest::run_tests(&config); } From e3b5cac3c8d4dcbfd767dc8ba99380ab4ac86a91 Mon Sep 17 00:00:00 2001 From: tamaron Date: Mon, 21 Feb 2022 13:25:53 +0900 Subject: [PATCH 44/55] fix --- clippy_lints/src/large_enum_variant.rs | 50 ++++++++++++-------------- tests/ui/large_enum_variant.rs | 24 +++++++++++++ tests/ui/large_enum_variant.stderr | 18 +++++++++- 3 files changed, 64 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 0191713f60d3f..d1dc6b775c567 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -84,34 +84,30 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { if adt.variants.len() <= 1 { return; } - let mut variants_size: Vec = adt - .variants - .iter() - .enumerate() - .map(|(i, variant)| { - let mut fields_size = Vec::new(); - let size: u64 = variant - .fields - .iter() - .enumerate() - .filter_map(|(i, f)| { - let ty = cx.tcx.type_of(f.did); - // don't count generics by filtering out everything - // that does not have a layout - cx.layout_of(ty).ok().map(|l| { - let size = l.size.bytes(); - fields_size.push(FieldInfo { ind: i, size }); - size - }) - }) - .sum(); - VariantInfo { - ind: i, - size, - fields_size, + let mut variants_size: Vec = Vec::new(); + for (i, variant) in adt.variants.iter().enumerate() { + let mut fields_size = Vec::new(); + for (i, f) in variant.fields.iter().enumerate() { + let ty = cx.tcx.type_of(f.did); + // don't lint variants which have a field of generic type. + match cx.layout_of(ty) { + Ok(l) => { + let fsize = l.size.bytes(); + fields_size.push(FieldInfo { ind: i, size: fsize }); + }, + Err(_) => { + return; + }, } - }) - .collect(); + } + let size: u64 = fields_size.iter().map(|info| info.size).sum(); + + variants_size.push(VariantInfo { + ind: i, + size, + fields_size, + }); + } variants_size.sort_by(|a, b| (b.size.cmp(&a.size))); diff --git a/tests/ui/large_enum_variant.rs b/tests/ui/large_enum_variant.rs index b45cc849eaec4..cee9e2372c227 100644 --- a/tests/ui/large_enum_variant.rs +++ b/tests/ui/large_enum_variant.rs @@ -74,6 +74,30 @@ enum LargeEnum8 { ContainingMoreThanOneField([i32; 8000], [i32; 2], [i32; 9500], [i32; 30]), } +enum LargeEnum9 { + A(Struct<()>), + B(Struct2), +} + +enum LargeEnumOk2 { + A(T), + B(Struct2), +} + +enum LargeEnumOk3 { + A(Struct), + B(Struct2), +} + +struct Struct { + a: i32, + t: T, +} + +struct Struct2 { + a: [i32; 8000], +} + fn main() { large_enum_variant!(); } diff --git a/tests/ui/large_enum_variant.stderr b/tests/ui/large_enum_variant.stderr index 899f97ce2e1e9..cbf2ac972e2b2 100644 --- a/tests/ui/large_enum_variant.stderr +++ b/tests/ui/large_enum_variant.stderr @@ -111,5 +111,21 @@ help: consider boxing the large fields to reduce the total size of the enum LL | ContainingMoreThanOneField(Box<[i32; 8000]>, [i32; 2], Box<[i32; 9500]>, [i32; 30]), | ~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ -error: aborting due to 7 previous errors +error: large size difference between variants + --> $DIR/large_enum_variant.rs:79:5 + | +LL | B(Struct2), + | ^^^^^^^^^^ this variant is 32000 bytes + | +note: and the second-largest variant is 4 bytes: + --> $DIR/large_enum_variant.rs:78:5 + | +LL | A(Struct<()>), + | ^^^^^^^^^^^^^ +help: consider boxing the large fields to reduce the total size of the enum + | +LL | B(Box), + | ~~~~~~~~~~~~ + +error: aborting due to 8 previous errors From e390e6c46914b54ea2ffa901f3c6b046b9a7b7d4 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 7 Feb 2022 16:06:55 +0100 Subject: [PATCH 45/55] update clippy --- clippy_lints/src/functions/must_use.rs | 2 +- .../src/matches/redundant_pattern_match.rs | 4 ++-- clippy_lints/src/mut_key.rs | 2 +- clippy_lints/src/non_send_fields_in_send_ty.rs | 4 ++-- .../src/transmute/transmute_undefined_repr.rs | 7 +++---- clippy_utils/src/ty.rs | 18 +++++++++--------- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 3e3718b9445f7..ea9b68d1a40e3 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -193,7 +193,7 @@ fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &m || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path)) && substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)) }, - ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)), + ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)), ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys), ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => { mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys) diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index e195fddefaba3..677b8cdf2ba0c 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -58,8 +58,8 @@ fn type_needs_ordered_drop_inner<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, see // 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::Tuple(fields) => fields.iter().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)) diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index b4e29101b3961..ce9ca15430e42 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -142,7 +142,7 @@ fn is_interior_mutable_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Sp size.try_eval_usize(cx.tcx, cx.param_env).map_or(true, |u| u != 0) && is_interior_mutable_type(cx, inner_ty, span) }, - Tuple(..) => ty.tuple_fields().any(|ty| is_interior_mutable_type(cx, ty, span)), + Tuple(fields) => fields.iter().any(|ty| is_interior_mutable_type(cx, ty, span)), Adt(def, substs) => { // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to // that of their type parameters. Note: we don't include `HashSet` and `HashMap` diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index f4de999a9281a..5168ca67b6abb 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -202,8 +202,8 @@ fn ty_allowed_with_raw_pointer_heuristic<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'t // The type is known to be `!Send` and `!Copy` match ty.kind() { - ty::Tuple(_) => ty - .tuple_fields() + ty::Tuple(fields) => fields + .iter() .all(|ty| ty_allowed_with_raw_pointer_heuristic(cx, ty, send_trait)), ty::Array(ty, _) | ty::Slice(ty) => ty_allowed_with_raw_pointer_heuristic(cx, *ty, send_trait), ty::Adt(_, substs) => { diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 9ed5952a109a5..a57c819cb2256 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -2,7 +2,7 @@ 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::subst::Subst; use rustc_middle::ty::{self, Ty, TypeAndMut}; use rustc_span::Span; @@ -246,11 +246,10 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> 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 { + let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::OrderedFields(ty); }; - if iter.all(|ty| is_zero_sized_ty(cx, ty)) { + if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; continue; } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index b44899e6bd587..0d39226d97035 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -169,7 +169,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { // because we don't want to lint functions returning empty arrays is_must_use_ty(cx, *ty) }, - ty::Tuple(substs) => substs.types().any(|ty| is_must_use_ty(cx, ty)), + ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)), ty::Opaque(ref def_id, _) => { for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) { if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() { @@ -249,11 +249,11 @@ pub fn is_non_aggregate_primitive_type(ty: Ty<'_>) -> bool { /// Returns `true` if the given type is a primitive (a `bool` or `char`, any integer or /// floating-point number type, a `str`, or an array, slice, or tuple of those types). pub fn is_recursively_primitive_type(ty: Ty<'_>) -> bool { - match ty.kind() { + match *ty.kind() { ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true, ty::Ref(_, inner, _) if *inner.kind() == ty::Str => true, - ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(*inner_type), - ty::Tuple(inner_types) => inner_types.types().all(is_recursively_primitive_type), + ty::Array(inner_type, _) | ty::Slice(inner_type) => is_recursively_primitive_type(inner_type), + ty::Tuple(inner_types) => inner_types.iter().all(is_recursively_primitive_type), _ => false, } } @@ -393,9 +393,9 @@ pub fn same_type_and_consts<'tcx>(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { /// Checks if a given type looks safe to be uninitialized. pub fn is_uninit_value_valid_for_ty(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - match ty.kind() { - ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, *component), - ty::Tuple(types) => types.types().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), + match *ty.kind() { + ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component), + ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)), ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did), _ => false, } @@ -426,8 +426,8 @@ impl<'tcx> ExprFnSig<'tcx> { 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()), + Self::Closure(sig) => sig.input(0).map_bound(|ty| ty.tuple_fields()[i]), + Self::Trait(inputs, _) => inputs.map_bound(|ty| ty.tuple_fields()[i]), } } From 382b3f0601ea510e4cb4d0dd6c73310eb79708de Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 23 Feb 2022 01:04:49 -0500 Subject: [PATCH 46/55] Fix counting the number of unchangeable arguments in `ptr_arg` --- clippy_lints/src/ptr.rs | 2 +- tests/ui/ptr_arg.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index e0ce1b7db003a..2c328195f24e1 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -547,7 +547,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: // Helper function to handle early returns. let mut set_skip_flag = || { - if result.skip { + if !result.skip { self.skip_count += 1; } result.skip = true; diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 00b99da2631c6..97990fedd51f3 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -186,3 +186,11 @@ pub trait Trait { fn f(v: &mut Vec); fn f2(v: &mut Vec) {} } + +// Issue #8463 +fn two_vecs(a: &mut Vec, b: &mut Vec) { + a.push(0); + a.push(0); + a.push(0); + b.push(1); +} From db62821c03ed89404891499fb0840b0b7dc78e11 Mon Sep 17 00:00:00 2001 From: tamaron Date: Thu, 24 Feb 2022 00:16:24 +0900 Subject: [PATCH 47/55] fix --- clippy_lints/src/eta_reduction.rs | 8 ++++++++ tests/ui/eta.fixed | 19 +++++++++++++++++++ tests/ui/eta.rs | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 263bff4873caf..687e56e57f45f 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -10,6 +10,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind, Param, PatKind, Unsafety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; +use rustc_middle::ty::binding::BindingMode; use rustc_middle::ty::subst::Subst; use rustc_middle::ty::{self, ClosureKind, Ty, TypeFoldable}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -169,11 +170,18 @@ fn check_inputs(cx: &LateContext<'_>, params: &[Param<'_>], call_args: &[Expr<'_ if params.len() != call_args.len() { return false; } + let binding_modes = cx.typeck_results().pat_binding_modes(); std::iter::zip(params, call_args).all(|(param, arg)| { match param.pat.kind { PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {}, _ => return false, } + // checks that parameters are not bound as `ref` + //dbg!(binding_modes.get(param.pat.hir_id)); + if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) { + return false; + } + match *cx.typeck_results().expr_adjustments(arg) { [] => true, [ diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 618f80cdcf849..5aedbea381f23 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -256,3 +256,22 @@ fn arc_fp() { (0..5).map(|n| arc(n)); Some(4).map(|n| ref_arc(n)); } + +// #8460 Don't replace closures with params bounded as `ref` +mod bind_by_ref { + struct A; + struct B; + + impl From<&A> for B { + fn from(A: &A) -> Self { + B + } + } + + fn test() { + // should not lint + Some(A).map(|a| B::from(&a)); + // should not lint + Some(A).map(|ref a| B::from(a)); + } +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index a759e6eb514b4..5fdf7fb977169 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -256,3 +256,22 @@ fn arc_fp() { (0..5).map(|n| arc(n)); Some(4).map(|n| ref_arc(n)); } + +// #8460 Don't replace closures with params bounded as `ref` +mod bind_by_ref { + struct A; + struct B; + + impl From<&A> for B { + fn from(A: &A) -> Self { + B + } + } + + fn test() { + // should not lint + Some(A).map(|a| B::from(&a)); + // should not lint + Some(A).map(|ref a| B::from(a)); + } +} From 31b49b0be8e1153da13be71dcc9d6bff888217e2 Mon Sep 17 00:00:00 2001 From: tamaron Date: Thu, 24 Feb 2022 00:25:07 +0900 Subject: [PATCH 48/55] fix typo --- clippy_lints/src/eta_reduction.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 687e56e57f45f..d23c0c225e192 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -176,8 +176,7 @@ fn check_inputs(cx: &LateContext<'_>, params: &[Param<'_>], call_args: &[Expr<'_ PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {}, _ => return false, } - // checks that parameters are not bound as `ref` - //dbg!(binding_modes.get(param.pat.hir_id)); + // checks that parameters are not bound as `ref` or `ref mut` if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) { return false; } From 5ece8d2bc406643babe3fca84acc31a3ac58d9c4 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 24 Feb 2022 19:32:47 +0100 Subject: [PATCH 49/55] Bump Clippy Version -> 0.1.61 --- Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- clippy_utils/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef5b0a0cd7830..5cc5530f874dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.60" +version = "0.1.61" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 2053ca64ba23d..40d7dd702628f 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.60" +version = "0.1.61" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index afff6491aba64..d3ed8da4499f8 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.60" +version = "0.1.61" edition = "2021" publish = false From ce1904f6cf90bf16f51d226e1870abc643e5bfa2 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 24 Feb 2022 19:33:10 +0100 Subject: [PATCH 50/55] Bump nightly version -> 2022-02-24 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index f065f0bffc7bf..4d2c57619912f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-02-10" +channel = "nightly-2022-02-24" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From 862211d540fe54deb46daa661716cb06a74151f8 Mon Sep 17 00:00:00 2001 From: Florian Nagel Date: Fri, 25 Feb 2022 14:36:23 +0100 Subject: [PATCH 51/55] Disable ``[`new-without-default`]`` for new() methods that are marked with '#[doc(hidden)]' Fixes issue #8152 --- clippy_lints/src/new_without_default.rs | 4 ++++ tests/ui/new_without_default.rs | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 47121ad84f2c9..b40425bb6355c 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -85,6 +85,10 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // can't be implemented for unsafe new return; } + if clippy_utils::is_doc_hidden(cx.tcx.hir().attrs(id)) { + // shouldn't be implemented when it is hidden in docs + return; + } if impl_item .generics .params diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index c76e3b424383e..e94f99c95f481 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -201,4 +201,14 @@ pub mod issue7220 { } } +// see issue #8152 +// This should not create any lints +pub struct DocHidden; +impl DocHidden { + #[doc(hidden)] + pub fn new() -> Self { + DocHidden + } +} + fn main() {} From 2955db493ef9ceaf4cc9bcd3b83680c0206d001d Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 25 Feb 2022 15:38:06 +0000 Subject: [PATCH 52/55] Replace some more paths with diagnostic items --- clippy_lints/src/drop_forget_ref.rs | 38 +++++++++-------- clippy_lints/src/manual_bits.rs | 5 ++- clippy_lints/src/mem_forget.rs | 4 +- clippy_lints/src/mem_replace.rs | 4 +- .../src/methods/uninit_assumed_init.rs | 5 ++- clippy_lints/src/redundant_clone.rs | 2 +- clippy_lints/src/size_of_in_element_count.rs | 4 +- clippy_lints/src/undropped_manually_drops.rs | 12 ++++-- clippy_utils/src/macros.rs | 41 ++++++++++--------- clippy_utils/src/paths.rs | 34 --------------- 10 files changed, 63 insertions(+), 86 deletions(-) diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index a8f8e3d8a42c0..5c4b35fd4b9d2 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::ty::is_copy; -use clippy_utils::{match_def_path, paths}; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -128,14 +128,16 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { let arg_ty = cx.typeck_results().expr_ty(arg); if let ty::Ref(..) = arg_ty.kind() { - if match_def_path(cx, def_id, &paths::DROP) { - lint = DROP_REF; - msg = DROP_REF_SUMMARY.to_string(); - } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { - lint = FORGET_REF; - msg = FORGET_REF_SUMMARY.to_string(); - } else { - return; + match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::mem_drop) => { + lint = DROP_REF; + msg = DROP_REF_SUMMARY.to_string(); + }, + Some(sym::mem_forget) => { + lint = FORGET_REF; + msg = FORGET_REF_SUMMARY.to_string(); + }, + _ => return, } span_lint_and_note(cx, lint, @@ -144,14 +146,16 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { Some(arg.span), &format!("argument has type `{}`", arg_ty)); } else if is_copy(cx, arg_ty) { - if match_def_path(cx, def_id, &paths::DROP) { - lint = DROP_COPY; - msg = DROP_COPY_SUMMARY.to_string(); - } else if match_def_path(cx, def_id, &paths::MEM_FORGET) { - lint = FORGET_COPY; - msg = FORGET_COPY_SUMMARY.to_string(); - } else { - return; + match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::mem_drop) => { + lint = DROP_COPY; + msg = DROP_COPY_SUMMARY.to_string(); + }, + Some(sym::mem_forget) => { + lint = FORGET_COPY; + msg = FORGET_COPY_SUMMARY.to_string(); + }, + _ => return, } span_lint_and_note(cx, lint, diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 809aa168a7a0e..dcf44303cf449 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::{match_def_path, meets_msrv, msrvs, paths}; +use clippy_utils::{meets_msrv, msrvs}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; @@ -8,6 +8,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -99,7 +100,7 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option< if let Some(GenericArg::Type(real_ty)) = args.args.get(0); if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::MEM_SIZE_OF); + if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id); then { cx.typeck_results().node_substs(count_func.hir_id).types().next().map(|resolved_ty| (real_ty, resolved_ty)) } else { diff --git a/clippy_lints/src/mem_forget.rs b/clippy_lints/src/mem_forget.rs index 5ffcfd4d26417..d6c235b5a693a 100644 --- a/clippy_lints/src/mem_forget.rs +++ b/clippy_lints/src/mem_forget.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{match_def_path, paths}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -32,7 +32,7 @@ impl<'tcx> LateLintPass<'tcx> for MemForget { if let ExprKind::Call(path_expr, [ref first_arg, ..]) = e.kind { if let ExprKind::Path(ref qpath) = path_expr.kind { if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() { - if match_def_path(cx, def_id, &paths::MEM_FORGET) { + if cx.tcx.is_diagnostic_item(sym::mem_forget, def_id) { let forgot_ty = cx.typeck_results().expr_ty(first_arg); if forgot_ty.ty_adt_def().map_or(false, |def| def.has_dtor(cx.tcx)) { diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index a184806d021bc..054937e3e36b9 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_lang_ctor, match_def_path, meets_msrv, msrvs, paths}; +use clippy_utils::{is_default_equivalent, is_lang_ctor, meets_msrv, msrvs}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; @@ -249,7 +249,7 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace { if let ExprKind::Call(func, func_args) = expr.kind; if let ExprKind::Path(ref func_qpath) = func.kind; if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::MEM_REPLACE); + if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id); if let [dest, src] = func_args; then { check_replace_option_with_none(cx, src, dest, expr.span); diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs index ce89189bce977..77d21f1d3730c 100644 --- a/clippy_lints/src/methods/uninit_assumed_init.rs +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -1,8 +1,9 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::{is_expr_path_def_path, paths, ty::is_uninit_value_valid_for_ty}; +use clippy_utils::{is_expr_diagnostic_item, ty::is_uninit_value_valid_for_ty}; use if_chain::if_chain; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_span::sym; use super::UNINIT_ASSUMED_INIT; @@ -11,7 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr if_chain! { if let hir::ExprKind::Call(callee, args) = recv.kind; if args.is_empty(); - if is_expr_path_def_path(cx, callee, &paths::MEM_MAYBEUNINIT_UNINIT); + if is_expr_diagnostic_item(cx, callee, sym::maybe_uninit_uninit); if !is_uninit_value_valid_for_ty(cx, cx.typeck_results().expr_ty_adjusted(expr)); then { span_lint( diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 3e0e32857f1da..b3988973256c4 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { } if let ty::Adt(def, _) = arg_ty.kind() { - if match_def_path(cx, def.did, &paths::MEM_MANUALLY_DROP) { + if def.is_manually_drop() { continue; } } diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index f3515ea3c2dde..3d7dc49b406a6 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -9,6 +9,7 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty, TypeAndMut}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -44,8 +45,7 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: if !inverted; if let ExprKind::Path(ref count_func_qpath) = count_func.kind; if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::MEM_SIZE_OF) - || match_def_path(cx, def_id, &paths::MEM_SIZE_OF_VAL); + if matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::mem_size_of | sym::mem_size_of_val)); then { cx.typeck_results().node_substs(count_func.hir_id).types().next() } else { diff --git a/clippy_lints/src/undropped_manually_drops.rs b/clippy_lints/src/undropped_manually_drops.rs index 7557e14d11f52..db652766705c4 100644 --- a/clippy_lints/src/undropped_manually_drops.rs +++ b/clippy_lints/src/undropped_manually_drops.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::path_res; use clippy_utils::ty::is_type_lang_item; -use clippy_utils::{match_function_call, paths}; -use rustc_hir::{lang_items, Expr}; +use rustc_hir::{lang_items, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -38,9 +39,12 @@ declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]); impl<'tcx> LateLintPass<'tcx> for UndroppedManuallyDrops { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some([arg_0, ..]) = match_function_call(cx, expr, &paths::DROP) { + if_chain! { + if let ExprKind::Call(fun, [arg_0, ..]) = expr.kind; + if path_res(cx, fun).opt_def_id() == cx.tcx.get_diagnostic_item(sym::mem_drop); let ty = cx.typeck_results().expr_ty(arg_0); - if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) { + if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop); + then { span_lint_and_help( cx, UNDROPPED_MANUALLY_DROPS, diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 256f884ae1994..e7d4c5a49521d 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -1,7 +1,6 @@ #![allow(clippy::similar_names)] // `expr` and `expn` use crate::visitors::expr_visitor_no_bodies; -use crate::{match_def_path, paths}; use arrayvec::ArrayVec; use if_chain::if_chain; @@ -14,29 +13,31 @@ use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol}; use std::ops::ControlFlow; -const FORMAT_MACRO_PATHS: &[&[&str]] = &[ - &paths::FORMAT_ARGS_MACRO, - &paths::ASSERT_EQ_MACRO, - &paths::ASSERT_MACRO, - &paths::ASSERT_NE_MACRO, - &paths::EPRINT_MACRO, - &paths::EPRINTLN_MACRO, - &paths::PRINT_MACRO, - &paths::PRINTLN_MACRO, - &paths::WRITE_MACRO, - &paths::WRITELN_MACRO, +const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ + sym::assert_eq_macro, + sym::assert_macro, + sym::assert_ne_macro, + sym::debug_assert_eq_macro, + sym::debug_assert_macro, + sym::debug_assert_ne_macro, + sym::eprint_macro, + sym::eprintln_macro, + sym::format_args_macro, + sym::format_macro, + sym::print_macro, + sym::println_macro, + sym::std_panic_macro, + sym::write_macro, + sym::writeln_macro, ]; -const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[sym::format_macro, sym::std_panic_macro]; - /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`) pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { - FORMAT_MACRO_PATHS - .iter() - .any(|path| match_def_path(cx, macro_def_id, path)) - || FORMAT_MACRO_DIAG_ITEMS - .iter() - .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, macro_def_id)) + if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) { + FORMAT_MACRO_DIAG_ITEMS.contains(&name) + } else { + false + } } /// A macro call, like `vec![1, 2, 3]`. diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index b54bd3a4fef0b..2778e30388e26 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -16,44 +16,27 @@ pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [ #[cfg(feature = "internal")] pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"]; pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const ASSERT_EQ_MACRO: [&str; 3] = ["core", "macros", "assert_eq"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const ASSERT_MACRO: [&str; 4] = ["core", "macros", "builtin", "assert"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const ASSERT_NE_MACRO: [&str; 3] = ["core", "macros", "assert_ne"]; pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"]; pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; -/// Preferably use the diagnostic item `sym::Borrow` where possible -pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"]; -pub const BORROW_MUT_TRAIT: [&str; 3] = ["core", "borrow", "BorrowMut"]; pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"]; pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"]; pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"]; pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"]; pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"]; pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"]; -pub const DEBUG_TRAIT: [&str; 3] = ["core", "fmt", "Debug"]; pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"]; pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"]; /// Preferably use the diagnostic item `sym::deref_method` where possible pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"]; pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"]; -pub const DROP: [&str; 3] = ["core", "mem", "drop"]; #[cfg(feature = "internal")] pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const EPRINT_MACRO: [&str; 3] = ["std", "macros", "eprint"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const EPRINTLN_MACRO: [&str; 3] = ["std", "macros", "eprintln"]; pub const EXIT: [&str; 3] = ["std", "process", "exit"]; pub const F32_EPSILON: [&str; 4] = ["core", "f32", "", "EPSILON"]; pub const F64_EPSILON: [&str; 4] = ["core", "f64", "", "EPSILON"]; pub const FILE: [&str; 3] = ["std", "fs", "File"]; pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const FORMAT_ARGS_MACRO: [&str; 4] = ["core", "macros", "builtin", "format_args"]; pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"]; pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"]; @@ -85,17 +68,8 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; #[cfg(feature = "internal")] pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"]; -pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"]; -pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"]; -pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "ManuallyDrop"]; -pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"]; -pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"]; -pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"]; -pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"]; -pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"]; pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"]; pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"]; -pub const OPS_MODULE: [&str; 2] = ["core", "ops"]; /// Preferably use the diagnostic item `sym::Option` where possible pub const OPTION: [&str; 3] = ["core", "option", "Option"]; pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"]; @@ -116,10 +90,6 @@ pub const PERMISSIONS_FROM_MODE: [&str; 6] = ["std", "os", "unix", "fs", "Permis pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"]; pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"]; pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const PRINT_MACRO: [&str; 3] = ["std", "macros", "print"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const PRINTLN_MACRO: [&str; 3] = ["std", "macros", "println"]; pub const PTR_COPY: [&str; 3] = ["core", "intrinsics", "copy"]; pub const PTR_COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"]; pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"]; @@ -200,8 +170,4 @@ pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"]; pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"]; pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"]; pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const WRITE_MACRO: [&str; 3] = ["core", "macros", "write"]; -#[allow(clippy::invalid_paths)] // `check_path` does not seem to work for macros -pub const WRITELN_MACRO: [&str; 3] = ["core", "macros", "writeln"]; pub const PTR_NON_NULL: [&str; 4] = ["core", "ptr", "non_null", "NonNull"]; From bcbb07f4dc3c147b9f4259df7f8c6a8b2bf91729 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 17 Feb 2022 11:20:47 +0000 Subject: [PATCH 53/55] Rename RecursiveFormatImpl to FormatImpl --- .../src/{recursive_format_impl.rs => format_impl.rs} | 9 ++++----- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_correctness.rs | 2 +- clippy_lints/src/lib.register_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 ++-- 5 files changed, 9 insertions(+), 10 deletions(-) rename clippy_lints/src/{recursive_format_impl.rs => format_impl.rs} (97%) diff --git a/clippy_lints/src/recursive_format_impl.rs b/clippy_lints/src/format_impl.rs similarity index 97% rename from clippy_lints/src/recursive_format_impl.rs rename to clippy_lints/src/format_impl.rs index 5bb9740749b6c..02b2d72193993 100644 --- a/clippy_lints/src/recursive_format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -21,7 +21,6 @@ impl FormatTrait { } } } - declare_clippy_lint! { /// ### What it does /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself @@ -62,12 +61,12 @@ declare_clippy_lint! { } #[derive(Default)] -pub struct RecursiveFormatImpl { +pub struct FormatImpl { // Whether we are inside Display or Debug trait impl - None for neither format_trait_impl: Option, } -impl RecursiveFormatImpl { +impl FormatImpl { pub fn new() -> Self { Self { format_trait_impl: None, @@ -75,9 +74,9 @@ impl RecursiveFormatImpl { } } -impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]); +impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL]); -impl<'tcx> LateLintPass<'tcx> for RecursiveFormatImpl { +impl<'tcx> LateLintPass<'tcx> for FormatImpl { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let Some(format_trait_impl) = is_format_trait_impl(cx, item) { self.format_trait_impl = Some(format_trait_impl); diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index c6f8470cd7db7..b2d9889ba8652 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -68,6 +68,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(format::USELESS_FORMAT), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), @@ -244,7 +245,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ranges::MANUAL_RANGE_CONTAINS), LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), - LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 18fe44282ed0e..35b1e644a8a71 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -24,6 +24,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT), LintId::of(eq_op::EQ_OP), LintId::of(erasing_op::ERASING_OP), + LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF), LintId::of(if_let_mutex::IF_LET_MUTEX), @@ -52,7 +53,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ranges::REVERSED_EMPTY_RANGES), - LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(regex::INVALID_REGEX), LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(serde_api::SERDE_API_MISUSE), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 75ef1b0a9d511..1243fc978b7cb 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -152,6 +152,7 @@ store.register_lints(&[ format::USELESS_FORMAT, format_args::FORMAT_IN_FORMAT_ARGS, format_args::TO_STRING_IN_FORMAT_ARGS, + format_impl::RECURSIVE_FORMAT_IMPL, formatting::POSSIBLE_MISSING_COMMA, formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING, formatting::SUSPICIOUS_ELSE_FORMATTING, @@ -417,7 +418,6 @@ store.register_lints(&[ ranges::RANGE_PLUS_ONE, ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, - recursive_format_impl::RECURSIVE_FORMAT_IMPL, redundant_clone::REDUNDANT_CLONE, redundant_closure_call::REDUNDANT_CLOSURE_CALL, redundant_else::REDUNDANT_ELSE, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a21a87899aa16..230e2b2ccdfb5 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -226,6 +226,7 @@ mod float_literal; mod floating_point_arithmetic; mod format; mod format_args; +mod format_impl; mod formatting; mod from_over_into; mod from_str_radix_10; @@ -333,7 +334,6 @@ mod ptr_eq; mod ptr_offset_with_cast; mod question_mark; mod ranges; -mod recursive_format_impl; mod redundant_clone; mod redundant_closure_call; mod redundant_else; @@ -705,7 +705,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic)); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - store.register_late_pass(|| Box::new(recursive_format_impl::RecursiveFormatImpl::new())); + store.register_late_pass(|| Box::new(format_impl::FormatImpl::new())); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); From 52f3d61a2a97a84c6aa96b7b4cb80aaff1c5f898 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 17 Feb 2022 15:33:07 +0000 Subject: [PATCH 54/55] Add `print_in_format_impl` lint --- CHANGELOG.md | 1 + clippy_lints/src/format_impl.rs | 160 ++++++++++++++------ clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_suspicious.rs | 1 + tests/ui/print_in_format_impl.rs | 58 +++++++ tests/ui/print_in_format_impl.stderr | 46 ++++++ 7 files changed, 220 insertions(+), 48 deletions(-) create mode 100644 tests/ui/print_in_format_impl.rs create mode 100644 tests/ui/print_in_format_impl.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b52a6fcd05e9..35983b7fb506f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3370,6 +3370,7 @@ Released 2018-09-13 [`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence +[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl [`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal [`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr [`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 02b2d72193993..ef8be9e878f6c 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,26 +1,13 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn}; -use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators}; +use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use if_chain::if_chain; -use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, symbol::kw, Symbol}; -#[derive(Clone, Copy)] -enum FormatTrait { - Debug, - Display, -} - -impl FormatTrait { - fn name(self) -> Symbol { - match self { - FormatTrait::Debug => sym::Debug, - FormatTrait::Display => sym::Display, - } - } -} declare_clippy_lint! { /// ### What it does /// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself @@ -60,6 +47,55 @@ declare_clippy_lint! { "Format trait method called while implementing the same Format trait" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `println`, `print`, `eprintln` or `eprint` in an + /// implementation of a formatting trait. + /// + /// ### Why is this bad? + /// Using a print macro is likely unintentional since formatting traits + /// should write to the `Formatter`, not stdout/stderr. + /// + /// ### Example + /// ```rust + /// use std::fmt::{Display, Error, Formatter}; + /// + /// struct S; + /// impl Display for S { + /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + /// println!("S"); + /// + /// Ok(()) + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::fmt::{Display, Error, Formatter}; + /// + /// struct S; + /// impl Display for S { + /// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + /// writeln!(f, "S"); + /// + /// Ok(()) + /// } + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub PRINT_IN_FORMAT_IMPL, + suspicious, + "use of a print macro in a formatting trait impl" +} + +#[derive(Clone, Copy)] +struct FormatTrait { + /// e.g. `sym::Display` + name: Symbol, + /// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}` + formatter_name: Option, +} + #[derive(Default)] pub struct FormatImpl { // Whether we are inside Display or Debug trait impl - None for neither @@ -74,33 +110,29 @@ impl FormatImpl { } } -impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL]); +impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]); impl<'tcx> LateLintPass<'tcx> for FormatImpl { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if let Some(format_trait_impl) = is_format_trait_impl(cx, item) { - self.format_trait_impl = Some(format_trait_impl); - } + fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { + self.format_trait_impl = is_format_trait_impl(cx, impl_item); } - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { // Assume no nested Impl of Debug and Display within eachother - if is_format_trait_impl(cx, item).is_some() { + if is_format_trait_impl(cx, impl_item).is_some() { self.format_trait_impl = None; } } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - match self.format_trait_impl { - Some(FormatTrait::Display) => { - check_to_string_in_display(cx, expr); - check_self_in_format_args(cx, expr, FormatTrait::Display); - }, - Some(FormatTrait::Debug) => { - check_self_in_format_args(cx, expr, FormatTrait::Debug); - }, - None => {}, + let Some(format_trait_impl) = self.format_trait_impl else { return }; + + if format_trait_impl.name == sym::Display { + check_to_string_in_display(cx, expr); } + + check_self_in_format_args(cx, expr, format_trait_impl); + check_print_in_format_impl(cx, expr, format_trait_impl); } } @@ -139,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, if let Some(args) = format_args.args(); then { for arg in args { - if arg.format_trait != impl_trait.name() { + if arg.format_trait != impl_trait.name { continue; } check_format_arg_self(cx, expr, &arg, impl_trait); @@ -155,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs let reference = peel_ref_operators(cx, arg.value); let map = cx.tcx.hir(); // Is the reference self? - let symbol_ident = impl_trait.name().to_ident_string(); if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) { + let FormatTrait { name, .. } = impl_trait; span_lint( cx, RECURSIVE_FORMAT_IMPL, expr.span, - &format!( - "using `self` as `{}` in `impl {}` will cause infinite recursion", - &symbol_ident, &symbol_ident - ), + &format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"), ); } } -fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option { +fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) { + if_chain! { + if let Some(macro_call) = root_macro_call_first_node(cx, expr); + if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id); + then { + let replacement = match name { + sym::print_macro | sym::eprint_macro => "write", + sym::println_macro | sym::eprintln_macro => "writeln", + _ => return, + }; + + let name = name.as_str().strip_suffix("_macro").unwrap(); + + span_lint_and_sugg( + cx, + PRINT_IN_FORMAT_IMPL, + macro_call.span, + &format!("use of `{}!` in `{}` impl", name, impl_trait.name), + "replace with", + if let Some(formatter_name) = impl_trait.formatter_name { + format!("{}!({}, ..)", replacement, formatter_name) + } else { + format!("{}!(..)", replacement) + }, + Applicability::HasPlaceholders, + ); + } + } +} + +fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option { if_chain! { - // Are we at an Impl? - if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind; + if impl_item.ident.name == sym::fmt; + if let ImplItemKind::Fn(_, body_id) = impl_item.kind; + if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id()); if let Some(did) = trait_ref.trait_def_id(); if let Some(name) = cx.tcx.get_diagnostic_name(did); + if matches!(name, sym::Debug | sym::Display); then { - // Is Impl for Debug or Display? - match name { - sym::Debug => Some(FormatTrait::Debug), - sym::Display => Some(FormatTrait::Display), - _ => None, - } + let body = cx.tcx.hir().body(body_id); + let formatter_name = body.params.get(1) + .and_then(|param| param.pat.simple_ident()) + .map(|ident| ident.name); + + Some(FormatTrait { + name, + formatter_name, + }) } else { None } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index b2d9889ba8652..f6d467941e3ef 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -68,6 +68,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(format::USELESS_FORMAT), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), + LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 1243fc978b7cb..686482671b482 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -152,6 +152,7 @@ store.register_lints(&[ format::USELESS_FORMAT, format_args::FORMAT_IN_FORMAT_ARGS, format_args::TO_STRING_IN_FORMAT_ARGS, + format_impl::PRINT_IN_FORMAT_IMPL, format_impl::RECURSIVE_FORMAT_IMPL, formatting::POSSIBLE_MISSING_COMMA, formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING, diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 6a8859e19d71d..465baa8258174 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -10,6 +10,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), + LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING), diff --git a/tests/ui/print_in_format_impl.rs b/tests/ui/print_in_format_impl.rs new file mode 100644 index 0000000000000..64e8868660980 --- /dev/null +++ b/tests/ui/print_in_format_impl.rs @@ -0,0 +1,58 @@ +#![allow(unused, clippy::print_literal, clippy::write_literal)] +#![warn(clippy::print_in_format_impl)] +use std::fmt::{Debug, Display, Error, Formatter}; + +macro_rules! indirect { + () => {{ println!() }}; +} + +macro_rules! nested { + ($($tt:tt)*) => { + $($tt)* + }; +} + +struct Foo; +impl Debug for Foo { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + static WORKS_WITH_NESTED_ITEMS: bool = true; + + print!("{}", 1); + println!("{}", 2); + eprint!("{}", 3); + eprintln!("{}", 4); + nested! { + println!("nested"); + }; + + write!(f, "{}", 5); + writeln!(f, "{}", 6); + indirect!(); + + Ok(()) + } +} + +impl Display for Foo { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + print!("Display"); + write!(f, "Display"); + + Ok(()) + } +} + +struct UnnamedFormatter; +impl Debug for UnnamedFormatter { + fn fmt(&self, _: &mut Formatter) -> Result<(), Error> { + println!("UnnamedFormatter"); + Ok(()) + } +} + +fn main() { + print!("outside fmt"); + println!("outside fmt"); + eprint!("outside fmt"); + eprintln!("outside fmt"); +} diff --git a/tests/ui/print_in_format_impl.stderr b/tests/ui/print_in_format_impl.stderr new file mode 100644 index 0000000000000..63b7179bca7dc --- /dev/null +++ b/tests/ui/print_in_format_impl.stderr @@ -0,0 +1,46 @@ +error: use of `print!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:20:9 + | +LL | print!("{}", 1); + | ^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)` + | + = note: `-D clippy::print-in-format-impl` implied by `-D warnings` + +error: use of `println!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:21:9 + | +LL | println!("{}", 2); + | ^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)` + +error: use of `eprint!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:22:9 + | +LL | eprint!("{}", 3); + | ^^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)` + +error: use of `eprintln!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:23:9 + | +LL | eprintln!("{}", 4); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)` + +error: use of `println!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:25:13 + | +LL | println!("nested"); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)` + +error: use of `print!` in `Display` impl + --> $DIR/print_in_format_impl.rs:38:9 + | +LL | print!("Display"); + | ^^^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)` + +error: use of `println!` in `Debug` impl + --> $DIR/print_in_format_impl.rs:48:9 + | +LL | println!("UnnamedFormatter"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(..)` + +error: aborting due to 7 previous errors + From 0d4e58304dfc7824594e0373efda194f1273156e Mon Sep 17 00:00:00 2001 From: flip1995 Date: Sat, 26 Feb 2022 14:43:58 +0100 Subject: [PATCH 55/55] Update Cargo.lock (Clippy version bump) --- Cargo.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb397821d1c89..f4efcbc11c290 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,7 +621,7 @@ dependencies = [ [[package]] name = "clippy" -version = "0.1.60" +version = "0.1.61" dependencies = [ "cargo_metadata", "clippy_lints", @@ -632,6 +632,7 @@ dependencies = [ "futures 0.3.19", "if_chain", "itertools 0.10.1", + "num_cpus", "parking_lot", "quote", "regex", @@ -662,7 +663,7 @@ dependencies = [ [[package]] name = "clippy_lints" -version = "0.1.60" +version = "0.1.61" dependencies = [ "cargo_metadata", "clippy_utils", @@ -683,7 +684,7 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.60" +version = "0.1.61" dependencies = [ "arrayvec", "if_chain",