mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-27 23:20:39 +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
|
||||
|
||||
[e181011...master](https://github.com/rust-lang/rust-clippy/compare/e181011...master)
|
||||
[0eff589...master](https://github.com/rust-lang/rust-clippy/compare/0eff589...master)
|
||||
|
||||
## Rust 1.58 (beta)
|
||||
## Rust 1.59 (beta)
|
||||
|
||||
Current beta, release 2022-01-13
|
||||
Current beta, release 2022-02-24
|
||||
|
||||
[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
|
||||
|
||||
### New Lints
|
||||
|
||||
* [`index_refutable_slice`]
|
||||
[#7643](https://github.com/rust-lang/rust-clippy/pull/7643)
|
||||
* [`needless_splitn`]
|
||||
[#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
|
||||
* [`unnecessary_to_owned`]
|
||||
[#7978](https://github.com/rust-lang/rust-clippy/pull/7978)
|
||||
* [`needless_late_init`]
|
||||
[#7995](https://github.com/rust-lang/rust-clippy/pull/7995)
|
||||
* [`octal_escapes`] [#8007](https://github.com/rust-lang/rust-clippy/pull/8007)
|
||||
* [`return_self_not_must_use`]
|
||||
[#8071](https://github.com/rust-lang/rust-clippy/pull/8071)
|
||||
* [`init_numbered_fields`]
|
||||
[#8170](https://github.com/rust-lang/rust-clippy/pull/8170)
|
||||
|
||||
### Moves and Deprecations
|
||||
|
||||
* Move `if_then_panic` to `pedantic` and rename to [`manual_assert`] (now
|
||||
allow-by-default) [#7810](https://github.com/rust-lang/rust-clippy/pull/7810)
|
||||
* Rename `disallow_type` to [`disallowed_types`] and `disallowed_method` to
|
||||
[`disallowed_methods`]
|
||||
[#7984](https://github.com/rust-lang/rust-clippy/pull/7984)
|
||||
* Move [`map_flatten`] to `complexity` (now warn-by-default)
|
||||
[#8054](https://github.com/rust-lang/rust-clippy/pull/8054)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* [`match_overlapping_arm`]: Fix false negative where after included ranges,
|
||||
overlapping ranges weren't linted anymore
|
||||
[#7909](https://github.com/rust-lang/rust-clippy/pull/7909)
|
||||
* [`deprecated_cfg_attr`]: Now takes the specified MSRV into account
|
||||
[#7944](https://github.com/rust-lang/rust-clippy/pull/7944)
|
||||
* [`cast_lossless`]: Now also lints for `bool` to integer casts
|
||||
[#7948](https://github.com/rust-lang/rust-clippy/pull/7948)
|
||||
* [`let_underscore_lock`]: Also emit lints for the `parking_lot` crate
|
||||
[#7957](https://github.com/rust-lang/rust-clippy/pull/7957)
|
||||
* [`needless_borrow`]
|
||||
[#7977](https://github.com/rust-lang/rust-clippy/pull/7977)
|
||||
* Lint when a borrow is auto-dereffed more than once
|
||||
* Lint in the trailing expression of a block for a match arm
|
||||
* [`strlen_on_c_strings`]
|
||||
[8001](https://github.com/rust-lang/rust-clippy/pull/8001)
|
||||
* Lint when used without a fully-qualified path
|
||||
* Suggest removing the surrounding unsafe block when possible
|
||||
* [`non_ascii_literal`]: Now also lints on `char`s, not just `string`s
|
||||
[#8034](https://github.com/rust-lang/rust-clippy/pull/8034)
|
||||
* [`single_char_pattern`]: Now also lints on `split_inclusive`, `split_once`,
|
||||
`rsplit_once`, `replace`, and `replacen`
|
||||
[#8077](https://github.com/rust-lang/rust-clippy/pull/8077)
|
||||
* [`unwrap_or_else_default`]: Now also lints on `std` constructors like
|
||||
`Vec::new`, `HashSet::new`, and `HashMap::new`
|
||||
[#8163](https://github.com/rust-lang/rust-clippy/pull/8163)
|
||||
* [`shadow_reuse`]: Now also lints on shadowed `if let` bindings, instead of
|
||||
[`shadow_unrelated`]
|
||||
[#8165](https://github.com/rust-lang/rust-clippy/pull/8165)
|
||||
|
||||
### False Positive Fixes
|
||||
|
||||
* [`or_fun_call`], [`unnecessary_lazy_evaluations`]: Improve heuristics, so that
|
||||
cheap functions (e.g. calling `.len()` on a `Vec`) won't get linted anymore
|
||||
[#7639](https://github.com/rust-lang/rust-clippy/pull/7639)
|
||||
* [`manual_split_once`]: No longer suggests code changing the original behavior
|
||||
[#7896](https://github.com/rust-lang/rust-clippy/pull/7896)
|
||||
* Don't show [`no_effect`] or [`unnecessary_operation`] warning for unit struct
|
||||
implementing `FnOnce`
|
||||
[#7898](https://github.com/rust-lang/rust-clippy/pull/7898)
|
||||
* [`semicolon_if_nothing_returned`]: Fixed a bug, where the lint wrongly
|
||||
triggered on `let-else` statements
|
||||
[#7955](https://github.com/rust-lang/rust-clippy/pull/7955)
|
||||
* [`if_then_some_else_none`]: No longer lints if there is an early return
|
||||
[#7980](https://github.com/rust-lang/rust-clippy/pull/7980)
|
||||
* [`needless_collect`]: No longer suggests removal of `collect` when removal
|
||||
would create code requiring mutably borrowing a value multiple times
|
||||
[#7982](https://github.com/rust-lang/rust-clippy/pull/7982)
|
||||
* [`shadow_same`]: Fix false positive for `async` function's params
|
||||
[#7997](https://github.com/rust-lang/rust-clippy/pull/7997)
|
||||
* [`suboptimal_flops`]: No longer triggers in constant functions
|
||||
[#8009](https://github.com/rust-lang/rust-clippy/pull/8009)
|
||||
* [`type_complexity`]: No longer lints on associated types in traits
|
||||
[#8030](https://github.com/rust-lang/rust-clippy/pull/8030)
|
||||
* [`question_mark`]: No longer lints if returned object is not local
|
||||
[#8080](https://github.com/rust-lang/rust-clippy/pull/8080)
|
||||
* [`option_if_let_else`]: No longer lint on complex sub-patterns
|
||||
[#8086](https://github.com/rust-lang/rust-clippy/pull/8086)
|
||||
* [`blocks_in_if_conditions`]: No longer lints on empty closures
|
||||
[#8100](https://github.com/rust-lang/rust-clippy/pull/8100)
|
||||
* [`enum_variant_names`]: No longer lint when first prefix is only a substring
|
||||
of a camel-case word
|
||||
[#8127](https://github.com/rust-lang/rust-clippy/pull/8127)
|
||||
* [`identity_op`]: Only lint on integral operands
|
||||
[#8183](https://github.com/rust-lang/rust-clippy/pull/8183)
|
||||
|
||||
### Suggestion Fixes/Improvements
|
||||
|
||||
* [`search_is_some`]: Fix suggestion for `any()` not taking item by reference
|
||||
[#7463](https://github.com/rust-lang/rust-clippy/pull/7463)
|
||||
* [`almost_swapped`]: Now detects if there is a `no_std` or `no_core` attribute
|
||||
and adapts the suggestion accordingly
|
||||
[#7877](https://github.com/rust-lang/rust-clippy/pull/7877)
|
||||
* [`redundant_pattern_matching`]: Fix suggestion for deref expressions
|
||||
[#7949](https://github.com/rust-lang/rust-clippy/pull/7949)
|
||||
* [`explicit_counter_loop`]: Now also produces a suggestion for non-`usize`
|
||||
types [#7950](https://github.com/rust-lang/rust-clippy/pull/7950)
|
||||
* [`manual_map`]: Fix suggestion when used with unsafe functions and blocks
|
||||
[#7968](https://github.com/rust-lang/rust-clippy/pull/7968)
|
||||
* [`option_map_or_none`]: Suggest `map` over `and_then` when possible
|
||||
[#7971](https://github.com/rust-lang/rust-clippy/pull/7971)
|
||||
* [`option_if_let_else`]: No longer expands macros in the suggestion
|
||||
[#7974](https://github.com/rust-lang/rust-clippy/pull/7974)
|
||||
* [`iter_cloned_collect`]: Suggest `copied` over `cloned` when possible
|
||||
[#8006](https://github.com/rust-lang/rust-clippy/pull/8006)
|
||||
* [`doc_markdown`]: No longer uses inline hints to improve readability of
|
||||
suggestion [#8011](https://github.com/rust-lang/rust-clippy/pull/8011)
|
||||
* [`needless_question_mark`]: Now better explains the suggestion
|
||||
[#8028](https://github.com/rust-lang/rust-clippy/pull/8028)
|
||||
* [`single_char_pattern`]: Escape backslash `\` in suggestion
|
||||
[#8067](https://github.com/rust-lang/rust-clippy/pull/8067)
|
||||
* [`needless_bool`]: Suggest `a != b` over `!(a == b)`
|
||||
[#8117](https://github.com/rust-lang/rust-clippy/pull/8117)
|
||||
* [`iter_skip_next`]: Suggest to add a `mut` if it is necessary in order to
|
||||
apply this lints suggestion
|
||||
[#8133](https://github.com/rust-lang/rust-clippy/pull/8133)
|
||||
* [`neg_multiply`]: Now produces a suggestion
|
||||
[#8144](https://github.com/rust-lang/rust-clippy/pull/8144)
|
||||
* [`needless_return`]: Now suggests the unit type `()` over an empty block `{}`
|
||||
in match arms [#8185](https://github.com/rust-lang/rust-clippy/pull/8185)
|
||||
* [`suboptimal_flops`]: Now gives a syntactically correct suggestion for
|
||||
`to_radians` and `to_degrees`
|
||||
[#8187](https://github.com/rust-lang/rust-clippy/pull/8187)
|
||||
|
||||
### ICE Fixes
|
||||
|
||||
* [`undocumented_unsafe_blocks`]
|
||||
[#7945](https://github.com/rust-lang/rust-clippy/pull/7945)
|
||||
[#7988](https://github.com/rust-lang/rust-clippy/pull/7988)
|
||||
* [`unnecessary_cast`]
|
||||
[#8167](https://github.com/rust-lang/rust-clippy/pull/8167)
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
* [`print_stdout`], [`print_stderr`], [`dbg_macro`]: Document how the lint level
|
||||
can be changed crate-wide
|
||||
[#8040](https://github.com/rust-lang/rust-clippy/pull/8040)
|
||||
* Added a note to the `README` that config changes don't apply to already
|
||||
compiled code [#8175](https://github.com/rust-lang/rust-clippy/pull/8175)
|
||||
|
||||
### Others
|
||||
|
||||
* [Clippy's lint
|
||||
list](https://rust-lang.github.io/rust-clippy/master/index.html) now displays
|
||||
the version a lint was added. :tada:
|
||||
[#7813](https://github.com/rust-lang/rust-clippy/pull/7813)
|
||||
* New and improved issue templates
|
||||
[#8032](https://github.com/rust-lang/rust-clippy/pull/8032)
|
||||
* _Dev:_ Add `cargo dev lint` command, to run your modified Clippy version on a
|
||||
file [#7917](https://github.com/rust-lang/rust-clippy/pull/7917)
|
||||
|
||||
## Rust 1.58
|
||||
|
||||
Current stable, released 2022-01-13
|
||||
|
||||
[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
|
||||
|
||||
### Rust 1.58.1
|
||||
|
||||
* Move [`non_send_fields_in_send_ty`] to `nursery` (now allow-by-default)
|
||||
[#8075](https://github.com/rust-lang/rust-clippy/pull/8075)
|
||||
* [`useless_format`]: Handle implicit named arguments
|
||||
[#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
|
||||
|
||||
### New lints
|
||||
|
||||
* [`transmute_num_to_bytes`]
|
||||
|
@ -124,7 +295,7 @@ Current beta, release 2022-01-13
|
|||
|
||||
## Rust 1.57
|
||||
|
||||
Current stable, released 2021-12-02
|
||||
Released 2021-12-02
|
||||
|
||||
[7bfc26e...00e31fa](https://github.com/rust-lang/rust-clippy/compare/7bfc26e...00e31fa)
|
||||
|
||||
|
@ -2930,6 +3101,7 @@ Released 2018-09-13
|
|||
[`declare_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#declare_interior_mutable_const
|
||||
[`default_numeric_fallback`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_numeric_fallback
|
||||
[`default_trait_access`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_trait_access
|
||||
[`default_union_representation`]: https://rust-lang.github.io/rust-clippy/master/index.html#default_union_representation
|
||||
[`deprecated_cfg_attr`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_cfg_attr
|
||||
[`deprecated_semver`]: https://rust-lang.github.io/rust-clippy/master/index.html#deprecated_semver
|
||||
[`deref_addrof`]: https://rust-lang.github.io/rust-clippy/master/index.html#deref_addrof
|
||||
|
@ -3303,6 +3475,7 @@ Released 2018-09-13
|
|||
[`transmute_num_to_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_num_to_bytes
|
||||
[`transmute_ptr_to_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ptr
|
||||
[`transmute_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref
|
||||
[`transmute_undefined_repr`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmute_undefined_repr
|
||||
[`transmutes_expressible_as_ptr_casts`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmutes_expressible_as_ptr_casts
|
||||
[`transmuting_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#transmuting_null
|
||||
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
|
||||
|
|
|
@ -5,9 +5,7 @@ use std::ffi::OsStr;
|
|||
use std::fs;
|
||||
use std::lazy::SyncLazy;
|
||||
use std::path::{Path, PathBuf};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::clippy_project_root;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
static CARGO_CLIPPY_EXE: &str = "cargo-clippy";
|
||||
|
@ -24,43 +22,25 @@ static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::ne
|
|||
///
|
||||
/// Panics if the path to a test file is broken
|
||||
pub fn bless(ignore_timestamp: bool) {
|
||||
let test_suite_dirs = [
|
||||
clippy_project_root().join("tests").join("ui"),
|
||||
clippy_project_root().join("tests").join("ui-internal"),
|
||||
clippy_project_root().join("tests").join("ui-toml"),
|
||||
clippy_project_root().join("tests").join("ui-cargo"),
|
||||
];
|
||||
for test_suite_dir in &test_suite_dirs {
|
||||
WalkDir::new(test_suite_dir)
|
||||
.into_iter()
|
||||
.filter_map(Result::ok)
|
||||
.filter(|f| f.path().extension() == Some(OsStr::new("rs")))
|
||||
.for_each(|f| {
|
||||
let test_name = f.path().strip_prefix(test_suite_dir).unwrap();
|
||||
for &ext in &["stdout", "stderr", "fixed"] {
|
||||
let test_name_ext = format!("stage-id.{}", ext);
|
||||
update_reference_file(
|
||||
f.path().with_extension(ext),
|
||||
test_name.with_extension(test_name_ext),
|
||||
ignore_timestamp,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
let extensions = ["stdout", "stderr", "fixed"].map(OsStr::new);
|
||||
|
||||
WalkDir::new(build_dir())
|
||||
.into_iter()
|
||||
.map(Result::unwrap)
|
||||
.filter(|entry| entry.path().extension().map_or(false, |ext| extensions.contains(&ext)))
|
||||
.for_each(|entry| update_reference_file(&entry, ignore_timestamp));
|
||||
}
|
||||
|
||||
fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) {
|
||||
let test_output_path = build_dir().join(test_name);
|
||||
let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap();
|
||||
fn update_reference_file(test_output_entry: &DirEntry, ignore_timestamp: bool) {
|
||||
let test_output_path = test_output_entry.path();
|
||||
|
||||
// If compiletest did not write any changes during the test run,
|
||||
// we don't have to update anything
|
||||
if !test_output_path.exists() {
|
||||
return;
|
||||
}
|
||||
let reference_file_name = test_output_entry.file_name().to_str().unwrap().replace(".stage-id", "");
|
||||
let reference_file_path = Path::new("tests")
|
||||
.join(test_output_path.strip_prefix(build_dir()).unwrap())
|
||||
.with_file_name(reference_file_name);
|
||||
|
||||
// If the test output was not updated since the last clippy build, it may be outdated
|
||||
if !ignore_timestamp && !updated_since_clippy_build(&test_output_path).unwrap_or(true) {
|
||||
if !ignore_timestamp && !updated_since_clippy_build(test_output_entry).unwrap_or(true) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -69,23 +49,14 @@ fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignor
|
|||
|
||||
if test_output_file != reference_file {
|
||||
// If a test run caused an output file to change, update the reference file
|
||||
println!("updating {}", &relative_reference_file_path.display());
|
||||
println!("updating {}", reference_file_path.display());
|
||||
fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file");
|
||||
|
||||
// We need to re-read the file now because it was potentially updated from copying
|
||||
let reference_file = fs::read(&reference_file_path).unwrap_or_default();
|
||||
|
||||
if reference_file.is_empty() {
|
||||
// If we copied over an empty output file, we remove the now empty reference file
|
||||
println!("removing {}", &relative_reference_file_path.display());
|
||||
fs::remove_file(reference_file_path).expect("Could not remove reference file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn updated_since_clippy_build(path: &Path) -> Option<bool> {
|
||||
fn updated_since_clippy_build(entry: &DirEntry) -> Option<bool> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::get_parent_expr;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::source::snippet_block_with_applicability;
|
||||
use clippy_utils::ty::implements_trait;
|
||||
use clippy_utils::{differing_macro_contexts, get_parent_expr};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
|
@ -97,7 +97,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
|||
if let Some(ex) = &block.expr {
|
||||
// don't dig into the expression here, just suggest that they remove
|
||||
// the block
|
||||
if expr.span.from_expansion() || differing_macro_contexts(expr.span, ex.span) {
|
||||
if expr.span.from_expansion() || ex.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
|
@ -122,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for BlocksInIfConditions {
|
|||
}
|
||||
} else {
|
||||
let span = block.expr.as_ref().map_or_else(|| block.stmts[0].span, |e| e.span);
|
||||
if span.from_expansion() || differing_macro_contexts(expr.span, span) {
|
||||
if span.from_expansion() || expr.span.from_expansion() {
|
||||
return;
|
||||
}
|
||||
// move block higher
|
||||
|
|
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<'_>) {
|
||||
for (index, conf) in self.conf_disallowed.iter().enumerate() {
|
||||
let segs: Vec<_> = conf.path().split("::").collect();
|
||||
if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &segs) {
|
||||
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
|
||||
self.disallowed.insert(id, index);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedTypes {
|
|||
),
|
||||
};
|
||||
let segs: Vec<_> = path.split("::").collect();
|
||||
match clippy_utils::path_to_res(cx, &segs) {
|
||||
match clippy_utils::def_path_res(cx, &segs) {
|
||||
Res::Def(_, id) => {
|
||||
self.def_ids.insert(id, reason);
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::FormatArgsExpn;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, match_function_call, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -79,28 +80,22 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
"print".into(),
|
||||
)
|
||||
};
|
||||
let msg = format!("use of `{}.unwrap()`", used);
|
||||
if let [write_output] = *format_args.format_string_parts {
|
||||
let mut write_output = write_output.to_string();
|
||||
if write_output.ends_with('\n') {
|
||||
write_output.pop();
|
||||
}
|
||||
|
||||
let sugg = format!("{}{}!(\"{}\")", prefix, sugg_mac, write_output.escape_default());
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&msg,
|
||||
"try this",
|
||||
sugg,
|
||||
Applicability::MachineApplicable
|
||||
);
|
||||
} else {
|
||||
// We don't have a proper suggestion
|
||||
let help = format!("consider using `{}{}!` instead", prefix, sugg_mac);
|
||||
span_lint_and_help(cx, EXPLICIT_WRITE, expr.span, &msg, None, &help);
|
||||
}
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args.inputs_span(),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPLICIT_WRITE,
|
||||
expr.span,
|
||||
&format!("use of `{}.unwrap()`", used),
|
||||
"try this",
|
||||
format!("{}{}!({})", prefix, sugg_mac, inputs_snippet),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note};
|
||||
use clippy_utils::differing_macro_contexts;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
|
||||
|
@ -135,7 +134,7 @@ impl EarlyLintPass for Formatting {
|
|||
/// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint.
|
||||
fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind {
|
||||
if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion() {
|
||||
if !lhs.span.from_expansion() && !rhs.span.from_expansion() {
|
||||
let eq_span = lhs.span.between(rhs.span);
|
||||
if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind {
|
||||
if let Some(eq_snippet) = snippet_opt(cx, eq_span) {
|
||||
|
@ -165,7 +164,7 @@ fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) {
|
|||
fn check_unop(cx: &EarlyContext<'_>, expr: &Expr) {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref binop, ref lhs, ref rhs) = expr.kind;
|
||||
if !differing_macro_contexts(lhs.span, rhs.span) && !lhs.span.from_expansion();
|
||||
if !lhs.span.from_expansion() && !rhs.span.from_expansion();
|
||||
// span between BinOp LHS and RHS
|
||||
let binop_span = lhs.span.between(rhs.span);
|
||||
// if RHS is an UnOp
|
||||
|
@ -206,8 +205,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
|
|||
if_chain! {
|
||||
if let ExprKind::If(_, then, Some(else_)) = &expr.kind;
|
||||
if is_block(else_) || is_if(else_);
|
||||
if !differing_macro_contexts(then.span, else_.span);
|
||||
if !then.span.from_expansion() && !in_external_macro(cx.sess(), expr.span);
|
||||
if !then.span.from_expansion() && !else_.span.from_expansion();
|
||||
if !in_external_macro(cx.sess(), expr.span);
|
||||
|
||||
// workaround for rust-lang/rust#43081
|
||||
if expr.span.lo().0 != 0 && expr.span.hi().0 != 0;
|
||||
|
@ -268,7 +267,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
|
|||
for element in array {
|
||||
if_chain! {
|
||||
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
|
||||
if has_unary_equivalent(op.node) && !differing_macro_contexts(lhs.span, op.span);
|
||||
if has_unary_equivalent(op.node) && lhs.span.ctxt() == op.span.ctxt();
|
||||
let space_span = lhs.span.between(op.span);
|
||||
if let Some(space_snippet) = snippet_opt(cx, space_span);
|
||||
let lint_span = lhs.span.with_lo(lhs.span.hi());
|
||||
|
@ -291,8 +290,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
|
|||
|
||||
fn check_missing_else(cx: &EarlyContext<'_>, first: &Expr, second: &Expr) {
|
||||
if_chain! {
|
||||
if !differing_macro_contexts(first.span, second.span);
|
||||
if !first.span.from_expansion();
|
||||
if !first.span.from_expansion() && !second.span.from_expansion();
|
||||
if let ExprKind::If(cond_expr, ..) = &first.kind;
|
||||
if is_block(second) || is_if(second);
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ use rustc_typeck::hir_ty_to_ty;
|
|||
use if_chain::if_chain;
|
||||
|
||||
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::differing_macro_contexts;
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
|
||||
|
@ -123,7 +122,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
|
|||
vis.visit_ty(impl_.self_ty);
|
||||
|
||||
for target in &vis.found {
|
||||
if differing_macro_contexts(item.span, target.span()) {
|
||||
if item.span.ctxt() != target.span().ctxt() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths};
|
||||
use clippy_utils::{get_trait_def_id, higher, match_def_path, path_def_id, paths};
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
|
@ -167,13 +167,9 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
|
|||
},
|
||||
ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
|
||||
ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
|
||||
ExprKind::Call(path, _) => {
|
||||
if let ExprKind::Path(ref qpath) = path.kind {
|
||||
is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into()
|
||||
} else {
|
||||
Finite
|
||||
}
|
||||
},
|
||||
ExprKind::Call(path, _) => path_def_id(cx, path)
|
||||
.map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT))
|
||||
.into(),
|
||||
ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
|
||||
_ => Finite,
|
||||
}
|
||||
|
|
|
@ -277,6 +277,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
|
|||
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
|
||||
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
|
||||
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
|
||||
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
|
||||
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
||||
|
|
|
@ -58,6 +58,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
|
|||
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
|
||||
LintId::of(swap::ALMOST_SWAPPED),
|
||||
LintId::of(to_string_in_display::TO_STRING_IN_DISPLAY),
|
||||
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
|
||||
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
|
||||
LintId::of(transmute::WRONG_TRANSMUTE),
|
||||
LintId::of(transmuting_null::TRANSMUTING_NULL),
|
||||
|
|
|
@ -92,6 +92,7 @@ store.register_lints(&[
|
|||
default::DEFAULT_TRAIT_ACCESS,
|
||||
default::FIELD_REASSIGN_WITH_DEFAULT,
|
||||
default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK,
|
||||
default_union_representation::DEFAULT_UNION_REPRESENTATION,
|
||||
dereference::EXPLICIT_DEREF_METHODS,
|
||||
dereference::NEEDLESS_BORROW,
|
||||
dereference::REF_BINDING_TO_REFERENCE,
|
||||
|
@ -472,6 +473,7 @@ store.register_lints(&[
|
|||
transmute::TRANSMUTE_NUM_TO_BYTES,
|
||||
transmute::TRANSMUTE_PTR_TO_PTR,
|
||||
transmute::TRANSMUTE_PTR_TO_REF,
|
||||
transmute::TRANSMUTE_UNDEFINED_REPR,
|
||||
transmute::UNSOUND_COLLECTION_TRANSMUTE,
|
||||
transmute::USELESS_TRANSMUTE,
|
||||
transmute::WRONG_TRANSMUTE,
|
||||
|
|
|
@ -12,6 +12,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
|
|||
LintId::of(create_dir::CREATE_DIR),
|
||||
LintId::of(dbg_macro::DBG_MACRO),
|
||||
LintId::of(default_numeric_fallback::DEFAULT_NUMERIC_FALLBACK),
|
||||
LintId::of(default_union_representation::DEFAULT_UNION_REPRESENTATION),
|
||||
LintId::of(disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS),
|
||||
LintId::of(else_if_without_else::ELSE_IF_WITHOUT_ELSE),
|
||||
LintId::of(exhaustive_items::EXHAUSTIVE_ENUMS),
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#![feature(control_flow_enum)]
|
||||
#![feature(drain_filter)]
|
||||
#![feature(iter_intersperse)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(let_else)]
|
||||
#![feature(once_cell)]
|
||||
#![feature(rustc_private)]
|
||||
|
@ -17,6 +18,8 @@
|
|||
#![warn(rust_2018_idioms, unused_lifetimes)]
|
||||
// warn on rustc internal lints
|
||||
#![warn(rustc::internal)]
|
||||
// Disable this rustc lint for now, as it was also done in rustc
|
||||
#![allow(rustc::potential_query_instability)]
|
||||
|
||||
// FIXME: switch to something more ergonomic here, once available.
|
||||
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
|
||||
|
@ -189,6 +192,7 @@ mod create_dir;
|
|||
mod dbg_macro;
|
||||
mod default;
|
||||
mod default_numeric_fallback;
|
||||
mod default_union_representation;
|
||||
mod dereference;
|
||||
mod derivable_impls;
|
||||
mod derive;
|
||||
|
@ -859,6 +863,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
|
|||
store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames));
|
||||
store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv)));
|
||||
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
|
||||
store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
|
||||
// add lints here, do not remove this comment, it's used in `new_lint`
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use super::SINGLE_ELEMENT_LOOP;
|
||||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::single_segment_path;
|
||||
use clippy_utils::source::{indent_of, snippet};
|
||||
use clippy_utils::source::{indent_of, snippet_with_applicability};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind};
|
||||
use rustc_hir::{BorrowKind, Expr, ExprKind, Pat};
|
||||
use rustc_lint::LateContext;
|
||||
|
||||
pub(super) fn check<'tcx>(
|
||||
|
@ -16,24 +15,21 @@ pub(super) fn check<'tcx>(
|
|||
) {
|
||||
let arg_expr = match arg.kind {
|
||||
ExprKind::AddrOf(BorrowKind::Ref, _, ref_arg) => ref_arg,
|
||||
ExprKind::MethodCall(method, args, _) if args.len() == 1 && method.ident.name == rustc_span::sym::iter => {
|
||||
&args[0]
|
||||
},
|
||||
ExprKind::MethodCall(method, [arg], _) if method.ident.name == rustc_span::sym::iter => arg,
|
||||
_ => return,
|
||||
};
|
||||
if_chain! {
|
||||
if let PatKind::Binding(.., target, _) = pat.kind;
|
||||
if let ExprKind::Array([arg_expression]) = arg_expr.kind;
|
||||
if let ExprKind::Path(ref list_item) = arg_expression.kind;
|
||||
if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name);
|
||||
if let ExprKind::Block(block, _) = body.kind;
|
||||
if !block.stmts.is_empty();
|
||||
|
||||
then {
|
||||
let mut block_str = snippet(cx, block.span, "..").into_owned();
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let pat_snip = snippet_with_applicability(cx, pat.span, "..", &mut applicability);
|
||||
let arg_snip = snippet_with_applicability(cx, arg_expression.span, "..", &mut applicability);
|
||||
let mut block_str = snippet_with_applicability(cx, block.span, "..", &mut applicability).into_owned();
|
||||
block_str.remove(0);
|
||||
block_str.pop();
|
||||
|
||||
let indent = " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0));
|
||||
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -41,8 +37,8 @@ pub(super) fn check<'tcx>(
|
|||
expr.span,
|
||||
"for loop over a single element",
|
||||
"try",
|
||||
format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str),
|
||||
Applicability::MachineApplicable
|
||||
format!("{{\n{}let {} = &{};{}}}", indent, pat_snip, arg_snip, block_str),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use rustc_hir::intravisit::{walk_expr, walk_local, walk_pat, walk_stmt, Visitor}
|
|||
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, Local, Mutability, Pat, PatKind, Stmt};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::source_map::Spanned;
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
use rustc_typeck::hir_ty_to_ty;
|
||||
|
@ -332,18 +332,21 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
|
|||
} else {
|
||||
// (&x).into_iter() ==> x.iter()
|
||||
// (&mut x).into_iter() ==> x.iter_mut()
|
||||
match &arg.kind {
|
||||
ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
|
||||
if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() =>
|
||||
{
|
||||
let meth_name = match mutability {
|
||||
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
|
||||
match &arg_ty.kind() {
|
||||
ty::Ref(_, inner_ty, mutbl) if has_iter_method(cx, inner_ty).is_some() => {
|
||||
let method_name = match mutbl {
|
||||
Mutability::Mut => "iter_mut",
|
||||
Mutability::Not => "iter",
|
||||
};
|
||||
let caller = match &arg.kind {
|
||||
ExprKind::AddrOf(BorrowKind::Ref, _, arg_inner) => arg_inner,
|
||||
_ => arg,
|
||||
};
|
||||
format!(
|
||||
"{}.{}()",
|
||||
sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(),
|
||||
meth_name,
|
||||
sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(),
|
||||
method_name,
|
||||
)
|
||||
},
|
||||
_ => format!(
|
||||
|
|
|
@ -35,7 +35,8 @@ struct PathAndSpan {
|
|||
span: Span,
|
||||
}
|
||||
|
||||
/// `MacroRefData` includes the name of the macro.
|
||||
/// `MacroRefData` includes the name of the macro
|
||||
/// and the path from `SourceMap::span_to_filename`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MacroRefData {
|
||||
name: String,
|
||||
|
|
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::source::snippet_with_applicability;
|
||||
use clippy_utils::{method_chain_args, single_segment_path};
|
||||
use clippy_utils::{method_chain_args, path_def_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_lint::Lint;
|
||||
use rustc_middle::ty;
|
||||
use rustc_span::sym;
|
||||
use rustc_middle::ty::{self, DefIdTree};
|
||||
|
||||
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
|
||||
pub(super) fn check(
|
||||
|
@ -19,11 +18,9 @@ pub(super) fn check(
|
|||
) -> bool {
|
||||
if_chain! {
|
||||
if let Some(args) = method_chain_args(info.chain, chain_methods);
|
||||
if let hir::ExprKind::Call(fun, arg_char) = info.other.kind;
|
||||
if arg_char.len() == 1;
|
||||
if let hir::ExprKind::Path(ref qpath) = fun.kind;
|
||||
if let Some(segment) = single_segment_path(qpath);
|
||||
if segment.ident.name == sym::Some;
|
||||
if let hir::ExprKind::Call(fun, [arg_char]) = info.other.kind;
|
||||
if let Some(id) = path_def_id(cx, fun).and_then(|ctor_id| cx.tcx.parent(ctor_id));
|
||||
if Some(id) == cx.tcx.lang_items().option_some_variant();
|
||||
then {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0][0]).peel_refs();
|
||||
|
@ -42,7 +39,7 @@ pub(super) fn check(
|
|||
if info.eq { "" } else { "!" },
|
||||
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
||||
suggest,
|
||||
snippet_with_applicability(cx, arg_char[0].span, "..", &mut applicability)),
|
||||
snippet_with_applicability(cx, arg_char.span, "..", &mut applicability)),
|
||||
applicability,
|
||||
);
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
|
|||
if info.eq { "" } else { "!" },
|
||||
snippet_with_applicability(cx, args[0][0].span, "..", &mut applicability),
|
||||
suggest,
|
||||
c),
|
||||
c.escape_default()),
|
||||
applicability,
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::is_qpath_def_path;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{match_def_path, path_def_id};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast;
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -93,12 +93,12 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<M
|
|||
let ty_str = ty.to_string();
|
||||
|
||||
// `std::T::MAX` `std::T::MIN` constants
|
||||
if let hir::ExprKind::Path(path) = &expr.kind {
|
||||
if is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MAX"][..]) {
|
||||
if let Some(id) = path_def_id(cx, expr) {
|
||||
if match_def_path(cx, id, &["core", &ty_str, "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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -566,17 +566,20 @@ declare_clippy_lint! {
|
|||
///
|
||||
/// ### Why is this bad?
|
||||
/// Readability, this can be written more concisely as
|
||||
/// `_.flat_map(_)`
|
||||
/// `_.flat_map(_)` for `Iterator` or `_.and_then(_)` for `Option`
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// let vec = vec![vec![1]];
|
||||
/// let opt = Some(5);
|
||||
///
|
||||
/// // Bad
|
||||
/// vec.iter().map(|x| x.iter()).flatten();
|
||||
/// opt.map(|x| Some(x * 2)).flatten();
|
||||
///
|
||||
/// // Good
|
||||
/// vec.iter().flat_map(|x| x.iter());
|
||||
/// opt.and_then(|x| Some(x * 2));
|
||||
/// ```
|
||||
#[clippy::version = "1.31.0"]
|
||||
pub MAP_FLATTEN,
|
||||
|
@ -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", []) => {
|
||||
implicit_clone::check(cx, name, expr, recv);
|
||||
},
|
||||
("unwrap", []) => match method_call(recv) {
|
||||
Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
|
||||
Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),
|
||||
_ => unwrap_used::check(cx, expr, recv),
|
||||
("unwrap", []) => {
|
||||
match method_call(recv) {
|
||||
Some(("get", [recv, get_arg], _)) => {
|
||||
get_unwrap::check(cx, expr, recv, get_arg, false);
|
||||
},
|
||||
Some(("get_mut", [recv, get_arg], _)) => {
|
||||
get_unwrap::check(cx, expr, recv, get_arg, true);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
unwrap_used::check(cx, expr, recv);
|
||||
},
|
||||
("unwrap_or", [u_arg]) => match method_call(recv) {
|
||||
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), [lhs, rhs], _)) => {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{is_lang_ctor, single_segment_path};
|
||||
use clippy_utils::{is_lang_ctor, path_def_id};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::LangItem::{OptionNone, OptionSome};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::DefIdTree;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
use super::OPTION_MAP_OR_NONE;
|
||||
|
@ -76,13 +77,11 @@ pub(super) fn check<'tcx>(
|
|||
if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
|
||||
let arg_snippet = snippet(cx, span, "..");
|
||||
let body = cx.tcx.hir().body(id);
|
||||
if let Some((func, arg_char)) = reduce_unit_expression(cx, &body.value);
|
||||
if arg_char.len() == 1;
|
||||
if let hir::ExprKind::Path(ref qpath) = func.kind;
|
||||
if let Some(segment) = single_segment_path(qpath);
|
||||
if segment.ident.name == sym::Some;
|
||||
if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
|
||||
if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
|
||||
if Some(id) == cx.tcx.lang_items().option_some_variant();
|
||||
then {
|
||||
let func_snippet = snippet(cx, arg_char[0].span, "..");
|
||||
let func_snippet = snippet(cx, arg_char.span, "..");
|
||||
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
|
||||
`map(..)` instead";
|
||||
return span_lint_and_sugg(
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::differing_macro_contexts;
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::is_copy;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
|
@ -48,7 +47,7 @@ pub(super) fn check<'tcx>(
|
|||
}
|
||||
}
|
||||
|
||||
if differing_macro_contexts(unwrap_arg.span, map_span) {
|
||||
if unwrap_arg.span.ctxt() != map_span.ctxt() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,16 +45,16 @@ pub(super) fn check_manual_split_once(
|
|||
IterUsageKind::Next | IterUsageKind::Second => {
|
||||
let self_deref = {
|
||||
let adjust = cx.typeck_results().expr_adjustments(self_arg);
|
||||
if adjust.is_empty() {
|
||||
if adjust.len() < 2 {
|
||||
String::new()
|
||||
} else if cx.typeck_results().expr_ty(self_arg).is_box()
|
||||
|| adjust
|
||||
.iter()
|
||||
.any(|a| matches!(a.kind, Adjust::Deref(Some(_))) || a.target.is_box())
|
||||
{
|
||||
format!("&{}", "*".repeat(adjust.len() - 1))
|
||||
format!("&{}", "*".repeat(adjust.len().saturating_sub(1)))
|
||||
} else {
|
||||
"*".repeat(adjust.len() - 2)
|
||||
"*".repeat(adjust.len().saturating_sub(2))
|
||||
}
|
||||
};
|
||||
if matches!(usage.kind, IterUsageKind::Next) {
|
||||
|
|
|
@ -33,9 +33,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
|
|||
} else if !found_mapping && !mutates_arg {
|
||||
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
|
||||
match cx.typeck_results().expr_ty(&body.value).kind() {
|
||||
ty::Adt(adt, subst)
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) =>
|
||||
{
|
||||
ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && in_ty == subst.type_at(0) => {
|
||||
"filter"
|
||||
},
|
||||
_ => return,
|
||||
|
|
|
@ -20,8 +20,8 @@ use rustc_span::symbol::sym;
|
|||
use clippy_utils::consts::{constant, Constant};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{
|
||||
expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
|
||||
last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq,
|
||||
get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
|
||||
last_path_segment, match_any_def_paths, path_def_id, paths, unsext, SpanlessEq,
|
||||
};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -583,8 +583,7 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
|
|||
)
|
||||
},
|
||||
ExprKind::Call(path, [arg]) => {
|
||||
if expr_path_res(cx, path)
|
||||
.opt_def_id()
|
||||
if path_def_id(cx, path)
|
||||
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
|
||||
.is_some()
|
||||
{
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
use clippy_utils::attrs::is_doc_hidden;
|
||||
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_lint::{LateContext, LateLintPass, LintContext};
|
||||
use rustc_middle::ty;
|
||||
|
@ -57,6 +58,20 @@ impl MissingDoc {
|
|||
*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(
|
||||
&self,
|
||||
cx: &LateContext<'_>,
|
||||
|
@ -80,7 +95,9 @@ impl MissingDoc {
|
|||
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 {
|
||||
span_lint(
|
||||
cx,
|
||||
|
|
|
@ -58,7 +58,7 @@ impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]);
|
|||
impl LateLintPass<'_> for ImportRename {
|
||||
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
||||
for Rename { path, rename } in &self.conf_renames {
|
||||
if let Res::Def(_, id) = clippy_utils::path_to_res(cx, &path.split("::").collect::<Vec<_>>()) {
|
||||
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &path.split("::").collect::<Vec<_>>()) {
|
||||
self.renames.insert(id, Symbol::intern(rename));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,10 +92,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
|
|||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::If(..) => {
|
||||
self.found = true;
|
||||
return;
|
||||
},
|
||||
ExprKind::Path(_) => {
|
||||
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
|
||||
if adj
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::expr_sig;
|
||||
use clippy_utils::{
|
||||
expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths,
|
||||
};
|
||||
use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
|
@ -153,7 +151,9 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
|
||||
sig.decl.inputs,
|
||||
&[],
|
||||
) {
|
||||
)
|
||||
.filter(|arg| arg.mutability() == Mutability::Not)
|
||||
{
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
PTR_ARG,
|
||||
|
@ -170,10 +170,10 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||
let hir = cx.tcx.hir();
|
||||
let mut parents = hir.parent_iter(body.value.hir_id);
|
||||
let (item_id, decl) = match parents.next() {
|
||||
let (item_id, decl, is_trait_item) = match parents.next() {
|
||||
Some((_, Node::Item(i))) => {
|
||||
if let ItemKind::Fn(sig, ..) = &i.kind {
|
||||
(i.def_id, sig.decl)
|
||||
(i.def_id, sig.decl, false)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -185,14 +185,14 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
return;
|
||||
}
|
||||
if let ImplItemKind::Fn(sig, _) = &i.kind {
|
||||
(i.def_id, sig.decl)
|
||||
(i.def_id, sig.decl, false)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
Some((_, Node::TraitItem(i))) => {
|
||||
if let TraitItemKind::Fn(sig, _) = &i.kind {
|
||||
(i.def_id, sig.decl)
|
||||
(i.def_id, sig.decl, true)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
@ -202,7 +202,9 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
|
|||
|
||||
check_mut_from_ref(cx, decl);
|
||||
let sig = cx.tcx.fn_sig(item_id).skip_binder();
|
||||
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params).collect();
|
||||
let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
|
||||
.filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
|
||||
.collect();
|
||||
let results = check_ptr_arg_usage(cx, body, &lint_args);
|
||||
|
||||
for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
|
||||
|
@ -318,6 +320,10 @@ impl PtrArg<'_> {
|
|||
self.deref_ty.argless_str(),
|
||||
)
|
||||
}
|
||||
|
||||
fn mutability(&self) -> Mutability {
|
||||
self.ref_prefix.mutability
|
||||
}
|
||||
}
|
||||
|
||||
struct RefPrefix {
|
||||
|
@ -641,7 +647,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args:
|
|||
},
|
||||
_ => {
|
||||
skip_count += 1;
|
||||
results[arg.idx].skip = true;
|
||||
results[i].skip = true;
|
||||
None
|
||||
},
|
||||
}
|
||||
|
@ -665,8 +671,8 @@ fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutabil
|
|||
|
||||
fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
|
||||
if let ExprKind::Call(pathexp, []) = expr.kind {
|
||||
expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
|
||||
match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()
|
||||
path_def_id(cx, pathexp).map_or(false, |id| {
|
||||
matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))
|
||||
})
|
||||
} else {
|
||||
false
|
||||
|
|
|
@ -2,19 +2,18 @@ use clippy_utils::consts::{constant, Constant};
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, single_segment_path};
|
||||
use clippy_utils::{get_parent_expr, in_constant, is_integer_const, meets_msrv, msrvs, path_to_local};
|
||||
use clippy_utils::{higher, SpanlessEq};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::ast::RangeLimits;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath};
|
||||
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, PathSegment, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_semver::RustcVersion;
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::source_map::{Span, Spanned};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::symbol::Ident;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -220,12 +219,12 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'
|
|||
_ => return,
|
||||
};
|
||||
// value, name, order (higher/lower), inclusiveness
|
||||
if let (Some((lval, lname, name_span, lval_span, lord, linc)), Some((rval, rname, _, rval_span, rord, rinc))) =
|
||||
if let (Some((lval, lid, name_span, lval_span, lord, linc)), Some((rval, rid, _, rval_span, rord, rinc))) =
|
||||
(check_range_bounds(cx, l), check_range_bounds(cx, r))
|
||||
{
|
||||
// we only lint comparisons on the same name and with different
|
||||
// direction
|
||||
if lname != rname || lord == rord {
|
||||
if lid != rid || lord == rord {
|
||||
return;
|
||||
}
|
||||
let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval);
|
||||
|
@ -293,7 +292,7 @@ fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'
|
|||
}
|
||||
}
|
||||
|
||||
fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, Ident, Span, Span, Ordering, bool)> {
|
||||
fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, HirId, Span, Span, Ordering, bool)> {
|
||||
if let ExprKind::Binary(ref op, l, r) = ex.kind {
|
||||
let (inclusive, ordering) = match op.node {
|
||||
BinOpKind::Gt => (false, Ordering::Greater),
|
||||
|
@ -302,11 +301,11 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant,
|
|||
BinOpKind::Le => (true, Ordering::Less),
|
||||
_ => return None,
|
||||
};
|
||||
if let Some(id) = match_ident(l) {
|
||||
if let Some(id) = path_to_local(l) {
|
||||
if let Some((c, _)) = constant(cx, cx.typeck_results(), r) {
|
||||
return Some((c, id, l.span, r.span, ordering, inclusive));
|
||||
}
|
||||
} else if let Some(id) = match_ident(r) {
|
||||
} else if let Some(id) = path_to_local(r) {
|
||||
if let Some((c, _)) = constant(cx, cx.typeck_results(), l) {
|
||||
return Some((c, id, r.span, l.span, ordering.reverse(), inclusive));
|
||||
}
|
||||
|
@ -315,17 +314,6 @@ fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant,
|
|||
None
|
||||
}
|
||||
|
||||
fn match_ident(e: &Expr<'_>) -> Option<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) {
|
||||
if_chain! {
|
||||
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`
|
||||
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 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 {
|
||||
span_lint(cx,
|
||||
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::sugg::Sugg;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{can_mut_borrow_both, differing_macro_contexts, eq_expr_value, std_or_core};
|
||||
use clippy_utils::{can_mut_borrow_both, eq_expr_value, std_or_core};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
|
||||
|
@ -172,7 +172,7 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
|
|||
if_chain! {
|
||||
if let StmtKind::Semi(first) = w[0].kind;
|
||||
if let StmtKind::Semi(second) = w[1].kind;
|
||||
if !differing_macro_contexts(first.span, second.span);
|
||||
if first.span.ctxt() == second.span.ctxt();
|
||||
if let ExprKind::Assign(lhs0, rhs0, _) = first.kind;
|
||||
if let ExprKind::Assign(lhs1, rhs1, _) = second.kind;
|
||||
if eq_expr_value(cx, lhs0, rhs1);
|
||||
|
|
|
@ -7,6 +7,7 @@ mod transmute_num_to_bytes;
|
|||
mod transmute_ptr_to_ptr;
|
||||
mod transmute_ptr_to_ref;
|
||||
mod transmute_ref_to_ref;
|
||||
mod transmute_undefined_repr;
|
||||
mod transmutes_expressible_as_ptr_casts;
|
||||
mod unsound_collection_transmute;
|
||||
mod useless_transmute;
|
||||
|
@ -355,6 +356,30 @@ declare_clippy_lint! {
|
|||
"transmute between collections of layout-incompatible types"
|
||||
}
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for transmutes either to or from a type which does not have a defined representation.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The results of such a transmute are not defined.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```rust
|
||||
/// struct Foo<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 => [
|
||||
CROSSPOINTER_TRANSMUTE,
|
||||
TRANSMUTE_PTR_TO_REF,
|
||||
|
@ -369,13 +394,13 @@ declare_lint_pass!(Transmute => [
|
|||
TRANSMUTE_NUM_TO_BYTES,
|
||||
UNSOUND_COLLECTION_TRANSMUTE,
|
||||
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
|
||||
TRANSMUTE_UNDEFINED_REPR,
|
||||
]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Transmute {
|
||||
#[allow(clippy::similar_names, clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
if let ExprKind::Call(path_expr, args) = e.kind;
|
||||
if let ExprKind::Call(path_expr, [arg]) = e.kind;
|
||||
if let ExprKind::Path(ref qpath) = path_expr.kind;
|
||||
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::transmute, def_id);
|
||||
|
@ -385,28 +410,31 @@ impl<'tcx> LateLintPass<'tcx> for Transmute {
|
|||
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
|
||||
let const_context = in_constant(cx, e.hir_id);
|
||||
|
||||
let from_ty = cx.typeck_results().expr_ty(&args[0]);
|
||||
let from_ty = cx.typeck_results().expr_ty(arg);
|
||||
let to_ty = cx.typeck_results().expr_ty(e);
|
||||
|
||||
// If useless_transmute is triggered, the other lints can be skipped.
|
||||
if useless_transmute::check(cx, e, from_ty, to_ty, args) {
|
||||
if useless_transmute::check(cx, e, from_ty, to_ty, arg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty);
|
||||
linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty);
|
||||
linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath);
|
||||
linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args);
|
||||
linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args);
|
||||
linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args);
|
||||
linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= transmute_num_to_bytes::check(cx, e, from_ty, to_ty, args, const_context);
|
||||
linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty);
|
||||
let linted = wrong_transmute::check(cx, e, from_ty, to_ty)
|
||||
| crosspointer_transmute::check(cx, e, from_ty, to_ty)
|
||||
| transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, qpath)
|
||||
| transmute_int_to_char::check(cx, e, from_ty, to_ty, arg)
|
||||
| transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg)
|
||||
| transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg)
|
||||
| transmute_int_to_float::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_float_to_int::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| transmute_num_to_bytes::check(cx, e, from_ty, to_ty, arg, const_context)
|
||||
| (
|
||||
unsound_collection_transmute::check(cx, e, from_ty, to_ty)
|
||||
|| transmute_undefined_repr::check(cx, e, from_ty, to_ty)
|
||||
);
|
||||
|
||||
if !linted {
|
||||
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args);
|
||||
transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
mut arg: &'tcx Expr<'_>,
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
|
@ -26,37 +26,36 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||
|diag| {
|
||||
let mut expr = &args[0];
|
||||
let mut arg = sugg::Sugg::hir(cx, expr, "..");
|
||||
let mut sugg = sugg::Sugg::hir(cx, arg, "..");
|
||||
|
||||
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
|
||||
expr = inner_expr;
|
||||
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &arg.kind {
|
||||
arg = inner_expr;
|
||||
}
|
||||
|
||||
if_chain! {
|
||||
// if the expression is a float literal and it is unsuffixed then
|
||||
// add a suffix so the suggestion is valid and unambiguous
|
||||
if let ExprKind::Lit(lit) = &expr.kind;
|
||||
if let ExprKind::Lit(lit) = &arg.kind;
|
||||
if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node;
|
||||
then {
|
||||
let op = format!("{}{}", arg, float_ty.name_str()).into();
|
||||
match arg {
|
||||
sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op),
|
||||
_ => arg = sugg::Sugg::NonParen(op)
|
||||
let op = format!("{}{}", sugg, float_ty.name_str()).into();
|
||||
match sugg {
|
||||
sugg::Sugg::MaybeParen(_) => sugg = sugg::Sugg::MaybeParen(op),
|
||||
_ => sugg = sugg::Sugg::NonParen(op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into());
|
||||
sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_par()).into());
|
||||
|
||||
// cast the result of `to_bits` if `to_ty` is signed
|
||||
arg = if let ty::Int(int_ty) = to_ty.kind() {
|
||||
arg.as_ty(int_ty.name_str().to_string())
|
||||
sugg = if let ty::Int(int_ty) = to_ty.kind() {
|
||||
sugg.as_ty(int_ty.name_str().to_string())
|
||||
} else {
|
||||
arg
|
||||
sugg
|
||||
};
|
||||
|
||||
diag.span_suggestion(e.span, "consider using", arg.to_string(), Applicability::Unspecified);
|
||||
diag.span_suggestion(e.span, "consider using", sugg.to_string(), Applicability::Unspecified);
|
||||
},
|
||||
);
|
||||
true
|
||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => {
|
||||
|
@ -25,7 +25,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
&format!("transmute from a `{}` to a `bool`", from_ty),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
let zero = sugg::Sugg::NonParen(Cow::from("0"));
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
|
|
|
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => {
|
||||
|
@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
&format!("transmute from a `{}` to a `char`", from_ty),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
let arg = if let ty::Int(_) = from_ty.kind() {
|
||||
arg.as_ty(ast::UintTy::U32.name_str())
|
||||
} else {
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
|
@ -24,7 +24,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
let arg = if let ty::Int(int_ty) = from_ty.kind() {
|
||||
arg.as_ty(format!(
|
||||
"u{}",
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
|
@ -33,7 +33,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
&format!("transmute from a `{}` to a `{}`", from_ty, to_ty),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"consider using `to_ne_bytes()`",
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::RawPtr(_), ty::RawPtr(to_ty)) => {
|
||||
|
@ -23,7 +23,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
"transmute from a pointer to a pointer",
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty));
|
||||
diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
qpath: &'tcx QPath<'_>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
|
@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
|
|||
from_ty, to_ty
|
||||
),
|
||||
|diag| {
|
||||
let arg = sugg::Sugg::hir(cx, &args[0], "..");
|
||||
let arg = sugg::Sugg::hir(cx, arg, "..");
|
||||
let (deref, cast) = if *mutbl == Mutability::Mut {
|
||||
("&mut *", "*mut")
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
const_context: bool,
|
||||
) -> bool {
|
||||
let mut triggered = false;
|
||||
|
@ -41,7 +41,7 @@ pub(super) fn check<'tcx>(
|
|||
format!(
|
||||
"std::str::from_utf8{}({}).unwrap()",
|
||||
postfix,
|
||||
snippet(cx, args[0].span, ".."),
|
||||
snippet(cx, arg.span, ".."),
|
||||
),
|
||||
Applicability::Unspecified,
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ pub(super) fn check<'tcx>(
|
|||
TRANSMUTE_PTR_TO_PTR,
|
||||
e.span,
|
||||
"transmute from a reference to a reference",
|
||||
|diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|
||||
|diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
let ty_from_and_mut = ty::TypeAndMut {
|
||||
ty: ty_from,
|
||||
mutbl: *from_mutbl
|
||||
|
|
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<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) {
|
||||
span_lint_and_then(
|
||||
|
@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(
|
|||
from_ty, to_ty
|
||||
),
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
let sugg = arg.as_ty(&to_ty.to_string()).to_string();
|
||||
diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable);
|
||||
}
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
use super::utils::is_layout_incompatible;
|
||||
use super::UNSOUND_COLLECTION_TRANSMUTE;
|
||||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::match_any_diagnostic_items;
|
||||
use rustc_hir::Expr;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_span::symbol::{sym, Symbol};
|
||||
|
||||
// used to check for UNSOUND_COLLECTION_TRANSMUTE
|
||||
static COLLECTIONS: &[Symbol] = &[
|
||||
sym::Vec,
|
||||
sym::VecDeque,
|
||||
sym::BinaryHeap,
|
||||
sym::BTreeSet,
|
||||
sym::BTreeMap,
|
||||
sym::HashSet,
|
||||
sym::HashMap,
|
||||
];
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
/// Checks for `unsound_collection_transmute` lint.
|
||||
/// Returns `true` if it's triggered, otherwise returns `false`.
|
||||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
(ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
|
||||
if from_adt.did != to_adt.did || match_any_diagnostic_items(cx, to_adt.did, COLLECTIONS).is_none() {
|
||||
if from_adt.did != to_adt.did {
|
||||
return false;
|
||||
}
|
||||
if !matches!(
|
||||
cx.tcx.get_diagnostic_name(to_adt.did),
|
||||
Some(
|
||||
sym::BTreeMap
|
||||
| sym::BTreeSet
|
||||
| sym::BinaryHeap
|
||||
| sym::HashMap
|
||||
| sym::HashSet
|
||||
| sym::Vec
|
||||
| sym::VecDeque
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if from_substs
|
||||
|
|
|
@ -13,7 +13,7 @@ pub(super) fn check<'tcx>(
|
|||
e: &'tcx Expr<'_>,
|
||||
from_ty: Ty<'tcx>,
|
||||
to_ty: Ty<'tcx>,
|
||||
args: &'tcx [Expr<'_>],
|
||||
arg: &'tcx Expr<'_>,
|
||||
) -> bool {
|
||||
match (&from_ty.kind(), &to_ty.kind()) {
|
||||
_ if from_ty == to_ty => {
|
||||
|
@ -32,7 +32,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
"transmute from a reference to a pointer",
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
let rty_and_mut = ty::TypeAndMut {
|
||||
ty: rty,
|
||||
mutbl: *rty_mutbl,
|
||||
|
@ -57,7 +57,7 @@ pub(super) fn check<'tcx>(
|
|||
e.span,
|
||||
"transmute from an integer to a pointer",
|
||||
|diag| {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) {
|
||||
if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) {
|
||||
diag.span_suggestion(
|
||||
e.span,
|
||||
"try",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_ty_param_diagnostic_item;
|
||||
use clippy_utils::{path_def_id, qpath_generic_tys};
|
||||
use rustc_hir::{self as hir, def_id::DefId, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{sym, Symbol};
|
||||
|
||||
use super::BOX_COLLECTION;
|
||||
|
||||
|
@ -11,10 +11,9 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
if Some(def_id) == cx.tcx.lang_items().owned_box();
|
||||
if let Some(item_type) = get_std_collection(cx, qpath);
|
||||
then {
|
||||
let generic = if item_type == "String" {
|
||||
""
|
||||
} else {
|
||||
"<..>"
|
||||
let generic = match item_type {
|
||||
sym::String => "",
|
||||
_ => "<..>",
|
||||
};
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
@ -37,14 +36,10 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
}
|
||||
}
|
||||
|
||||
fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> {
|
||||
if is_ty_param_diagnostic_item(cx, qpath, sym::Vec).is_some() {
|
||||
Some("Vec")
|
||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() {
|
||||
Some("String")
|
||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::HashMap).is_some() {
|
||||
Some("HashMap")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Symbol> {
|
||||
let param = qpath_generic_tys(qpath).next()?;
|
||||
let id = path_def_id(cx, param)?;
|
||||
cx.tcx
|
||||
.get_diagnostic_name(id)
|
||||
.filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use clippy_utils::diagnostics::span_lint;
|
||||
use clippy_utils::is_ty_param_diagnostic_item;
|
||||
use clippy_utils::{path_def_id, qpath_generic_tys};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{self as hir, def_id::DefId, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
@ -7,16 +8,21 @@ use rustc_span::symbol::sym;
|
|||
use super::OPTION_OPTION;
|
||||
|
||||
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_ty_param_diagnostic_item(cx, qpath, sym::Option).is_some() {
|
||||
span_lint(
|
||||
cx,
|
||||
OPTION_OPTION,
|
||||
hir_ty.span,
|
||||
"consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
|
||||
enum if you need to distinguish all 3 cases",
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
if_chain! {
|
||||
if cx.tcx.is_diagnostic_item(sym::Option, def_id);
|
||||
if let Some(arg) = qpath_generic_tys(qpath).next();
|
||||
if path_def_id(cx, arg) == Some(def_id);
|
||||
then {
|
||||
span_lint(
|
||||
cx,
|
||||
OPTION_OPTION,
|
||||
hir_ty.span,
|
||||
"consider using `Option<T>` instead of `Option<Option<T>>` or a custom \
|
||||
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::source::snippet_with_applicability;
|
||||
use clippy_utils::{get_qpath_generic_tys, is_ty_param_diagnostic_item};
|
||||
use clippy_utils::{path_def_id, qpath_generic_tys};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -20,12 +20,17 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
format!("Rc<{}>", alternate),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Vec) {
|
||||
} else {
|
||||
let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
|
||||
let Some(id) = path_def_id(cx, ty) else { return false };
|
||||
if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
|
||||
return false;
|
||||
}
|
||||
let qpath = match &ty.kind {
|
||||
TyKind::Path(qpath) => qpath,
|
||||
_ => return false,
|
||||
};
|
||||
let inner_span = match get_qpath_generic_tys(qpath).next() {
|
||||
let inner_span = match qpath_generic_tys(qpath).next() {
|
||||
Some(ty) => ty.span,
|
||||
None => return false,
|
||||
};
|
||||
|
@ -55,12 +60,16 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
format!("Arc<{}>", alternate),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Vec) {
|
||||
} else if let Some(ty) = qpath_generic_tys(qpath).next() {
|
||||
let Some(id) = path_def_id(cx, ty) else { return false };
|
||||
if !cx.tcx.is_diagnostic_item(sym::Vec, id) {
|
||||
return false;
|
||||
}
|
||||
let qpath = match &ty.kind {
|
||||
TyKind::Path(qpath) => qpath,
|
||||
_ => return false,
|
||||
};
|
||||
let inner_span = match get_qpath_generic_tys(qpath).next() {
|
||||
let inner_span = match qpath_generic_tys(qpath).next() {
|
||||
Some(ty) => ty.span,
|
||||
None => return false,
|
||||
};
|
||||
|
@ -85,13 +94,13 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
}
|
||||
|
||||
fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> {
|
||||
if is_ty_param_diagnostic_item(cx, qpath, sym::String).is_some() {
|
||||
Some("str")
|
||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::OsString).is_some() {
|
||||
Some("std::ffi::OsStr")
|
||||
} else if is_ty_param_diagnostic_item(cx, qpath, sym::PathBuf).is_some() {
|
||||
Some("std::path::Path")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let ty = qpath_generic_tys(qpath).next()?;
|
||||
let id = path_def_id(cx, ty)?;
|
||||
let path = match cx.tcx.get_diagnostic_name(id)? {
|
||||
sym::String => "str",
|
||||
sym::OsString => "std::ffi::OsStr",
|
||||
sym::PathBuf => "std::path::Path",
|
||||
_ => return None,
|
||||
};
|
||||
Some(path)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_help;
|
||||
use clippy_utils::is_ty_param_diagnostic_item;
|
||||
use clippy_utils::{path_def_id, qpath_generic_tys};
|
||||
use if_chain::if_chain;
|
||||
use rustc_hir::{self as hir, def_id::DefId, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
|
@ -10,7 +10,9 @@ use super::RC_MUTEX;
|
|||
pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool {
|
||||
if_chain! {
|
||||
if cx.tcx.is_diagnostic_item(sym::Rc, def_id) ;
|
||||
if let Some(_) = is_ty_param_diagnostic_item(cx, qpath, sym::Mutex) ;
|
||||
if let Some(arg) = qpath_generic_tys(qpath).next();
|
||||
if let Some(id) = path_def_id(cx, arg);
|
||||
if cx.tcx.is_diagnostic_item(sym::Mutex, id);
|
||||
then {
|
||||
span_lint_and_help(
|
||||
cx,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::source::{snippet, snippet_with_applicability};
|
||||
use clippy_utils::{get_qpath_generic_tys, is_ty_param_diagnostic_item, is_ty_param_lang_item};
|
||||
use clippy_utils::{path_def_id, qpath_generic_tys};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{self as hir, def_id::DefId, LangItem, QPath, TyKind};
|
||||
use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::symbol::sym;
|
||||
|
||||
|
@ -39,21 +39,20 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_
|
|||
return true;
|
||||
}
|
||||
|
||||
let (inner_sym, ty) = if let Some(ty) = is_ty_param_lang_item(cx, qpath, LangItem::OwnedBox) {
|
||||
("Box", ty)
|
||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Rc) {
|
||||
("Rc", ty)
|
||||
} else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Arc) {
|
||||
("Arc", ty)
|
||||
} else {
|
||||
return false;
|
||||
let Some(ty) = qpath_generic_tys(qpath).next() else { return false };
|
||||
let Some(id) = path_def_id(cx, ty) else { return false };
|
||||
let (inner_sym, ty) = match cx.tcx.get_diagnostic_name(id) {
|
||||
Some(sym::Arc) => ("Arc", ty),
|
||||
Some(sym::Rc) => ("Rc", ty),
|
||||
_ if Some(id) == cx.tcx.lang_items().owned_box() => ("Box", ty),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let inner_qpath = match &ty.kind {
|
||||
TyKind::Path(inner_qpath) => inner_qpath,
|
||||
_ => return false,
|
||||
};
|
||||
let inner_span = match get_qpath_generic_tys(inner_qpath).next() {
|
||||
let inner_span = match qpath_generic_tys(inner_qpath).next() {
|
||||
Some(ty) => {
|
||||
// Box<Box<dyn T>> is smaller than Box<dyn T> because of wide pointers
|
||||
if matches!(ty.kind, TyKind::TraitObject(..)) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::higher;
|
||||
use clippy_utils::ty::is_type_diagnostic_item;
|
||||
use clippy_utils::{differing_macro_contexts, path_to_local, usage::is_potentially_mutated};
|
||||
use clippy_utils::{path_to_local, usage::is_potentially_mutated};
|
||||
use if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, Visitor};
|
||||
|
@ -238,8 +238,9 @@ impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> {
|
|||
if let Some(unwrappable) = self.unwrappables.iter()
|
||||
.find(|u| u.local_id == id);
|
||||
// Span contexts should not differ with the conditional branch
|
||||
if !differing_macro_contexts(unwrappable.branch.span, expr.span);
|
||||
if !differing_macro_contexts(unwrappable.branch.span, unwrappable.check.span);
|
||||
let span_ctxt = expr.span.ctxt();
|
||||
if unwrappable.branch.span.ctxt() == span_ctxt;
|
||||
if unwrappable.check.span.ctxt() == span_ctxt;
|
||||
then {
|
||||
if call_to_unwrap == unwrappable.safe_to_unwrap {
|
||||
let is_entire_condition = unwrappable.is_entire_condition;
|
||||
|
|
|
@ -322,6 +322,9 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
|||
let mut current = env::var_os("CLIPPY_CONF_DIR")
|
||||
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
|
||||
.map_or_else(|| PathBuf::from("."), PathBuf::from);
|
||||
|
||||
let mut found_config: Option<PathBuf> = None;
|
||||
|
||||
loop {
|
||||
for config_file_name in &CONFIG_FILE_NAMES {
|
||||
if let Ok(config_file) = current.join(config_file_name).canonicalize() {
|
||||
|
@ -329,11 +332,26 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
|
|||
Err(e) if e.kind() == io::ErrorKind::NotFound => {},
|
||||
Err(e) => return Err(e),
|
||||
Ok(md) if md.is_dir() => {},
|
||||
Ok(_) => return Ok(Some(config_file)),
|
||||
Ok(_) => {
|
||||
// warn if we happen to find two config files #8323
|
||||
if let Some(ref found_config_) = found_config {
|
||||
eprintln!(
|
||||
"Using config file `{}`\nWarning: `{}` will be ignored.",
|
||||
found_config_.display(),
|
||||
config_file.display(),
|
||||
);
|
||||
} else {
|
||||
found_config = Some(config_file);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found_config.is_some() {
|
||||
return Ok(found_config);
|
||||
}
|
||||
|
||||
// If the current directory has no parent, we're done searching.
|
||||
if !current.pop() {
|
||||
return Ok(None);
|
||||
|
|
|
@ -4,8 +4,8 @@ use clippy_utils::macros::root_macro_call_first_node;
|
|||
use clippy_utils::source::snippet;
|
||||
use clippy_utils::ty::match_type;
|
||||
use clippy_utils::{
|
||||
higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path, method_calls,
|
||||
path_to_res, paths, peel_blocks_with_stmt, SpanlessEq,
|
||||
def_path_res, higher, is_else_clause, is_expn_of, is_expr_path_def_path, is_lint_allowed, match_def_path,
|
||||
method_calls, paths, peel_blocks_with_stmt, SpanlessEq,
|
||||
};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast as ast;
|
||||
|
@ -844,7 +844,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
|
|||
// Extract the path to the matched type
|
||||
if let Some(segments) = path_to_matched_type(cx, ty_path);
|
||||
let segments: Vec<&str> = segments.iter().map(Symbol::as_str).collect();
|
||||
if let Some(ty_did) = path_to_res(cx, &segments[..]).opt_def_id();
|
||||
if let Some(ty_did) = def_path_res(cx, &segments[..]).opt_def_id();
|
||||
// Check if the matched type is a diagnostic item
|
||||
if let Some(item_name) = cx.tcx.get_diagnostic_name(ty_did);
|
||||
then {
|
||||
|
@ -917,7 +917,7 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve
|
|||
// 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.
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -999,7 +999,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
|
|||
}
|
||||
|
||||
for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] {
|
||||
if let Some(def_id) = path_to_res(cx, module).opt_def_id() {
|
||||
if let Some(def_id) = def_path_res(cx, module).opt_def_id() {
|
||||
for item in cx.tcx.module_children(def_id).iter() {
|
||||
if_chain! {
|
||||
if let Res::Def(DefKind::Const, item_def_id) = item.res;
|
||||
|
|
|
@ -612,8 +612,8 @@ fn get_lint_group_and_level_or_lint(
|
|||
}
|
||||
|
||||
fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
|
||||
for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
|
||||
if IGNORED_LINT_GROUPS.contains(group_name) {
|
||||
for (group_name, lints, _) in cx.lint_store.get_lint_groups() {
|
||||
if IGNORED_LINT_GROUPS.contains(&group_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -331,17 +331,16 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
|
|||
let def_path: Vec<&str> = def_path.iter().take(4).map(Symbol::as_str).collect();
|
||||
if let ["core", "num", int_impl, "max_value"] = *def_path;
|
||||
then {
|
||||
let value = match int_impl {
|
||||
"<impl i8>" => i8::MAX as u128,
|
||||
"<impl i16>" => i16::MAX as u128,
|
||||
"<impl i32>" => i32::MAX as u128,
|
||||
"<impl i64>" => i64::MAX as u128,
|
||||
"<impl i128>" => i128::MAX as u128,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Constant::Int(value))
|
||||
}
|
||||
else {
|
||||
let value = match int_impl {
|
||||
"<impl i8>" => i8::MAX as u128,
|
||||
"<impl i16>" => i16::MAX as u128,
|
||||
"<impl i32>" => i32::MAX as u128,
|
||||
"<impl i64>" => i64::MAX as u128,
|
||||
"<impl i128>" => i128::MAX as u128,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Constant::Int(value))
|
||||
} else {
|
||||
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 {
|
||||
// `vec![elem; size]` case
|
||||
Some(VecArgs::Repeat(&args[0], &args[1]))
|
||||
}
|
||||
else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
|
||||
} else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
|
||||
// `vec![a, b, c]` case
|
||||
if_chain! {
|
||||
if let hir::ExprKind::Box(boxed) = args[0].kind;
|
||||
|
@ -296,11 +295,9 @@ impl<'a> VecArgs<'a> {
|
|||
}
|
||||
|
||||
None
|
||||
}
|
||||
else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
|
||||
} else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
|
||||
Some(VecArgs::Vec(&[]))
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
|
@ -456,7 +453,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
|
|||
if let ExprKind::Lit(lit) = &arg.kind;
|
||||
if let LitKind::Int(num, _) = lit.node;
|
||||
then {
|
||||
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
|
||||
return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?));
|
||||
}
|
||||
}
|
||||
return Some(VecInitKind::WithExprCapacity(arg.hir_id));
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::consts::{constant_context, constant_simple};
|
||||
use crate::differing_macro_contexts;
|
||||
use crate::source::snippet_opt;
|
||||
use rustc_ast::ast::InlineAsmTemplatePiece;
|
||||
use rustc_data_structures::fx::FxHasher;
|
||||
|
@ -186,7 +185,7 @@ impl HirEqInterExpr<'_, '_, '_> {
|
|||
|
||||
#[allow(clippy::similar_names)]
|
||||
pub fn eq_expr(&mut self, left: &Expr<'_>, right: &Expr<'_>) -> bool {
|
||||
if !self.inner.allow_side_effects && differing_macro_contexts(left.span, right.span) {
|
||||
if !self.inner.allow_side_effects && left.span.ctxt() != right.span.ctxt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -77,9 +77,9 @@ use rustc_hir::itemlikevisit::ItemLikeVisitor;
|
|||
use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk};
|
||||
use rustc_hir::{
|
||||
def, lang_items, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr,
|
||||
ExprKind, FnDecl, ForeignItem, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem,
|
||||
Local, MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind,
|
||||
Target, TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
|
||||
ExprKind, FnDecl, ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local,
|
||||
MatchSource, Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, Target,
|
||||
TraitItem, TraitItemKind, TraitRef, TyKind, UnOp,
|
||||
};
|
||||
use rustc_lint::{LateContext, Level, Lint, LintContext};
|
||||
use rustc_middle::hir::place::PlaceBase;
|
||||
|
@ -134,13 +134,6 @@ macro_rules! extract_msrv_attr {
|
|||
};
|
||||
}
|
||||
|
||||
/// Returns `true` if the two spans come from differing expansions (i.e., one is
|
||||
/// from a macro and one isn't).
|
||||
#[must_use]
|
||||
pub fn differing_macro_contexts(lhs: Span, rhs: Span) -> bool {
|
||||
rhs.ctxt() != lhs.ctxt()
|
||||
}
|
||||
|
||||
/// If the given expression is a local binding, find the initializer expression.
|
||||
/// If that initializer expression is another local binding, find its initializer again.
|
||||
/// This process repeats as long as possible (but usually no more than once). Initializer
|
||||
|
@ -262,44 +255,6 @@ pub fn is_wild(pat: &Pat<'_>) -> bool {
|
|||
matches!(pat.kind, PatKind::Wild)
|
||||
}
|
||||
|
||||
/// Checks if the first type parameter is a lang item.
|
||||
pub fn is_ty_param_lang_item<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
qpath: &QPath<'tcx>,
|
||||
item: LangItem,
|
||||
) -> Option<&'tcx hir::Ty<'tcx>> {
|
||||
let ty = get_qpath_generic_tys(qpath).next()?;
|
||||
|
||||
if let TyKind::Path(qpath) = &ty.kind {
|
||||
cx.qpath_res(qpath, ty.hir_id)
|
||||
.opt_def_id()
|
||||
.map_or(false, |id| {
|
||||
cx.tcx.lang_items().require(item).map_or(false, |lang_id| id == lang_id)
|
||||
})
|
||||
.then(|| ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the first type parameter is a diagnostic item.
|
||||
pub fn is_ty_param_diagnostic_item<'tcx>(
|
||||
cx: &LateContext<'_>,
|
||||
qpath: &QPath<'tcx>,
|
||||
item: Symbol,
|
||||
) -> Option<&'tcx hir::Ty<'tcx>> {
|
||||
let ty = get_qpath_generic_tys(qpath).next()?;
|
||||
|
||||
if let TyKind::Path(qpath) = &ty.kind {
|
||||
cx.qpath_res(qpath, ty.hir_id)
|
||||
.opt_def_id()
|
||||
.map_or(false, |id| cx.tcx.is_diagnostic_item(item, id))
|
||||
.then(|| ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the method call given in `expr` belongs to the given trait.
|
||||
/// This is a deprecated function, consider using [`is_trait_method`].
|
||||
pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str]) -> bool {
|
||||
|
@ -360,35 +315,17 @@ pub fn last_path_segment<'tcx>(path: &QPath<'tcx>) -> &'tcx PathSegment<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_qpath_generics<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx GenericArgs<'tcx>> {
|
||||
match path {
|
||||
QPath::Resolved(_, p) => p.segments.last().and_then(|s| s.args),
|
||||
QPath::TypeRelative(_, s) => s.args,
|
||||
QPath::LangItem(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_qpath_generic_tys<'tcx>(path: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
|
||||
get_qpath_generics(path)
|
||||
.map_or([].as_ref(), |a| a.args)
|
||||
pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tcx hir::Ty<'tcx>> {
|
||||
last_path_segment(qpath)
|
||||
.args
|
||||
.map_or(&[][..], |a| a.args)
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
if let hir::GenericArg::Type(ty) = a {
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.filter_map(|a| match a {
|
||||
hir::GenericArg::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn single_segment_path<'tcx>(path: &QPath<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
|
||||
match *path {
|
||||
QPath::Resolved(_, path) => path.segments.get(0),
|
||||
QPath::TypeRelative(_, seg) => Some(seg),
|
||||
QPath::LangItem(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
|
||||
/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
|
||||
/// `QPath::Resolved.1.res.opt_def_id()`.
|
||||
|
@ -420,37 +357,17 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// If the expression is a path, resolve it. Otherwise, return `Res::Err`.
|
||||
pub fn expr_path_res(cx: &LateContext<'_>, expr: &Expr<'_>) -> Res {
|
||||
if let ExprKind::Path(p) = &expr.kind {
|
||||
cx.qpath_res(p, expr.hir_id)
|
||||
} else {
|
||||
Res::Err
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the path to a `DefId` and checks if it matches the given path.
|
||||
pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool {
|
||||
cx.qpath_res(path, hir_id)
|
||||
.opt_def_id()
|
||||
.map_or(false, |id| match_def_path(cx, id, segments))
|
||||
}
|
||||
|
||||
/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
|
||||
///
|
||||
/// Please use `is_expr_diagnostic_item` if the target is a diagnostic item.
|
||||
pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
|
||||
expr_path_res(cx, expr)
|
||||
.opt_def_id()
|
||||
.map_or(false, |id| match_def_path(cx, id, segments))
|
||||
path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, segments))
|
||||
}
|
||||
|
||||
/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given
|
||||
/// diagnostic item.
|
||||
pub fn is_expr_diagnostic_item(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
|
||||
expr_path_res(cx, expr)
|
||||
.opt_def_id()
|
||||
.map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
|
||||
path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(diag_item, id))
|
||||
}
|
||||
|
||||
/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
|
||||
|
@ -497,8 +414,46 @@ pub fn path_to_local_id(expr: &Expr<'_>, id: HirId) -> bool {
|
|||
path_to_local(expr) == Some(id)
|
||||
}
|
||||
|
||||
/// Gets the definition associated to a path.
|
||||
pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
|
||||
pub trait MaybePath<'hir> {
|
||||
fn hir_id(&self) -> HirId;
|
||||
fn qpath_opt(&self) -> Option<&QPath<'hir>>;
|
||||
}
|
||||
|
||||
macro_rules! maybe_path {
|
||||
($ty:ident, $kind:ident) => {
|
||||
impl<'hir> MaybePath<'hir> for hir::$ty<'hir> {
|
||||
fn hir_id(&self) -> HirId {
|
||||
self.hir_id
|
||||
}
|
||||
fn qpath_opt(&self) -> Option<&QPath<'hir>> {
|
||||
match &self.kind {
|
||||
hir::$kind::Path(qpath) => Some(qpath),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
maybe_path!(Expr, ExprKind);
|
||||
maybe_path!(Pat, PatKind);
|
||||
maybe_path!(Ty, TyKind);
|
||||
|
||||
/// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err`
|
||||
pub fn path_res<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Res {
|
||||
match maybe_path.qpath_opt() {
|
||||
None => Res::Err,
|
||||
Some(qpath) => cx.qpath_res(qpath, maybe_path.hir_id()),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `maybe_path` is a path node which resolves to an item, retrieves the item ID
|
||||
pub fn path_def_id<'tcx>(cx: &LateContext<'_>, maybe_path: &impl MaybePath<'tcx>) -> Option<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 {
|
||||
($e:expr) => {
|
||||
match $e {
|
||||
|
@ -574,7 +529,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Res {
|
|||
/// Convenience function to get the `DefId` of a trait by path.
|
||||
/// It could be a trait or trait alias.
|
||||
pub fn get_trait_def_id(cx: &LateContext<'_>, path: &[&str]) -> Option<DefId> {
|
||||
match path_to_res(cx, path) {
|
||||
match def_path_res(cx, path) {
|
||||
Res::Def(DefKind::Trait | DefKind::TraitAlias, trait_id) => Some(trait_id),
|
||||
_ => None,
|
||||
}
|
||||
|
@ -603,7 +558,9 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, def_id: LocalDefId) ->
|
|||
if parent_impl != CRATE_DEF_ID;
|
||||
if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent_impl);
|
||||
if let hir::ItemKind::Impl(impl_) = &item.kind;
|
||||
then { return impl_.of_trait.as_ref(); }
|
||||
then {
|
||||
return impl_.of_trait.as_ref();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -713,12 +670,7 @@ pub fn is_default_equivalent_call(cx: &LateContext<'_>, repl_func: &Expr<'_>) ->
|
|||
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
|
||||
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|
||||
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
|
||||
then {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
then { true } else { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1553,8 +1505,7 @@ pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tc
|
|||
if arms.len() == 2;
|
||||
if arms[0].guard.is_none();
|
||||
if arms[1].guard.is_none();
|
||||
if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) ||
|
||||
(is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
|
||||
if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) || (is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
|
||||
then {
|
||||
return Some(expr);
|
||||
}
|
||||
|
@ -1644,7 +1595,7 @@ pub fn match_function_call<'tcx>(
|
|||
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
|
||||
if match_def_path(cx, fun_def_id, path);
|
||||
then {
|
||||
return Some(args)
|
||||
return Some(args);
|
||||
}
|
||||
};
|
||||
None
|
||||
|
@ -1653,7 +1604,7 @@ pub fn match_function_call<'tcx>(
|
|||
/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
|
||||
/// any.
|
||||
///
|
||||
/// Please use `match_any_diagnostic_items` if the targets are all diagnostic items.
|
||||
/// Please use `tcx.get_diagnostic_name` if the targets are all diagnostic items.
|
||||
pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
|
||||
let search_path = cx.get_def_path(did);
|
||||
paths
|
||||
|
@ -1661,14 +1612,6 @@ pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]])
|
|||
.position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
|
||||
}
|
||||
|
||||
/// Checks if the given `DefId` matches any of provided diagnostic items. Returns the index of
|
||||
/// matching path, if any.
|
||||
pub fn match_any_diagnostic_items(cx: &LateContext<'_>, def_id: DefId, diag_items: &[Symbol]) -> Option<usize> {
|
||||
diag_items
|
||||
.iter()
|
||||
.position(|item| cx.tcx.is_diagnostic_item(*item, def_id))
|
||||
}
|
||||
|
||||
/// Checks if the given `DefId` matches the path.
|
||||
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
|
||||
// We should probably move to Symbols in Clippy as well rather than interning every time.
|
||||
|
@ -1821,8 +1764,7 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
|
|||
|
||||
match expr.kind {
|
||||
ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
|
||||
ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY),
|
||||
_ => false,
|
||||
_ => path_def_id(cx, expr).map_or(false, |id| match_def_path(cx, id, &paths::CONVERT_IDENTITY)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2174,7 +2116,7 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn(
|
|||
|
||||
/// Checks if the function containing the given `HirId` is a `#[test]` function
|
||||
///
|
||||
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
|
||||
/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
|
||||
pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
||||
with_test_item_names(tcx, tcx.parent_module(id), |names| {
|
||||
tcx.hir()
|
||||
|
@ -2197,7 +2139,7 @@ pub fn is_in_test_function(tcx: TyCtxt<'_>, id: hir::HirId) -> bool {
|
|||
/// Checks whether item either has `test` attribute applied, or
|
||||
/// is a module with `test` in its name.
|
||||
///
|
||||
/// Note: If you use this function, please add a `#[test]` case in `tests/ui_test`.
|
||||
/// Note: Add `// compile-flags: --test` to UI tests with a `#[test]` function
|
||||
pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
|
||||
is_in_test_function(tcx, item.hir_id())
|
||||
|| matches!(item.kind, ItemKind::Mod(..))
|
||||
|
|
|
@ -9,7 +9,7 @@ use rustc_hir::intravisit::Visitor;
|
|||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_span::def_id::DefId;
|
||||
use rustc_span::hygiene::{MacroKind, SyntaxContext};
|
||||
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
|
||||
use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
|
@ -306,6 +306,7 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
|
|||
}
|
||||
|
||||
/// A parsed `format_args!` expansion
|
||||
#[derive(Debug)]
|
||||
pub struct FormatArgsExpn<'tcx> {
|
||||
/// Span of the first argument, the format string
|
||||
pub format_string_span: Span,
|
||||
|
@ -462,7 +463,11 @@ impl<'tcx> FormatArgsExpn<'tcx> {
|
|||
if let Ok(i) = usize::try_from(position);
|
||||
if let Some(&(j, format_trait)) = self.formatters.get(i);
|
||||
then {
|
||||
Some(FormatArgsArg { value: self.value_args[j], format_trait, spec: Some(spec) })
|
||||
Some(FormatArgsArg {
|
||||
value: self.value_args[j],
|
||||
format_trait,
|
||||
spec: Some(spec),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -471,11 +476,13 @@ impl<'tcx> FormatArgsExpn<'tcx> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Span of all inputs
|
||||
/// Source callsite span of all inputs
|
||||
pub fn inputs_span(&self) -> Span {
|
||||
match *self.value_args {
|
||||
[] => self.format_string_span,
|
||||
[.., last] => self.format_string_span.to(last.span),
|
||||
[.., last] => self
|
||||
.format_string_span
|
||||
.to(hygiene::walk_chain(last.span, self.format_string_span.ctxt())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use rustc_trait_selection::infer::InferCtxtExt;
|
|||
use rustc_trait_selection::traits::query::normalize::AtExt;
|
||||
use std::iter;
|
||||
|
||||
use crate::{expr_path_res, match_def_path, must_use_attr};
|
||||
use crate::{match_def_path, must_use_attr, path_res};
|
||||
|
||||
// Checks if the given type implements copy.
|
||||
pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
|
||||
|
@ -443,7 +443,7 @@ impl<'tcx> ExprFnSig<'tcx> {
|
|||
|
||||
/// If the expression is function like, get the signature for it.
|
||||
pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<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)))
|
||||
} else {
|
||||
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);
|
||||
```
|
||||
|
||||
- `differing_macro_contexts()`: returns true if the two given spans are not from the same context
|
||||
- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it
|
||||
|
||||
One thing `SpanContext` is useful for is to check if two spans are in the same context. For example,
|
||||
in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some
|
||||
expression with a different context from `a`.
|
||||
|
||||
```rust
|
||||
macro_rules! m {
|
||||
|
@ -252,7 +256,7 @@ Use the following functions to deal with macros:
|
|||
// These spans are not from the same context
|
||||
// x.is_some() is from inside the macro
|
||||
// x.unwrap() is from outside the macro
|
||||
assert_eq!(differing_macro_contexts(x_is_some_span, x_unwrap_span), true);
|
||||
assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt());
|
||||
```
|
||||
|
||||
[TyS]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyS.html
|
||||
|
|
|
@ -11,6 +11,7 @@ use std::env::{self, remove_var, set_var, var_os};
|
|||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::lazy::SyncLazy;
|
||||
use std::path::{Path, PathBuf};
|
||||
use test_utils::IS_RUSTC_TEST_SUITE;
|
||||
|
||||
|
@ -64,11 +65,11 @@ extern crate tokio;
|
|||
/// dependencies must be added to Cargo.toml at the project root. Test
|
||||
/// dependencies that are not *directly* used by this test module require an
|
||||
/// `extern crate` declaration.
|
||||
fn extern_flags() -> String {
|
||||
static EXTERN_FLAGS: SyncLazy<String> = SyncLazy::new(|| {
|
||||
let current_exe_depinfo = {
|
||||
let mut path = env::current_exe().unwrap();
|
||||
path.set_extension("d");
|
||||
std::fs::read_to_string(path).unwrap()
|
||||
fs::read_to_string(path).unwrap()
|
||||
};
|
||||
let mut crates: HashMap<&str, &str> = HashMap::with_capacity(TEST_DEPENDENCIES.len());
|
||||
for line in current_exe_depinfo.lines() {
|
||||
|
@ -112,16 +113,17 @@ fn extern_flags() -> String {
|
|||
.into_iter()
|
||||
.map(|(name, path)| format!(" --extern {}={}", name, path))
|
||||
.collect()
|
||||
}
|
||||
});
|
||||
|
||||
fn default_config() -> compiletest::Config {
|
||||
fn base_config(test_dir: &str) -> compiletest::Config {
|
||||
let mut config = compiletest::Config {
|
||||
edition: Some("2021".into()),
|
||||
mode: TestMode::Ui,
|
||||
..compiletest::Config::default()
|
||||
};
|
||||
|
||||
if let Ok(filters) = env::var("TESTNAME") {
|
||||
config.filters = filters.split(',').map(std::string::ToString::to_string).collect();
|
||||
config.filters = filters.split(',').map(ToString::to_string).collect();
|
||||
}
|
||||
|
||||
if let Some(path) = option_env!("RUSTC_LIB_PATH") {
|
||||
|
@ -129,7 +131,7 @@ fn default_config() -> compiletest::Config {
|
|||
config.run_lib_path = path.clone();
|
||||
config.compile_lib_path = path;
|
||||
}
|
||||
let current_exe_path = std::env::current_exe().unwrap();
|
||||
let current_exe_path = env::current_exe().unwrap();
|
||||
let deps_path = current_exe_path.parent().unwrap();
|
||||
let profile_path = deps_path.parent().unwrap();
|
||||
|
||||
|
@ -143,10 +145,11 @@ fn default_config() -> compiletest::Config {
|
|||
"--emit=metadata -Dwarnings -Zui-testing -L dependency={}{}{}",
|
||||
deps_path.display(),
|
||||
host_libs,
|
||||
extern_flags(),
|
||||
&*EXTERN_FLAGS,
|
||||
));
|
||||
|
||||
config.build_base = profile_path.join("test");
|
||||
config.src_base = Path::new("tests").join(test_dir);
|
||||
config.build_base = profile_path.join("test").join(test_dir);
|
||||
config.rustc_path = profile_path.join(if cfg!(windows) {
|
||||
"clippy-driver.exe"
|
||||
} else {
|
||||
|
@ -155,38 +158,23 @@ fn default_config() -> compiletest::Config {
|
|||
config
|
||||
}
|
||||
|
||||
fn run_ui(cfg: &mut compiletest::Config) {
|
||||
cfg.mode = TestMode::Ui;
|
||||
cfg.src_base = Path::new("tests").join("ui");
|
||||
fn run_ui() {
|
||||
let config = base_config("ui");
|
||||
// use tests/clippy.toml
|
||||
let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
|
||||
compiletest::run_tests(cfg);
|
||||
let _g = VarGuard::set("CARGO_MANIFEST_DIR", fs::canonicalize("tests").unwrap());
|
||||
compiletest::run_tests(&config);
|
||||
}
|
||||
|
||||
fn run_ui_test(cfg: &mut compiletest::Config) {
|
||||
cfg.mode = TestMode::Ui;
|
||||
cfg.src_base = Path::new("tests").join("ui_test");
|
||||
let _g = VarGuard::set("CARGO_MANIFEST_DIR", std::fs::canonicalize("tests").unwrap());
|
||||
let rustcflags = cfg.target_rustcflags.get_or_insert_with(Default::default);
|
||||
let len = rustcflags.len();
|
||||
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) {
|
||||
fn run_internal_tests() {
|
||||
// only run internal tests with the internal-tests feature
|
||||
if !RUN_INTERNAL_TESTS {
|
||||
return;
|
||||
}
|
||||
cfg.mode = TestMode::Ui;
|
||||
cfg.src_base = Path::new("tests").join("ui-internal");
|
||||
compiletest::run_tests(cfg);
|
||||
let config = base_config("ui-internal");
|
||||
compiletest::run_tests(&config);
|
||||
}
|
||||
|
||||
fn run_ui_toml(config: &mut compiletest::Config) {
|
||||
fn run_ui_toml() {
|
||||
fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
|
||||
let mut result = true;
|
||||
let opts = compiletest::test_opts(config);
|
||||
|
@ -222,12 +210,12 @@ fn run_ui_toml(config: &mut compiletest::Config) {
|
|||
Ok(result)
|
||||
}
|
||||
|
||||
config.mode = TestMode::Ui;
|
||||
config.src_base = Path::new("tests").join("ui-toml").canonicalize().unwrap();
|
||||
let mut config = base_config("ui-toml");
|
||||
config.src_base = config.src_base.canonicalize().unwrap();
|
||||
|
||||
let tests = compiletest::make_tests(config);
|
||||
let tests = compiletest::make_tests(&config);
|
||||
|
||||
let res = run_tests(config, tests);
|
||||
let res = run_tests(&config, tests);
|
||||
match res {
|
||||
Ok(true) => {},
|
||||
Ok(false) => panic!("Some tests failed"),
|
||||
|
@ -237,7 +225,7 @@ fn run_ui_toml(config: &mut compiletest::Config) {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_ui_cargo(config: &mut compiletest::Config) {
|
||||
fn run_ui_cargo() {
|
||||
fn run_tests(
|
||||
config: &compiletest::Config,
|
||||
filters: &[String],
|
||||
|
@ -310,13 +298,13 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
|
|||
return;
|
||||
}
|
||||
|
||||
config.mode = TestMode::Ui;
|
||||
config.src_base = Path::new("tests").join("ui-cargo").canonicalize().unwrap();
|
||||
let mut config = base_config("ui-cargo");
|
||||
config.src_base = config.src_base.canonicalize().unwrap();
|
||||
|
||||
let tests = compiletest::make_tests(config);
|
||||
let tests = compiletest::make_tests(&config);
|
||||
|
||||
let current_dir = env::current_dir().unwrap();
|
||||
let res = run_tests(config, &config.filters, tests);
|
||||
let res = run_tests(&config, &config.filters, tests);
|
||||
env::set_current_dir(current_dir).unwrap();
|
||||
|
||||
match res {
|
||||
|
@ -331,12 +319,10 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
|
|||
#[test]
|
||||
fn compile_test() {
|
||||
set_var("CLIPPY_DISABLE_DOCS_LINKS", "true");
|
||||
let mut config = default_config();
|
||||
run_ui(&mut config);
|
||||
run_ui_test(&mut config);
|
||||
run_ui_toml(&mut config);
|
||||
run_ui_cargo(&mut config);
|
||||
run_internal_tests(&mut config);
|
||||
run_ui();
|
||||
run_ui_toml();
|
||||
run_ui_cargo();
|
||||
run_internal_tests();
|
||||
}
|
||||
|
||||
/// Restores an env var on drop
|
||||
|
|
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
|
||||
|
||||
#![warn(clippy::unsound_collection_transmute)]
|
||||
#![allow(clippy::transmute_undefined_repr)]
|
||||
|
||||
trait Trait {
|
||||
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() {
|
||||
// simple values and comparisons
|
||||
1 == 1;
|
||||
"no" == "no";
|
||||
let _ = 1 == 1;
|
||||
let _ = "no" == "no";
|
||||
// even though I agree that no means no ;-)
|
||||
false != false;
|
||||
1.5 < 1.5;
|
||||
1u64 >= 1u64;
|
||||
let _ = false != false;
|
||||
let _ = 1.5 < 1.5;
|
||||
let _ = 1u64 >= 1u64;
|
||||
|
||||
// casts, methods, parentheses
|
||||
(1 as u64) & (1 as u64);
|
||||
1 ^ ((((((1))))));
|
||||
let _ = (1u32 as u64) & (1u32 as u64);
|
||||
#[rustfmt::skip]
|
||||
{
|
||||
let _ = 1 ^ ((((((1))))));
|
||||
};
|
||||
|
||||
// unary and binary operators
|
||||
(-(2) < -(2));
|
||||
((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
(1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
||||
let _ = (-(2) < -(2));
|
||||
let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
||||
|
||||
// various other things
|
||||
([1] != [1]);
|
||||
((1, 2) != (1, 2));
|
||||
vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros
|
||||
let _ = ([1] != [1]);
|
||||
let _ = ((1, 2) != (1, 2));
|
||||
let _ = vec![1, 2, 3] == vec![1, 2, 3]; //no error yet, as we don't match macros
|
||||
|
||||
// const folding
|
||||
1 + 1 == 2;
|
||||
1 - 1 == 0;
|
||||
let _ = 1 + 1 == 2;
|
||||
let _ = 1 - 1 == 0;
|
||||
|
||||
1 - 1;
|
||||
1 / 1;
|
||||
true && true;
|
||||
|
||||
true || true;
|
||||
let _ = 1 - 1;
|
||||
let _ = 1 / 1;
|
||||
let _ = true && true;
|
||||
|
||||
let _ = true || true;
|
||||
|
||||
let a: u32 = 0;
|
||||
let b: u32 = 0;
|
||||
|
||||
a == b && b == a;
|
||||
a != b && b != a;
|
||||
a < b && b > a;
|
||||
a <= b && b >= a;
|
||||
let _ = a == b && b == a;
|
||||
let _ = a != b && b != a;
|
||||
let _ = a < b && b > a;
|
||||
let _ = a <= b && b >= a;
|
||||
|
||||
let mut a = vec![1];
|
||||
a == a;
|
||||
2*a.len() == 2*a.len(); // ok, functions
|
||||
a.pop() == a.pop(); // ok, functions
|
||||
let _ = a == a;
|
||||
let _ = 2 * a.len() == 2 * a.len(); // ok, functions
|
||||
let _ = a.pop() == a.pop(); // ok, functions
|
||||
|
||||
check_ignore_macro();
|
||||
|
||||
|
@ -63,15 +61,14 @@ fn main() {
|
|||
const D: u32 = A / A;
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
macro_rules! check_if_named_foo {
|
||||
($expression:expr) => (
|
||||
($expression:expr) => {
|
||||
if stringify!($expression) == "foo" {
|
||||
println!("foo!");
|
||||
} else {
|
||||
println!("not foo.");
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! bool_macro {
|
||||
|
@ -80,11 +77,10 @@ macro_rules! bool_macro {
|
|||
};
|
||||
}
|
||||
|
||||
#[allow(clippy::short_circuit_statement)]
|
||||
fn check_ignore_macro() {
|
||||
check_if_named_foo!(foo);
|
||||
// checks if the lint ignores macros with `!` operator
|
||||
!bool_macro!(1) && !bool_macro!("");
|
||||
let _ = !bool_macro!(1) && !bool_macro!("");
|
||||
}
|
||||
|
||||
struct Nested {
|
||||
|
@ -95,3 +91,18 @@ fn check_nested(n1: &Nested, n2: &Nested) -> bool {
|
|||
// `n2.inner.0.0` mistyped as `n1.inner.0.0`
|
||||
(n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_op_shouldnt_trigger_in_tests() {
|
||||
let a = 1;
|
||||
let result = a + 1 == 1 + a;
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq_op_macros_shouldnt_trigger_in_tests() {
|
||||
let a = 1;
|
||||
let b = 2;
|
||||
assert_eq!(a, a);
|
||||
assert_eq!(a + b, b + a);
|
||||
}
|
||||
|
|
|
@ -1,174 +1,172 @@
|
|||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:12:5
|
||||
--> $DIR/eq_op.rs:8:13
|
||||
|
|
||||
LL | 1 == 1;
|
||||
| ^^^^^^
|
||||
LL | let _ = 1 == 1;
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: `-D clippy::eq-op` implied by `-D warnings`
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:13:5
|
||||
--> $DIR/eq_op.rs:9:13
|
||||
|
|
||||
LL | "no" == "no";
|
||||
| ^^^^^^^^^^^^
|
||||
LL | let _ = "no" == "no";
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `!=`
|
||||
--> $DIR/eq_op.rs:15:5
|
||||
--> $DIR/eq_op.rs:11:13
|
||||
|
|
||||
LL | false != false;
|
||||
| ^^^^^^^^^^^^^^
|
||||
LL | let _ = false != false;
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `<`
|
||||
--> $DIR/eq_op.rs:16:5
|
||||
--> $DIR/eq_op.rs:12:13
|
||||
|
|
||||
LL | 1.5 < 1.5;
|
||||
| ^^^^^^^^^
|
||||
LL | let _ = 1.5 < 1.5;
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `>=`
|
||||
--> $DIR/eq_op.rs:17:5
|
||||
--> $DIR/eq_op.rs:13:13
|
||||
|
|
||||
LL | 1u64 >= 1u64;
|
||||
| ^^^^^^^^^^^^
|
||||
LL | let _ = 1u64 >= 1u64;
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&`
|
||||
--> $DIR/eq_op.rs:20:5
|
||||
--> $DIR/eq_op.rs:16:13
|
||||
|
|
||||
LL | (1 as u64) & (1 as u64);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = (1u32 as u64) & (1u32 as u64);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `^`
|
||||
--> $DIR/eq_op.rs:21:5
|
||||
--> $DIR/eq_op.rs:19:17
|
||||
|
|
||||
LL | 1 ^ ((((((1))))));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = 1 ^ ((((((1))))));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `<`
|
||||
--> $DIR/eq_op.rs:24:5
|
||||
--> $DIR/eq_op.rs:23:13
|
||||
|
|
||||
LL | (-(2) < -(2));
|
||||
| ^^^^^^^^^^^^^
|
||||
LL | let _ = (-(2) < -(2));
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:25:5
|
||||
--> $DIR/eq_op.rs:24:13
|
||||
|
|
||||
LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&`
|
||||
--> $DIR/eq_op.rs:25:6
|
||||
--> $DIR/eq_op.rs:24:14
|
||||
|
|
||||
LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&`
|
||||
--> $DIR/eq_op.rs:25:27
|
||||
--> $DIR/eq_op.rs:24:35
|
||||
|
|
||||
LL | ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = ((1 + 1) & (1 + 1) == (1 + 1) & (1 + 1));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:26:5
|
||||
--> $DIR/eq_op.rs:25:13
|
||||
|
|
||||
LL | (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = (1 * 2) + (3 * 4) == 1 * 2 + 3 * 4;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `!=`
|
||||
--> $DIR/eq_op.rs:29:5
|
||||
--> $DIR/eq_op.rs:28:13
|
||||
|
|
||||
LL | ([1] != [1]);
|
||||
| ^^^^^^^^^^^^
|
||||
LL | let _ = ([1] != [1]);
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `!=`
|
||||
--> $DIR/eq_op.rs:30:5
|
||||
--> $DIR/eq_op.rs:29:13
|
||||
|
|
||||
LL | ((1, 2) != (1, 2));
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
LL | let _ = ((1, 2) != (1, 2));
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:34:5
|
||||
--> $DIR/eq_op.rs:33:13
|
||||
|
|
||||
LL | 1 + 1 == 2;
|
||||
| ^^^^^^^^^^
|
||||
LL | let _ = 1 + 1 == 2;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:35:5
|
||||
--> $DIR/eq_op.rs:34:13
|
||||
|
|
||||
LL | 1 - 1 == 0;
|
||||
| ^^^^^^^^^^
|
||||
LL | let _ = 1 - 1 == 0;
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `-`
|
||||
--> $DIR/eq_op.rs:35:5
|
||||
--> $DIR/eq_op.rs:34:13
|
||||
|
|
||||
LL | 1 - 1 == 0;
|
||||
| ^^^^^
|
||||
LL | let _ = 1 - 1 == 0;
|
||||
| ^^^^^
|
||||
|
||||
error: equal expressions as operands to `-`
|
||||
--> $DIR/eq_op.rs:37:5
|
||||
--> $DIR/eq_op.rs:36:13
|
||||
|
|
||||
LL | 1 - 1;
|
||||
| ^^^^^
|
||||
LL | let _ = 1 - 1;
|
||||
| ^^^^^
|
||||
|
||||
error: equal expressions as operands to `/`
|
||||
--> $DIR/eq_op.rs:38:5
|
||||
--> $DIR/eq_op.rs:37:13
|
||||
|
|
||||
LL | 1 / 1;
|
||||
| ^^^^^
|
||||
LL | let _ = 1 / 1;
|
||||
| ^^^^^
|
||||
|
||||
error: equal expressions as operands to `&&`
|
||||
--> $DIR/eq_op.rs:39:5
|
||||
--> $DIR/eq_op.rs:38:13
|
||||
|
|
||||
LL | true && true;
|
||||
| ^^^^^^^^^^^^
|
||||
LL | let _ = true && true;
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `||`
|
||||
--> $DIR/eq_op.rs:41:5
|
||||
--> $DIR/eq_op.rs:40:13
|
||||
|
|
||||
LL | true || true;
|
||||
| ^^^^^^^^^^^^
|
||||
LL | let _ = true || true;
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&&`
|
||||
--> $DIR/eq_op.rs:47:5
|
||||
--> $DIR/eq_op.rs:45:13
|
||||
|
|
||||
LL | a == b && b == a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
LL | let _ = a == b && b == a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&&`
|
||||
--> $DIR/eq_op.rs:48:5
|
||||
--> $DIR/eq_op.rs:46:13
|
||||
|
|
||||
LL | a != b && b != a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
LL | let _ = a != b && b != a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&&`
|
||||
--> $DIR/eq_op.rs:49:5
|
||||
--> $DIR/eq_op.rs:47:13
|
||||
|
|
||||
LL | a < b && b > a;
|
||||
| ^^^^^^^^^^^^^^
|
||||
LL | let _ = a < b && b > a;
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `&&`
|
||||
--> $DIR/eq_op.rs:50:5
|
||||
--> $DIR/eq_op.rs:48:13
|
||||
|
|
||||
LL | a <= b && b >= a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
LL | let _ = a <= b && b >= a;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:53:5
|
||||
--> $DIR/eq_op.rs:51:13
|
||||
|
|
||||
LL | a == a;
|
||||
| ^^^^^^
|
||||
LL | let _ = a == a;
|
||||
| ^^^^^^
|
||||
|
||||
error: equal expressions as operands to `/`
|
||||
--> $DIR/eq_op.rs:63:20
|
||||
--> $DIR/eq_op.rs:61:20
|
||||
|
|
||||
LL | const D: u32 = A / A;
|
||||
| ^^^^^
|
||||
|
||||
error: equal expressions as operands to `==`
|
||||
--> $DIR/eq_op.rs:96:5
|
||||
--> $DIR/eq_op.rs:92:5
|
||||
|
|
||||
LL | (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[deny(clippy::eq_op)]` on by default
|
||||
|
||||
error: aborting due to 28 previous errors
|
||||
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
|
||||
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
||||
|
||||
macro_rules! one {
|
||||
() => {
|
||||
1
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
struct Foo;
|
||||
|
||||
|
@ -31,6 +37,9 @@ fn main() {
|
|||
let with_none_and_as_str: Option<i32> = None;
|
||||
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(());
|
||||
with_ok.expect("error");
|
||||
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
|
||||
/// Checks implementation of the `EXPECT_FUN_CALL` lint
|
||||
|
||||
macro_rules! one {
|
||||
() => {
|
||||
1
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
struct Foo;
|
||||
|
||||
|
@ -31,6 +37,9 @@ fn main() {
|
|||
let with_none_and_as_str: Option<i32> = None;
|
||||
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(());
|
||||
with_ok.expect("error");
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:29:26
|
||||
--> $DIR/expect_fun_call.rs:35:26
|
||||
|
|
||||
LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
|
||||
|
@ -7,70 +7,76 @@ LL | with_none_and_format.expect(&format!("Error {}: fake error", error_code
|
|||
= note: `-D clippy::expect-fun-call` implied by `-D warnings`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:32:26
|
||||
--> $DIR/expect_fun_call.rs:38:26
|
||||
|
|
||||
LL | with_none_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", error_code))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:42:25
|
||||
--> $DIR/expect_fun_call.rs:41:37
|
||||
|
|
||||
LL | with_none_and_format_with_macro.expect(format!("Error {}: fake error", one!()).as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("Error {}: fake error", one!()))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:51:25
|
||||
|
|
||||
LL | with_err_and_format.expect(&format!("Error {}: fake error", error_code));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:45:25
|
||||
--> $DIR/expect_fun_call.rs:54:25
|
||||
|
|
||||
LL | with_err_and_as_str.expect(format!("Error {}: fake error", error_code).as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| panic!("Error {}: fake error", error_code))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:57:17
|
||||
--> $DIR/expect_fun_call.rs:66:17
|
||||
|
|
||||
LL | Some("foo").expect(format!("{} {}", 1, 2).as_ref());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{} {}", 1, 2))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:78:21
|
||||
--> $DIR/expect_fun_call.rs:87:21
|
||||
|
|
||||
LL | Some("foo").expect(&get_string());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:79:21
|
||||
--> $DIR/expect_fun_call.rs:88:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_ref());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:80:21
|
||||
--> $DIR/expect_fun_call.rs:89:21
|
||||
|
|
||||
LL | Some("foo").expect(get_string().as_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_string()) })`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:82:21
|
||||
--> $DIR/expect_fun_call.rs:91:21
|
||||
|
|
||||
LL | Some("foo").expect(get_static_str());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_static_str()) })`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:83:21
|
||||
--> $DIR/expect_fun_call.rs:92:21
|
||||
|
|
||||
LL | Some("foo").expect(get_non_static_str(&0));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| { panic!("{}", get_non_static_str(&0).to_string()) })`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:87:16
|
||||
--> $DIR/expect_fun_call.rs:96:16
|
||||
|
|
||||
LL | Some(true).expect(&format!("key {}, {}", 1, 2));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("key {}, {}", 1, 2))`
|
||||
|
||||
error: use of `expect` followed by a function call
|
||||
--> $DIR/expect_fun_call.rs:93:17
|
||||
--> $DIR/expect_fun_call.rs:102:17
|
||||
|
|
||||
LL | opt_ref.expect(&format!("{:?}", opt_ref));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| panic!("{:?}", opt_ref))`
|
||||
|
||||
error: aborting due to 12 previous errors
|
||||
error: aborting due to 13 previous errors
|
||||
|
||||
|
|
|
@ -46,13 +46,13 @@ error: the variable `idx_usize` is used as a loop counter
|
|||
--> $DIR/explicit_counter_loop.rs:170:9
|
||||
|
|
||||
LL | for _item in slice {
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.into_iter().enumerate()`
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_usize, _item) in slice.iter().enumerate()`
|
||||
|
||||
error: the variable `idx_u32` is used as a loop counter
|
||||
--> $DIR/explicit_counter_loop.rs:182:9
|
||||
|
|
||||
LL | for _item in slice {
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.into_iter())`
|
||||
| ^^^^^^^^^^^^^^^^^^ help: consider using: `for (idx_u32, _item) in (0_u32..).zip(slice.iter())`
|
||||
|
|
||||
= note: `idx_u32` is of type `u32`, making it ineligible for `Iterator::enumerate`
|
||||
|
||||
|
|
|
@ -10,6 +10,12 @@ fn stderr() -> String {
|
|||
String::new()
|
||||
}
|
||||
|
||||
macro_rules! one {
|
||||
() => {
|
||||
1
|
||||
};
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// these should warn
|
||||
{
|
||||
|
@ -24,6 +30,12 @@ fn main() {
|
|||
// including newlines
|
||||
println!("test\ntest");
|
||||
eprintln!("test\ntest");
|
||||
|
||||
let value = 1;
|
||||
eprintln!("with {}", value);
|
||||
eprintln!("with {} {}", 2, value);
|
||||
eprintln!("with {value}");
|
||||
eprintln!("macro arg {}", one!());
|
||||
}
|
||||
// these should not warn, different destination
|
||||
{
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue