mirror of
https://github.com/rust-lang/rust-clippy
synced 2025-02-26 04:17:21 +00:00
Merge remote-tracking branch 'upstream/master' into rustup
This commit is contained in:
commit
0775c06d32
127 changed files with 4486 additions and 3170 deletions
181
CHANGELOG.md
181
CHANGELOG.md
|
@ -6,14 +6,185 @@ document.
|
||||||
|
|
||||||
## Unreleased / In Rust Nightly
|
## 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)
|
[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
|
### New lints
|
||||||
|
|
||||||
* [`transmute_num_to_bytes`]
|
* [`transmute_num_to_bytes`]
|
||||||
|
@ -124,7 +295,7 @@ Current beta, release 2022-01-13
|
||||||
|
|
||||||
## Rust 1.57
|
## 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)
|
[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
|
[`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_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_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_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
|
[`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_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_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_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_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
|
[`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
|
[`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
|
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
|
||||||
|
|
|
@ -5,9 +5,7 @@ use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::lazy::SyncLazy;
|
use std::lazy::SyncLazy;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use walkdir::WalkDir;
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use crate::clippy_project_root;
|
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
||||||
|
@ -24,43 +22,25 @@ static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::ne
|
||||||
///
|
///
|
||||||
/// Panics if the path to a test file is broken
|
/// Panics if the path to a test file is broken
|
||||||
pub fn bless(ignore_timestamp: bool) {
|
pub fn bless(ignore_timestamp: bool) {
|
||||||
let test_suite_dirs = [
|
let extensions = ["stdout", "stderr", "fixed"].map(OsStr::new);
|
||||||
clippy_project_root().join("tests").join("ui"),
|
|
||||||
clippy_project_root().join("tests").join("ui-internal"),
|
WalkDir::new(build_dir())
|
||||||
clippy_project_root().join("tests").join("ui-toml"),
|
.into_iter()
|
||||||
clippy_project_root().join("tests").join("ui-cargo"),
|
.map(Result::unwrap)
|
||||||
];
|
.filter(|entry| entry.path().extension().map_or(false, |ext| extensions.contains(&ext)))
|
||||||
for test_suite_dir in &test_suite_dirs {
|
.for_each(|entry| update_reference_file(&entry, ignore_timestamp));
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) {
|
fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) {
|
||||||
let test_output_path = build_dir().join(test_name);
|
let test_output_path = test_output_entry.path();
|
||||||
let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap();
|
|
||||||
|
|
||||||
// If compiletest did not write any changes during the test run,
|
let reference_file_name = test_output_entry.file_name().to_str().unwrap().replace(".stage-id", "");
|
||||||
// we don't have to update anything
|
let reference_file_path = Path::new("tests")
|
||||||
if !test_output_path.exists() {
|
.join(test_output_path.strip_prefix(build_dir()).unwrap())
|
||||||
return;
|
.with_file_name(reference_file_name);
|
||||||
}
|
|
||||||
|
|
||||||
// If the test output was not updated since the last clippy build, it may be outdated
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,23 +49,14 @@ fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignor
|
||||||
|
|
||||||
if test_output_file != reference_file {
|
if test_output_file != reference_file {
|
||||||
// If a test run caused an output file to change, update the 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");
|
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<bool> {
|
fn updated_since_clippy_build(entry: &DirEntry) -> Option<bool> {
|
||||||
let clippy_build_time = (*CLIPPY_BUILD_TIME)?;
|
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)
|
Some(modified >= clippy_build_time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||||
|
use clippy_utils::get_parent_expr;
|
||||||
use clippy_utils::higher;
|
use clippy_utils::higher;
|
||||||
use clippy_utils::source::snippet_block_with_applicability;
|
use clippy_utils::source::snippet_block_with_applicability;
|
||||||
use clippy_utils::ty::implements_trait;
|
use clippy_utils::ty::implements_trait;
|
||||||
use clippy_utils::{differing_macro_contexts, get_parent_expr};
|
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||||
|
@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||||
if let Some(ex) = &block.expr {
|
if let Some(ex) = &block.expr {
|
||||||
// don't dig into the expression here, just suggest that they remove
|
// don't dig into the expression here, just suggest that they remove
|
||||||
// the block
|
// the block
|
||||||
if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
|
if expr.span.from_expansion() || ex.span.from_expansion() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
|
@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||||
if span.from_expansion() || differing_macro_contexts(expr.span, span) {
|
if span.from_expansion() || expr.span.from_expansion() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// move block higher
|
// move block higher
|
||||||
|
|
105
clippy_lints/src/default_union_representation.rs
Normal file
105
clippy_lints/src/default_union_representation.rs
Normal file
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods {
|
||||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||||
for (index, conf) in self.conf_disallowed.iter().enumerate() {
|
for (index, conf) in self.conf_disallowed.iter().enumerate() {
|
||||||
let segs: Vec<_> = conf.path().split("::").collect();
|
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);
|
self.disallowed.insert(id, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let segs: Vec<_> = path.split("::").collect();
|
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) => {
|
Res::Def(_, id) => {
|
||||||
self.def_ids.insert(id, reason);
|
self.def_ids.insert(id, reason);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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::macros::FormatArgsExpn;
|
||||||
|
use clippy_utils::source::snippet_with_applicability;
|
||||||
use clippy_utils::{is_expn_of, match_function_call, paths};
|
use clippy_utils::{is_expn_of, match_function_call, paths};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
|
@ -79,28 +80,22 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
||||||
"print".into(),
|
"print".into(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let msg = format!("use of `{}.unwrap()`", used);
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
if let [write_output] = *format_args.format_string_parts {
|
let inputs_snippet = snippet_with_applicability(
|
||||||
let mut write_output = write_output.to_string();
|
cx,
|
||||||
if write_output.ends_with('\n') {
|
format_args.inputs_span(),
|
||||||
write_output.pop();
|
"..",
|
||||||
}
|
&mut applicability,
|
||||||
|
);
|
||||||
let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default());
|
span_lint_and_sugg(
|
||||||
span_lint_and_sugg(
|
cx,
|
||||||
cx,
|
EXPLICIT_WRITE,
|
||||||
EXPLICIT_WRITE,
|
expr.span,
|
||||||
expr.span,
|
&format!("use of `{}.unwrap()`", used),
|
||||||
&msg,
|
"try this",
|
||||||
"try this",
|
format!("{}{}!({})", prefix, sugg_mac, inputs_snippet),
|
||||||
sugg,
|
applicability,
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
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 clippy_utils::source::snippet_opt;
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
|
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.
|
/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
|
||||||
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||||
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
|
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);
|
let eq_span = lhs.span.between(rhs.span);
|
||||||
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
|
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
|
||||||
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
|
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) {
|
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
|
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
|
// span between BinOp LHS and RHS
|
||||||
let binop_span = lhs.span.between(rhs.span);
|
let binop_span = lhs.span.between(rhs.span);
|
||||||
// if RHS is an UnOp
|
// if RHS is an UnOp
|
||||||
|
@ -206,8 +205,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
|
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
|
||||||
if is_block(else_) || is_if(else_);
|
if is_block(else_) || is_if(else_);
|
||||||
if !differing_macro_contexts(then.span, else_.span);
|
if !then.span.from_expansion() && !else_.span.from_expansion();
|
||||||
if !then.span.from_expansion() && !in_external_macro(cx.sess(), expr.span);
|
if !in_external_macro(cx.sess(), expr.span);
|
||||||
|
|
||||||
// workaround for rust-lang/rust#43081
|
// workaround for rust-lang/rust#43081
|
||||||
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
|
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 {
|
for element in array {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
|
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);
|
let space_span = lhs.span.between(op.span);
|
||||||
if let Some(space_snippet) = snippet_opt(cx, space_span);
|
if let Some(space_snippet) = snippet_opt(cx, space_span);
|
||||||
let lint_span = lhs.span.with_lo(lhs.span.hi());
|
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) {
|
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if !differing_macro_contexts(first.span, second.span);
|
if !first.span.from_expansion() && !second.span.from_expansion();
|
||||||
if !first.span.from_expansion();
|
|
||||||
if let ExprKind::If(cond_expr, ..) = &first.kind;
|
if let ExprKind::If(cond_expr, ..) = &first.kind;
|
||||||
if is_block(second) || is_if(second);
|
if is_block(second) || is_if(second);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ use rustc_typeck::hir_ty_to_ty;
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
|
|
||||||
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
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::source::{snippet, snippet_opt};
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
||||||
vis.visit_ty(impl_.self_ty);
|
vis.visit_ty(impl_.self_ty);
|
||||||
|
|
||||||
for target in &vis.found {
|
for target in &vis.found {
|
||||||
if differing_macro_contexts(item.span, target.span()) {
|
if item.span.ctxt() != target.span().ctxt() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
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_hir::{BorrowKind, Expr, ExprKind};
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
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::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::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
|
||||||
ExprKind::Call(path, _) => {
|
ExprKind::Call(path, _) => path_def_id(cx, path)
|
||||||
if let ExprKind::Path(ref qpath) = path.kind {
|
.map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT))
|
||||||
is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into()
|
.into(),
|
||||||
} else {
|
|
||||||
Finite
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
|
ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
|
||||||
_ => Finite,
|
_ => Finite,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_INT_TO_FLOAT),
|
||||||
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
|
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
|
||||||
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
|
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
|
||||||
|
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
|
||||||
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||||
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
||||||
|
|
|
@ -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(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
|
||||||
LintId::of(swap::ALMOST_SWAPPED),
|
LintId::of(swap::ALMOST_SWAPPED),
|
||||||
LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
|
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::UNSOUND_COLLECTION_TRANSMUTE),
|
||||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||||
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
||||||
|
|
|
@ -92,6 +92,7 @@ store.register_lints(&[
|
||||||
default::DEFAULT_TRAIT_ACCESS,
|
default::DEFAULT_TRAIT_ACCESS,
|
||||||
default::FIELD_REASSIGN_WITH_DEFAULT,
|
default::FIELD_REASSIGN_WITH_DEFAULT,
|
||||||
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
|
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
|
||||||
|
default_union_representation::DEFAULT_UNION_REPRESENTATION,
|
||||||
dereference::EXPLICIT_DEREF_METHODS,
|
dereference::EXPLICIT_DEREF_METHODS,
|
||||||
dereference::NEEDLESS_BORROW,
|
dereference::NEEDLESS_BORROW,
|
||||||
dereference::REF_BINDING_TO_REFERENCE,
|
dereference::REF_BINDING_TO_REFERENCE,
|
||||||
|
@ -472,6 +473,7 @@ store.register_lints(&[
|
||||||
transmute::TRANSMUTE_NUM_TO_BYTES,
|
transmute::TRANSMUTE_NUM_TO_BYTES,
|
||||||
transmute::TRANSMUTE_PTR_TO_PTR,
|
transmute::TRANSMUTE_PTR_TO_PTR,
|
||||||
transmute::TRANSMUTE_PTR_TO_REF,
|
transmute::TRANSMUTE_PTR_TO_REF,
|
||||||
|
transmute::TRANSMUTE_UNDEFINED_REPR,
|
||||||
transmute::UNSOUND_COLLECTION_TRANSMUTE,
|
transmute::UNSOUND_COLLECTION_TRANSMUTE,
|
||||||
transmute::USELESS_TRANSMUTE,
|
transmute::USELESS_TRANSMUTE,
|
||||||
transmute::WRONG_TRANSMUTE,
|
transmute::WRONG_TRANSMUTE,
|
||||||
|
|
|
@ -12,6 +12,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
||||||
LintId::of(create_dir::CREATE_DIR),
|
LintId::of(create_dir::CREATE_DIR),
|
||||||
LintId::of(dbg_macro::DBG_MACRO),
|
LintId::of(dbg_macro::DBG_MACRO),
|
||||||
LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
|
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(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
|
||||||
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
|
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
|
||||||
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
|
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#![feature(control_flow_enum)]
|
#![feature(control_flow_enum)]
|
||||||
#![feature(drain_filter)]
|
#![feature(drain_filter)]
|
||||||
#![feature(iter_intersperse)]
|
#![feature(iter_intersperse)]
|
||||||
|
#![feature(let_chains)]
|
||||||
#![feature(let_else)]
|
#![feature(let_else)]
|
||||||
#![feature(once_cell)]
|
#![feature(once_cell)]
|
||||||
#![feature(rustc_private)]
|
#![feature(rustc_private)]
|
||||||
|
@ -17,6 +18,8 @@
|
||||||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||||
// warn on rustc internal lints
|
// warn on rustc internal lints
|
||||||
#![warn(rustc::internal)]
|
#![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.
|
// FIXME: switch to something more ergonomic here, once available.
|
||||||
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
|
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
|
||||||
|
@ -189,6 +192,7 @@ mod create_dir;
|
||||||
mod dbg_macro;
|
mod dbg_macro;
|
||||||
mod default;
|
mod default;
|
||||||
mod default_numeric_fallback;
|
mod default_numeric_fallback;
|
||||||
|
mod default_union_representation;
|
||||||
mod dereference;
|
mod dereference;
|
||||||
mod derivable_impls;
|
mod derivable_impls;
|
||||||
mod derive;
|
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_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(borrow_as_ptr::BorrowAsPtr::new(msrv)));
|
||||||
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
|
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
|
||||||
|
store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
|
||||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::SINGLE_ELEMENT_LOOP;
|
use super::SINGLE_ELEMENT_LOOP;
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::single_segment_path;
|
use clippy_utils::source::{indent_of, snippet_with_applicability};
|
||||||
use clippy_utils::source::{indent_of, snippet};
|
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind};
|
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
|
||||||
pub(super) fn check<'tcx>(
|
pub(super) fn check<'tcx>(
|
||||||
|
@ -16,24 +15,21 @@ pub(super) fn check<'tcx>(
|
||||||
) {
|
) {
|
||||||
let arg_expr = match arg.kind {
|
let arg_expr = match arg.kind {
|
||||||
ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg,
|
ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg,
|
||||||
ExprKind::MethodCall(method, args, _) if args.len() == 1 && method.ident.name == rustc_span::sym::iter => {
|
ExprKind::MethodCall(method, [arg], _) if method.ident.name == rustc_span::sym::iter => arg,
|
||||||
&args[0]
|
|
||||||
},
|
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let PatKind::Binding(.., target, _) = pat.kind;
|
|
||||||
if let ExprKind::Array([arg_expression]) = arg_expr.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 let ExprKind::Block(block, _) = body.kind;
|
||||||
if !block.stmts.is_empty();
|
if !block.stmts.is_empty();
|
||||||
|
|
||||||
then {
|
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.remove(0);
|
||||||
block_str.pop();
|
block_str.pop();
|
||||||
|
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
|
||||||
|
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
|
@ -41,8 +37,8 @@ pub(super) fn check<'tcx>(
|
||||||
expr.span,
|
expr.span,
|
||||||
"for loop over a single element",
|
"for loop over a single element",
|
||||||
"try",
|
"try",
|
||||||
format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str),
|
format!("{{\n{}let {} = &{};{}}}", indent, pat_snip, arg_snip, block_str),
|
||||||
Applicability::MachineApplicable
|
applicability,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_middle::hir::nested_filter;
|
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::source_map::Spanned;
|
||||||
use rustc_span::symbol::{sym, Symbol};
|
use rustc_span::symbol::{sym, Symbol};
|
||||||
use rustc_typeck::hir_ty_to_ty;
|
use rustc_typeck::hir_ty_to_ty;
|
||||||
|
@ -332,18 +332,21 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
|
||||||
} else {
|
} else {
|
||||||
// (&x).into_iter() ==> x.iter()
|
// (&x).into_iter() ==> x.iter()
|
||||||
// (&mut x).into_iter() ==> x.iter_mut()
|
// (&mut x).into_iter() ==> x.iter_mut()
|
||||||
match &arg.kind {
|
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||||
ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
|
match &arg_ty.kind() {
|
||||||
if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() =>
|
ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, inner_ty).is_some() => {
|
||||||
{
|
let method_name = match mutbl {
|
||||||
let meth_name = match mutability {
|
|
||||||
Mutability::Mut => "iter_mut",
|
Mutability::Mut => "iter_mut",
|
||||||
Mutability::Not => "iter",
|
Mutability::Not => "iter",
|
||||||
};
|
};
|
||||||
|
let caller = match &arg.kind {
|
||||||
|
ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner,
|
||||||
|
_ => arg,
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
"{}.{}()",
|
"{}.{}()",
|
||||||
sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(),
|
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
|
||||||
meth_name,
|
method_name,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
_ => format!(
|
_ => format!(
|
||||||
|
|
|
@ -35,7 +35,8 @@ struct PathAndSpan {
|
||||||
span: Span,
|
span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `MacroRefData` includes the name of the macro.
|
/// `MacroRefData` includes the name of the macro
|
||||||
|
/// and the path from `SourceMap::span_to_filename`.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MacroRefData {
|
pub struct MacroRefData {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
44
clippy_lints/src/matches/infalliable_detructuring_match.rs
Normal file
44
clippy_lints/src/matches/infalliable_detructuring_match.rs
Normal file
|
@ -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
|
||||||
|
}
|
85
clippy_lints/src/matches/match_as_ref.rs
Normal file
85
clippy_lints/src/matches/match_as_ref.rs
Normal file
|
@ -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<BindingAnnotation> = 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<BindingAnnotation> {
|
||||||
|
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
|
||||||
|
}
|
75
clippy_lints/src/matches/match_bool.rs
Normal file
75
clippy_lints/src/matches/match_bool.rs
Normal file
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
166
clippy_lints/src/matches/match_like_matches.rs
Normal file
166
clippy_lints/src/matches/match_like_matches.rs
Normal file
|
@ -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<bool> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
66
clippy_lints/src/matches/match_ref_pats.rs
Normal file
66
clippy_lints/src/matches/match_ref_pats.rs
Normal file
|
@ -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<Item = &'a Pat<'b>>,
|
||||||
|
{
|
||||||
|
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<Item = &'a Pat<'b>>,
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}
|
111
clippy_lints/src/matches/match_same_arms.rs
Normal file
111
clippy_lints/src/matches/match_same_arms.rs
Normal file
|
@ -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<HirId> = 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, "<pat1>");
|
||||||
|
let rhs = snippet(cx, j.pat.span, "<pat2>");
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
166
clippy_lints/src/matches/match_single_binding.rs
Normal file
166
clippy_lints/src/matches/match_single_binding.rs
Normal file
|
@ -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
|
||||||
|
}
|
198
clippy_lints/src/matches/match_wild_enum.rs
Normal file
198
clippy_lints/src/matches/match_wild_enum.rs
Normal file
|
@ -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)
|
||||||
|
}
|
51
clippy_lints/src/matches/match_wild_err_arm.rs
Normal file
51
clippy_lints/src/matches/match_wild_err_arm.rs
Normal file
|
@ -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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
646
clippy_lints/src/matches/mod.rs
Normal file
646
clippy_lints/src/matches/mod.rs
Normal file
|
@ -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<i32, &str> = 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::<i32, i32>(42) {}
|
||||||
|
/// if let Err(_) = Err::<i32, i32>(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::<i32, i32>(42) {
|
||||||
|
/// Ok(_) => true,
|
||||||
|
/// Err(_) => false,
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The more idiomatic use would be:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::task::Poll;
|
||||||
|
/// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
/// if Ok::<i32, i32>(42).is_ok() {}
|
||||||
|
/// if Err::<i32, i32>(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::<i32, i32>(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<RustcVersion>,
|
||||||
|
infallible_destructuring_match_linted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Matches {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(msrv: Option<RustcVersion>) -> 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);
|
||||||
|
}
|
181
clippy_lints/src/matches/overlapping_arms.rs
Normal file
181
clippy_lints/src/matches/overlapping_arms.rs
Normal file
|
@ -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<SpannedRange<FullInt>> {
|
||||||
|
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<T> {
|
||||||
|
Included(T),
|
||||||
|
Excluded(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
struct SpannedRange<T> {
|
||||||
|
pub span: Span,
|
||||||
|
pub node: (T, EndBound<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlapping<T>(ranges: &[SpannedRange<T>]) -> Option<(&SpannedRange<T>, &SpannedRange<T>)>
|
||||||
|
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<T>);
|
||||||
|
|
||||||
|
impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
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::<u8>(&[]));
|
||||||
|
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))
|
||||||
|
],)
|
||||||
|
);
|
||||||
|
}
|
436
clippy_lints/src/matches/redundant_pattern_match.rs
Normal file
436
clippy_lints/src/matches/redundant_pattern_match.rs
Normal file
|
@ -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<Ty<'tcx>>) -> 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<Ty<'_>> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
29
clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs
Normal file
29
clippy_lints/src/matches/rest_pat_in_fully_bound_struct.rs
Normal file
|
@ -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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
269
clippy_lints/src/matches/single_match.rs
Normal file
269
clippy_lints/src/matches/single_match.rs
Normal file
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
24
clippy_lints/src/matches/wild_in_or_pats.rs
Normal file
24
clippy_lints/src/matches/wild_in_or_pats.rs
Normal file
|
@ -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",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet_with_applicability;
|
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 if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_lint::Lint;
|
use rustc_lint::Lint;
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty::{self, DefIdTree};
|
||||||
use rustc_span::sym;
|
|
||||||
|
|
||||||
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
|
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
|
||||||
pub(super) fn check(
|
pub(super) fn check(
|
||||||
|
@ -19,11 +18,9 @@ pub(super) fn check(
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let Some(args) = method_chain_args(info.chain, chain_methods);
|
if let Some(args) = method_chain_args(info.chain, chain_methods);
|
||||||
if let hir::ExprKind::Call(fun, arg_char) = info.other.kind;
|
if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind;
|
||||||
if arg_char.len() == 1;
|
if let Some(id) = path_def_id(cx, fun).and_then(|ctor_id| cx.tcx.parent(ctor_id));
|
||||||
if let hir::ExprKind::Path(ref qpath) = fun.kind;
|
if Some(id) == cx.tcx.lang_items().option_some_variant();
|
||||||
if let Some(segment) = single_segment_path(qpath);
|
|
||||||
if segment.ident.name == sym::Some;
|
|
||||||
then {
|
then {
|
||||||
let mut applicability = Applicability::MachineApplicable;
|
let mut applicability = Applicability::MachineApplicable;
|
||||||
let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs();
|
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 { "!" },
|
if info.eq { "" } else { "!" },
|
||||||
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
||||||
suggest,
|
suggest,
|
||||||
snippet_with_applicability(cx, arg_char[0].span, "..", &mut applicability)),
|
snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
|
||||||
if info.eq { "" } else { "!" },
|
if info.eq { "" } else { "!" },
|
||||||
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
||||||
suggest,
|
suggest,
|
||||||
c),
|
c.escape_default()),
|
||||||
applicability,
|
applicability,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
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::source::snippet_with_applicability;
|
||||||
|
use clippy_utils::{match_def_path, path_def_id};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast::ast;
|
use rustc_ast::ast;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
|
@ -93,12 +93,12 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<M
|
||||||
let ty_str = ty.to_string();
|
let ty_str = ty.to_string();
|
||||||
|
|
||||||
// `std::T::MAX` `std::T::MIN` constants
|
// `std::T::MAX` `std::T::MIN` constants
|
||||||
if let hir::ExprKind::Path(path) = &expr.kind {
|
if let Some(id) = path_def_id(cx, expr) {
|
||||||
if is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MAX"][..]) {
|
if match_def_path(cx, id, &["core", &ty_str, "MAX"]) {
|
||||||
return Some(MinMax::Max);
|
return Some(MinMax::Max);
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MIN"][..]) {
|
if match_def_path(cx, id, &["core", &ty_str, "MIN"]) {
|
||||||
return Some(MinMax::Min);
|
return Some(MinMax::Min);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -566,17 +566,20 @@ declare_clippy_lint! {
|
||||||
///
|
///
|
||||||
/// ### Why is this bad?
|
/// ### Why is this bad?
|
||||||
/// Readability, this can be written more concisely as
|
/// Readability, this can be written more concisely as
|
||||||
/// `_.flat_map(_)`
|
/// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// let vec = vec![vec![1]];
|
/// let vec = vec![vec![1]];
|
||||||
|
/// let opt = Some(5);
|
||||||
///
|
///
|
||||||
/// // Bad
|
/// // Bad
|
||||||
/// vec.iter().map(|x| x.iter()).flatten();
|
/// vec.iter().map(|x| x.iter()).flatten();
|
||||||
|
/// opt.map(|x| Some(x * 2)).flatten();
|
||||||
///
|
///
|
||||||
/// // Good
|
/// // Good
|
||||||
/// vec.iter().flat_map(|x| x.iter());
|
/// vec.iter().flat_map(|x| x.iter());
|
||||||
|
/// opt.and_then(|x| Some(x * 2));
|
||||||
/// ```
|
/// ```
|
||||||
#[clippy::version = "1.31.0"]
|
#[clippy::version = "1.31.0"]
|
||||||
pub MAP_FLATTEN,
|
pub MAP_FLATTEN,
|
||||||
|
@ -2399,10 +2402,17 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
|
||||||
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
|
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
|
||||||
implicit_clone::check(cx, name, expr, recv);
|
implicit_clone::check(cx, name, expr, recv);
|
||||||
},
|
},
|
||||||
("unwrap", []) => match method_call(recv) {
|
("unwrap", []) => {
|
||||||
Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
|
match method_call(recv) {
|
||||||
Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),
|
Some(("get", [recv, get_arg], _)) => {
|
||||||
_ => unwrap_used::check(cx, expr, recv),
|
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) {
|
("unwrap_or", [u_arg]) => match method_call(recv) {
|
||||||
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
|
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
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_errors::Applicability;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
use rustc_middle::ty::DefIdTree;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
use super::OPTION_MAP_OR_NONE;
|
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;
|
if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
|
||||||
let arg_snippet = snippet(cx, span, "..");
|
let arg_snippet = snippet(cx, span, "..");
|
||||||
let body = cx.tcx.hir().body(id);
|
let body = cx.tcx.hir().body(id);
|
||||||
if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value);
|
if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
|
||||||
if arg_char.len() == 1;
|
if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
|
||||||
if let hir::ExprKind::Path(ref qpath) = func.kind;
|
if Some(id) == cx.tcx.lang_items().option_some_variant();
|
||||||
if let Some(segment) = single_segment_path(qpath);
|
|
||||||
if segment.ident.name == sym::Some;
|
|
||||||
then {
|
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 \
|
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
|
||||||
`map(..)` instead";
|
`map(..)` instead";
|
||||||
return span_lint_and_sugg(
|
return span_lint_and_sugg(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::differing_macro_contexts;
|
|
||||||
use clippy_utils::source::snippet_with_applicability;
|
use clippy_utils::source::snippet_with_applicability;
|
||||||
use clippy_utils::ty::is_copy;
|
use clippy_utils::ty::is_copy;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,16 +45,16 @@ pub(super) fn check_manual_split_once(
|
||||||
IterUsageKind::Next | IterUsageKind::Second => {
|
IterUsageKind::Next | IterUsageKind::Second => {
|
||||||
let self_deref = {
|
let self_deref = {
|
||||||
let adjust = cx.typeck_results().expr_adjustments(self_arg);
|
let adjust = cx.typeck_results().expr_adjustments(self_arg);
|
||||||
if adjust.is_empty() {
|
if adjust.len() < 2 {
|
||||||
String::new()
|
String::new()
|
||||||
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|
||||||
|| adjust
|
|| adjust
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
|
.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 {
|
} else {
|
||||||
"*".repeat(adjust.len() - 2)
|
"*".repeat(adjust.len().saturating_sub(2))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if matches!(usage.kind, IterUsageKind::Next) {
|
if matches!(usage.kind, IterUsageKind::Next) {
|
||||||
|
|
|
@ -33,9 +33,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
|
||||||
} else if !found_mapping && !mutates_arg {
|
} else if !found_mapping && !mutates_arg {
|
||||||
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
|
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
|
||||||
match cx.typeck_results().expr_ty(&body.value).kind() {
|
match cx.typeck_results().expr_ty(&body.value).kind() {
|
||||||
ty::Adt(adt, subst)
|
ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => {
|
||||||
if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) =>
|
|
||||||
{
|
|
||||||
"filter"
|
"filter"
|
||||||
},
|
},
|
||||||
_ => return,
|
_ => return,
|
||||||
|
|
|
@ -20,8 +20,8 @@ use rustc_span::symbol::sym;
|
||||||
use clippy_utils::consts::{constant, Constant};
|
use clippy_utils::consts::{constant, Constant};
|
||||||
use clippy_utils::sugg::Sugg;
|
use clippy_utils::sugg::Sugg;
|
||||||
use clippy_utils::{
|
use clippy_utils::{
|
||||||
expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
|
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,
|
last_path_segment, match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
|
||||||
};
|
};
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
|
@ -583,8 +583,7 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
ExprKind::Call(path, [arg]) => {
|
ExprKind::Call(path, [arg]) => {
|
||||||
if expr_path_res(cx, path)
|
if path_def_id(cx, path)
|
||||||
.opt_def_id()
|
|
||||||
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
|
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
use clippy_utils::attrs::is_doc_hidden;
|
use clippy_utils::attrs::is_doc_hidden;
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use rustc_ast::ast;
|
use if_chain::if_chain;
|
||||||
|
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
use rustc_lint::{LateContext, LateLintPass, LintContext};
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
|
@ -57,6 +58,20 @@ impl MissingDoc {
|
||||||
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
*self.doc_hidden_stack.last().expect("empty doc_hidden_stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_include(meta: Option<MetaItem>) -> bool {
|
||||||
|
if_chain! {
|
||||||
|
if let Some(meta) = meta;
|
||||||
|
if let MetaItemKind::List(list) = meta.kind;
|
||||||
|
if let Some(meta) = list.get(0);
|
||||||
|
if let Some(name) = meta.ident();
|
||||||
|
then {
|
||||||
|
name.name == sym::include
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_missing_docs_attrs(
|
fn check_missing_docs_attrs(
|
||||||
&self,
|
&self,
|
||||||
cx: &LateContext<'_>,
|
cx: &LateContext<'_>,
|
||||||
|
@ -80,7 +95,9 @@ impl MissingDoc {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_doc = attrs.iter().any(|a| a.doc_str().is_some());
|
let has_doc = attrs
|
||||||
|
.iter()
|
||||||
|
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
|
||||||
if !has_doc {
|
if !has_doc {
|
||||||
span_lint(
|
span_lint(
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
|
||||||
impl LateLintPass<'_> for ImportRename {
|
impl LateLintPass<'_> for ImportRename {
|
||||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||||
for Rename { path, rename } in &self.conf_renames {
|
for Rename { path, rename } in &self.conf_renames {
|
||||||
if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &path.split("::").collect::<Vec<_>>()) {
|
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::<Vec<_>>()) {
|
||||||
self.renames.insert(id, Symbol::intern(rename));
|
self.renames.insert(id, Symbol::intern(rename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,10 +92,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
||||||
self.found = true;
|
self.found = true;
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
ExprKind::If(..) => {
|
|
||||||
self.found = true;
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
ExprKind::Path(_) => {
|
ExprKind::Path(_) => {
|
||||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||||
if adj
|
if adj
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||||
use clippy_utils::source::snippet_opt;
|
use clippy_utils::source::snippet_opt;
|
||||||
use clippy_utils::ty::expr_sig;
|
use clippy_utils::ty::expr_sig;
|
||||||
use clippy_utils::{
|
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
|
||||||
expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths,
|
|
||||||
};
|
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::def_id::DefId;
|
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(),
|
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
|
||||||
sig.decl.inputs,
|
sig.decl.inputs,
|
||||||
&[],
|
&[],
|
||||||
) {
|
)
|
||||||
|
.filter(|arg| arg.mutability() == Mutability::Not)
|
||||||
|
{
|
||||||
span_lint_and_sugg(
|
span_lint_and_sugg(
|
||||||
cx,
|
cx,
|
||||||
PTR_ARG,
|
PTR_ARG,
|
||||||
|
@ -170,10 +170,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||||
let hir = cx.tcx.hir();
|
let hir = cx.tcx.hir();
|
||||||
let mut parents = hir.parent_iter(body.value.hir_id);
|
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))) => {
|
Some((_, Node::Item(i))) => {
|
||||||
if let ItemKind::Fn(sig, ..) = &i.kind {
|
if let ItemKind::Fn(sig, ..) = &i.kind {
|
||||||
(i.def_id, sig.decl)
|
(i.def_id, sig.decl, false)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -185,14 +185,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let ImplItemKind::Fn(sig, _) = &i.kind {
|
if let ImplItemKind::Fn(sig, _) = &i.kind {
|
||||||
(i.def_id, sig.decl)
|
(i.def_id, sig.decl, false)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some((_, Node::TraitItem(i))) => {
|
Some((_, Node::TraitItem(i))) => {
|
||||||
if let TraitItemKind::Fn(sig, _) = &i.kind {
|
if let TraitItemKind::Fn(sig, _) = &i.kind {
|
||||||
(i.def_id, sig.decl)
|
(i.def_id, sig.decl, true)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,9 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
||||||
|
|
||||||
check_mut_from_ref(cx, decl);
|
check_mut_from_ref(cx, decl);
|
||||||
let sig = cx.tcx.fn_sig(item_id).skip_binder();
|
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);
|
let results = check_ptr_arg_usage(cx, body, &lint_args);
|
||||||
|
|
||||||
for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
|
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(),
|
self.deref_ty.argless_str(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mutability(&self) -> Mutability {
|
||||||
|
self.ref_prefix.mutability
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RefPrefix {
|
struct RefPrefix {
|
||||||
|
@ -641,7 +647,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
skip_count += 1;
|
skip_count += 1;
|
||||||
results[arg.idx].skip = true;
|
results[i].skip = true;
|
||||||
None
|
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 {
|
fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||||
if let ExprKind::Call(pathexp, []) = expr.kind {
|
if let ExprKind::Call(pathexp, []) = expr.kind {
|
||||||
expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
|
path_def_id(cx, pathexp).map_or(false, |id| {
|
||||||
match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()
|
matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
|
@ -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::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||||
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
|
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
|
||||||
use clippy_utils::sugg::Sugg;
|
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 clippy_utils::{higher, SpanlessEq};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast::ast::RangeLimits;
|
use rustc_ast::ast::RangeLimits;
|
||||||
use rustc_errors::Applicability;
|
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_lint::{LateContext, LateLintPass};
|
||||||
use rustc_middle::ty;
|
use rustc_middle::ty;
|
||||||
use rustc_semver::RustcVersion;
|
use rustc_semver::RustcVersion;
|
||||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||||
use rustc_span::source_map::{Span, Spanned};
|
use rustc_span::source_map::{Span, Spanned};
|
||||||
use rustc_span::sym;
|
use rustc_span::sym;
|
||||||
use rustc_span::symbol::Ident;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
declare_clippy_lint! {
|
declare_clippy_lint! {
|
||||||
|
@ -220,12 +219,12 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'
|
||||||
_ => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
// value, name, order (higher/lower), inclusiveness
|
// 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))
|
(check_range_bounds(cx, l), check_range_bounds(cx, r))
|
||||||
{
|
{
|
||||||
// we only lint comparisons on the same name and with different
|
// we only lint comparisons on the same name and with different
|
||||||
// direction
|
// direction
|
||||||
if lname != rname || lord == rord {
|
if lid != rid || lord == rord {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval);
|
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 {
|
if let ExprKind::Binary(ref op, l, r) = ex.kind {
|
||||||
let (inclusive, ordering) = match op.node {
|
let (inclusive, ordering) = match op.node {
|
||||||
BinOpKind::Gt => (false, Ordering::Greater),
|
BinOpKind::Gt => (false, Ordering::Greater),
|
||||||
|
@ -302,11 +301,11 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant,
|
||||||
BinOpKind::Le => (true, Ordering::Less),
|
BinOpKind::Le => (true, Ordering::Less),
|
||||||
_ => return None,
|
_ => 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) {
|
if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
|
||||||
return Some((c, id, l.span, r.span, ordering, inclusive));
|
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) {
|
if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
|
||||||
return Some((c, id, r.span, l.span, ordering.reverse(), inclusive));
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_ident(e: &Expr<'_>) -> Option<Ident> {
|
|
||||||
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) {
|
fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if path.ident.as_str() == "zip";
|
if path.ident.as_str() == "zip";
|
||||||
|
@ -342,7 +330,7 @@ fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args:
|
||||||
// `.iter()` and `.len()` called on same `Path`
|
// `.iter()` and `.len()` called on same `Path`
|
||||||
if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
|
if let ExprKind::Path(QPath::Resolved(_, iter_path)) = iter_args[0].kind;
|
||||||
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
|
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
|
||||||
if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments);
|
if SpanlessEq::new(cx).eq_path_segments(iter_path.segments, len_path.segments);
|
||||||
then {
|
then {
|
||||||
span_lint(cx,
|
span_lint(cx,
|
||||||
RANGE_ZIP_WITH_LEN,
|
RANGE_ZIP_WITH_LEN,
|
||||||
|
|
|
@ -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::source::snippet_with_applicability;
|
||||||
use clippy_utils::sugg::Sugg;
|
use clippy_utils::sugg::Sugg;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
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 if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
|
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_chain! {
|
||||||
if let StmtKind::Semi(first) = w[0].kind;
|
if let StmtKind::Semi(first) = w[0].kind;
|
||||||
if let StmtKind::Semi(second) = w[1].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(lhs0, rhs0, _) = first.kind;
|
||||||
if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
|
if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
|
||||||
if eq_expr_value(cx, lhs0, rhs1);
|
if eq_expr_value(cx, lhs0, rhs1);
|
||||||
|
|
|
@ -7,6 +7,7 @@ mod transmute_num_to_bytes;
|
||||||
mod transmute_ptr_to_ptr;
|
mod transmute_ptr_to_ptr;
|
||||||
mod transmute_ptr_to_ref;
|
mod transmute_ptr_to_ref;
|
||||||
mod transmute_ref_to_ref;
|
mod transmute_ref_to_ref;
|
||||||
|
mod transmute_undefined_repr;
|
||||||
mod transmutes_expressible_as_ptr_casts;
|
mod transmutes_expressible_as_ptr_casts;
|
||||||
mod unsound_collection_transmute;
|
mod unsound_collection_transmute;
|
||||||
mod useless_transmute;
|
mod useless_transmute;
|
||||||
|
@ -355,6 +356,30 @@ declare_clippy_lint! {
|
||||||
"transmute between collections of layout-incompatible types"
|
"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<T>(u32, T);
|
||||||
|
/// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(Foo(0u32, 0u32)) };
|
||||||
|
/// ```
|
||||||
|
/// Use instead:
|
||||||
|
/// ```rust
|
||||||
|
/// #[repr(C)]
|
||||||
|
/// struct Foo<T>(u32, T);
|
||||||
|
/// let _ = unsafe { core::mem::transmute::<Foo<u32>, Foo<i32>>(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 => [
|
declare_lint_pass!(Transmute => [
|
||||||
CROSSPOINTER_TRANSMUTE,
|
CROSSPOINTER_TRANSMUTE,
|
||||||
TRANSMUTE_PTR_TO_REF,
|
TRANSMUTE_PTR_TO_REF,
|
||||||
|
@ -369,13 +394,13 @@ declare_lint_pass!(Transmute => [
|
||||||
TRANSMUTE_NUM_TO_BYTES,
|
TRANSMUTE_NUM_TO_BYTES,
|
||||||
UNSOUND_COLLECTION_TRANSMUTE,
|
UNSOUND_COLLECTION_TRANSMUTE,
|
||||||
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
||||||
|
TRANSMUTE_UNDEFINED_REPR,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
impl<'tcx> LateLintPass<'tcx> for Transmute {
|
impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||||
#[allow(clippy::similar_names, clippy::too_many_lines)]
|
|
||||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||||
if_chain! {
|
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 ExprKind::Path(ref qpath) = path_expr.kind;
|
||||||
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
|
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);
|
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.
|
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
|
||||||
let const_context = in_constant(cx, e.hir_id);
|
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);
|
let to_ty = cx.typeck_results().expr_ty(e);
|
||||||
|
|
||||||
// If useless_transmute is triggered, the other lints can be skipped.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty);
|
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
|
||||||
linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty);
|
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
|
||||||
linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath);
|
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
|
||||||
linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args);
|
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg)
|
||||||
linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context);
|
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||||
linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args);
|
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
|
||||||
linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
|
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
|
||||||
linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
|
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||||
linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
|
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||||
linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
|
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||||
linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
|
| (
|
||||||
|
unsound_collection_transmute::check(cx, e, from_ty, to_ty)
|
||||||
|
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty)
|
||||||
|
);
|
||||||
|
|
||||||
if !linted {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
mut arg: &'tcx Expr<'_>,
|
||||||
const_context: bool,
|
const_context: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
|
@ -26,37 +26,36 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||||
|diag| {
|
|diag| {
|
||||||
let mut expr = &args[0];
|
let mut sugg = sugg::Sugg::hir(cx, arg, "..");
|
||||||
let mut arg = sugg::Sugg::hir(cx, expr, "..");
|
|
||||||
|
|
||||||
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
|
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
|
||||||
expr = inner_expr;
|
arg = inner_expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if_chain! {
|
if_chain! {
|
||||||
// if the expression is a float literal and it is unsuffixed then
|
// if the expression is a float literal and it is unsuffixed then
|
||||||
// add a suffix so the suggestion is valid and unambiguous
|
// 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;
|
if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node;
|
||||||
then {
|
then {
|
||||||
let op = format!("{}{}", arg, float_ty.name_str()).into();
|
let op = format!("{}{}", sugg, float_ty.name_str()).into();
|
||||||
match arg {
|
match sugg {
|
||||||
sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op),
|
sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
|
||||||
_ => arg = sugg::Sugg::NonParen(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
|
// cast the result of `to_bits` if `to_ty` is signed
|
||||||
arg = if let ty::Int(int_ty) = to_ty.kind() {
|
sugg = if let ty::Int(int_ty) = to_ty.kind() {
|
||||||
arg.as_ty(int_ty.name_str().to_string())
|
sugg.as_ty(int_ty.name_str().to_string())
|
||||||
} else {
|
} 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
|
true
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
(ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => {
|
(ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => {
|
||||||
|
@ -25,7 +25,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{}` to a `bool`", from_ty),
|
&format!("transmute from a `{}` to a `bool`", from_ty),
|
||||||
|diag| {
|
|diag| {
|
||||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||||
let zero = sugg::Sugg::NonParen(Cow::from("0"));
|
let zero = sugg::Sugg::NonParen(Cow::from("0"));
|
||||||
diag.span_suggestion(
|
diag.span_suggestion(
|
||||||
e.span,
|
e.span,
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
|
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
|
||||||
|
@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{}` to a `char`", from_ty),
|
&format!("transmute from a `{}` to a `char`", from_ty),
|
||||||
|diag| {
|
|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() {
|
let arg = if let ty::Int(_) = from_ty.kind() {
|
||||||
arg.as_ty(ast::UintTy::U32.name_str())
|
arg.as_ty(ast::UintTy::U32.name_str())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
const_context: bool,
|
const_context: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
|
@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||||
|diag| {
|
|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() {
|
let arg = if let ty::Int(int_ty) = from_ty.kind() {
|
||||||
arg.as_ty(format!(
|
arg.as_ty(format!(
|
||||||
"u{}",
|
"u{}",
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
const_context: bool,
|
const_context: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
|
@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||||
|diag| {
|
|diag| {
|
||||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||||
diag.span_suggestion(
|
diag.span_suggestion(
|
||||||
e.span,
|
e.span,
|
||||||
"consider using `to_ne_bytes()`",
|
"consider using `to_ne_bytes()`",
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
(ty::RawPtr(_), ty::RawPtr(to_ty)) => {
|
(ty::RawPtr(_), ty::RawPtr(to_ty)) => {
|
||||||
|
@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
"transmute from a pointer to a pointer",
|
"transmute from a pointer to a pointer",
|
||||||
|diag| {
|
|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));
|
let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty));
|
||||||
diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified);
|
diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
qpath: &'tcx QPath<'_>,
|
qpath: &'tcx QPath<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
|
@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
|
||||||
from_ty, to_ty
|
from_ty, to_ty
|
||||||
),
|
),
|
||||||
|diag| {
|
|diag| {
|
||||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||||
let (deref, cast) = if *mutbl == Mutability::Mut {
|
let (deref, cast) = if *mutbl == Mutability::Mut {
|
||||||
("&mut *", "*mut")
|
("&mut *", "*mut")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
const_context: bool,
|
const_context: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut triggered = false;
|
let mut triggered = false;
|
||||||
|
@ -41,7 +41,7 @@ pub(super) fn check<'tcx>(
|
||||||
format!(
|
format!(
|
||||||
"std::str::from_utf8{}({}).unwrap()",
|
"std::str::from_utf8{}({}).unwrap()",
|
||||||
postfix,
|
postfix,
|
||||||
snippet(cx, args[0].span, ".."),
|
snippet(cx, arg.span, ".."),
|
||||||
),
|
),
|
||||||
Applicability::Unspecified,
|
Applicability::Unspecified,
|
||||||
);
|
);
|
||||||
|
@ -54,7 +54,7 @@ pub(super) fn check<'tcx>(
|
||||||
TRANSMUTE_PTR_TO_PTR,
|
TRANSMUTE_PTR_TO_PTR,
|
||||||
e.span,
|
e.span,
|
||||||
"transmute from a reference to a reference",
|
"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 {
|
let ty_from_and_mut = ty::TypeAndMut {
|
||||||
ty: ty_from,
|
ty: ty_from,
|
||||||
mutbl: *from_mutbl
|
mutbl: *from_mutbl
|
||||||
|
|
289
clippy_lints/src/transmute/transmute_undefined_repr.rs
Normal file
289
clippy_lints/src/transmute/transmute_undefined_repr.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
|
if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
|
||||||
span_lint_and_then(
|
span_lint_and_then(
|
||||||
|
@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(
|
||||||
from_ty, to_ty
|
from_ty, to_ty
|
||||||
),
|
),
|
||||||
|diag| {
|
|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();
|
let sugg = arg.as_ty(&to_ty.to_string()).to_string();
|
||||||
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
|
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
use super::utils::is_layout_incompatible;
|
use super::utils::is_layout_incompatible;
|
||||||
use super::UNSOUND_COLLECTION_TRANSMUTE;
|
use super::UNSOUND_COLLECTION_TRANSMUTE;
|
||||||
use clippy_utils::diagnostics::span_lint;
|
use clippy_utils::diagnostics::span_lint;
|
||||||
use clippy_utils::match_any_diagnostic_items;
|
|
||||||
use rustc_hir::Expr;
|
use rustc_hir::Expr;
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_middle::ty::{self, Ty};
|
use rustc_middle::ty::{self, Ty};
|
||||||
use rustc_span::symbol::{sym, Symbol};
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
// used to check for UNSOUND_COLLECTION_TRANSMUTE
|
|
||||||
static COLLECTIONS: &[Symbol] = &[
|
|
||||||
sym::Vec,
|
|
||||||
sym::VecDeque,
|
|
||||||
sym::BinaryHeap,
|
|
||||||
sym::BTreeSet,
|
|
||||||
sym::BTreeMap,
|
|
||||||
sym::HashSet,
|
|
||||||
sym::HashMap,
|
|
||||||
];
|
|
||||||
|
|
||||||
/// Checks for `unsound_collection_transmute` lint.
|
/// Checks for `unsound_collection_transmute` lint.
|
||||||
/// Returns `true` if it's triggered, otherwise returns `false`.
|
/// 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 {
|
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()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
(ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
|
(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;
|
return false;
|
||||||
}
|
}
|
||||||
if from_substs
|
if from_substs
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
||||||
e: &'tcx Expr<'_>,
|
e: &'tcx Expr<'_>,
|
||||||
from_ty: Ty<'tcx>,
|
from_ty: Ty<'tcx>,
|
||||||
to_ty: Ty<'tcx>,
|
to_ty: Ty<'tcx>,
|
||||||
args: &'tcx [Expr<'_>],
|
arg: &'tcx Expr<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (&from_ty.kind(), &to_ty.kind()) {
|
match (&from_ty.kind(), &to_ty.kind()) {
|
||||||
_ if from_ty == to_ty => {
|
_ if from_ty == to_ty => {
|
||||||
|
@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
"transmute from a reference to a pointer",
|
"transmute from a reference to a pointer",
|
||||||
|diag| {
|
|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 {
|
let rty_and_mut = ty::TypeAndMut {
|
||||||
ty: rty,
|
ty: rty,
|
||||||
mutbl: *rty_mutbl,
|
mutbl: *rty_mutbl,
|
||||||
|
@ -57,7 +57,7 @@ pub(super) fn check<'tcx>(
|
||||||
e.span,
|
e.span,
|
||||||
"transmute from an integer to a pointer",
|
"transmute from an integer to a pointer",
|
||||||
|diag| {
|
|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(
|
diag.span_suggestion(
|
||||||
e.span,
|
e.span,
|
||||||
"try",
|
"try",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_help;
|
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_hir::{self as hir, def_id::DefId, QPath};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::{sym, Symbol};
|
||||||
|
|
||||||
use super::BOX_COLLECTION;
|
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 Some(def_id) == cx.tcx.lang_items().owned_box();
|
||||||
if let Some(item_type) = get_std_collection(cx, qpath);
|
if let Some(item_type) = get_std_collection(cx, qpath);
|
||||||
then {
|
then {
|
||||||
let generic = if item_type == "String" {
|
let generic = match item_type {
|
||||||
""
|
sym::String => "",
|
||||||
} else {
|
_ => "<..>",
|
||||||
"<..>"
|
|
||||||
};
|
};
|
||||||
span_lint_and_help(
|
span_lint_and_help(
|
||||||
cx,
|
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> {
|
fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Symbol> {
|
||||||
if is_ty_param_diagnostic_item(cx, qpath, sym::Vec).is_some() {
|
let param = qpath_generic_tys(qpath).next()?;
|
||||||
Some("Vec")
|
let id = path_def_id(cx, param)?;
|
||||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() {
|
cx.tcx
|
||||||
Some("String")
|
.get_diagnostic_name(id)
|
||||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::HashMap).is_some() {
|
.filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec))
|
||||||
Some("HashMap")
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint;
|
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_hir::{self as hir, def_id::DefId, QPath};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
|
@ -7,16 +8,21 @@ use rustc_span::symbol::sym;
|
||||||
use super::OPTION_OPTION;
|
use super::OPTION_OPTION;
|
||||||
|
|
||||||
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
|
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() {
|
if_chain! {
|
||||||
span_lint(
|
if cx.tcx.is_diagnostic_item(sym::Option, def_id);
|
||||||
cx,
|
if let Some(arg) = qpath_generic_tys(qpath).next();
|
||||||
OPTION_OPTION,
|
if path_def_id(cx, arg) == Some(def_id);
|
||||||
hir_ty.span,
|
then {
|
||||||
"consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
|
span_lint(
|
||||||
enum if you need to distinguish all 3 cases",
|
cx,
|
||||||
);
|
OPTION_OPTION,
|
||||||
true
|
hir_ty.span,
|
||||||
} else {
|
"consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
|
||||||
false
|
enum if you need to distinguish all 3 cases",
|
||||||
|
);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||||
use clippy_utils::source::snippet_with_applicability;
|
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_errors::Applicability;
|
||||||
use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
|
use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
|
@ -20,12 +20,17 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||||
format!("Rc<{}>", alternate),
|
format!("Rc<{}>", alternate),
|
||||||
Applicability::MachineApplicable,
|
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 {
|
let qpath = match &ty.kind {
|
||||||
TyKind::Path(qpath) => qpath,
|
TyKind::Path(qpath) => qpath,
|
||||||
_ => return false,
|
_ => 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,
|
Some(ty) => ty.span,
|
||||||
None => return false,
|
None => return false,
|
||||||
};
|
};
|
||||||
|
@ -55,12 +60,16 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||||
format!("Arc<{}>", alternate),
|
format!("Arc<{}>", alternate),
|
||||||
Applicability::MachineApplicable,
|
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 {
|
let qpath = match &ty.kind {
|
||||||
TyKind::Path(qpath) => qpath,
|
TyKind::Path(qpath) => qpath,
|
||||||
_ => return false,
|
_ => 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,
|
Some(ty) => ty.span,
|
||||||
None => return false,
|
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> {
|
fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> {
|
||||||
if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() {
|
let ty = qpath_generic_tys(qpath).next()?;
|
||||||
Some("str")
|
let id = path_def_id(cx, ty)?;
|
||||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::OsString).is_some() {
|
let path = match cx.tcx.get_diagnostic_name(id)? {
|
||||||
Some("std::ffi::OsStr")
|
sym::String => "str",
|
||||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::PathBuf).is_some() {
|
sym::OsString => "std::ffi::OsStr",
|
||||||
Some("std::path::Path")
|
sym::PathBuf => "std::path::Path",
|
||||||
} else {
|
_ => return None,
|
||||||
None
|
};
|
||||||
}
|
Some(path)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_help;
|
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 if_chain::if_chain;
|
||||||
use rustc_hir::{self as hir, def_id::DefId, QPath};
|
use rustc_hir::{self as hir, def_id::DefId, QPath};
|
||||||
use rustc_lint::LateContext;
|
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 {
|
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ;
|
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 {
|
then {
|
||||||
span_lint_and_help(
|
span_lint_and_help(
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
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_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_lint::LateContext;
|
||||||
use rustc_span::symbol::sym;
|
use rustc_span::symbol::sym;
|
||||||
|
|
||||||
|
@ -39,21 +39,20 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (inner_sym, ty) = if let Some(ty) = is_ty_param_lang_item(cx, qpath, LangItem::OwnedBox) {
|
let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
|
||||||
("Box", ty)
|
let Some(id) = path_def_id(cx, ty) else { return false };
|
||||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Rc) {
|
let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) {
|
||||||
("Rc", ty)
|
Some(sym::Arc) => ("Arc", ty),
|
||||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Arc) {
|
Some(sym::Rc) => ("Rc", ty),
|
||||||
("Arc", ty)
|
_ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty),
|
||||||
} else {
|
_ => return false,
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let inner_qpath = match &ty.kind {
|
let inner_qpath = match &ty.kind {
|
||||||
TyKind::Path(inner_qpath) => inner_qpath,
|
TyKind::Path(inner_qpath) => inner_qpath,
|
||||||
_ => return false,
|
_ => 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) => {
|
Some(ty) => {
|
||||||
// Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
|
// Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
|
||||||
if matches!(ty.kind, TyKind::TraitObject(..)) {
|
if matches!(ty.kind, TyKind::TraitObject(..)) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::higher;
|
use clippy_utils::higher;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
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 if_chain::if_chain;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::Applicability;
|
||||||
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
|
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()
|
if let Some(unwrappable) = self.unwrappables.iter()
|
||||||
.find(|u| u.local_id == id);
|
.find(|u| u.local_id == id);
|
||||||
// Span contexts should not differ with the conditional branch
|
// Span contexts should not differ with the conditional branch
|
||||||
if !differing_macro_contexts(unwrappable.branch.span, expr.span);
|
let span_ctxt = expr.span.ctxt();
|
||||||
if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
|
if unwrappable.branch.span.ctxt() == span_ctxt;
|
||||||
|
if unwrappable.check.span.ctxt() == span_ctxt;
|
||||||
then {
|
then {
|
||||||
if call_to_unwrap == unwrappable.safe_to_unwrap {
|
if call_to_unwrap == unwrappable.safe_to_unwrap {
|
||||||
let is_entire_condition = unwrappable.is_entire_condition;
|
let is_entire_condition = unwrappable.is_entire_condition;
|
||||||
|
|
|
@ -322,6 +322,9 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
||||||
let mut current = env::var_os("CLIPPY_CONF_DIR")
|
let mut current = env::var_os("CLIPPY_CONF_DIR")
|
||||||
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
|
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
|
||||||
.map_or_else(|| PathBuf::from("."), PathBuf::from);
|
.map_or_else(|| PathBuf::from("."), PathBuf::from);
|
||||||
|
|
||||||
|
let mut found_config: Option<PathBuf> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
for config_file_name in &CONFIG_FILE_NAMES {
|
for config_file_name in &CONFIG_FILE_NAMES {
|
||||||
if let Ok(config_file) = current.join(config_file_name).canonicalize() {
|
if let Ok(config_file) = current.join(config_file_name).canonicalize() {
|
||||||
|
@ -329,11 +332,26 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
||||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {},
|
Err(e) if e.kind() == io::ErrorKind::NotFound => {},
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
Ok(md) if md.is_dir() => {},
|
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 the current directory has no parent, we're done searching.
|
||||||
if !current.pop() {
|
if !current.pop() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
|
|
|
@ -4,8 +4,8 @@ use clippy_utils::macros::root_macro_call_first_node;
|
||||||
use clippy_utils::source::snippet;
|
use clippy_utils::source::snippet;
|
||||||
use clippy_utils::ty::match_type;
|
use clippy_utils::ty::match_type;
|
||||||
use clippy_utils::{
|
use clippy_utils::{
|
||||||
higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls,
|
def_path_res, higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path,
|
||||||
path_to_res, paths, peel_blocks_with_stmt, SpanlessEq,
|
method_calls, paths, peel_blocks_with_stmt, SpanlessEq,
|
||||||
};
|
};
|
||||||
use if_chain::if_chain;
|
use if_chain::if_chain;
|
||||||
use rustc_ast as ast;
|
use rustc_ast as ast;
|
||||||
|
@ -844,7 +844,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
|
||||||
// Extract the path to the matched type
|
// Extract the path to the matched type
|
||||||
if let Some(segments) = path_to_matched_type(cx, ty_path);
|
if let Some(segments) = path_to_matched_type(cx, ty_path);
|
||||||
let segments: Vec<&str> = segments.iter().map(Symbol::as_str).collect();
|
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
|
// Check if the matched type is a diagnostic item
|
||||||
if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
|
if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
|
||||||
then {
|
then {
|
||||||
|
@ -917,7 +917,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
|
||||||
// This is not a complete resolver for paths. It works on all the paths currently used in the paths
|
// This is not a complete resolver for paths. It works on all the paths currently used in the paths
|
||||||
// module. That's all it does and all it needs to do.
|
// module. That's all it does and all it needs to do.
|
||||||
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
|
pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
|
||||||
if path_to_res(cx, path) != Res::Err {
|
if def_path_res(cx, path) != Res::Err {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -999,7 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
|
||||||
}
|
}
|
||||||
|
|
||||||
for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
|
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() {
|
for item in cx.tcx.module_children(def_id).iter() {
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let Res::Def(DefKind::Const, item_def_id) = item.res;
|
if let Res::Def(DefKind::Const, item_def_id) = item.res;
|
||||||
|
|
|
@ -612,8 +612,8 @@ fn get_lint_group_and_level_or_lint(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
|
fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
|
||||||
for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
|
for (group_name, lints, _) in cx.lint_store.get_lint_groups() {
|
||||||
if IGNORED_LINT_GROUPS.contains(group_name) {
|
if IGNORED_LINT_GROUPS.contains(&group_name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
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;
|
if let ["core", "num", int_impl, "max_value"] = *def_path;
|
||||||
then {
|
then {
|
||||||
let value = match int_impl {
|
let value = match int_impl {
|
||||||
"<impl i8>" => i8::MAX as u128,
|
"<impl i8>" => i8::MAX as u128,
|
||||||
"<impl i16>" => i16::MAX as u128,
|
"<impl i16>" => i16::MAX as u128,
|
||||||
"<impl i32>" => i32::MAX as u128,
|
"<impl i32>" => i32::MAX as u128,
|
||||||
"<impl i64>" => i64::MAX as u128,
|
"<impl i64>" => i64::MAX as u128,
|
||||||
"<impl i128>" => i128::MAX as u128,
|
"<impl i128>" => i128::MAX as u128,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some(Constant::Int(value))
|
Some(Constant::Int(value))
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,8 +284,7 @@ impl<'a> VecArgs<'a> {
|
||||||
return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
|
return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
|
||||||
// `vec![elem; size]` case
|
// `vec![elem; size]` case
|
||||||
Some(VecArgs::Repeat(&args[0], &args[1]))
|
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
|
// `vec![a, b, c]` case
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let hir::ExprKind::Box(boxed) = args[0].kind;
|
if let hir::ExprKind::Box(boxed) = args[0].kind;
|
||||||
|
@ -296,11 +295,9 @@ impl<'a> VecArgs<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
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(&[]))
|
Some(VecArgs::Vec(&[]))
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
None
|
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 ExprKind::Lit(lit) = &arg.kind;
|
||||||
if let LitKind::Int(num, _) = lit.node;
|
if let LitKind::Int(num, _) = lit.node;
|
||||||
then {
|
then {
|
||||||
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
|
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Some(VecInitKind::WithExprCapacity(arg.hir_id));
|
return Some(VecInitKind::WithExprCapacity(arg.hir_id));
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::consts::{constant_context, constant_simple};
|
use crate::consts::{constant_context, constant_simple};
|
||||||
use crate::differing_macro_contexts;
|
|
||||||
use crate::source::snippet_opt;
|
use crate::source::snippet_opt;
|
||||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||||
use rustc_data_structures::fx::FxHasher;
|
use rustc_data_structures::fx::FxHasher;
|
||||||
|
@ -186,7 +185,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
||||||
|
|
||||||
#[allow(clippy::similar_names)]
|
#[allow(clippy::similar_names)]
|
||||||
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -77,9 +77,9 @@ use rustc_hir::itemlikevisit::ItemLikeVisitor;
|
||||||
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
|
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
|
||||||
use rustc_hir::{
|
use rustc_hir::{
|
||||||
def, lang_items, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr,
|
def, lang_items, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr,
|
||||||
ExprKind, FnDecl, ForeignItem, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem,
|
ExprKind, FnDecl, ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local,
|
||||||
Local, MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
|
MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, Target,
|
||||||
Target, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
|
TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
|
||||||
};
|
};
|
||||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||||
use rustc_middle::hir::place::PlaceBase;
|
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 the given expression is a local binding, find the initializer expression.
|
||||||
/// If that initializer expression is another local binding, find its initializer again.
|
/// 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
|
/// 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)
|
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.
|
/// Checks if the method call given in `expr` belongs to the given trait.
|
||||||
/// This is a deprecated function, consider using [`is_trait_method`].
|
/// This is a deprecated function, consider using [`is_trait_method`].
|
||||||
pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
|
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>> {
|
pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
|
||||||
match path {
|
last_path_segment(qpath)
|
||||||
QPath::Resolved(_, p) => p.segments.last().and_then(|s| s.args),
|
.args
|
||||||
QPath::TypeRelative(_, s) => s.args,
|
.map_or(&[][..], |a| a.args)
|
||||||
QPath::LangItem(..) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_qpath_generic_tys<'tcx>(path: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
|
|
||||||
get_qpath_generics(path)
|
|
||||||
.map_or([].as_ref(), |a| a.args)
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|a| {
|
.filter_map(|a| match a {
|
||||||
if let hir::GenericArg::Type(ty) = a {
|
hir::GenericArg::Type(ty) => Some(ty),
|
||||||
Some(ty)
|
_ => None,
|
||||||
} else {
|
|
||||||
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
|
/// 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
|
/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
|
||||||
/// `QPath::Resolved.1.res.opt_def_id()`.
|
/// `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.
|
/// 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.
|
/// 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 {
|
pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
|
||||||
expr_path_res(cx, expr)
|
path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
|
||||||
.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
|
/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
|
||||||
/// diagnostic item.
|
/// diagnostic item.
|
||||||
pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
|
pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
|
||||||
expr_path_res(cx, expr)
|
path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
|
||||||
.opt_def_id()
|
|
||||||
.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
|
/// 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)
|
path_to_local(expr) == Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the definition associated to a path.
|
pub trait MaybePath<'hir> {
|
||||||
pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
|
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<DefId> {
|
||||||
|
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 {
|
macro_rules! try_res {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
match $e {
|
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.
|
/// Convenience function to get the `DefId` of a trait by path.
|
||||||
/// It could be a trait or trait alias.
|
/// It could be a trait or trait alias.
|
||||||
pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
|
pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
|
||||||
match path_to_res(cx, path) {
|
match def_path_res(cx, path) {
|
||||||
Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
|
Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
|
||||||
_ => None,
|
_ => 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 parent_impl != CRATE_DEF_ID;
|
||||||
if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl);
|
if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl);
|
||||||
if let hir::ItemKind::Impl(impl_) = &item.kind;
|
if let hir::ItemKind::Impl(impl_) = &item.kind;
|
||||||
then { return impl_.of_trait.as_ref(); }
|
then {
|
||||||
|
return impl_.of_trait.as_ref();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
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 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)
|
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|
||||||
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
|
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
|
||||||
then {
|
then { true } else { false }
|
||||||
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.len() == 2;
|
||||||
if arms[0].guard.is_none();
|
if arms[0].guard.is_none();
|
||||||
if arms[1].guard.is_none();
|
if arms[1].guard.is_none();
|
||||||
if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) ||
|
if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
|
||||||
(is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
|
|
||||||
then {
|
then {
|
||||||
return Some(expr);
|
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 let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
|
||||||
if match_def_path(cx, fun_def_id, path);
|
if match_def_path(cx, fun_def_id, path);
|
||||||
then {
|
then {
|
||||||
return Some(args)
|
return Some(args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
None
|
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
|
/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
|
||||||
/// any.
|
/// 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<usize> {
|
pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
|
||||||
let search_path = cx.get_def_path(did);
|
let search_path = cx.get_def_path(did);
|
||||||
paths
|
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()))
|
.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<usize> {
|
|
||||||
diag_items
|
|
||||||
.iter()
|
|
||||||
.position(|item| cx.tcx.is_diagnostic_item(*item, def_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the given `DefId` matches the path.
|
/// Checks if the given `DefId` matches the path.
|
||||||
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
|
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.
|
// 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 {
|
match expr.kind {
|
||||||
ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
|
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),
|
_ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)),
|
||||||
_ => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
/// 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 {
|
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||||
with_test_item_names(tcx, tcx.parent_module(id), |names| {
|
with_test_item_names(tcx, tcx.parent_module(id), |names| {
|
||||||
tcx.hir()
|
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
|
/// Checks whether item either has `test` attribute applied, or
|
||||||
/// is a module with `test` in its name.
|
/// 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 {
|
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
||||||
is_in_test_function(tcx, item.hir_id())
|
is_in_test_function(tcx, item.hir_id())
|
||||||
|| matches!(item.kind, ItemKind::Mod(..))
|
|| matches!(item.kind, ItemKind::Mod(..))
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rustc_hir::intravisit::Visitor;
|
||||||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
|
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
|
||||||
use rustc_lint::LateContext;
|
use rustc_lint::LateContext;
|
||||||
use rustc_span::def_id::DefId;
|
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 rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
|
|
||||||
|
@ -306,6 +306,7 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A parsed `format_args!` expansion
|
/// A parsed `format_args!` expansion
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct FormatArgsExpn<'tcx> {
|
pub struct FormatArgsExpn<'tcx> {
|
||||||
/// Span of the first argument, the format string
|
/// Span of the first argument, the format string
|
||||||
pub format_string_span: Span,
|
pub format_string_span: Span,
|
||||||
|
@ -462,7 +463,11 @@ impl<'tcx> FormatArgsExpn<'tcx> {
|
||||||
if let Ok(i) = usize::try_from(position);
|
if let Ok(i) = usize::try_from(position);
|
||||||
if let Some(&(j, format_trait)) = self.formatters.get(i);
|
if let Some(&(j, format_trait)) = self.formatters.get(i);
|
||||||
then {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -471,11 +476,13 @@ impl<'tcx> FormatArgsExpn<'tcx> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Span of all inputs
|
/// Source callsite span of all inputs
|
||||||
pub fn inputs_span(&self) -> Span {
|
pub fn inputs_span(&self) -> Span {
|
||||||
match *self.value_args {
|
match *self.value_args {
|
||||||
[] => self.format_string_span,
|
[] => 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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use rustc_trait_selection::infer::InferCtxtExt;
|
||||||
use rustc_trait_selection::traits::query::normalize::AtExt;
|
use rustc_trait_selection::traits::query::normalize::AtExt;
|
||||||
use std::iter;
|
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.
|
// Checks if the given type implements copy.
|
||||||
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
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.
|
/// If the expression is function like, get the signature for it.
|
||||||
pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> {
|
pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<ExprFnSig<'tcx>> {
|
||||||
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)))
|
Some(ExprFnSig::Sig(cx.tcx.fn_sig(id)))
|
||||||
} else {
|
} else {
|
||||||
let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs();
|
let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs();
|
||||||
|
|
|
@ -235,7 +235,11 @@ Use the following functions to deal with macros:
|
||||||
assert_eq!(in_external_macro(cx.sess(), match_span), true);
|
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
|
```rust
|
||||||
macro_rules! m {
|
macro_rules! m {
|
||||||
|
@ -252,7 +256,7 @@ Use the following functions to deal with macros:
|
||||||
// These spans are not from the same context
|
// These spans are not from the same context
|
||||||
// x.is_some() is from inside the macro
|
// x.is_some() is from inside the macro
|
||||||
// x.unwrap() is from outside 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
|
[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
|
||||||
|
|
|
@ -11,6 +11,7 @@ use std::env::{self, remove_var, set_var, var_os};
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::lazy::SyncLazy;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use test_utils::IS_RUSTC_TEST_SUITE;
|
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 must be added to Cargo.toml at the project root. Test
|
||||||
/// dependencies that are not *directly* used by this test module require an
|
/// dependencies that are not *directly* used by this test module require an
|
||||||
/// `extern crate` declaration.
|
/// `extern crate` declaration.
|
||||||
fn extern_flags() -> String {
|
static EXTERN_FLAGS: SyncLazy<String> = SyncLazy::new(|| {
|
||||||
let current_exe_depinfo = {
|
let current_exe_depinfo = {
|
||||||
let mut path = env::current_exe().unwrap();
|
let mut path = env::current_exe().unwrap();
|
||||||
path.set_extension("d");
|
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());
|
let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
|
||||||
for line in current_exe_depinfo.lines() {
|
for line in current_exe_depinfo.lines() {
|
||||||
|
@ -112,16 +113,17 @@ fn extern_flags() -> String {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, path)| format!(" --extern {}={}", name, path))
|
.map(|(name, path)| format!(" --extern {}={}", name, path))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
});
|
||||||
|
|
||||||
fn default_config() -> compiletest::Config {
|
fn base_config(test_dir: &str) -> compiletest::Config {
|
||||||
let mut config = compiletest::Config {
|
let mut config = compiletest::Config {
|
||||||
edition: Some("2021".into()),
|
edition: Some("2021".into()),
|
||||||
|
mode: TestMode::Ui,
|
||||||
..compiletest::Config::default()
|
..compiletest::Config::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(filters) = env::var("TESTNAME") {
|
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") {
|
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.run_lib_path = path.clone();
|
||||||
config.compile_lib_path = path;
|
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 deps_path = current_exe_path.parent().unwrap();
|
||||||
let profile_path = deps_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={}{}{}",
|
"--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}",
|
||||||
deps_path.display(),
|
deps_path.display(),
|
||||||
host_libs,
|
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) {
|
config.rustc_path = profile_path.join(if cfg!(windows) {
|
||||||
"clippy-driver.exe"
|
"clippy-driver.exe"
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,38 +158,23 @@ fn default_config() -> compiletest::Config {
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ui(cfg: &mut compiletest::Config) {
|
fn run_ui() {
|
||||||
cfg.mode = TestMode::Ui;
|
let config = base_config("ui");
|
||||||
cfg.src_base = Path::new("tests").join("ui");
|
|
||||||
// use tests/clippy.toml
|
// use tests/clippy.toml
|
||||||
let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
|
let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap());
|
||||||
compiletest::run_tests(cfg);
|
compiletest::run_tests(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ui_test(cfg: &mut compiletest::Config) {
|
fn run_internal_tests() {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_internal_tests(cfg: &mut compiletest::Config) {
|
|
||||||
// only run internal tests with the internal-tests feature
|
// only run internal tests with the internal-tests feature
|
||||||
if !RUN_INTERNAL_TESTS {
|
if !RUN_INTERNAL_TESTS {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cfg.mode = TestMode::Ui;
|
let config = base_config("ui-internal");
|
||||||
cfg.src_base = Path::new("tests").join("ui-internal");
|
compiletest::run_tests(&config);
|
||||||
compiletest::run_tests(cfg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_ui_toml(config: &mut compiletest::Config) {
|
fn run_ui_toml() {
|
||||||
fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
|
fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
|
||||||
let mut result = true;
|
let mut result = true;
|
||||||
let opts = compiletest::test_opts(config);
|
let opts = compiletest::test_opts(config);
|
||||||
|
@ -222,12 +210,12 @@ fn run_ui_toml(config: &mut compiletest::Config) {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.mode = TestMode::Ui;
|
let mut config = base_config("ui-toml");
|
||||||
config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap();
|
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 {
|
match res {
|
||||||
Ok(true) => {},
|
Ok(true) => {},
|
||||||
Ok(false) => panic!("Some tests failed"),
|
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(
|
fn run_tests(
|
||||||
config: &compiletest::Config,
|
config: &compiletest::Config,
|
||||||
filters: &[String],
|
filters: &[String],
|
||||||
|
@ -310,13 +298,13 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.mode = TestMode::Ui;
|
let mut config = base_config("ui-cargo");
|
||||||
config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
|
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 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();
|
env::set_current_dir(current_dir).unwrap();
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
|
@ -331,12 +319,10 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_test() {
|
fn compile_test() {
|
||||||
set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
|
set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
|
||||||
let mut config = default_config();
|
run_ui();
|
||||||
run_ui(&mut config);
|
run_ui_toml();
|
||||||
run_ui_test(&mut config);
|
run_ui_cargo();
|
||||||
run_ui_toml(&mut config);
|
run_internal_tests();
|
||||||
run_ui_cargo(&mut config);
|
|
||||||
run_internal_tests(&mut config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restores an env var on drop
|
/// Restores an env var on drop
|
||||||
|
|
8
tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml
Normal file
8
tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml
Normal file
|
@ -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]
|
1
tests/ui-cargo/multiple_config_files/no_warn/clippy.toml
Normal file
1
tests/ui-cargo/multiple_config_files/no_warn/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
avoid-breaking-exported-api = false
|
3
tests/ui-cargo/multiple_config_files/no_warn/src/main.rs
Normal file
3
tests/ui-cargo/multiple_config_files/no_warn/src/main.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
1
tests/ui-cargo/multiple_config_files/warn/.clippy.toml
Normal file
1
tests/ui-cargo/multiple_config_files/warn/.clippy.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
avoid-breaking-exported-api = false
|
8
tests/ui-cargo/multiple_config_files/warn/Cargo.toml
Normal file
8
tests/ui-cargo/multiple_config_files/warn/Cargo.toml
Normal file
|
@ -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]
|
1
tests/ui-cargo/multiple_config_files/warn/clippy.toml
Normal file
1
tests/ui-cargo/multiple_config_files/warn/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
avoid-breaking-exported-api = false
|
5
tests/ui-cargo/multiple_config_files/warn/src/main.rs
Normal file
5
tests/ui-cargo/multiple_config_files/warn/src/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// ignore-windows
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Hello, world!");
|
||||||
|
}
|
|
@ -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.
|
|
@ -3,6 +3,7 @@
|
||||||
// Test for https://github.com/rust-lang/rust-clippy/issues/4968
|
// Test for https://github.com/rust-lang/rust-clippy/issues/4968
|
||||||
|
|
||||||
#![warn(clippy::unsound_collection_transmute)]
|
#![warn(clippy::unsound_collection_transmute)]
|
||||||
|
#![allow(clippy::transmute_undefined_repr)]
|
||||||
|
|
||||||
trait Trait {
|
trait Trait {
|
||||||
type Assoc;
|
type Assoc;
|
||||||
|
|
6
tests/ui/crashes/ice-8250.rs
Normal file
6
tests/ui/crashes/ice-8250.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
fn _f(s: &str) -> Option<()> {
|
||||||
|
let _ = s[1..].splitn(2, '.').next()?;
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
18
tests/ui/crashes/ice-8250.stderr
Normal file
18
tests/ui/crashes/ice-8250.stderr
Normal file
|
@ -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
|
||||||
|
|
3
tests/ui/crashes/ice-8386.rs
Normal file
3
tests/ui/crashes/ice-8386.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn f(x: u32, mut arg: &String) {}
|
||||||
|
|
||||||
|
fn main() {}
|
78
tests/ui/default_union_representation.rs
Normal file
78
tests/ui/default_union_representation.rs
Normal file
|
@ -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() {}
|
48
tests/ui/default_union_representation.stderr
Normal file
48
tests/ui/default_union_representation.stderr
Normal file
|
@ -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
|
||||||
|
|
|
@ -1,58 +1,56 @@
|
||||||
// does not test any rustfixable lints
|
// compile-flags: --test
|
||||||
|
|
||||||
|
#![warn(clippy::eq_op)]
|
||||||
|
#![allow(clippy::double_parens, clippy::identity_op, clippy::nonminimal_bool)]
|
||||||
|
|
||||||
#[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)]
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// simple values and comparisons
|
// simple values and comparisons
|
||||||
1 == 1;
|
let _ = 1 == 1;
|
||||||
"no" == "no";
|
let _ = "no" == "no";
|
||||||
// even though I agree that no means no ;-)
|
// even though I agree that no means no ;-)
|
||||||
false != false;
|
let _ = false != false;
|
||||||
1.5 < 1.5;
|
let _ = 1.5 < 1.5;
|
||||||
1u64 >= 1u64;
|
let _ = 1u64 >= 1u64;
|
||||||
|
|
||||||
// casts, methods, parentheses
|
// casts, methods, parentheses
|
||||||
(1 as u64) & (1 as u64);
|
let _ = (1u32 as u64) & (1u32 as u64);
|
||||||
1 ^ ((((((1))))));
|
#[rustfmt::skip]
|
||||||
|
{
|
||||||
|
let _ = 1 ^ ((((((1))))));
|
||||||
|
};
|
||||||
|
|
||||||
// unary and binary operators
|
// unary and binary operators
|
||||||
(-(2) < -(2));
|
let _ = (-(2) < -(2));
|
||||||
((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||||
(1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
||||||
|
|
||||||
// various other things
|
// various other things
|
||||||
([1] != [1]);
|
let _ = ([1] != [1]);
|
||||||
((1, 2) != (1, 2));
|
let _ = ((1, 2) != (1, 2));
|
||||||
vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros
|
let _ = vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros
|
||||||
|
|
||||||
// const folding
|
// const folding
|
||||||
1 + 1 == 2;
|
let _ = 1 + 1 == 2;
|
||||||
1 - 1 == 0;
|
let _ = 1 - 1 == 0;
|
||||||
|
|
||||||
1 - 1;
|
let _ = 1 - 1;
|
||||||
1 / 1;
|
let _ = 1 / 1;
|
||||||
true && true;
|
let _ = true && true;
|
||||||
|
|
||||||
true || true;
|
|
||||||
|
|
||||||
|
let _ = true || true;
|
||||||
|
|
||||||
let a: u32 = 0;
|
let a: u32 = 0;
|
||||||
let b: u32 = 0;
|
let b: u32 = 0;
|
||||||
|
|
||||||
a == b && b == a;
|
let _ = a == b && b == a;
|
||||||
a != b && b != a;
|
let _ = a != b && b != a;
|
||||||
a < b && b > a;
|
let _ = a < b && b > a;
|
||||||
a <= b && b >= a;
|
let _ = a <= b && b >= a;
|
||||||
|
|
||||||
let mut a = vec![1];
|
let mut a = vec![1];
|
||||||
a == a;
|
let _ = a == a;
|
||||||
2*a.len() == 2*a.len(); // ok, functions
|
let _ = 2 * a.len() == 2 * a.len(); // ok, functions
|
||||||
a.pop() == a.pop(); // ok, functions
|
let _ = a.pop() == a.pop(); // ok, functions
|
||||||
|
|
||||||
check_ignore_macro();
|
check_ignore_macro();
|
||||||
|
|
||||||
|
@ -63,15 +61,14 @@ fn main() {
|
||||||
const D: u32 = A / A;
|
const D: u32 = A / A;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
macro_rules! check_if_named_foo {
|
macro_rules! check_if_named_foo {
|
||||||
($expression:expr) => (
|
($expression:expr) => {
|
||||||
if stringify!($expression) == "foo" {
|
if stringify!($expression) == "foo" {
|
||||||
println!("foo!");
|
println!("foo!");
|
||||||
} else {
|
} else {
|
||||||
println!("not foo.");
|
println!("not foo.");
|
||||||
}
|
}
|
||||||
)
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! bool_macro {
|
macro_rules! bool_macro {
|
||||||
|
@ -80,11 +77,10 @@ macro_rules! bool_macro {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::short_circuit_statement)]
|
|
||||||
fn check_ignore_macro() {
|
fn check_ignore_macro() {
|
||||||
check_if_named_foo!(foo);
|
check_if_named_foo!(foo);
|
||||||
// checks if the lint ignores macros with `!` operator
|
// checks if the lint ignores macros with `!` operator
|
||||||
!bool_macro!(1) && !bool_macro!("");
|
let _ = !bool_macro!(1) && !bool_macro!("");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Nested {
|
struct Nested {
|
||||||
|
@ -95,3 +91,18 @@ fn check_nested(n1: &Nested, n2: &Nested) -> bool {
|
||||||
// `n2.inner.0.0` mistyped as `n1.inner.0.0`
|
// `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
|
(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);
|
||||||
|
}
|
||||||
|
|
|
@ -1,174 +1,172 @@
|
||||||
error: equal expressions as operands to `==`
|
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`
|
= note: `-D clippy::eq-op` implied by `-D warnings`
|
||||||
|
|
||||||
error: equal expressions as operands to `==`
|
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 `!=`
|
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 `<`
|
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 `>=`
|
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 `&`
|
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 `^`
|
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 `<`
|
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 `==`
|
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 `&`
|
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 `&`
|
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 `==`
|
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 `!=`
|
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 `!=`
|
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 `==`
|
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 `==`
|
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 `-`
|
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 `-`
|
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 `/`
|
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 `&&`
|
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 `||`
|
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 `&&`
|
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 `&&`
|
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 `&&`
|
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 `&&`
|
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 `==`
|
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 `/`
|
error: equal expressions as operands to `/`
|
||||||
--> $DIR/eq_op.rs:63:20
|
--> $DIR/eq_op.rs:61:20
|
||||||
|
|
|
|
||||||
LL | const D: u32 = A / A;
|
LL | const D: u32 = A / A;
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error: equal expressions as operands to `==`
|
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
|
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
|
error: aborting due to 28 previous errors
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
|
|
||||||
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
||||||
|
|
||||||
|
macro_rules! one {
|
||||||
|
() => {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
struct Foo;
|
struct Foo;
|
||||||
|
|
||||||
|
@ -31,6 +37,9 @@ fn main() {
|
||||||
let with_none_and_as_str: Option<i32> = None;
|
let with_none_and_as_str: Option<i32> = None;
|
||||||
with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
|
with_none_and_as_str.unwrap_or_else(|| panic!("Error {}: fake error", error_code));
|
||||||
|
|
||||||
|
let with_none_and_format_with_macro: Option<i32> = None;
|
||||||
|
with_none_and_format_with_macro.unwrap_or_else(|| panic!("Error {}: fake error", one!()));
|
||||||
|
|
||||||
let with_ok: Result<(), ()> = Ok(());
|
let with_ok: Result<(), ()> = Ok(());
|
||||||
with_ok.expect("error");
|
with_ok.expect("error");
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,12 @@
|
||||||
|
|
||||||
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
||||||
|
|
||||||
|
macro_rules! one {
|
||||||
|
() => {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
struct Foo;
|
struct Foo;
|
||||||
|
|
||||||
|
@ -31,6 +37,9 @@ fn main() {
|
||||||
let with_none_and_as_str: Option<i32> = None;
|
let with_none_and_as_str: Option<i32> = None;
|
||||||
with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
|
with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
|
||||||
|
|
||||||
|
let with_none_and_format_with_macro: Option<i32> = None;
|
||||||
|
with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str());
|
||||||
|
|
||||||
let with_ok: Result<(), ()> = Ok(());
|
let with_ok: Result<(), ()> = Ok(());
|
||||||
with_ok.expect("error");
|
with_ok.expect("error");
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error: use of `expect` followed by a function call
|
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));
|
LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("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`
|
= note: `-D clippy::expect-fun-call` implied by `-D warnings`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
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))`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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));
|
LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("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
|
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());
|
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))`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
LL | Some("foo").expect(&get_string());
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
| ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
LL | Some("foo").expect(get_string().as_ref());
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
LL | Some("foo").expect(get_string().as_str());
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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());
|
LL | Some("foo").expect(get_static_str());
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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));
|
LL | Some("foo").expect(get_non_static_str(&0));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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));
|
LL | Some(true).expect(&format!("key {}, {}", 1, 2));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
|
||||||
|
|
||||||
error: use of `expect` followed by a function call
|
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));
|
LL | opt_ref.expect(&format!("{:?}", opt_ref));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", 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
|
||||||
|
|
||||||
|
|
|
@ -46,13 +46,13 @@ error: the variable `idx_usize` is used as a loop counter
|
||||||
--> $DIR/explicit_counter_loop.rs:170:9
|
--> $DIR/explicit_counter_loop.rs:170:9
|
||||||
|
|
|
|
||||||
LL | for _item in slice {
|
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
|
error: the variable `idx_u32` is used as a loop counter
|
||||||
--> $DIR/explicit_counter_loop.rs:182:9
|
--> $DIR/explicit_counter_loop.rs:182:9
|
||||||
|
|
|
|
||||||
LL | for _item in slice {
|
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`
|
= note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate`
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ fn stderr() -> String {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! one {
|
||||||
|
() => {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// these should warn
|
// these should warn
|
||||||
{
|
{
|
||||||
|
@ -24,6 +30,12 @@ fn main() {
|
||||||
// including newlines
|
// including newlines
|
||||||
println!("test\ntest");
|
println!("test\ntest");
|
||||||
eprintln!("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
|
// these should not warn, different destination
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue