1
Fork 0

Merge commit 'b40ea209e7' into clippyup

This commit is contained in:
flip1995 2021-04-08 17:50:13 +02:00
parent cde58f7174
commit f6d1f368db
349 changed files with 10420 additions and 6013 deletions

View file

@ -6,11 +6,146 @@ document.
## Unreleased / In Rust Nightly ## Unreleased / In Rust Nightly
[3e41797...master](https://github.com/rust-lang/rust-clippy/compare/3e41797...master) [6ed6f1e...master](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...master)
## Rust 1.52
Current beta, release 2021-05-06
[3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e)
### New Lints
* [`from_str_radix_10`]
[#6717](https://github.com/rust-lang/rust-clippy/pull/6717)
* [`implicit_clone`]
[#6730](https://github.com/rust-lang/rust-clippy/pull/6730)
* [`semicolon_if_nothing_returned`]
[#6681](https://github.com/rust-lang/rust-clippy/pull/6681)
* [`manual_flatten`]
[#6646](https://github.com/rust-lang/rust-clippy/pull/6646)
* [`inconsistent_struct_constructor`]
[#6769](https://github.com/rust-lang/rust-clippy/pull/6769)
* [`iter_count`]
[#6791](https://github.com/rust-lang/rust-clippy/pull/6791)
* [`default_numeric_fallback`]
[#6662](https://github.com/rust-lang/rust-clippy/pull/6662)
* [`bytes_nth`]
[#6695](https://github.com/rust-lang/rust-clippy/pull/6695)
* [`filter_map_identity`]
[#6685](https://github.com/rust-lang/rust-clippy/pull/6685)
* [`manual_map`]
[#6573](https://github.com/rust-lang/rust-clippy/pull/6573)
### Moves and Deprecations
* Moved [`upper_case_acronyms`] to `pedantic`
[#6775](https://github.com/rust-lang/rust-clippy/pull/6775)
* Moved [`manual_map`] to `nursery`
[#6796](https://github.com/rust-lang/rust-clippy/pull/6796)
* Moved [`unnecessary_wraps`] to `pedantic`
[#6765](https://github.com/rust-lang/rust-clippy/pull/6765)
* Moved [`trivial_regex`] to `nursery`
[#6696](https://github.com/rust-lang/rust-clippy/pull/6696)
* Moved [`naive_bytecount`] to `pedantic`
[#6825](https://github.com/rust-lang/rust-clippy/pull/6825)
* Moved [`upper_case_acronyms`] to `style`
[#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
* Moved [`manual_map`] to `style`
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
### Enhancements
* [`disallowed_method`]: Now supports functions in addition to methods
[#6674](https://github.com/rust-lang/rust-clippy/pull/6674)
* [`upper_case_acronyms`]: Added a new configuration `upper-case-acronyms-aggressive` to
trigger the lint if there is more than one uppercase character next to each other
[#6788](https://github.com/rust-lang/rust-clippy/pull/6788)
* [`collapsible_match`]: Now supports block comparison with different value names
[#6754](https://github.com/rust-lang/rust-clippy/pull/6754)
* [`unnecessary_wraps`]: Will now suggest removing unnecessary wrapped return unit type, like `Option<()>`
[#6665](https://github.com/rust-lang/rust-clippy/pull/6665)
* Improved value usage detection in closures
[#6698](https://github.com/rust-lang/rust-clippy/pull/6698)
### False Positive Fixes
* [`use_self`]: No longer lints in macros
[#6833](https://github.com/rust-lang/rust-clippy/pull/6833)
* [`use_self`]: Fixed multiple false positives for: generics, associated types and derive implementations
[#6179](https://github.com/rust-lang/rust-clippy/pull/6179)
* [`missing_inline_in_public_items`]: No longer lints for procedural macros
[#6814](https://github.com/rust-lang/rust-clippy/pull/6814)
* [`inherent_to_string`]: No longer lints on functions with function generics
[#6771](https://github.com/rust-lang/rust-clippy/pull/6771)
* [`doc_markdown`]: Add `OpenDNS` to the default configuration as an allowed identifier
[#6783](https://github.com/rust-lang/rust-clippy/pull/6783)
* [`missing_panics_doc`]: No longer lints on [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html)
[#6700](https://github.com/rust-lang/rust-clippy/pull/6700)
* [`collapsible_if`]: No longer lints on if statements with attributes
[#6701](https://github.com/rust-lang/rust-clippy/pull/6701)
* [`match_same_arms`]: Only considers empty blocks as equal if the tokens contained are the same
[#6843](https://github.com/rust-lang/rust-clippy/pull/6843)
* [`redundant_closure`]: Now ignores macros
[#6871](https://github.com/rust-lang/rust-clippy/pull/6871)
* [`manual_map`]: Fixed false positives when control flow statements like `return`, `break` etc. are used
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
* [`vec_init_then_push`]: Fixed false positives for loops and if statements
[#6697](https://github.com/rust-lang/rust-clippy/pull/6697)
* [`len_without_is_empty`]: Will now consider multiple impl blocks and `#[allow]` on
the `len` method as well as the type definition.
[#6853](https://github.com/rust-lang/rust-clippy/pull/6853)
* [`let_underscore_drop`]: Only lints on types which implement `Drop`
[#6682](https://github.com/rust-lang/rust-clippy/pull/6682)
* [`unit_arg`]: No longer lints on unit arguments when they come from a path expression.
[#6601](https://github.com/rust-lang/rust-clippy/pull/6601)
* [`cargo_common_metadata`]: No longer lints if
[`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)
is defined in the manifest
[#6650](https://github.com/rust-lang/rust-clippy/pull/6650)
### Suggestion Fixes/Improvements
* [`collapsible_match`]: Fixed lint message capitalization
[#6766](https://github.com/rust-lang/rust-clippy/pull/6766)
* [`or_fun_call`]: Improved suggestions for `or_insert(vec![])`
[#6790](https://github.com/rust-lang/rust-clippy/pull/6790)
* [`manual_map`]: No longer expands macros in the suggestions
[#6801](https://github.com/rust-lang/rust-clippy/pull/6801)
* Aligned Clippy's lint messages with the rustc dev guide
[#6787](https://github.com/rust-lang/rust-clippy/pull/6787)
### ICE Fixes
* [`zero_sized_map_values`]
[#6866](https://github.com/rust-lang/rust-clippy/pull/6866)
### Documentation Improvements
* [`useless_format`]: Improved the documentation example
[#6854](https://github.com/rust-lang/rust-clippy/pull/6854)
* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper
[#6782](https://github.com/rust-lang/rust-clippy/pull/6782)
### Others
* Running `cargo clippy` after `cargo check` now works as expected
(`cargo clippy` and `cargo check` no longer shares the same build cache)
[#6687](https://github.com/rust-lang/rust-clippy/pull/6687)
* Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed.
[#6834](https://github.com/rust-lang/rust-clippy/pull/6834)
* Extracted Clippy's `utils` module into the new `clippy_utils` crate
[#6756](https://github.com/rust-lang/rust-clippy/pull/6756)
* Clippy lintcheck tool improvements
[#6800](https://github.com/rust-lang/rust-clippy/pull/6800)
[#6735](https://github.com/rust-lang/rust-clippy/pull/6735)
[#6764](https://github.com/rust-lang/rust-clippy/pull/6764)
[#6708](https://github.com/rust-lang/rust-clippy/pull/6708)
[#6780](https://github.com/rust-lang/rust-clippy/pull/6780)
[#6686](https://github.com/rust-lang/rust-clippy/pull/6686)
## Rust 1.51 ## Rust 1.51
Current beta, release 2021-03-25 Current stable, released 2021-03-25
[4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797) [4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797)
@ -125,7 +260,7 @@ Current beta, release 2021-03-25
## Rust 1.50 ## Rust 1.50
Current stable, released 2021-02-11 Released 2021-02-11
[b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1) [b20d4c1...4bd77a1](https://github.com/rust-lang/rust-clippy/compare/b20d4c1...4bd77a1)
@ -1970,6 +2105,7 @@ Released 2018-09-13
[configuration file]: ./rust-clippy#configuration [configuration file]: ./rust-clippy#configuration
[pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665 [pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665
[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md [adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md
[`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md
<!-- lint disable no-unused-definitions --> <!-- lint disable no-unused-definitions -->
<!-- begin autogenerated links to lint list --> <!-- begin autogenerated links to lint list -->
@ -1993,6 +2129,7 @@ Released 2018-09-13
[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box [`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
[`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec [`box_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_vec
[`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local [`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local
[`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code
[`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow [`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
@ -2233,6 +2370,7 @@ Released 2018-09-13
[`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect [`needless_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_collect
[`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue [`needless_continue`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_continue
[`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main [`needless_doctest_main`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_doctest_main
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes [`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
@ -2246,6 +2384,7 @@ Released 2018-09-13
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default [`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect [`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal [`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
[`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool
[`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
@ -2253,6 +2392,7 @@ Released 2018-09-13
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref [`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref [`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap [`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
[`option_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_filter_map
[`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else [`option_if_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_if_let_else
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none [`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn [`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn

View file

@ -5,7 +5,7 @@
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
[There are over 400 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) [There are over 450 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

View file

@ -68,7 +68,7 @@ pub fn run(check: bool, verbose: bool) {
continue; continue;
} }
success &= rustfmt(context, &path)?; success &= rustfmt(context, path)?;
} }
Ok(success) Ok(success)

View file

@ -101,7 +101,7 @@ impl Lint {
#[must_use] #[must_use]
pub fn gen_lint_group_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> { pub fn gen_lint_group_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
lints lints
.map(|l| format!(" LintId::of(&{}::{}),", l.module, l.name.to_uppercase())) .map(|l| format!(" LintId::of({}::{}),", l.module, l.name.to_uppercase()))
.sorted() .sorted()
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }
@ -154,17 +154,17 @@ pub fn gen_register_lint_list<'a>(
let header = " store.register_lints(&[".to_string(); let header = " store.register_lints(&[".to_string();
let footer = " ]);".to_string(); let footer = " ]);".to_string();
let internal_lints = internal_lints let internal_lints = internal_lints
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) .sorted_by_key(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
.map(|l| { .map(|l| {
format!( format!(
" #[cfg(feature = \"internal-lints\")]\n &{}::{},", " #[cfg(feature = \"internal-lints\")]\n {}::{},",
l.module, l.module,
l.name.to_uppercase() l.name.to_uppercase()
) )
}); });
let other_lints = usable_lints let other_lints = usable_lints
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) .sorted_by_key(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
.map(|l| format!(" &{}::{},", l.module, l.name.to_uppercase())) .map(|l| format!(" {}::{},", l.module, l.name.to_uppercase()))
.sorted(); .sorted();
let mut lint_list = vec![header]; let mut lint_list = vec![header];
lint_list.extend(internal_lints); lint_list.extend(internal_lints);
@ -550,9 +550,9 @@ fn test_gen_lint_group_list() {
Lint::new("internal", "internal_style", "abc", None, "module_name"), Lint::new("internal", "internal_style", "abc", None, "module_name"),
]; ];
let expected = vec![ let expected = vec![
" LintId::of(&module_name::ABC),".to_string(), " LintId::of(module_name::ABC),".to_string(),
" LintId::of(&module_name::INTERNAL),".to_string(), " LintId::of(module_name::INTERNAL),".to_string(),
" LintId::of(&module_name::SHOULD_ASSERT_EQ),".to_string(), " LintId::of(module_name::SHOULD_ASSERT_EQ),".to_string(),
]; ];
assert_eq!(expected, gen_lint_group_list(lints.iter())); assert_eq!(expected, gen_lint_group_list(lints.iter()));
} }

View file

@ -0,0 +1,173 @@
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use crate::consts::{constant, Constant};
use clippy_utils::comparisons::{normalize_comparison, Rel};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_isize_or_usize;
use clippy_utils::{clip, int_bits, unsext};
declare_clippy_lint! {
/// **What it does:** Checks for comparisons where one side of the relation is
/// either the minimum or maximum value for its type and warns if it involves a
/// case that is always true or always false. Only integer and boolean types are
/// checked.
///
/// **Why is this bad?** An expression like `min <= x` may misleadingly imply
/// that it is possible for `x` to be less than the minimum. Expressions like
/// `max < x` are probably mistakes.
///
/// **Known problems:** For `usize` the size of the current compile target will
/// be assumed (e.g., 64 bits on 64 bit systems). This means code that uses such
/// a comparison to detect target pointer width will trigger this lint. One can
/// use `mem::sizeof` and compare its value or conditional compilation
/// attributes
/// like `#[cfg(target_pointer_width = "64")] ..` instead.
///
/// **Example:**
///
/// ```rust
/// let vec: Vec<isize> = Vec::new();
/// if vec.len() <= 0 {}
/// if 100 > i32::MAX {}
/// ```
pub ABSURD_EXTREME_COMPARISONS,
correctness,
"a comparison with a maximum or minimum value that is always true or false"
}
declare_lint_pass!(AbsurdExtremeComparisons => [ABSURD_EXTREME_COMPARISONS]);
impl<'tcx> LateLintPass<'tcx> for AbsurdExtremeComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
if let Some((culprit, result)) = detect_absurd_comparison(cx, cmp.node, lhs, rhs) {
if !expr.span.from_expansion() {
let msg = "this comparison involving the minimum or maximum element for this \
type contains a case that is always true or always false";
let conclusion = match result {
AbsurdComparisonResult::AlwaysFalse => "this comparison is always false".to_owned(),
AbsurdComparisonResult::AlwaysTrue => "this comparison is always true".to_owned(),
AbsurdComparisonResult::InequalityImpossible => format!(
"the case where the two sides are not equal never occurs, consider using `{} == {}` \
instead",
snippet(cx, lhs.span, "lhs"),
snippet(cx, rhs.span, "rhs")
),
};
let help = format!(
"because `{}` is the {} value for this type, {}",
snippet(cx, culprit.expr.span, "x"),
match culprit.which {
ExtremeType::Minimum => "minimum",
ExtremeType::Maximum => "maximum",
},
conclusion
);
span_lint_and_help(cx, ABSURD_EXTREME_COMPARISONS, expr.span, msg, None, &help);
}
}
}
}
}
enum ExtremeType {
Minimum,
Maximum,
}
struct ExtremeExpr<'a> {
which: ExtremeType,
expr: &'a Expr<'a>,
}
enum AbsurdComparisonResult {
AlwaysFalse,
AlwaysTrue,
InequalityImpossible,
}
fn is_cast_between_fixed_and_target<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
if let ExprKind::Cast(cast_exp, _) = expr.kind {
let precast_ty = cx.typeck_results().expr_ty(cast_exp);
let cast_ty = cx.typeck_results().expr_ty(expr);
return is_isize_or_usize(precast_ty) != is_isize_or_usize(cast_ty);
}
false
}
fn detect_absurd_comparison<'tcx>(
cx: &LateContext<'tcx>,
op: BinOpKind,
lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>,
) -> Option<(ExtremeExpr<'tcx>, AbsurdComparisonResult)> {
use AbsurdComparisonResult::{AlwaysFalse, AlwaysTrue, InequalityImpossible};
use ExtremeType::{Maximum, Minimum};
// absurd comparison only makes sense on primitive types
// primitive types don't implement comparison operators with each other
if cx.typeck_results().expr_ty(lhs) != cx.typeck_results().expr_ty(rhs) {
return None;
}
// comparisons between fix sized types and target sized types are considered unanalyzable
if is_cast_between_fixed_and_target(cx, lhs) || is_cast_between_fixed_and_target(cx, rhs) {
return None;
}
let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;
let lx = detect_extreme_expr(cx, normalized_lhs);
let rx = detect_extreme_expr(cx, normalized_rhs);
Some(match rel {
Rel::Lt => {
match (lx, rx) {
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, AlwaysFalse), // max < x
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, AlwaysFalse), // x < min
_ => return None,
}
},
Rel::Le => {
match (lx, rx) {
(Some(l @ ExtremeExpr { which: Minimum, .. }), _) => (l, AlwaysTrue), // min <= x
(Some(l @ ExtremeExpr { which: Maximum, .. }), _) => (l, InequalityImpossible), // max <= x
(_, Some(r @ ExtremeExpr { which: Minimum, .. })) => (r, InequalityImpossible), // x <= min
(_, Some(r @ ExtremeExpr { which: Maximum, .. })) => (r, AlwaysTrue), // x <= max
_ => return None,
}
},
Rel::Ne | Rel::Eq => return None,
})
}
fn detect_extreme_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<ExtremeExpr<'tcx>> {
let ty = cx.typeck_results().expr_ty(expr);
let cv = constant(cx, cx.typeck_results(), expr)?.0;
let which = match (ty.kind(), cv) {
(&ty::Bool, Constant::Bool(false)) | (&ty::Uint(_), Constant::Int(0)) => ExtremeType::Minimum,
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MIN >> (128 - int_bits(cx.tcx, ity)), ity) => {
ExtremeType::Minimum
},
(&ty::Bool, Constant::Bool(true)) => ExtremeType::Maximum,
(&ty::Int(ity), Constant::Int(i)) if i == unsext(cx.tcx, i128::MAX >> (128 - int_bits(cx.tcx, ity)), ity) => {
ExtremeType::Maximum
},
(&ty::Uint(uty), Constant::Int(i)) if clip(cx.tcx, u128::MAX, uty) == i => ExtremeType::Maximum,
_ => return None,
};
Some(ExtremeExpr { which, expr })
}

View file

@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
return; return;
} }
if_chain! { if_chain! {
if let ExprKind::Unary(_, ref lit) = e.kind; if let ExprKind::Unary(_, lit) = e.kind;
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit); if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), lit);
if is_true; if is_true;
then { then {
@ -82,7 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants {
if assert_span.from_expansion() { if assert_span.from_expansion() {
return; return;
} }
if let Some(assert_match) = match_assert_with_message(&cx, e) { if let Some(assert_match) = match_assert_with_message(cx, e) {
match assert_match { match assert_match {
// matched assert but not message // matched assert but not message
AssertKind::WithoutMessage(false) => lint_false_without_message(), AssertKind::WithoutMessage(false) => lint_false_without_message(),
@ -113,17 +113,17 @@ enum AssertKind {
/// where `message` is any expression and `c` is a constant bool. /// where `message` is any expression and `c` is a constant bool.
fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> { fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<AssertKind> {
if_chain! { if_chain! {
if let ExprKind::If(ref cond, ref then, _) = expr.kind; if let ExprKind::If(cond, then, _) = expr.kind;
if let ExprKind::Unary(UnOp::Not, ref expr) = cond.kind; if let ExprKind::Unary(UnOp::Not, expr) = cond.kind;
// bind the first argument of the `assert!` macro // bind the first argument of the `assert!` macro
if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr); if let Some((Constant::Bool(is_true), _)) = constant(cx, cx.typeck_results(), expr);
// block // block
if let ExprKind::Block(ref block, _) = then.kind; if let ExprKind::Block(block, _) = then.kind;
if block.stmts.is_empty(); if block.stmts.is_empty();
if let Some(block_expr) = &block.expr; if let Some(block_expr) = &block.expr;
// inner block is optional. unwrap it if it exists, or use the expression as is otherwise. // inner block is optional. unwrap it if it exists, or use the expression as is otherwise.
if let Some(begin_panic_call) = match block_expr.kind { if let Some(begin_panic_call) = match block_expr.kind {
ExprKind::Block(ref inner_block, _) => &inner_block.expr, ExprKind::Block(inner_block, _) => &inner_block.expr,
_ => &block.expr, _ => &block.expr,
}; };
// function call // function call

View file

@ -85,7 +85,7 @@ fn match_ordering_def_path(cx: &LateContext<'_>, did: DefId, orderings: &[&str])
fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind; if let ExprKind::MethodCall(method_path, _, args, _) = &expr.kind;
let method = method_path.ident.name.as_str(); let method = method_path.ident.name.as_str();
if type_is_atomic(cx, &args[0]); if type_is_atomic(cx, &args[0]);
if method == "load" || method == "store"; if method == "load" || method == "store";
@ -120,7 +120,7 @@ fn check_atomic_load_store(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_memory_fence(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(ref func, ref args) = expr.kind; if let ExprKind::Call(func, args) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind; if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if ["fence", "compiler_fence"] if ["fence", "compiler_fence"]
@ -152,7 +152,7 @@ fn opt_ordering_defid(cx: &LateContext<'_>, ord_arg: &Expr<'_>) -> Option<DefId>
fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_atomic_compare_exchange(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref method_path, _, args, _) = &expr.kind; if let ExprKind::MethodCall(method_path, _, args, _) = &expr.kind;
let method = method_path.ident.name.as_str(); let method = method_path.ident.name.as_str();
if type_is_atomic(cx, &args[0]); if type_is_atomic(cx, &args[0]);
if method == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update"; if method == "compare_exchange" || method == "compare_exchange_weak" || method == "fetch_update";

View file

@ -250,12 +250,8 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
if let Some(items) = &attr.meta_item_list() { if let Some(items) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() { if let Some(ident) = attr.ident() {
let ident = &*ident.as_str(); if is_lint_level(ident.name) {
match ident { check_clippy_lint_names(cx, ident.name, items);
"allow" | "warn" | "deny" | "forbid" => {
check_clippy_lint_names(cx, ident, items);
},
_ => {},
} }
if items.is_empty() || !attr.has_name(sym::deprecated) { if items.is_empty() || !attr.has_name(sym::deprecated) {
return; return;
@ -288,9 +284,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
return; return;
} }
if let Some(lint_list) = &attr.meta_item_list() { if let Some(lint_list) = &attr.meta_item_list() {
if let Some(ident) = attr.ident() { if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
match &*ident.as_str() {
"allow" | "warn" | "deny" | "forbid" => {
// permit `unused_imports`, `deprecated`, `unreachable_pub`, // permit `unused_imports`, `deprecated`, `unreachable_pub`,
// `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items // `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items
// and `unused_imports` for `extern crate` items with `macro_use` // and `unused_imports` for `extern crate` items with `macro_use`
@ -301,8 +295,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
|| is_word(lint, sym::deprecated) || is_word(lint, sym::deprecated)
|| is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unreachable_pub))
|| is_word(lint, sym!(unused)) || is_word(lint, sym!(unused))
|| extract_clippy_lint(lint) || extract_clippy_lint(lint).map_or(false, |s| s == "wildcard_imports")
.map_or(false, |s| s == "wildcard_imports")
|| extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use") || extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use")
{ {
return; return;
@ -340,9 +333,6 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
); );
} }
} }
},
_ => {},
}
} }
} }
} }
@ -371,18 +361,18 @@ fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> {
if meta_item.path.segments.len() > 1; if meta_item.path.segments.len() > 1;
if let tool_name = meta_item.path.segments[0].ident; if let tool_name = meta_item.path.segments[0].ident;
if tool_name.name == sym::clippy; if tool_name.name == sym::clippy;
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
then { then {
let lint_name = meta_item.path.segments.last().unwrap().ident.name;
return Some(lint_name.as_str()); return Some(lint_name.as_str());
} }
} }
None None
} }
fn check_clippy_lint_names(cx: &LateContext<'_>, ident: &str, items: &[NestedMetaItem]) { fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
for lint in items { for lint in items {
if let Some(lint_name) = extract_clippy_lint(lint) { if let Some(lint_name) = extract_clippy_lint(lint) {
if lint_name == "restriction" && ident != "allow" { if lint_name == "restriction" && name != sym::allow {
span_lint_and_help( span_lint_and_help(
cx, cx,
BLANKET_CLIPPY_RESTRICTION_LINTS, BLANKET_CLIPPY_RESTRICTION_LINTS,
@ -602,7 +592,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
if let NestedMetaItem::MetaItem(meta) = item { if let NestedMetaItem::MetaItem(meta) = item {
match &meta.kind { match &meta.kind {
MetaItemKind::List(list) => { MetaItemKind::List(list) => {
mismatched.extend(find_mismatched_target_os(&list)); mismatched.extend(find_mismatched_target_os(list));
}, },
MetaItemKind::Word => { MetaItemKind::Word => {
if_chain! { if_chain! {
@ -629,7 +619,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
then { then {
let mess = "operating system used in target family position"; let mess = "operating system used in target family position";
span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, &mess, |diag| { span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
// Avoid showing the unix suggestion multiple times in case // Avoid showing the unix suggestion multiple times in case
// we have more than one mismatch for unix-like systems // we have more than one mismatch for unix-like systems
let mut unix_suggested = false; let mut unix_suggested = false;
@ -647,3 +637,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
} }
} }
} }
fn is_lint_level(symbol: Symbol) -> bool {
matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
}

View file

@ -101,7 +101,7 @@ impl LateLintPass<'_> for AwaitHolding {
let typeck_results = cx.tcx.typeck_body(body_id); let typeck_results = cx.tcx.typeck_body(body_id);
check_interior_types( check_interior_types(
cx, cx,
&typeck_results.generator_interior_types.as_ref().skip_binder(), typeck_results.generator_interior_types.as_ref().skip_binder(),
body.value.span, body.value.span,
); );
} }

View file

@ -38,19 +38,17 @@ declare_lint_pass!(ByteCount => [NAIVE_BYTECOUNT]);
impl<'tcx> LateLintPass<'tcx> for ByteCount { impl<'tcx> LateLintPass<'tcx> for ByteCount {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref count, _, ref count_args, _) = expr.kind; if let ExprKind::MethodCall(count, _, count_args, _) = expr.kind;
if count.ident.name == sym!(count); if count.ident.name == sym!(count);
if count_args.len() == 1; if count_args.len() == 1;
if let ExprKind::MethodCall(ref filter, _, ref filter_args, _) = count_args[0].kind; if let ExprKind::MethodCall(filter, _, filter_args, _) = count_args[0].kind;
if filter.ident.name == sym!(filter); if filter.ident.name == sym!(filter);
if filter_args.len() == 2; if filter_args.len() == 2;
if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind; if let ExprKind::Closure(_, _, body_id, _, _) = filter_args[1].kind;
then {
let body = cx.tcx.hir().body(body_id); let body = cx.tcx.hir().body(body_id);
if_chain! {
if body.params.len() == 1; if body.params.len() == 1;
if let Some(argname) = get_pat_name(&body.params[0].pat); if let Some(argname) = get_pat_name(body.params[0].pat);
if let ExprKind::Binary(ref op, ref l, ref r) = body.value.kind; if let ExprKind::Binary(ref op, l, r) = body.value.kind;
if op.node == BinOpKind::Eq; if op.node == BinOpKind::Eq;
if match_type(cx, if match_type(cx,
cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(), cx.typeck_results().expr_ty(&filter_args[0]).peel_refs(),
@ -66,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() { if ty::Uint(UintTy::U8) != *cx.typeck_results().expr_ty(needle).peel_refs().kind() {
return; return;
} }
let haystack = if let ExprKind::MethodCall(ref path, _, ref args, _) = let haystack = if let ExprKind::MethodCall(path, _, args, _) =
filter_args[0].kind { filter_args[0].kind {
let p = path.ident.name; let p = path.ident.name;
if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 { if (p == sym::iter || p == sym!(iter_mut)) && args.len() == 1 {
@ -92,8 +90,6 @@ impl<'tcx> LateLintPass<'tcx> for ByteCount {
} }
}; };
} }
};
}
} }
fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool { fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool {
@ -102,10 +98,10 @@ fn check_arg(name: Symbol, arg: Symbol, needle: &Expr<'_>) -> bool {
fn get_path_name(expr: &Expr<'_>) -> Option<Symbol> { fn get_path_name(expr: &Expr<'_>) -> Option<Symbol> {
match expr.kind { match expr.kind {
ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) | ExprKind::Unary(UnOp::Deref, ref e) => { ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) | ExprKind::Unary(UnOp::Deref, e) => {
get_path_name(e) get_path_name(e)
}, },
ExprKind::Block(ref b, _) => { ExprKind::Block(b, _) => {
if b.stmts.is_empty() { if b.stmts.is_empty() {
b.expr.as_ref().and_then(|p| get_path_name(p)) b.expr.as_ref().and_then(|p| get_path_name(p))
} else { } else {

View file

@ -20,11 +20,10 @@ declare_clippy_lint! {
/// ///
/// **Example:** /// **Example:**
/// ```toml /// ```toml
/// # This `Cargo.toml` is missing an authors field: /// # This `Cargo.toml` is missing a description field:
/// [package] /// [package]
/// name = "clippy" /// name = "clippy"
/// version = "0.0.212" /// version = "0.0.212"
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
/// repository = "https://github.com/rust-lang/rust-clippy" /// repository = "https://github.com/rust-lang/rust-clippy"
/// readme = "README.md" /// readme = "README.md"
/// license = "MIT OR Apache-2.0" /// license = "MIT OR Apache-2.0"
@ -32,14 +31,13 @@ declare_clippy_lint! {
/// categories = ["development-tools", "development-tools::cargo-plugins"] /// categories = ["development-tools", "development-tools::cargo-plugins"]
/// ``` /// ```
/// ///
/// Should include an authors field like: /// Should include a description field like:
/// ///
/// ```toml /// ```toml
/// # This `Cargo.toml` includes all common metadata /// # This `Cargo.toml` includes all common metadata
/// [package] /// [package]
/// name = "clippy" /// name = "clippy"
/// version = "0.0.212" /// version = "0.0.212"
/// authors = ["Someone <someone@rust-lang.org>"]
/// description = "A bunch of helpful lints to avoid common pitfalls in Rust" /// description = "A bunch of helpful lints to avoid common pitfalls in Rust"
/// repository = "https://github.com/rust-lang/rust-clippy" /// repository = "https://github.com/rust-lang/rust-clippy"
/// readme = "README.md" /// readme = "README.md"
@ -97,10 +95,6 @@ impl LateLintPass<'_> for CargoCommonMetadata {
// only run the lint if publish is `None` (`publish = true` or skipped entirely) // only run the lint if publish is `None` (`publish = true` or skipped entirely)
// or if the vector isn't empty (`publish = ["something"]`) // or if the vector isn't empty (`publish = ["something"]`)
if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish { if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || self.ignore_publish {
if is_empty_vec(&package.authors) {
missing_warning(cx, &package, "package.authors");
}
if is_empty_str(&package.description) { if is_empty_str(&package.description) {
missing_warning(cx, &package, "package.description"); missing_warning(cx, &package, "package.description");
} }

View file

@ -10,7 +10,7 @@ use rustc_target::abi::LayoutOf;
use super::CAST_PTR_ALIGNMENT; use super::CAST_PTR_ALIGNMENT;
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
if is_hir_ty_cfg_dependant(cx, cast_to) { if is_hir_ty_cfg_dependant(cx, cast_to) {
return; return;
} }

View file

@ -30,7 +30,7 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
} }
// Don't lint for positive constants. // Don't lint for positive constants.
let const_val = constant(cx, &cx.typeck_results(), cast_op); let const_val = constant(cx, cx.typeck_results(), cast_op);
if_chain! { if_chain! {
if let Some((Constant::Int(n), _)) = const_val; if let Some((Constant::Int(n), _)) = const_val;
if let ty::Int(ity) = *cast_from.kind(); if let ty::Int(ity) = *cast_from.kind();
@ -41,14 +41,14 @@ fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast
} }
// Don't lint for the result of methods that always return non-negative values. // Don't lint for the result of methods that always return non-negative values.
if let ExprKind::MethodCall(ref path, _, _, _) = cast_op.kind { if let ExprKind::MethodCall(path, _, _, _) = cast_op.kind {
let mut method_name = path.ident.name.as_str(); let mut method_name = path.ident.name.as_str();
let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"];
if_chain! { if_chain! {
if method_name == "unwrap"; if method_name == "unwrap";
if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]); if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]);
if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind; if let ExprKind::MethodCall(inner_path, _, _, _) = &arglist[0][0].kind;
then { then {
method_name = inner_path.ident.name.as_str(); method_name = inner_path.ident.name.as_str();
} }

View file

@ -372,7 +372,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
return; return;
} }
if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { if let ExprKind::Cast(cast_expr, cast_to) = expr.kind {
if is_hir_ty_cfg_dependant(cx, cast_to) { if is_hir_ty_cfg_dependant(cx, cast_to) {
return; return;
} }

View file

@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions {
let result = if_chain! { let result = if_chain! {
if !in_external_macro(cx.sess(), item.span); if !in_external_macro(cx.sess(), item.span);
if let ExprKind::Binary(op, ref left, ref right) = &item.kind; if let ExprKind::Binary(op, left, right) = &item.kind;
then { then {
match op.node { match op.node {
@ -200,7 +200,7 @@ impl ConversionType {
/// Check for `expr <= (to_type::MAX as from_type)` /// Check for `expr <= (to_type::MAX as from_type)`
fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> { fn check_upper_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
if_chain! { if_chain! {
if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind; if let ExprKind::Binary(ref op, left, right) = &expr.kind;
if let Some((candidate, check)) = normalize_le_ge(op, left, right); if let Some((candidate, check)) = normalize_le_ge(op, left, right);
if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX"); if let Some((from, to)) = get_types_from_cast(check, INTS, "max_value", "MAX");
@ -219,7 +219,7 @@ fn check_lower_bound<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> {
} }
// First of we need a binary containing the expression & the cast // First of we need a binary containing the expression & the cast
if let ExprKind::Binary(ref op, ref left, ref right) = &expr.kind { if let ExprKind::Binary(ref op, left, right) = &expr.kind {
normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r)) normalize_le_ge(op, right, left).and_then(|(l, r)| check_function(l, r))
} else { } else {
None None
@ -260,7 +260,7 @@ fn get_types_from_cast<'a>(
// or `to_type::MAX as from_type` // or `to_type::MAX as from_type`
let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! { let call_from_cast: Option<(&Expr<'_>, &str)> = if_chain! {
// to_type::max_value(), from_type // to_type::max_value(), from_type
if let ExprKind::Cast(ref limit, ref from_type) = &expr.kind; if let ExprKind::Cast(limit, from_type) = &expr.kind;
if let TyKind::Path(ref from_type_path) = &from_type.kind; if let TyKind::Path(ref from_type_path) = &from_type.kind;
if let Some(from_sym) = int_ty_to_sym(from_type_path); if let Some(from_sym) = int_ty_to_sym(from_type_path);
@ -275,7 +275,7 @@ fn get_types_from_cast<'a>(
let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| {
if_chain! { if_chain! {
// `from_type::from, to_type::max_value()` // `from_type::from, to_type::max_value()`
if let ExprKind::Call(ref from_func, ref args) = &expr.kind; if let ExprKind::Call(from_func, args) = &expr.kind;
// `to_type::max_value()` // `to_type::max_value()`
if args.len() == 1; if args.len() == 1;
if let limit = &args[0]; if let limit = &args[0];
@ -317,13 +317,12 @@ fn get_types_from_cast<'a>(
/// Gets the type which implements the called function /// Gets the type which implements the called function
fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> {
if_chain! { if_chain! {
if let QPath::TypeRelative(ref ty, ref path) = &path; if let QPath::TypeRelative(ty, path) = &path;
if path.ident.name.as_str() == function; if path.ident.name.as_str() == function;
if let TyKind::Path(QPath::Resolved(None, ref tp)) = &ty.kind; if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
if let [int] = &*tp.segments; if let [int] = &*tp.segments;
let name = &int.ident.name.as_str();
then { then {
let name = &int.ident.name.as_str();
candidates.iter().find(|c| name == *c).cloned() candidates.iter().find(|c| name == *c).cloned()
} else { } else {
None None
@ -334,11 +333,10 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
/// Gets the type as a string, if it is a supported integer /// Gets the type as a string, if it is a supported integer
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! { if_chain! {
if let QPath::Resolved(_, ref path) = *path; if let QPath::Resolved(_, path) = *path;
if let [ty] = &*path.segments; if let [ty] = &*path.segments;
let name = &ty.ident.name.as_str();
then { then {
let name = &ty.ident.name.as_str();
INTS.iter().find(|c| name == *c).cloned() INTS.iter().find(|c| name == *c).cloned()
} else { } else {
None None

View file

@ -152,7 +152,7 @@ impl<'tcx> Visitor<'tcx> for CcHelper {
ExprKind::If(_, _, _) => { ExprKind::If(_, _, _) => {
self.cc += 1; self.cc += 1;
}, },
ExprKind::Match(_, ref arms, _) => { ExprKind::Match(_, arms, _) => {
if arms.len() > 1 { if arms.len() > 1 {
self.cc += 1; self.cc += 1;
} }

View file

@ -62,8 +62,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
} }
fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) { fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext<'tcx>) {
if_chain! {
let expr = strip_singleton_blocks(arm.body); let expr = strip_singleton_blocks(arm.body);
if_chain! {
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind; if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
// the outer arm pattern and the inner match // the outer arm pattern and the inner match
if expr_in.span.ctxt() == arm.pat.span.ctxt(); if expr_in.span.ctxt() == arm.pat.span.ctxt();

View file

@ -71,10 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
} }
for cond in conds.windows(2) { for cond in conds.windows(2) {
if let ( if let (&ExprKind::Binary(ref kind1, lhs1, rhs1), &ExprKind::Binary(ref kind2, lhs2, rhs2)) =
&ExprKind::Binary(ref kind1, ref lhs1, ref rhs1), (&cond[0].kind, &cond[1].kind)
&ExprKind::Binary(ref kind2, ref lhs2, ref rhs2),
) = (&cond[0].kind, &cond[1].kind)
{ {
if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) { if !kind_is_cmp(kind1.node) || !kind_is_cmp(kind2.node) {
return; return;

View file

@ -1,9 +1,19 @@
use clippy_utils::diagnostics::span_lint_and_note; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
use clippy_utils::{get_parent_expr, if_sequence}; use clippy_utils::{
use rustc_hir::{Block, Expr, ExprKind}; both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, parent_node_is_if_expr,
run_lints, search_same, ContainsName, SpanlessEq, SpanlessHash,
};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, HirId};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{source_map::Span, symbol::Symbol, BytePos};
use std::borrow::Cow;
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for consecutive `if`s with the same condition. /// **What it does:** Checks for consecutive `if`s with the same condition.
@ -104,14 +114,53 @@ declare_clippy_lint! {
"`if` with the same `then` and `else` blocks" "`if` with the same `then` and `else` blocks"
} }
declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]); declare_clippy_lint! {
/// **What it does:** Checks if the `if` and `else` block contain shared code that can be
/// moved out of the blocks.
///
/// **Why is this bad?** Duplicate code is less maintainable.
///
/// **Known problems:** Hopefully none.
///
/// **Example:**
/// ```ignore
/// let foo = if … {
/// println!("Hello World");
/// 13
/// } else {
/// println!("Hello World");
/// 42
/// };
/// ```
///
/// Could be written as:
/// ```ignore
/// println!("Hello World");
/// let foo = if … {
/// 13
/// } else {
/// 42
/// };
/// ```
pub BRANCHES_SHARING_CODE,
complexity,
"`if` statement with shared code in all blocks"
}
declare_lint_pass!(CopyAndPaste => [
IFS_SAME_COND,
SAME_FUNCTIONS_IN_IF_CONDITION,
IF_SAME_THEN_ELSE,
BRANCHES_SHARING_CODE
]);
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !expr.span.from_expansion() { if !expr.span.from_expansion() {
if let ExprKind::If(_, _, _) = expr.kind {
// skip ifs directly in else, it will be checked in the parent if // skip ifs directly in else, it will be checked in the parent if
if let Some(&Expr { if let Some(&Expr {
kind: ExprKind::If(_, _, Some(ref else_expr)), kind: ExprKind::If(_, _, Some(else_expr)),
.. ..
}) = get_parent_expr(cx, expr) }) = get_parent_expr(cx, expr)
{ {
@ -121,27 +170,398 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
} }
let (conds, blocks) = if_sequence(expr); let (conds, blocks) = if_sequence(expr);
lint_same_then_else(cx, &blocks); // Conditions
lint_same_cond(cx, &conds); lint_same_cond(cx, &conds);
lint_same_fns_in_if_cond(cx, &conds); lint_same_fns_in_if_cond(cx, &conds);
// Block duplication
lint_same_then_else(cx, &blocks, conds.len() == blocks.len(), expr);
}
} }
} }
} }
/// Implementation of `IF_SAME_THEN_ELSE`. /// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal.
fn lint_same_then_else(cx: &LateContext<'_>, blocks: &[&Block<'_>]) { fn lint_same_then_else<'tcx>(
let eq: &dyn Fn(&&Block<'_>, &&Block<'_>) -> bool = cx: &LateContext<'tcx>,
&|&lhs, &rhs| -> bool { SpanlessEq::new(cx).eq_block(lhs, rhs) }; blocks: &[&Block<'tcx>],
has_conditional_else: bool,
expr: &'tcx Expr<'_>,
) {
// We only lint ifs with multiple blocks
if blocks.len() < 2 || parent_node_is_if_expr(expr, cx) {
return;
}
if let Some((i, j)) = search_same_sequenced(blocks, eq) { // Check if each block has shared code
let has_expr = blocks[0].expr.is_some();
let (start_eq, mut end_eq, expr_eq) = scan_block_for_eq(cx, blocks);
// BRANCHES_SHARING_CODE prerequisites
if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) {
return;
}
// Only the start is the same
if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) {
let block = blocks[0];
let start_stmts = block.stmts.split_at(start_eq).0;
let mut start_walker = UsedValueFinderVisitor::new(cx);
for stmt in start_stmts {
intravisit::walk_stmt(&mut start_walker, stmt);
}
emit_branches_sharing_code_lint(
cx,
start_eq,
0,
false,
check_for_warn_of_moved_symbol(cx, &start_walker.def_symbols, expr),
blocks,
expr,
);
} else if end_eq != 0 || (has_expr && expr_eq) {
let block = blocks[blocks.len() - 1];
let (start_stmts, block_stmts) = block.stmts.split_at(start_eq);
let (block_stmts, end_stmts) = block_stmts.split_at(block_stmts.len() - end_eq);
// Scan start
let mut start_walker = UsedValueFinderVisitor::new(cx);
for stmt in start_stmts {
intravisit::walk_stmt(&mut start_walker, stmt);
}
let mut moved_syms = start_walker.def_symbols;
// Scan block
let mut block_walker = UsedValueFinderVisitor::new(cx);
for stmt in block_stmts {
intravisit::walk_stmt(&mut block_walker, stmt);
}
let mut block_defs = block_walker.defs;
// Scan moved stmts
let mut moved_start: Option<usize> = None;
let mut end_walker = UsedValueFinderVisitor::new(cx);
for (index, stmt) in end_stmts.iter().enumerate() {
intravisit::walk_stmt(&mut end_walker, stmt);
for value in &end_walker.uses {
// Well we can't move this and all prev statements. So reset
if block_defs.contains(value) {
moved_start = Some(index + 1);
end_walker.defs.drain().for_each(|x| {
block_defs.insert(x);
});
end_walker.def_symbols.clear();
}
}
end_walker.uses.clear();
}
if let Some(moved_start) = moved_start {
end_eq -= moved_start;
}
let end_linable = block.expr.map_or_else(
|| end_eq != 0,
|expr| {
intravisit::walk_expr(&mut end_walker, expr);
end_walker.uses.iter().any(|x| !block_defs.contains(x))
},
);
if end_linable {
end_walker.def_symbols.drain().for_each(|x| {
moved_syms.insert(x);
});
}
emit_branches_sharing_code_lint(
cx,
start_eq,
end_eq,
end_linable,
check_for_warn_of_moved_symbol(cx, &moved_syms, expr),
blocks,
expr,
);
}
}
fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> (usize, usize, bool) {
let mut start_eq = usize::MAX;
let mut end_eq = usize::MAX;
let mut expr_eq = true;
for win in blocks.windows(2) {
let l_stmts = win[0].stmts;
let r_stmts = win[1].stmts;
// `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752.
// The comparison therefore needs to be done in a way that builds the correct context.
let mut evaluator = SpanlessEq::new(cx);
let mut evaluator = evaluator.inter_expr();
let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r));
let current_end_eq = {
// We skip the middle statements which can't be equal
let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq;
let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count);
let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count);
it1.zip(it2)
.fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 })
};
let block_expr_eq = both(&win[0].expr, &win[1].expr, |l, r| evaluator.eq_expr(l, r));
// IF_SAME_THEN_ELSE
if_chain! {
if block_expr_eq;
if l_stmts.len() == r_stmts.len();
if l_stmts.len() == current_start_eq;
if run_lints(cx, &[IF_SAME_THEN_ELSE], win[0].hir_id);
if run_lints(cx, &[IF_SAME_THEN_ELSE], win[1].hir_id);
then {
span_lint_and_note( span_lint_and_note(
cx, cx,
IF_SAME_THEN_ELSE, IF_SAME_THEN_ELSE,
j.span, win[0].span,
"this `if` has identical blocks", "this `if` has identical blocks",
Some(i.span), Some(win[1].span),
"same as this", "same as this",
); );
return (0, 0, false);
}
}
start_eq = start_eq.min(current_start_eq);
end_eq = end_eq.min(current_end_eq);
expr_eq &= block_expr_eq;
}
let has_expr = blocks[0].expr.is_some();
if has_expr && !expr_eq {
end_eq = 0;
}
// Check if the regions are overlapping. Set `end_eq` to prevent the overlap
let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap();
if (start_eq + end_eq) > min_block_size {
end_eq = min_block_size - start_eq;
}
(start_eq, end_eq, expr_eq)
}
fn check_for_warn_of_moved_symbol(
cx: &LateContext<'tcx>,
symbols: &FxHashSet<Symbol>,
if_expr: &'tcx Expr<'_>,
) -> bool {
get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| {
let ignore_span = block.span.shrink_to_lo().to(if_expr.span);
symbols
.iter()
.filter(|sym| !sym.as_str().starts_with('_'))
.any(move |sym| {
let mut walker = ContainsName {
name: *sym,
result: false,
};
// Scan block
block
.stmts
.iter()
.filter(|stmt| !ignore_span.overlaps(stmt.span))
.for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt));
if let Some(expr) = block.expr {
intravisit::walk_expr(&mut walker, expr);
}
walker.result
})
})
}
fn emit_branches_sharing_code_lint(
cx: &LateContext<'tcx>,
start_stmts: usize,
end_stmts: usize,
lint_end: bool,
warn_about_moved_symbol: bool,
blocks: &[&Block<'tcx>],
if_expr: &'tcx Expr<'_>,
) {
if start_stmts == 0 && !lint_end {
return;
}
// (help, span, suggestion)
let mut suggestions: Vec<(&str, Span, String)> = vec![];
let mut add_expr_note = false;
// Construct suggestions
if start_stmts > 0 {
let block = blocks[0];
let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo();
let span_end = block.stmts[start_stmts - 1].span.source_callsite();
let cond_span = first_line_of_span(cx, if_expr.span).until(block.span);
let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None);
let cond_indent = indent_of(cx, cond_span);
let moved_span = block.stmts[0].span.source_callsite().to(span_end);
let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{";
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent);
let span = span_start.to(span_end);
suggestions.push(("start", span, suggestion.to_string()));
}
if lint_end {
let block = blocks[blocks.len() - 1];
let span_end = block.span.shrink_to_hi();
let moved_start = if end_stmts == 0 && block.expr.is_some() {
block.expr.unwrap().span
} else {
block.stmts[block.stmts.len() - end_stmts].span
}
.source_callsite();
let moved_end = block
.expr
.map_or_else(|| block.stmts[block.stmts.len() - 1].span, |expr| expr.span)
.source_callsite();
let moved_span = moved_start.to(moved_end);
let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None);
let indent = indent_of(cx, if_expr.span.shrink_to_hi());
let suggestion = "}\n".to_string() + &moved_snipped;
let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent);
let mut span = moved_start.to(span_end);
// Improve formatting if the inner block has indention (i.e. normal Rust formatting)
let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt());
if snippet_opt(cx, test_span)
.map(|snip| snip == " ")
.unwrap_or_default()
{
span = span.with_lo(test_span.lo());
}
suggestions.push(("end", span, suggestion.to_string()));
add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit()
}
let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
if add_expr_note {
diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
}
if warn_about_moved_symbol {
diag.warn("Some moved values might need to be renamed to avoid wrong references");
}
};
// Emit lint
if suggestions.len() == 1 {
let (place_str, span, sugg) = suggestions.pop().unwrap();
let msg = format!("all if blocks contain the same code at the {}", place_str);
let help = format!("consider moving the {} statements out like this", place_str);
span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| {
diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified);
add_optional_msgs(diag);
});
} else if suggestions.len() == 2 {
let (_, end_span, end_sugg) = suggestions.pop().unwrap();
let (_, start_span, start_sugg) = suggestions.pop().unwrap();
span_lint_and_then(
cx,
BRANCHES_SHARING_CODE,
start_span,
"all if blocks contain the same code at the start and the end. Here at the start",
move |diag| {
diag.span_note(end_span, "and here at the end");
diag.span_suggestion(
start_span,
"consider moving the start statements out like this",
start_sugg,
Applicability::Unspecified,
);
diag.span_suggestion(
end_span,
"and consider moving the end statements out like this",
end_sugg,
Applicability::Unspecified,
);
add_optional_msgs(diag);
},
);
}
}
/// This visitor collects `HirId`s and Symbols of defined symbols and `HirId`s of used values.
struct UsedValueFinderVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
/// The `HirId`s of defined values in the scanned statements
defs: FxHashSet<HirId>,
/// The Symbols of the defined symbols in the scanned statements
def_symbols: FxHashSet<Symbol>,
/// The `HirId`s of the used values
uses: FxHashSet<HirId>,
}
impl<'a, 'tcx> UsedValueFinderVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
UsedValueFinderVisitor {
cx,
defs: FxHashSet::default(),
def_symbols: FxHashSet::default(),
uses: FxHashSet::default(),
}
}
}
impl<'a, 'tcx> Visitor<'tcx> for UsedValueFinderVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::All(self.cx.tcx.hir())
}
fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) {
let local_id = l.pat.hir_id;
self.defs.insert(local_id);
if let Some(sym) = l.pat.simple_ident() {
self.def_symbols.insert(sym.name);
}
if let Some(expr) = l.init {
intravisit::walk_expr(self, expr);
}
}
fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) {
if let rustc_hir::QPath::Resolved(_, path) = *qpath {
if path.segments.len() == 1 {
if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) {
self.uses.insert(var);
}
}
}
} }
} }
@ -198,15 +618,3 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
); );
} }
} }
fn search_same_sequenced<T, Eq>(exprs: &[T], eq: Eq) -> Option<(&T, &T)>
where
Eq: Fn(&T, &T) -> bool,
{
for win in exprs.windows(2) {
if eq(&win[0], &win[1]) {
return Some((&win[0], &win[1]));
}
}
None
}

View file

@ -33,7 +33,7 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]);
impl LateLintPass<'_> for CreateDir { impl LateLintPass<'_> for CreateDir {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(ref func, ref args) = expr.kind; if let ExprKind::Call(func, args) = expr.kind;
if let ExprKind::Path(ref path) = func.kind; if let ExprKind::Path(ref path) = func.kind;
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR); if match_def_path(cx, def_id, &paths::STD_FS_CREATE_DIR);

View file

@ -77,16 +77,16 @@ impl LateLintPass<'_> for Default {
if_chain! { if_chain! {
// Avoid cases already linted by `field_reassign_with_default` // Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span); if !self.reassigned_linted.contains(&expr.span);
if let ExprKind::Call(ref path, ..) = expr.kind; if let ExprKind::Call(path, ..) = expr.kind;
if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id); if !any_parent_is_automatically_derived(cx.tcx, expr.hir_id);
if let ExprKind::Path(ref qpath) = path.kind; if let ExprKind::Path(ref qpath) = path.kind;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD); if match_def_path(cx, def_id, &paths::DEFAULT_TRAIT_METHOD);
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type. // Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
if let QPath::Resolved(None, _path) = qpath; if let QPath::Resolved(None, _path) = qpath;
then {
let expr_ty = cx.typeck_results().expr_ty(expr); let expr_ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, ..) = expr_ty.kind() { if let ty::Adt(def, ..) = expr_ty.kind();
then {
// TODO: Work out a way to put "whatever the imported way of referencing // TODO: Work out a way to put "whatever the imported way of referencing
// this type in this file" rather than a fully-qualified type. // this type in this file" rather than a fully-qualified type.
let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did)); let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
@ -102,7 +102,6 @@ impl LateLintPass<'_> for Default {
} }
} }
} }
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) { fn check_block<'tcx>(&mut self, cx: &LateContext<'tcx>, block: &Block<'tcx>) {
@ -202,6 +201,7 @@ impl LateLintPass<'_> for Default {
let binding_type = if_chain! { let binding_type = if_chain! {
if let ty::Adt(adt_def, substs) = binding_type.kind(); if let ty::Adt(adt_def, substs) = binding_type.kind();
if !substs.is_empty(); if !substs.is_empty();
then {
let adt_def_ty_name = cx.tcx.item_name(adt_def.did); let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
let generic_args = substs.iter().collect::<Vec<_>>(); let generic_args = substs.iter().collect::<Vec<_>>();
let tys_str = generic_args let tys_str = generic_args
@ -209,7 +209,6 @@ impl LateLintPass<'_> for Default {
.map(ToString::to_string) .map(ToString::to_string)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(", "); .join(", ");
then {
format!("{}::<{}>", adt_def_ty_name, &tys_str) format!("{}::<{}>", adt_def_ty_name, &tys_str)
} else { } else {
binding_type.to_string() binding_type.to_string()
@ -247,7 +246,7 @@ impl LateLintPass<'_> for Default {
/// Checks if the given expression is the `default` method belonging to the `Default` trait. /// Checks if the given expression is the `default` method belonging to the `Default` trait.
fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool { fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool {
if_chain! { if_chain! {
if let ExprKind::Call(ref fn_expr, _) = &expr.kind; if let ExprKind::Call(fn_expr, _) = &expr.kind;
if let ExprKind::Path(qpath) = &fn_expr.kind; if let ExprKind::Path(qpath) = &fn_expr.kind;
if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id); if let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id);
then { then {
@ -263,11 +262,11 @@ fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool
fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> {
if_chain! { if_chain! {
// only take assignments // only take assignments
if let StmtKind::Semi(ref later_expr) = this.kind; if let StmtKind::Semi(later_expr) = this.kind;
if let ExprKind::Assign(ref assign_lhs, ref assign_rhs, _) = later_expr.kind; if let ExprKind::Assign(assign_lhs, assign_rhs, _) = later_expr.kind;
// only take assignments to fields where the left-hand side field is a field of // only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement // the same binding as the previous statement
if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind; if let ExprKind::Field(binding, field_ident) = assign_lhs.kind;
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last(); if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name; if second_binding_name.ident.name == binding_name;

View file

@ -131,8 +131,8 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> {
}, },
ExprKind::Struct(_, fields, base) => { ExprKind::Struct(_, fields, base) => {
if_chain! {
let ty = self.cx.typeck_results().expr_ty(expr); let ty = self.cx.typeck_results().expr_ty(expr);
if_chain! {
if let Some(adt_def) = ty.ty_adt_def(); if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct(); if adt_def.is_struct();
if let Some(variant) = adt_def.variants.iter().next(); if let Some(variant) = adt_def.variants.iter().next();

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_then};
use clippy_utils::paths; use clippy_utils::paths;
use clippy_utils::ty::is_copy; use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{get_trait_def_id, is_allowed, is_automatically_derived, match_def_path}; use clippy_utils::{get_trait_def_id, is_allowed, is_automatically_derived, match_def_path};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
@ -12,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span; use rustc_span::{def_id::LOCAL_CRATE, source_map::Span};
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq`
@ -199,7 +199,7 @@ fn check_hash_peq<'tcx>(
then { then {
// Look for the PartialEq implementations for `ty` // Look for the PartialEq implementations for `ty`
cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| {
let peq_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id)); let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
if peq_is_automatically_derived == hash_is_automatically_derived { if peq_is_automatically_derived == hash_is_automatically_derived {
return; return;
@ -253,7 +253,7 @@ fn check_ord_partial_ord<'tcx>(
then { then {
// Look for the PartialOrd implementations for `ty` // Look for the PartialOrd implementations for `ty`
cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| {
let partial_ord_is_automatically_derived = is_automatically_derived(&cx.tcx.get_attrs(impl_id)); let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id));
if partial_ord_is_automatically_derived == ord_is_automatically_derived { if partial_ord_is_automatically_derived == ord_is_automatically_derived {
return; return;
@ -293,38 +293,44 @@ fn check_ord_partial_ord<'tcx>(
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
if cx let clone_id = match cx.tcx.lang_items().clone_trait() {
.tcx Some(id) if trait_ref.trait_def_id() == Some(id) => id,
.lang_items() _ => return,
.clone_trait() };
.map_or(false, |id| Some(id) == trait_ref.trait_def_id()) let copy_id = match cx.tcx.lang_items().copy_trait() {
{ Some(id) => id,
None => return,
};
let (ty_adt, ty_subs) = match *ty.kind() {
// Unions can't derive clone.
ty::Adt(adt, subs) if !adt.is_union() => (adt, subs),
_ => return,
};
// If the current self type doesn't implement Copy (due to generic constraints), search to see if
// there's a Copy impl for any instance of the adt.
if !is_copy(cx, ty) { if !is_copy(cx, ty) {
if ty_subs.non_erasable_generics().next().is_some() {
let has_copy_impl = cx
.tcx
.all_local_trait_impls(LOCAL_CRATE)
.get(&copy_id)
.map_or(false, |impls| {
impls
.iter()
.any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
});
if !has_copy_impl {
return; return;
} }
} else {
match *ty.kind() {
ty::Adt(def, _) if def.is_union() => return,
// Some types are not Clone by default but could be cloned “by hand” if necessary
ty::Adt(def, substs) => {
for variant in &def.variants {
for field in &variant.fields {
if let ty::FnDef(..) = field.ty(cx.tcx, substs).kind() {
return; return;
} }
} }
for subst in substs { // Derive constrains all generic types to requiring Clone. Check if any type is not constrained for
if let ty::subst::GenericArgKind::Type(subst) = subst.unpack() { // this impl.
if let ty::Param(_) = subst.kind() { if ty_subs.types().any(|ty| !implements_trait(cx, ty, clone_id, &[])) {
return; return;
} }
}
}
}
},
_ => (),
}
span_lint_and_note( span_lint_and_note(
cx, cx,
@ -334,7 +340,6 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
Some(item.span), Some(item.span),
"consider deriving `Clone` or removing `Copy`", "consider deriving `Clone` or removing `Copy`",
); );
}
} }
/// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint. /// Implementation of the `UNSAFE_DERIVE_DESERIALIZE` lint.

View file

@ -11,7 +11,7 @@ use rustc_errors::emitter::EmitterWriter;
use rustc_errors::Handler; use rustc_errors::Handler;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{Expr, ExprKind, QPath}; use rustc_hir::{AnonConst, Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
@ -710,16 +710,22 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
// check for `begin_panic` // check for `begin_panic`
if_chain! { if_chain! {
if let ExprKind::Call(ref func_expr, _) = expr.kind; if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id(); if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.cx, path_def_id); if match_panic_def_id(self.cx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none(); if is_expn_of(expr.span, "unreachable").is_none();
if !is_expn_of_debug_assertions(expr.span);
then { then {
self.panic_span = Some(expr.span); self.panic_span = Some(expr.span);
} }
} }
// check for `assert_eq` or `assert_ne`
if is_expn_of(expr.span, "assert_eq").is_some() || is_expn_of(expr.span, "assert_ne").is_some() {
self.panic_span = Some(expr.span);
}
// check for `unwrap` // check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
@ -734,7 +740,15 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
intravisit::walk_expr(self, expr); intravisit::walk_expr(self, expr);
} }
// Panics in const blocks will cause compilation to fail.
fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
} }
} }
fn is_expn_of_debug_assertions(span: Span) -> bool {
const MACRO_NAMES: &[&str] = &["debug_assert", "debug_assert_eq", "debug_assert_ne"];
MACRO_NAMES.iter().any(|name| is_expn_of(span, name).is_some())
}

View file

@ -47,7 +47,7 @@ impl<'tcx> DoubleComparisons {
}, },
_ => return, _ => return,
}; };
if !(eq_expr_value(cx, &llhs, &rlhs) && eq_expr_value(cx, &lrhs, &rrhs)) { if !(eq_expr_value(cx, llhs, rlhs) && eq_expr_value(cx, lrhs, rrhs)) {
return; return;
} }
macro_rules! lint_double_comparison { macro_rules! lint_double_comparison {
@ -88,7 +88,7 @@ impl<'tcx> DoubleComparisons {
impl<'tcx> LateLintPass<'tcx> for DoubleComparisons { impl<'tcx> LateLintPass<'tcx> for DoubleComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = expr.kind { if let ExprKind::Binary(ref kind, lhs, rhs) = expr.kind {
Self::check_binop(cx, kind.node, lhs, rhs, expr.span); Self::check_binop(cx, kind.node, lhs, rhs, expr.span);
} }
} }

View file

@ -50,7 +50,7 @@ impl EarlyLintPass for DoubleParens {
match expr.kind { match expr.kind {
ExprKind::Paren(ref in_paren) => match in_paren.kind { ExprKind::Paren(ref in_paren) => match in_paren.kind {
ExprKind::Paren(_) | ExprKind::Tup(_) => { ExprKind::Paren(_) | ExprKind::Tup(_) => {
span_lint(cx, DOUBLE_PARENS, expr.span, &msg); span_lint(cx, DOUBLE_PARENS, expr.span, msg);
}, },
_ => {}, _ => {},
}, },
@ -58,7 +58,7 @@ impl EarlyLintPass for DoubleParens {
if params.len() == 1 { if params.len() == 1 {
let param = &params[0]; let param = &params[0];
if let ExprKind::Paren(_) = param.kind { if let ExprKind::Paren(_) = param.kind {
span_lint(cx, DOUBLE_PARENS, param.span, &msg); span_lint(cx, DOUBLE_PARENS, param.span, msg);
} }
} }
}, },
@ -66,7 +66,7 @@ impl EarlyLintPass for DoubleParens {
if params.len() == 2 { if params.len() == 2 {
let param = &params[1]; let param = &params[1];
if let ExprKind::Paren(_) = param.kind { if let ExprKind::Paren(_) = param.kind {
span_lint(cx, DOUBLE_PARENS, param.span, &msg); span_lint(cx, DOUBLE_PARENS, param.span, msg);
} }
} }
}, },

View file

@ -113,7 +113,7 @@ declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COP
impl<'tcx> LateLintPass<'tcx> for DropForgetRef { impl<'tcx> LateLintPass<'tcx> for DropForgetRef {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(ref path, ref args) = expr.kind; if let ExprKind::Call(path, args) = expr.kind;
if let ExprKind::Path(ref qpath) = path.kind; if let ExprKind::Path(ref qpath) = path.kind;
if args.len() == 1; if args.len() == 1;
if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id();

View file

@ -43,8 +43,8 @@ declare_lint_pass!(DurationSubsec => [DURATION_SUBSEC]);
impl<'tcx> LateLintPass<'tcx> for DurationSubsec { impl<'tcx> LateLintPass<'tcx> for DurationSubsec {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, ref left, ref right) = expr.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Div, .. }, left, right) = expr.kind;
if let ExprKind::MethodCall(ref method_path, _ , ref args, _) = left.kind; if let ExprKind::MethodCall(method_path, _ , args, _) = left.kind;
if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION); if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::DURATION);
if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right); if let Some((Constant::Int(divisor), _)) = constant(cx, cx.typeck_results(), right);
then { then {

View file

@ -57,14 +57,14 @@ declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
impl<'tcx> LateLintPass<'tcx> for HashMapPass { impl<'tcx> LateLintPass<'tcx> for HashMapPass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::If(ref check, ref then_block, ref else_block) = expr.kind { if let ExprKind::If(check, then_block, ref else_block) = expr.kind {
if let ExprKind::Unary(UnOp::Not, ref check) = check.kind { if let ExprKind::Unary(UnOp::Not, check) = check.kind {
if let Some((ty, map, key)) = check_cond(cx, check) { if let Some((ty, map, key)) = check_cond(cx, check) {
// in case of `if !m.contains_key(&k) { m.insert(k, v); }` // in case of `if !m.contains_key(&k) { m.insert(k, v); }`
// we can give a better error message // we can give a better error message
let sole_expr = { let sole_expr = {
else_block.is_none() else_block.is_none()
&& if let ExprKind::Block(ref then_block, _) = then_block.kind { && if let ExprKind::Block(then_block, _) = then_block.kind {
(then_block.expr.is_some() as usize) + then_block.stmts.len() == 1 (then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
} else { } else {
true true
@ -81,9 +81,9 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
sole_expr, sole_expr,
}; };
walk_expr(&mut visitor, &**then_block); walk_expr(&mut visitor, then_block);
} }
} else if let Some(ref else_block) = *else_block { } else if let Some(else_block) = *else_block {
if let Some((ty, map, key)) = check_cond(cx, check) { if let Some((ty, map, key)) = check_cond(cx, check) {
let mut visitor = InsertVisitor { let mut visitor = InsertVisitor {
cx, cx,
@ -103,10 +103,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass {
fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> { fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref path, _, ref params, _) = check.kind; if let ExprKind::MethodCall(path, _, params, _) = check.kind;
if params.len() >= 2; if params.len() >= 2;
if path.ident.name == sym!(contains_key); if path.ident.name == sym!(contains_key);
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref key) = params[1].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, key) = params[1].kind;
then { then {
let map = &params[0]; let map = &params[0];
let obj_ty = cx.typeck_results().expr_ty(map).peel_refs(); let obj_ty = cx.typeck_results().expr_ty(map).peel_refs();
@ -140,7 +140,7 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref path, _, ref params, _) = expr.kind; if let ExprKind::MethodCall(path, _, params, _) = expr.kind;
if params.len() == 3; if params.len() == 3;
if path.ident.name == sym!(insert); if path.ident.name == sym!(insert);
if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]); if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]);

View file

@ -65,12 +65,12 @@ const ASSERT_MACRO_NAMES: [&str; 4] = ["assert_eq", "assert_ne", "debug_assert_e
impl<'tcx> LateLintPass<'tcx> for EqOp { impl<'tcx> LateLintPass<'tcx> for EqOp {
#[allow(clippy::similar_names, clippy::too_many_lines)] #[allow(clippy::similar_names, clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Block(ref block, _) = e.kind { if let ExprKind::Block(block, _) = e.kind {
for stmt in block.stmts { for stmt in block.stmts {
for amn in &ASSERT_MACRO_NAMES { for amn in &ASSERT_MACRO_NAMES {
if_chain! { if_chain! {
if is_expn_of(stmt.span, amn).is_some(); if is_expn_of(stmt.span, amn).is_some();
if let StmtKind::Semi(ref matchexpr) = stmt.kind; if let StmtKind::Semi(matchexpr) = stmt.kind;
if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr); if let Some(macro_args) = higher::extract_assert_macro_args(matchexpr);
if macro_args.len() == 2; if macro_args.len() == 2;
let (lhs, rhs) = (macro_args[0], macro_args[1]); let (lhs, rhs) = (macro_args[0], macro_args[1]);
@ -88,12 +88,12 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
} }
} }
} }
if let ExprKind::Binary(op, ref left, ref right) = e.kind { if let ExprKind::Binary(op, left, right) = e.kind {
if e.span.from_expansion() { if e.span.from_expansion() {
return; return;
} }
let macro_with_not_op = |expr_kind: &ExprKind<'_>| { let macro_with_not_op = |expr_kind: &ExprKind<'_>| {
if let ExprKind::Unary(_, ref expr) = *expr_kind { if let ExprKind::Unary(_, expr) = *expr_kind {
in_macro(expr.span) in_macro(expr.span)
} else { } else {
false false
@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
// do not suggest to dereference literals // do not suggest to dereference literals
(&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {}, (&ExprKind::Lit(..), _) | (_, &ExprKind::Lit(..)) => {},
// &foo == &bar // &foo == &bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => { (&ExprKind::AddrOf(BorrowKind::Ref, _, l), &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let lty = cx.typeck_results().expr_ty(l); let lty = cx.typeck_results().expr_ty(l);
let rty = cx.typeck_results().expr_ty(r); let rty = cx.typeck_results().expr_ty(r);
let lcpy = is_copy(cx, lty); let lcpy = is_copy(cx, lty);
@ -198,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
} }
}, },
// &foo == bar // &foo == bar
(&ExprKind::AddrOf(BorrowKind::Ref, _, ref l), _) => { (&ExprKind::AddrOf(BorrowKind::Ref, _, l), _) => {
let lty = cx.typeck_results().expr_ty(l); let lty = cx.typeck_results().expr_ty(l);
let lcpy = is_copy(cx, lty); let lcpy = is_copy(cx, lty);
if (requires_ref || lcpy) if (requires_ref || lcpy)
@ -222,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
} }
}, },
// foo == &bar // foo == &bar
(_, &ExprKind::AddrOf(BorrowKind::Ref, _, ref r)) => { (_, &ExprKind::AddrOf(BorrowKind::Ref, _, r)) => {
let rty = cx.typeck_results().expr_ty(r); let rty = cx.typeck_results().expr_ty(r);
let rcpy = is_copy(cx, rty); let rcpy = is_copy(cx, rty);
if (requires_ref || rcpy) if (requires_ref || rcpy)

View file

@ -34,7 +34,7 @@ impl<'tcx> LateLintPass<'tcx> for ErasingOp {
if e.span.from_expansion() { if e.span.from_expansion() {
return; return;
} }
if let ExprKind::Binary(ref cmp, ref left, ref right) = e.kind { if let ExprKind::Binary(ref cmp, left, right) = e.kind {
match cmp.node { match cmp.node {
BinOpKind::Mul | BinOpKind::BitAnd => { BinOpKind::Mul | BinOpKind::BitAnd => {
check(cx, left, e.span); check(cx, left, e.span);

View file

@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
} }
fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Closure(_, ref decl, eid, _, _) = expr.kind { if let ExprKind::Closure(_, decl, eid, _, _) = expr.kind {
let body = cx.tcx.hir().body(eid); let body = cx.tcx.hir().body(eid);
let ex = &body.value; let ex = &body.value;
@ -109,7 +109,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
} }
if_chain!( if_chain!(
if let ExprKind::Call(ref caller, ref args) = ex.kind; if let ExprKind::Call(caller, args) = ex.kind;
if let ExprKind::Path(_) = caller.kind; if let ExprKind::Path(_) = caller.kind;
@ -142,7 +142,7 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) {
); );
if_chain!( if_chain!(
if let ExprKind::MethodCall(ref path, _, ref args, _) = ex.kind; if let ExprKind::MethodCall(path, _, args, _) = ex.kind;
// Not the same number of arguments, there is no way the closure is the same as the function return; // Not the same number of arguments, there is no way the closure is the same as the function return;
if args.len() == decl.inputs.len(); if args.len() == decl.inputs.len();
@ -178,7 +178,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
let actual_type_of_self = &cx.typeck_results().node_type(self_arg.hir_id); let actual_type_of_self = &cx.typeck_results().node_type(self_arg.hir_id);
if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) { if let Some(trait_id) = cx.tcx.trait_of_item(method_def_id) {
if match_borrow_depth(expected_type_of_self, &actual_type_of_self) if match_borrow_depth(expected_type_of_self, actual_type_of_self)
&& implements_trait(cx, actual_type_of_self, trait_id, &[]) && implements_trait(cx, actual_type_of_self, trait_id, &[])
{ {
return Some(cx.tcx.def_path_str(trait_id)); return Some(cx.tcx.def_path_str(trait_id));
@ -187,8 +187,8 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
cx.tcx.impl_of_method(method_def_id).and_then(|_| { cx.tcx.impl_of_method(method_def_id).and_then(|_| {
//a type may implicitly implement other type's methods (e.g. Deref) //a type may implicitly implement other type's methods (e.g. Deref)
if match_types(expected_type_of_self, &actual_type_of_self) { if match_types(expected_type_of_self, actual_type_of_self) {
return Some(get_type_name(cx, &actual_type_of_self)); return Some(get_type_name(cx, actual_type_of_self));
} }
None None
}) })
@ -196,7 +196,7 @@ fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: def_id::DefId, self_a
fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool { fn match_borrow_depth(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
match (&lhs.kind(), &rhs.kind()) { match (&lhs.kind(), &rhs.kind()) {
(ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(&t1, &t2), (ty::Ref(_, t1, mut1), ty::Ref(_, t2, mut2)) => mut1 == mut2 && match_borrow_depth(t1, t2),
(l, r) => !matches!((l, r), (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _))), (l, r) => !matches!((l, r), (ty::Ref(_, _, _), _) | (_, ty::Ref(_, _, _))),
} }
} }
@ -218,7 +218,7 @@ fn match_types(lhs: Ty<'_>, rhs: Ty<'_>) -> bool {
fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String { fn get_type_name(cx: &LateContext<'_>, ty: Ty<'_>) -> String {
match ty.kind() { match ty.kind() {
ty::Adt(t, _) => cx.tcx.def_path_str(t.did), ty::Adt(t, _) => cx.tcx.def_path_str(t.did),
ty::Ref(_, r, _) => get_type_name(cx, &r), ty::Ref(_, r, _) => get_type_name(cx, r),
_ => ty.to_string(), _ => ty.to_string(),
} }
} }
@ -230,7 +230,7 @@ fn compare_inputs(
for (closure_input, function_arg) in closure_inputs.zip(call_args) { for (closure_input, function_arg) in closure_inputs.zip(call_args) {
if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind { if let PatKind::Binding(_, _, ident, _) = closure_input.pat.kind {
// XXXManishearth Should I be checking the binding mode here? // XXXManishearth Should I be checking the binding mode here?
if let ExprKind::Path(QPath::Resolved(None, ref p)) = function_arg.kind { if let ExprKind::Path(QPath::Resolved(None, p)) = function_arg.kind {
if p.segments.len() != 1 { if p.segments.len() != 1 {
// If it's a proper path, it can't be a local variable // If it's a proper path, it can't be a local variable
return false; return false;

View file

@ -71,7 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Find a write to a local variable. // Find a write to a local variable.
match expr.kind { match expr.kind {
ExprKind::Assign(ref lhs, ..) | ExprKind::AssignOp(_, ref lhs, _) => { ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) => {
if let Some(var) = path_to_local(lhs) { if let Some(var) = path_to_local(lhs) {
let mut visitor = ReadVisitor { let mut visitor = ReadVisitor {
cx, cx,
@ -87,12 +87,12 @@ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
} }
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
match stmt.kind { match stmt.kind {
StmtKind::Local(ref local) => { StmtKind::Local(local) => {
if let Local { init: Some(ref e), .. } = **local { if let Local { init: Some(e), .. } = local {
DivergenceVisitor { cx }.visit_expr(e); DivergenceVisitor { cx }.visit_expr(e);
} }
}, },
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => DivergenceVisitor { cx }.maybe_walk_expr(e), StmtKind::Expr(e) | StmtKind::Semi(e) => DivergenceVisitor { cx }.maybe_walk_expr(e),
StmtKind::Item(..) => {}, StmtKind::Item(..) => {},
} }
} }
@ -106,7 +106,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) { fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind { match e.kind {
ExprKind::Closure(..) => {}, ExprKind::Closure(..) => {},
ExprKind::Match(ref e, arms, _) => { ExprKind::Match(e, arms, _) => {
self.visit_expr(e); self.visit_expr(e);
for arm in arms { for arm in arms {
if let Some(Guard::If(if_expr)) = arm.guard { if let Some(Guard::If(if_expr)) = arm.guard {
@ -130,7 +130,7 @@ impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> {
fn visit_expr(&mut self, e: &'tcx Expr<'_>) { fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind { match e.kind {
ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e), ExprKind::Continue(_) | ExprKind::Break(_, _) | ExprKind::Ret(_) => self.report_diverging_sub_expr(e),
ExprKind::Call(ref func, _) => { ExprKind::Call(func, _) => {
let typ = self.cx.typeck_results().expr_ty(func); let typ = self.cx.typeck_results().expr_ty(func);
match typ.kind() { match typ.kind() {
ty::FnDef(..) | ty::FnPtr(_) => { ty::FnDef(..) | ty::FnPtr(_) => {
@ -266,10 +266,10 @@ fn check_expr<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, expr: &'tcx Expr<'_>) -
fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly { fn check_stmt<'a, 'tcx>(vis: &mut ReadVisitor<'a, 'tcx>, stmt: &'tcx Stmt<'_>) -> StopEarly {
match stmt.kind { match stmt.kind {
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => check_expr(vis, expr), StmtKind::Expr(expr) | StmtKind::Semi(expr) => check_expr(vis, expr),
// If the declaration is of a local variable, check its initializer // If the declaration is of a local variable, check its initializer
// expression if it has one. Otherwise, keep going. // expression if it has one. Otherwise, keep going.
StmtKind::Local(ref local) => local StmtKind::Local(local) => local
.init .init
.as_ref() .as_ref()
.map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)), .map_or(StopEarly::KeepGoing, |expr| check_expr(vis, expr)),
@ -343,7 +343,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
/// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`. /// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`.
fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, expr) { if let Some(parent) = get_parent_expr(cx, expr) {
if let ExprKind::Assign(ref lhs, ..) = parent.kind { if let ExprKind::Assign(lhs, ..) = parent.kind {
return lhs.hir_id == expr.hir_id; return lhs.hir_id == expr.hir_id;
} }
} }

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{attr_by_name, in_macro, match_path_ast}; use clippy_utils::{in_macro, match_path_ast};
use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind}; use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind};
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span; use rustc_span::{sym, Span};
use std::convert::TryInto; use std::convert::TryInto;
@ -138,7 +138,7 @@ impl EarlyLintPass for ExcessiveBools {
} }
match &item.kind { match &item.kind {
ItemKind::Struct(variant_data, _) => { ItemKind::Struct(variant_data, _) => {
if attr_by_name(&item.attrs, "repr").is_some() { if item.attrs.iter().any(|attr| attr.has_name(sym::repr)) {
return; return;
} }

View file

@ -28,21 +28,19 @@ declare_lint_pass!(Exit => [EXIT]);
impl<'tcx> LateLintPass<'tcx> for Exit { impl<'tcx> LateLintPass<'tcx> for Exit {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(ref path_expr, ref _args) = e.kind; if let ExprKind::Call(path_expr, _args) = e.kind;
if let ExprKind::Path(ref path) = path_expr.kind; if let ExprKind::Path(ref path) = path_expr.kind;
if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(path, path_expr.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::EXIT); if match_def_path(cx, def_id, &paths::EXIT);
then {
let parent = cx.tcx.hir().get_parent_item(e.hir_id); let parent = cx.tcx.hir().get_parent_item(e.hir_id);
if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent) { if let Some(Node::Item(Item{kind: ItemKind::Fn(..), ..})) = cx.tcx.hir().find(parent);
// If the next item up is a function we check if it is an entry point // If the next item up is a function we check if it is an entry point
// and only then emit a linter warning // and only then emit a linter warning
let def_id = cx.tcx.hir().local_def_id(parent); let def_id = cx.tcx.hir().local_def_id(parent);
if !is_entrypoint_fn(cx, def_id.to_def_id()) { if !is_entrypoint_fn(cx, def_id.to_def_id());
then {
span_lint(cx, EXIT, e.span, "usage of `process::exit`"); span_lint(cx, EXIT, e.span, "usage of `process::exit`");
} }
} }
} }
}
}
} }

View file

@ -34,11 +34,11 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// match call to unwrap // match call to unwrap
if let ExprKind::MethodCall(ref unwrap_fun, _, ref unwrap_args, _) = expr.kind; if let ExprKind::MethodCall(unwrap_fun, _, unwrap_args, _) = expr.kind;
if unwrap_fun.ident.name == sym::unwrap; if unwrap_fun.ident.name == sym::unwrap;
// match call to write_fmt // match call to write_fmt
if !unwrap_args.is_empty(); if !unwrap_args.is_empty();
if let ExprKind::MethodCall(ref write_fun, _, write_args, _) = if let ExprKind::MethodCall(write_fun, _, write_args, _) =
unwrap_args[0].kind; unwrap_args[0].kind;
if write_fun.ident.name == sym!(write_fmt); if write_fun.ident.name == sym!(write_fmt);
// match calls to std::io::stdout() / std::io::stderr () // match calls to std::io::stdout() / std::io::stderr ()
@ -135,10 +135,10 @@ fn write_output_string(write_args: &[Expr<'_>]) -> Option<String> {
if_chain! { if_chain! {
// Obtain the string that should be printed // Obtain the string that should be printed
if write_args.len() > 1; if write_args.len() > 1;
if let ExprKind::Call(_, ref output_args) = write_args[1].kind; if let ExprKind::Call(_, output_args) = write_args[1].kind;
if !output_args.is_empty(); if !output_args.is_empty();
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref output_string_expr) = output_args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, output_string_expr) = output_args[0].kind;
if let ExprKind::Array(ref string_exprs) = output_string_expr.kind; if let ExprKind::Array(string_exprs) = output_string_expr.kind;
// we only want to provide an automatic suggestion for simple (non-format) strings // we only want to provide an automatic suggestion for simple (non-format) strings
if string_exprs.len() == 1; if string_exprs.len() == 1;
if let ExprKind::Lit(ref lit) = string_exprs[0].kind; if let ExprKind::Lit(ref lit) = string_exprs[0].kind;

View file

@ -81,8 +81,8 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
// check for `begin_panic` // check for `begin_panic`
if_chain! { if_chain! {
if let ExprKind::Call(ref func_expr, _) = expr.kind; if let ExprKind::Call(func_expr, _) = expr.kind;
if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; if let ExprKind::Path(QPath::Resolved(_, path)) = func_expr.kind;
if let Some(path_def_id) = path.res.opt_def_id(); if let Some(path_def_id) = path.res.opt_def_id();
if match_panic_def_id(self.lcx, path_def_id); if match_panic_def_id(self.lcx, path_def_id);
if is_expn_of(expr.span, "unreachable").is_none(); if is_expn_of(expr.span, "unreachable").is_none();

View file

@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
let rhs; let rhs;
// check if expr is a binary expression with a lt or gt operator // check if expr is a binary expression with a lt or gt operator
if let ExprKind::Binary(op, ref left, ref right) = expr.kind { if let ExprKind::Binary(op, left, right) = expr.kind {
match op.node { match op.node {
BinOpKind::Lt => { BinOpKind::Lt => {
lhs = left; lhs = left;
@ -88,8 +88,8 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs {
if let ty::Float(_) = t_val_r.kind(); if let ty::Float(_) = t_val_r.kind();
then { then {
let sug_l = sugg::Sugg::hir(cx, &val_l, ".."); let sug_l = sugg::Sugg::hir(cx, val_l, "..");
let sug_r = sugg::Sugg::hir(cx, &val_r, ".."); let sug_r = sugg::Sugg::hir(cx, val_r, "..");
// format the suggestion // format the suggestion
let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()); let suggestion = format!("{}.abs()", sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par());
// spans the lint // spans the lint

View file

@ -61,8 +61,8 @@ declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]);
impl<'tcx> LateLintPass<'tcx> for FloatLiteral { impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if_chain! {
if let ty::Float(fty) = *ty.kind(); if let ty::Float(fty) = *ty.kind();
if let hir::ExprKind::Lit(ref lit) = expr.kind; if let hir::ExprKind::Lit(ref lit) = expr.kind;
if let LitKind::Float(sym, lit_float_ty) = lit.node; if let LitKind::Float(sym, lit_float_ty) = lit.node;

View file

@ -131,7 +131,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su
let mut suggestion = Sugg::hir(cx, expr, ".."); let mut suggestion = Sugg::hir(cx, expr, "..");
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
expr = &inner_expr; expr = inner_expr;
} }
if_chain! { if_chain! {
@ -313,8 +313,8 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
Spanned { Spanned {
node: BinOpKind::Add, .. node: BinOpKind::Add, ..
}, },
ref lhs, lhs,
ref rhs, rhs,
) = parent.kind ) = parent.kind
{ {
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
@ -329,7 +329,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
"{}.mul_add({}, {})", "{}.mul_add({}, {})",
Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], ".."),
Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], ".."),
Sugg::hir(cx, &other_addend, ".."), Sugg::hir(cx, other_addend, ".."),
), ),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
@ -356,18 +356,18 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
Spanned { Spanned {
node: BinOpKind::Add, .. node: BinOpKind::Add, ..
}, },
ref add_lhs, add_lhs,
ref add_rhs, add_rhs,
) = args[0].kind ) = args[0].kind
{ {
// check if expression of the form x * x + y * y // check if expression of the form x * x + y * y
if_chain! { if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lmul_lhs, ref lmul_rhs) = add_lhs.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lmul_lhs, lmul_rhs) = add_lhs.kind;
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref rmul_lhs, ref rmul_rhs) = add_rhs.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, rmul_lhs, rmul_rhs) = add_rhs.kind;
if eq_expr_value(cx, lmul_lhs, lmul_rhs); if eq_expr_value(cx, lmul_lhs, lmul_rhs);
if eq_expr_value(cx, rmul_lhs, rmul_rhs); if eq_expr_value(cx, rmul_lhs, rmul_rhs);
then { then {
return Some(format!("{}.hypot({})", Sugg::hir(cx, &lmul_lhs, ".."), Sugg::hir(cx, &rmul_lhs, ".."))); return Some(format!("{}.hypot({})", Sugg::hir(cx, lmul_lhs, ".."), Sugg::hir(cx, rmul_lhs, "..")));
} }
} }
@ -376,13 +376,13 @@ fn detect_hypot(cx: &LateContext<'_>, args: &[Expr<'_>]) -> Option<String> {
if let ExprKind::MethodCall( if let ExprKind::MethodCall(
PathSegment { ident: lmethod_name, .. }, PathSegment { ident: lmethod_name, .. },
ref _lspan, ref _lspan,
ref largs, largs,
_ _
) = add_lhs.kind; ) = add_lhs.kind;
if let ExprKind::MethodCall( if let ExprKind::MethodCall(
PathSegment { ident: rmethod_name, .. }, PathSegment { ident: rmethod_name, .. },
ref _rspan, ref _rspan,
ref rargs, rargs,
_ _
) = add_rhs.kind; ) = add_rhs.kind;
if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi"; if lmethod_name.as_str() == "powi" && rmethod_name.as_str() == "powi";
@ -416,11 +416,11 @@ fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) {
// and suggest usage of `x.exp_m1() - (y - 1)` instead // and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, ref lhs, ref rhs) = expr.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Sub, .. }, lhs, rhs) = expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point(); if cx.typeck_results().expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs); if let Some((value, _)) = constant(cx, cx.typeck_results(), rhs);
if F32(1.0) == value || F64(1.0) == value; if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(ref path, _, ref method_args, _) = lhs.kind; if let ExprKind::MethodCall(path, _, method_args, _) = lhs.kind;
if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point(); if cx.typeck_results().expr_ty(&method_args[0]).is_floating_point();
if path.ident.name.as_str() == "exp"; if path.ident.name.as_str() == "exp";
then { then {
@ -442,7 +442,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if_chain! { if_chain! {
if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, ref lhs, ref rhs) = &expr.kind; if let ExprKind::Binary(Spanned { node: BinOpKind::Mul, .. }, lhs, rhs) = &expr.kind;
if cx.typeck_results().expr_ty(lhs).is_floating_point(); if cx.typeck_results().expr_ty(lhs).is_floating_point();
if cx.typeck_results().expr_ty(rhs).is_floating_point(); if cx.typeck_results().expr_ty(rhs).is_floating_point();
then { then {
@ -604,8 +604,8 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool { fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if_chain! { if_chain! {
if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, ref args_a, _) = expr_a.kind; if let ExprKind::MethodCall(PathSegment { ident: method_name_a, .. }, _, args_a, _) = expr_a.kind;
if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, ref args_b, _) = expr_b.kind; if let ExprKind::MethodCall(PathSegment { ident: method_name_b, .. }, _, args_b, _) = expr_b.kind;
then { then {
return method_name_a.as_str() == method_name_b.as_str() && return method_name_a.as_str() == method_name_b.as_str() &&
args_a.len() == args_b.len() && args_a.len() == args_b.len() &&
@ -630,8 +630,8 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
rhs, rhs,
) = &expr.kind; ) = &expr.kind;
if are_same_base_logs(cx, lhs, rhs); if are_same_base_logs(cx, lhs, rhs);
if let ExprKind::MethodCall(_, _, ref largs, _) = lhs.kind; if let ExprKind::MethodCall(_, _, largs, _) = lhs.kind;
if let ExprKind::MethodCall(_, _, ref rargs, _) = rhs.kind; if let ExprKind::MethodCall(_, _, rargs, _) = rhs.kind;
then { then {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -675,7 +675,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span, expr.span,
"conversion to degrees can be done more accurately", "conversion to degrees can be done more accurately",
"consider using", "consider using",
format!("{}.to_degrees()", Sugg::hir(cx, &mul_lhs, "..")), format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..")),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} else if } else if
@ -688,7 +688,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
expr.span, expr.span,
"conversion to radians can be done more accurately", "conversion to radians can be done more accurately",
"consider using", "consider using",
format!("{}.to_radians()", Sugg::hir(cx, &mul_lhs, "..")), format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..")),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }
@ -698,7 +698,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic { impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::MethodCall(ref path, _, args, _) = &expr.kind { if let ExprKind::MethodCall(path, _, args, _) = &expr.kind {
let recv_ty = cx.typeck_results().expr_ty(&args[0]); let recv_ty = cx.typeck_results().expr_ty(&args[0]);
if recv_ty.is_floating_point() { if recv_ty.is_floating_point() {

View file

@ -78,8 +78,8 @@ fn span_useless_format<T: LintContext>(cx: &T, span: Span, help: &str, mut sugg:
fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) -> Option<String> { fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) -> Option<String> {
if_chain! { if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref format_args) = expr.kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, format_args) = expr.kind;
if let ExprKind::Array(ref elems) = arms[0].body.kind; if let ExprKind::Array(elems) = arms[0].body.kind;
if elems.len() == 1; if elems.len() == 1;
if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW); if let Some(args) = match_function_call(cx, &elems[0], &paths::FMT_ARGUMENTV1_NEW);
// matches `core::fmt::Display::fmt` // matches `core::fmt::Display::fmt`
@ -88,10 +88,10 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
if let Some(did) = cx.qpath_res(qpath, args[1].hir_id).opt_def_id(); if let Some(did) = cx.qpath_res(qpath, args[1].hir_id).opt_def_id();
if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD); if match_def_path(cx, did, &paths::DISPLAY_FMT_METHOD);
// check `(arg0,)` in match block // check `(arg0,)` in match block
if let PatKind::Tuple(ref pats, None) = arms[0].pat.kind; if let PatKind::Tuple(pats, None) = arms[0].pat.kind;
if pats.len() == 1; if pats.len() == 1;
then { then {
let ty = cx.typeck_results().pat_ty(&pats[0]).peel_refs(); let ty = cx.typeck_results().pat_ty(pats[0]).peel_refs();
if *ty.kind() != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym::string_type) { if *ty.kind() != rustc_middle::ty::Str && !is_type_diagnostic_item(cx, ty, sym::string_type) {
return None; return None;
} }
@ -101,7 +101,7 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
} }
} else { } else {
let snip = snippet(cx, format_args.span, "<arg>"); let snip = snippet(cx, format_args.span, "<arg>");
if let ExprKind::MethodCall(ref path, _, _, _) = format_args.kind { if let ExprKind::MethodCall(path, _, _, _) = format_args.kind {
if path.ident.name == sym!(to_string) { if path.ident.name == sym!(to_string) {
return Some(format!("{}", snip)); return Some(format!("{}", snip));
} }
@ -120,16 +120,16 @@ fn on_new_v1<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Strin
if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1); if let Some(args) = match_function_call(cx, expr, &paths::FMT_ARGUMENTS_NEW_V1);
if args.len() == 2; if args.len() == 2;
// Argument 1 in `new_v1()` // Argument 1 in `new_v1()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
if let ExprKind::Array(ref pieces) = arr.kind; if let ExprKind::Array(pieces) = arr.kind;
if pieces.len() == 1; if pieces.len() == 1;
if let ExprKind::Lit(ref lit) = pieces[0].kind; if let ExprKind::Lit(ref lit) = pieces[0].kind;
if let LitKind::Str(ref s, _) = lit.node; if let LitKind::Str(ref s, _) = lit.node;
// Argument 2 in `new_v1()` // Argument 2 in `new_v1()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
if arms.len() == 1; if arms.len() == 1;
if let ExprKind::Tup(ref tup) = matchee.kind; if let ExprKind::Tup(tup) = matchee.kind;
then { then {
// `format!("foo")` expansion contains `match () { () => [], }` // `format!("foo")` expansion contains `match () { () => [], }`
if tup.is_empty() { if tup.is_empty() {
@ -152,16 +152,16 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
if args.len() == 3; if args.len() == 3;
if check_unformatted(&args[2]); if check_unformatted(&args[2]);
// Argument 1 in `new_v1_formatted()` // Argument 1 in `new_v1_formatted()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arr) = args[0].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arr) = args[0].kind;
if let ExprKind::Array(ref pieces) = arr.kind; if let ExprKind::Array(pieces) = arr.kind;
if pieces.len() == 1; if pieces.len() == 1;
if let ExprKind::Lit(ref lit) = pieces[0].kind; if let ExprKind::Lit(ref lit) = pieces[0].kind;
if let LitKind::Str(..) = lit.node; if let LitKind::Str(..) = lit.node;
// Argument 2 in `new_v1_formatted()` // Argument 2 in `new_v1_formatted()`
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg1) = args[1].kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arg1) = args[1].kind;
if let ExprKind::Match(ref matchee, ref arms, MatchSource::Normal) = arg1.kind; if let ExprKind::Match(matchee, arms, MatchSource::Normal) = arg1.kind;
if arms.len() == 1; if arms.len() == 1;
if let ExprKind::Tup(ref tup) = matchee.kind; if let ExprKind::Tup(tup) = matchee.kind;
then { then {
return on_argumentv1_new(cx, &tup[0], arms); return on_argumentv1_new(cx, &tup[0], arms);
} }
@ -182,14 +182,14 @@ fn on_new_v1_fmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<S
/// ``` /// ```
fn check_unformatted(expr: &Expr<'_>) -> bool { fn check_unformatted(expr: &Expr<'_>) -> bool {
if_chain! { if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) = expr.kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, expr) = expr.kind;
if let ExprKind::Array(ref exprs) = expr.kind; if let ExprKind::Array(exprs) = expr.kind;
if exprs.len() == 1; if exprs.len() == 1;
// struct `core::fmt::rt::v1::Argument` // struct `core::fmt::rt::v1::Argument`
if let ExprKind::Struct(_, ref fields, _) = exprs[0].kind; if let ExprKind::Struct(_, fields, _) = exprs[0].kind;
if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format); if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
// struct `core::fmt::rt::v1::FormatSpec` // struct `core::fmt::rt::v1::FormatSpec`
if let ExprKind::Struct(_, ref fields, _) = format_field.expr.kind; if let ExprKind::Struct(_, fields, _) = format_field.expr.kind;
if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision); if let Some(precision_field) = fields.iter().find(|f| f.ident.name == sym::precision);
if let ExprKind::Path(ref precision_path) = precision_field.expr.kind; if let ExprKind::Path(ref precision_path) = precision_field.expr.kind;
if last_path_segment(precision_path).ident.name == sym::Implied; if last_path_segment(precision_path).ident.name == sym::Implied;

View file

@ -217,9 +217,8 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
if let Some(else_snippet) = snippet_opt(cx, else_span); if let Some(else_snippet) = snippet_opt(cx, else_span);
if let Some(else_pos) = else_snippet.find("else"); if let Some(else_pos) = else_snippet.find("else");
if else_snippet[else_pos..].contains('\n'); if else_snippet[else_pos..].contains('\n');
let else_desc = if is_if(else_) { "if" } else { "{..}" };
then { then {
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note( span_lint_and_note(
cx, cx,
SUSPICIOUS_ELSE_FORMATTING, SUSPICIOUS_ELSE_FORMATTING,

View file

@ -1,738 +0,0 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{is_must_use_ty, is_type_diagnostic_item, type_is_unsafe_function};
use clippy_utils::{
attr_by_name, attrs::is_proc_macro, is_trait_impl_item, iter_input_pats, match_def_path, must_use_attr,
path_to_local, return_ty, trait_ref_of_method,
};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_hir::{def::Res, def_id::DefId, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use rustc_span::sym;
use rustc_target::spec::abi::Abi;
use rustc_typeck::hir_ty_to_ty;
declare_clippy_lint! {
/// **What it does:** Checks for functions with too many parameters.
///
/// **Why is this bad?** Functions with lots of parameters are considered bad
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
/// grouping some parameters into a new type.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct Color;
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
/// // ..
/// }
/// ```
pub TOO_MANY_ARGUMENTS,
complexity,
"functions with too many arguments"
}
declare_clippy_lint! {
/// **What it does:** Checks for functions with a large amount of lines.
///
/// **Why is this bad?** Functions with a lot of lines are harder to understand
/// due to having to look at a larger amount of code to understand what the
/// function is doing. Consider splitting the body of the function into
/// multiple functions.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// fn im_too_long() {
/// println!("");
/// // ... 100 more LoC
/// println!("");
/// }
/// ```
pub TOO_MANY_LINES,
pedantic,
"functions with too many lines"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that dereference raw pointer
/// arguments but are not marked unsafe.
///
/// **Why is this bad?** The function should probably be marked `unsafe`, since
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
/// valid.
///
/// **Known problems:**
///
/// * It does not check functions recursively so if the pointer is passed to a
/// private non-`unsafe` function which does the dereferencing, the lint won't
/// trigger.
/// * It only checks for arguments whose type are raw pointers, not raw pointers
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
/// `some_argument.get_raw_ptr()`).
///
/// **Example:**
/// ```rust,ignore
/// // Bad
/// pub fn foo(x: *const u8) {
/// println!("{}", unsafe { *x });
/// }
///
/// // Good
/// pub unsafe fn foo(x: *const u8) {
/// println!("{}", unsafe { *x });
/// }
/// ```
pub NOT_UNSAFE_PTR_ARG_DEREF,
correctness,
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
}
declare_clippy_lint! {
/// **What it does:** Checks for a [`#[must_use]`] attribute on
/// unit-returning functions and methods.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** Unit values are useless. The attribute is likely
/// a remnant of a refactoring that removed the return type.
///
/// **Known problems:** None.
///
/// **Examples:**
/// ```rust
/// #[must_use]
/// fn useless() { }
/// ```
pub MUST_USE_UNIT,
style,
"`#[must_use]` attribute on a unit-returning function / method"
}
declare_clippy_lint! {
/// **What it does:** Checks for a [`#[must_use]`] attribute without
/// further information on functions and methods that return a type already
/// marked as `#[must_use]`.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** The attribute isn't needed. Not using the result
/// will already be reported. Alternatively, one can add some text to the
/// attribute to improve the lint message.
///
/// **Known problems:** None.
///
/// **Examples:**
/// ```rust
/// #[must_use]
/// fn double_must_use() -> Result<(), ()> {
/// unimplemented!();
/// }
/// ```
pub DOUBLE_MUST_USE,
style,
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that have no
/// [`#[must_use]`] attribute, but return something not already marked
/// must-use, have no mutable arg and mutate no statics.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** Not bad at all, this lint just shows places where
/// you could add the attribute.
///
/// **Known problems:** The lint only checks the arguments for mutable
/// types without looking if they are actually changed. On the other hand,
/// it also ignores a broad range of potentially interesting side effects,
/// because we cannot decide whether the programmer intends the function to
/// be called for the side effect or the result. Expect many false
/// positives. At least we don't lint if the result type is unit or already
/// `#[must_use]`.
///
/// **Examples:**
/// ```rust
/// // this could be annotated with `#[must_use]`.
/// fn id<T>(t: T) -> T { t }
/// ```
pub MUST_USE_CANDIDATE,
pedantic,
"function or method that could take a `#[must_use]` attribute"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that return a `Result`
/// with an `Err` type of `()`. It suggests using a custom type that
/// implements [`std::error::Error`].
///
/// **Why is this bad?** Unit does not implement `Error` and carries no
/// further information about what went wrong.
///
/// **Known problems:** Of course, this lint assumes that `Result` is used
/// for a fallible operation (which is after all the intended use). However
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
/// the suggestion is misguided, and the code should use a custom enum
/// instead.
///
/// **Examples:**
/// ```rust
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
/// ```
/// should become
/// ```rust,should_panic
/// use std::fmt;
///
/// #[derive(Debug)]
/// pub struct EndOfStream;
///
/// impl fmt::Display for EndOfStream {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "End of Stream")
/// }
/// }
///
/// impl std::error::Error for EndOfStream { }
///
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
///# fn main() {
///# read_u8().unwrap();
///# }
/// ```
///
/// Note that there are crates that simplify creating the error type, e.g.
/// [`thiserror`](https://docs.rs/thiserror).
pub RESULT_UNIT_ERR,
style,
"public function returning `Result` with an `Err` type of `()`"
}
#[derive(Copy, Clone)]
pub struct Functions {
threshold: u64,
max_lines: u64,
}
impl Functions {
pub fn new(threshold: u64, max_lines: u64) -> Self {
Self { threshold, max_lines }
}
}
impl_lint_pass!(Functions => [
TOO_MANY_ARGUMENTS,
TOO_MANY_LINES,
NOT_UNSAFE_PTR_ARG_DEREF,
MUST_USE_UNIT,
DOUBLE_MUST_USE,
MUST_USE_CANDIDATE,
RESULT_UNIT_ERR,
]);
impl<'tcx> LateLintPass<'tcx> for Functions {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: intravisit::FnKind<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
body: &'tcx hir::Body<'_>,
span: Span,
hir_id: hir::HirId,
) {
let unsafety = match kind {
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
intravisit::FnKind::Closure => return,
};
// don't warn for implementations, it's not their fault
if !is_trait_impl_item(cx, hir_id) {
// don't lint extern functions decls, it's not their fault either
match kind {
intravisit::FnKind::Method(
_,
&hir::FnSig {
header: hir::FnHeader { abi: Abi::Rust, .. },
..
},
_,
)
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => {
self.check_arg_number(cx, decl, span.with_hi(decl.output.span().hi()))
},
_ => {},
}
}
Self::check_raw_ptr(cx, unsafety, decl, body, hir_id);
self.check_line_number(cx, span, body);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public {
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
}
if let Some(attr) = attr {
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
return;
}
if is_public && !is_proc_macro(cx.sess(), attrs) && attr_by_name(attrs, "no_mangle").is_none() {
check_must_use_candidate(
cx,
&sig.decl,
cx.tcx.hir().body(*body_id),
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this function could have a `#[must_use]` attribute",
);
}
}
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
}
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let Some(attr) = attr {
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none()
{
check_must_use_candidate(
cx,
&sig.decl,
cx.tcx.hir().body(*body_id),
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this method could have a `#[must_use]` attribute",
);
}
}
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
// don't lint extern functions decls, it's not their fault
if sig.header.abi == Abi::Rust {
self.check_arg_number(cx, &sig.decl, item.span.with_hi(sig.decl.output.span().hi()));
}
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public {
check_result_unit_err(cx, &sig.decl, item.span, fn_header_span);
}
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let Some(attr) = attr {
check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr);
}
if let hir::TraitFn::Provided(eid) = *eid {
let body = cx.tcx.hir().body(eid);
Self::check_raw_ptr(cx, sig.header.unsafety, &sig.decl, body, item.hir_id());
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
check_must_use_candidate(
cx,
&sig.decl,
body,
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this method could have a `#[must_use]` attribute",
);
}
}
}
}
}
impl<'tcx> Functions {
fn check_arg_number(self, cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span) {
let args = decl.inputs.len() as u64;
if args > self.threshold {
span_lint(
cx,
TOO_MANY_ARGUMENTS,
fn_span,
&format!("this function has too many arguments ({}/{})", args, self.threshold),
);
}
}
fn check_line_number(self, cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>) {
if in_external_macro(cx.sess(), span) {
return;
}
let code_snippet = snippet(cx, body.value.span, "..");
let mut line_count: u64 = 0;
let mut in_comment = false;
let mut code_in_line;
// Skip the surrounding function decl.
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
for mut line in function_lines {
code_in_line = false;
loop {
line = line.trim_start();
if line.is_empty() {
break;
}
if in_comment {
if let Some(i) = line.find("*/") {
line = &line[i + 2..];
in_comment = false;
continue;
}
} else {
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
let single_idx = line.find("//").unwrap_or_else(|| line.len());
code_in_line |= multi_idx > 0 && single_idx > 0;
// Implies multi_idx is below line.len()
if multi_idx < single_idx {
line = &line[multi_idx + 2..];
in_comment = true;
continue;
}
}
break;
}
if code_in_line {
line_count += 1;
}
}
if line_count > self.max_lines {
span_lint(
cx,
TOO_MANY_LINES,
span,
&format!("this function has too many lines ({}/{})", line_count, self.max_lines),
)
}
}
fn check_raw_ptr(
cx: &LateContext<'tcx>,
unsafety: hir::Unsafety,
decl: &'tcx hir::FnDecl<'_>,
body: &'tcx hir::Body<'_>,
hir_id: hir::HirId,
) {
let expr = &body.value;
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
let raw_ptrs = iter_input_pats(decl, body)
.zip(decl.inputs.iter())
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
.collect::<FxHashSet<_>>();
if !raw_ptrs.is_empty() {
let typeck_results = cx.tcx.typeck_body(body.id());
let mut v = DerefVisitor {
cx,
ptrs: raw_ptrs,
typeck_results,
};
intravisit::walk_expr(&mut v, expr);
}
}
}
}
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
if_chain! {
if !in_external_macro(cx.sess(), item_span);
if let hir::FnRetTy::Return(ref ty) = decl.output;
let ty = hir_ty_to_ty(cx.tcx, ty);
if is_type_diagnostic_item(cx, ty, sym::result_type);
if let ty::Adt(_, substs) = ty.kind();
let err_ty = substs.type_at(1);
if err_ty.is_unit();
then {
span_lint_and_help(
cx,
RESULT_UNIT_ERR,
fn_header_span,
"this returns a `Result<_, ()>",
None,
"use a custom Error type instead",
);
}
}
}
fn check_needless_must_use(
cx: &LateContext<'_>,
decl: &hir::FnDecl<'_>,
item_id: hir::HirId,
item_span: Span,
fn_header_span: Span,
attr: &Attribute,
) {
if in_external_macro(cx.sess(), item_span) {
return;
}
if returns_unit(decl) {
span_lint_and_then(
cx,
MUST_USE_UNIT,
fn_header_span,
"this unit-returning function has a `#[must_use]` attribute",
|diag| {
diag.span_suggestion(
attr.span,
"remove the attribute",
"".into(),
Applicability::MachineApplicable,
);
},
);
} else if !attr.value_str().is_some() && is_must_use_ty(cx, return_ty(cx, item_id)) {
span_lint_and_help(
cx,
DOUBLE_MUST_USE,
fn_header_span,
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
None,
"either add some descriptive text or remove the attribute",
);
}
}
fn check_must_use_candidate<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
body: &'tcx hir::Body<'_>,
item_span: Span,
item_id: hir::HirId,
fn_span: Span,
msg: &str,
) {
if has_mutable_arg(cx, body)
|| mutates_static(cx, body)
|| in_external_macro(cx.sess(), item_span)
|| returns_unit(decl)
|| !cx.access_levels.is_exported(item_id)
|| is_must_use_ty(cx, return_ty(cx, item_id))
{
return;
}
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
if let Some(snippet) = snippet_opt(cx, fn_span) {
diag.span_suggestion(
fn_span,
"add the attribute",
format!("#[must_use] {}", snippet),
Applicability::MachineApplicable,
);
}
});
}
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
match decl.output {
hir::FnRetTy::DefaultReturn(_) => true,
hir::FnRetTy::Return(ref ty) => match ty.kind {
hir::TyKind::Tup(ref tys) => tys.is_empty(),
hir::TyKind::Never => true,
_ => false,
},
}
}
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
let mut tys = FxHashSet::default();
body.params.iter().any(|param| is_mutable_pat(cx, &param.pat, &mut tys))
}
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet<DefId>) -> bool {
if let hir::PatKind::Wild = pat.kind {
return false; // ignore `_` patterns
}
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
} else {
false
}
}
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut FxHashSet<DefId>) -> bool {
match *ty.kind() {
// primitive types are never mutable
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
ty::Adt(ref adt, ref substs) => {
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
},
ty::Tuple(ref substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
},
// calling something constitutes a side effect, so return true on all callables
// also never calls need not be used, so return true for them, too
_ => true,
}
}
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
Some(id)
} else {
None
}
}
struct DerefVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
ptrs: FxHashSet<hir::HirId>,
typeck_results: &'a ty::TypeckResults<'tcx>,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
match expr.kind {
hir::ExprKind::Call(ref f, args) => {
let ty = self.typeck_results.expr_ty(f);
if type_is_unsafe_function(self.cx, ty) {
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::MethodCall(_, _, args, _) => {
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
let base_type = self.cx.tcx.type_of(def_id);
if type_is_unsafe_function(self.cx, base_type) {
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::Unary(hir::UnOp::Deref, ref ptr) => self.check_arg(ptr),
_ => (),
}
intravisit::walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
fn check_arg(&self, ptr: &hir::Expr<'_>) {
if let Some(id) = path_to_local(ptr) {
if self.ptrs.contains(&id) {
span_lint(
self.cx,
NOT_UNSAFE_PTR_ARG_DEREF,
ptr.span,
"this public function dereferences a raw pointer but is not marked `unsafe`",
);
}
}
}
}
struct StaticMutVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
mutates_static: bool,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
if self.mutates_static {
return;
}
match expr.kind {
Call(_, args) | MethodCall(_, _, args, _) => {
let mut tys = FxHashSet::default();
for arg in args {
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
self.cx,
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
self.mutates_static = true;
return;
}
tys.clear();
}
},
Assign(ref target, ..) | AssignOp(_, ref target, _) | AddrOf(_, hir::Mutability::Mut, ref target) => {
self.mutates_static |= is_mutated_static(target)
},
_ => {},
}
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
use hir::ExprKind::{Field, Index, Path};
match e.kind {
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
Path(_) => true,
Field(ref inner, _) | Index(ref inner, _) => is_mutated_static(inner),
_ => false,
}
}
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
let mut v = StaticMutVisitor {
cx,
mutates_static: false,
};
intravisit::walk_expr(&mut v, &body.value);
v.mutates_static
}

View file

@ -0,0 +1,267 @@
mod must_use;
mod not_unsafe_ptr_arg_deref;
mod result_unit_err;
mod too_many_arguments;
mod too_many_lines;
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
declare_clippy_lint! {
/// **What it does:** Checks for functions with too many parameters.
///
/// **Why is this bad?** Functions with lots of parameters are considered bad
/// style and reduce readability (“what does the 5th parameter mean?”). Consider
/// grouping some parameters into a new type.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct Color;
/// fn foo(x: u32, y: u32, name: &str, c: Color, w: f32, h: f32, a: f32, b: f32) {
/// // ..
/// }
/// ```
pub TOO_MANY_ARGUMENTS,
complexity,
"functions with too many arguments"
}
declare_clippy_lint! {
/// **What it does:** Checks for functions with a large amount of lines.
///
/// **Why is this bad?** Functions with a lot of lines are harder to understand
/// due to having to look at a larger amount of code to understand what the
/// function is doing. Consider splitting the body of the function into
/// multiple functions.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// fn im_too_long() {
/// println!("");
/// // ... 100 more LoC
/// println!("");
/// }
/// ```
pub TOO_MANY_LINES,
pedantic,
"functions with too many lines"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that dereference raw pointer
/// arguments but are not marked `unsafe`.
///
/// **Why is this bad?** The function should probably be marked `unsafe`, since
/// for an arbitrary raw pointer, there is no way of telling for sure if it is
/// valid.
///
/// **Known problems:**
///
/// * It does not check functions recursively so if the pointer is passed to a
/// private non-`unsafe` function which does the dereferencing, the lint won't
/// trigger.
/// * It only checks for arguments whose type are raw pointers, not raw pointers
/// got from an argument in some other way (`fn foo(bar: &[*const u8])` or
/// `some_argument.get_raw_ptr()`).
///
/// **Example:**
/// ```rust,ignore
/// // Bad
/// pub fn foo(x: *const u8) {
/// println!("{}", unsafe { *x });
/// }
///
/// // Good
/// pub unsafe fn foo(x: *const u8) {
/// println!("{}", unsafe { *x });
/// }
/// ```
pub NOT_UNSAFE_PTR_ARG_DEREF,
correctness,
"public functions dereferencing raw pointer arguments but not marked `unsafe`"
}
declare_clippy_lint! {
/// **What it does:** Checks for a [`#[must_use]`] attribute on
/// unit-returning functions and methods.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** Unit values are useless. The attribute is likely
/// a remnant of a refactoring that removed the return type.
///
/// **Known problems:** None.
///
/// **Examples:**
/// ```rust
/// #[must_use]
/// fn useless() { }
/// ```
pub MUST_USE_UNIT,
style,
"`#[must_use]` attribute on a unit-returning function / method"
}
declare_clippy_lint! {
/// **What it does:** Checks for a [`#[must_use]`] attribute without
/// further information on functions and methods that return a type already
/// marked as `#[must_use]`.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** The attribute isn't needed. Not using the result
/// will already be reported. Alternatively, one can add some text to the
/// attribute to improve the lint message.
///
/// **Known problems:** None.
///
/// **Examples:**
/// ```rust
/// #[must_use]
/// fn double_must_use() -> Result<(), ()> {
/// unimplemented!();
/// }
/// ```
pub DOUBLE_MUST_USE,
style,
"`#[must_use]` attribute on a `#[must_use]`-returning function / method"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that have no
/// [`#[must_use]`] attribute, but return something not already marked
/// must-use, have no mutable arg and mutate no statics.
///
/// [`#[must_use]`]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
///
/// **Why is this bad?** Not bad at all, this lint just shows places where
/// you could add the attribute.
///
/// **Known problems:** The lint only checks the arguments for mutable
/// types without looking if they are actually changed. On the other hand,
/// it also ignores a broad range of potentially interesting side effects,
/// because we cannot decide whether the programmer intends the function to
/// be called for the side effect or the result. Expect many false
/// positives. At least we don't lint if the result type is unit or already
/// `#[must_use]`.
///
/// **Examples:**
/// ```rust
/// // this could be annotated with `#[must_use]`.
/// fn id<T>(t: T) -> T { t }
/// ```
pub MUST_USE_CANDIDATE,
pedantic,
"function or method that could take a `#[must_use]` attribute"
}
declare_clippy_lint! {
/// **What it does:** Checks for public functions that return a `Result`
/// with an `Err` type of `()`. It suggests using a custom type that
/// implements `std::error::Error`.
///
/// **Why is this bad?** Unit does not implement `Error` and carries no
/// further information about what went wrong.
///
/// **Known problems:** Of course, this lint assumes that `Result` is used
/// for a fallible operation (which is after all the intended use). However
/// code may opt to (mis)use it as a basic two-variant-enum. In that case,
/// the suggestion is misguided, and the code should use a custom enum
/// instead.
///
/// **Examples:**
/// ```rust
/// pub fn read_u8() -> Result<u8, ()> { Err(()) }
/// ```
/// should become
/// ```rust,should_panic
/// use std::fmt;
///
/// #[derive(Debug)]
/// pub struct EndOfStream;
///
/// impl fmt::Display for EndOfStream {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "End of Stream")
/// }
/// }
///
/// impl std::error::Error for EndOfStream { }
///
/// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
///# fn main() {
///# read_u8().unwrap();
///# }
/// ```
///
/// Note that there are crates that simplify creating the error type, e.g.
/// [`thiserror`](https://docs.rs/thiserror).
pub RESULT_UNIT_ERR,
style,
"public function returning `Result` with an `Err` type of `()`"
}
#[derive(Copy, Clone)]
pub struct Functions {
too_many_arguments_threshold: u64,
too_many_lines_threshold: u64,
}
impl Functions {
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64) -> Self {
Self {
too_many_arguments_threshold,
too_many_lines_threshold,
}
}
}
impl_lint_pass!(Functions => [
TOO_MANY_ARGUMENTS,
TOO_MANY_LINES,
NOT_UNSAFE_PTR_ARG_DEREF,
MUST_USE_UNIT,
DOUBLE_MUST_USE,
MUST_USE_CANDIDATE,
RESULT_UNIT_ERR,
]);
impl<'tcx> LateLintPass<'tcx> for Functions {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: intravisit::FnKind<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
body: &'tcx hir::Body<'_>,
span: Span,
hir_id: hir::HirId,
) {
too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold);
too_many_lines::check_fn(cx, span, body, self.too_many_lines_threshold);
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, hir_id);
}
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
must_use::check_item(cx, item);
result_unit_err::check_item(cx, item);
}
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
must_use::check_impl_item(cx, item);
result_unit_err::check_impl_item(cx, item);
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold);
not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
must_use::check_trait_item(cx, item);
result_unit_err::check_trait_item(cx, item);
}
}

View file

@ -0,0 +1,272 @@
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{self as hir, def::Res, intravisit, QPath};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::{
hir::map::Map,
lint::in_external_macro,
ty::{self, Ty},
};
use rustc_span::{sym, Span};
use clippy_utils::attrs::is_proc_macro;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_must_use_ty;
use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method};
use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let hir::ItemKind::Fn(ref sig, ref _generics, ref body_id) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
return;
} else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
check_must_use_candidate(
cx,
sig.decl,
cx.tcx.hir().body(*body_id),
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this function could have a `#[must_use]` attribute",
);
}
}
}
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if let hir::ImplItemKind::Fn(ref sig, ref body_id) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
} else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() {
check_must_use_candidate(
cx,
sig.decl,
cx.tcx.hir().body(*body_id),
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this method could have a `#[must_use]` attribute",
);
}
}
}
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
if let hir::TraitItemKind::Fn(ref sig, ref eid) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
let attrs = cx.tcx.hir().attrs(item.hir_id());
let attr = must_use_attr(attrs);
if let Some(attr) = attr {
check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr);
} else if let hir::TraitFn::Provided(eid) = *eid {
let body = cx.tcx.hir().body(eid);
if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
check_must_use_candidate(
cx,
sig.decl,
body,
item.span,
item.hir_id(),
item.span.with_hi(sig.decl.output.span().hi()),
"this method could have a `#[must_use]` attribute",
);
}
}
}
}
fn check_needless_must_use(
cx: &LateContext<'_>,
decl: &hir::FnDecl<'_>,
item_id: hir::HirId,
item_span: Span,
fn_header_span: Span,
attr: &Attribute,
) {
if in_external_macro(cx.sess(), item_span) {
return;
}
if returns_unit(decl) {
span_lint_and_then(
cx,
MUST_USE_UNIT,
fn_header_span,
"this unit-returning function has a `#[must_use]` attribute",
|diag| {
diag.span_suggestion(
attr.span,
"remove the attribute",
"".into(),
Applicability::MachineApplicable,
);
},
);
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
span_lint_and_help(
cx,
DOUBLE_MUST_USE,
fn_header_span,
"this function has an empty `#[must_use]` attribute, but returns a type already marked as `#[must_use]`",
None,
"either add some descriptive text or remove the attribute",
);
}
}
fn check_must_use_candidate<'tcx>(
cx: &LateContext<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
body: &'tcx hir::Body<'_>,
item_span: Span,
item_id: hir::HirId,
fn_span: Span,
msg: &str,
) {
if has_mutable_arg(cx, body)
|| mutates_static(cx, body)
|| in_external_macro(cx.sess(), item_span)
|| returns_unit(decl)
|| !cx.access_levels.is_exported(item_id)
|| is_must_use_ty(cx, return_ty(cx, item_id))
{
return;
}
span_lint_and_then(cx, MUST_USE_CANDIDATE, fn_span, msg, |diag| {
if let Some(snippet) = snippet_opt(cx, fn_span) {
diag.span_suggestion(
fn_span,
"add the attribute",
format!("#[must_use] {}", snippet),
Applicability::MachineApplicable,
);
}
});
}
fn returns_unit(decl: &hir::FnDecl<'_>) -> bool {
match decl.output {
hir::FnRetTy::DefaultReturn(_) => true,
hir::FnRetTy::Return(ty) => match ty.kind {
hir::TyKind::Tup(tys) => tys.is_empty(),
hir::TyKind::Never => true,
_ => false,
},
}
}
fn has_mutable_arg(cx: &LateContext<'_>, body: &hir::Body<'_>) -> bool {
let mut tys = DefIdSet::default();
body.params.iter().any(|param| is_mutable_pat(cx, param.pat, &mut tys))
}
fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut DefIdSet) -> bool {
if let hir::PatKind::Wild = pat.kind {
return false; // ignore `_` patterns
}
if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) {
is_mutable_ty(cx, cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys)
} else {
false
}
}
static KNOWN_WRAPPER_TYS: &[&[&str]] = &[&["alloc", "rc", "Rc"], &["std", "sync", "Arc"]];
fn is_mutable_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, span: Span, tys: &mut DefIdSet) -> bool {
match *ty.kind() {
// primitive types are never mutable
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
ty::Adt(adt, substs) => {
tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
|| KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
},
ty::Tuple(substs) => substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys)),
ty::Array(ty, _) | ty::Slice(ty) => is_mutable_ty(cx, ty, span, tys),
ty::RawPtr(ty::TypeAndMut { ty, mutbl }) | ty::Ref(_, ty, mutbl) => {
mutbl == hir::Mutability::Mut || is_mutable_ty(cx, ty, span, tys)
},
// calling something constitutes a side effect, so return true on all callables
// also never calls need not be used, so return true for them, too
_ => true,
}
}
struct StaticMutVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
mutates_static: bool,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
use hir::ExprKind::{AddrOf, Assign, AssignOp, Call, MethodCall};
if self.mutates_static {
return;
}
match expr.kind {
Call(_, args) | MethodCall(_, _, args, _) => {
let mut tys = DefIdSet::default();
for arg in args {
if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id())
&& is_mutable_ty(
self.cx,
self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg),
arg.span,
&mut tys,
)
&& is_mutated_static(arg)
{
self.mutates_static = true;
return;
}
tys.clear();
}
},
Assign(target, ..) | AssignOp(_, target, _) | AddrOf(_, hir::Mutability::Mut, target) => {
self.mutates_static |= is_mutated_static(target)
},
_ => {},
}
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
fn is_mutated_static(e: &hir::Expr<'_>) -> bool {
use hir::ExprKind::{Field, Index, Path};
match e.kind {
Path(QPath::Resolved(_, path)) => !matches!(path.res, Res::Local(_)),
Path(_) => true,
Field(inner, _) | Index(inner, _) => is_mutated_static(inner),
_ => false,
}
}
fn mutates_static<'tcx>(cx: &LateContext<'tcx>, body: &'tcx hir::Body<'_>) -> bool {
let mut v = StaticMutVisitor {
cx,
mutates_static: false,
};
intravisit::walk_expr(&mut v, &body.value);
v.mutates_static
}

View file

@ -0,0 +1,124 @@
use rustc_hir::{self as hir, intravisit, HirIdSet};
use rustc_lint::LateContext;
use rustc_middle::{hir::map::Map, ty};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::type_is_unsafe_function;
use clippy_utils::{iter_input_pats, path_to_local};
use super::NOT_UNSAFE_PTR_ARG_DEREF;
pub(super) fn check_fn(
cx: &LateContext<'tcx>,
kind: intravisit::FnKind<'tcx>,
decl: &'tcx hir::FnDecl<'tcx>,
body: &'tcx hir::Body<'tcx>,
hir_id: hir::HirId,
) {
let unsafety = match kind {
intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }, _) => unsafety,
intravisit::FnKind::Method(_, sig, _) => sig.header.unsafety,
intravisit::FnKind::Closure => return,
};
check_raw_ptr(cx, unsafety, decl, body, hir_id);
}
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
if let hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(eid)) = item.kind {
let body = cx.tcx.hir().body(eid);
check_raw_ptr(cx, sig.header.unsafety, sig.decl, body, item.hir_id());
}
}
fn check_raw_ptr(
cx: &LateContext<'tcx>,
unsafety: hir::Unsafety,
decl: &'tcx hir::FnDecl<'tcx>,
body: &'tcx hir::Body<'tcx>,
hir_id: hir::HirId,
) {
let expr = &body.value;
if unsafety == hir::Unsafety::Normal && cx.access_levels.is_exported(hir_id) {
let raw_ptrs = iter_input_pats(decl, body)
.zip(decl.inputs.iter())
.filter_map(|(arg, ty)| raw_ptr_arg(arg, ty))
.collect::<HirIdSet>();
if !raw_ptrs.is_empty() {
let typeck_results = cx.tcx.typeck_body(body.id());
let mut v = DerefVisitor {
cx,
ptrs: raw_ptrs,
typeck_results,
};
intravisit::walk_expr(&mut v, expr);
}
}
}
fn raw_ptr_arg(arg: &hir::Param<'_>, ty: &hir::Ty<'_>) -> Option<hir::HirId> {
if let (&hir::PatKind::Binding(_, id, _, _), &hir::TyKind::Ptr(_)) = (&arg.pat.kind, &ty.kind) {
Some(id)
} else {
None
}
}
struct DerefVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
ptrs: HirIdSet,
typeck_results: &'a ty::TypeckResults<'tcx>,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for DerefVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
match expr.kind {
hir::ExprKind::Call(f, args) => {
let ty = self.typeck_results.expr_ty(f);
if type_is_unsafe_function(self.cx, ty) {
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::MethodCall(_, _, args, _) => {
let def_id = self.typeck_results.type_dependent_def_id(expr.hir_id).unwrap();
let base_type = self.cx.tcx.type_of(def_id);
if type_is_unsafe_function(self.cx, base_type) {
for arg in args {
self.check_arg(arg);
}
}
},
hir::ExprKind::Unary(hir::UnOp::Deref, ptr) => self.check_arg(ptr),
_ => (),
}
intravisit::walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
impl<'a, 'tcx> DerefVisitor<'a, 'tcx> {
fn check_arg(&self, ptr: &hir::Expr<'_>) {
if let Some(id) = path_to_local(ptr) {
if self.ptrs.contains(&id) {
span_lint(
self.cx,
NOT_UNSAFE_PTR_ARG_DEREF,
ptr.span,
"this public function dereferences a raw pointer but is not marked `unsafe`",
);
}
}
}
}

View file

@ -0,0 +1,66 @@
use rustc_hir as hir;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_span::{sym, Span};
use rustc_typeck::hir_ty_to_ty;
use if_chain::if_chain;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::trait_ref_of_method;
use clippy_utils::ty::is_type_diagnostic_item;
use super::RESULT_UNIT_ERR;
pub(super) fn check_item(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
if let hir::ItemKind::Fn(ref sig, ref _generics, _) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public {
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
}
}
}
pub(super) fn check_impl_item(cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
if let hir::ImplItemKind::Fn(ref sig, _) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public && trait_ref_of_method(cx, item.hir_id()).is_none() {
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
}
}
}
pub(super) fn check_trait_item(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
let is_public = cx.access_levels.is_exported(item.hir_id());
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if is_public {
check_result_unit_err(cx, sig.decl, item.span, fn_header_span);
}
}
}
fn check_result_unit_err(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, item_span: Span, fn_header_span: Span) {
if_chain! {
if !in_external_macro(cx.sess(), item_span);
if let hir::FnRetTy::Return(ty) = decl.output;
let ty = hir_ty_to_ty(cx.tcx, ty);
if is_type_diagnostic_item(cx, ty, sym::result_type);
if let ty::Adt(_, substs) = ty.kind();
let err_ty = substs.type_at(1);
if err_ty.is_unit();
then {
span_lint_and_help(
cx,
RESULT_UNIT_ERR,
fn_header_span,
"this returns a `Result<_, ()>`",
None,
"use a custom `Error` type instead",
);
}
}
}

View file

@ -0,0 +1,73 @@
use rustc_hir::{self as hir, intravisit};
use rustc_lint::LateContext;
use rustc_span::Span;
use rustc_target::spec::abi::Abi;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_trait_impl_item;
use super::TOO_MANY_ARGUMENTS;
pub(super) fn check_fn(
cx: &LateContext<'tcx>,
kind: intravisit::FnKind<'tcx>,
decl: &'tcx hir::FnDecl<'_>,
span: Span,
hir_id: hir::HirId,
too_many_arguments_threshold: u64,
) {
// don't warn for implementations, it's not their fault
if !is_trait_impl_item(cx, hir_id) {
// don't lint extern functions decls, it's not their fault either
match kind {
intravisit::FnKind::Method(
_,
&hir::FnSig {
header: hir::FnHeader { abi: Abi::Rust, .. },
..
},
_,
)
| intravisit::FnKind::ItemFn(_, _, hir::FnHeader { abi: Abi::Rust, .. }, _) => check_arg_number(
cx,
decl,
span.with_hi(decl.output.span().hi()),
too_many_arguments_threshold,
),
_ => {},
}
}
}
pub(super) fn check_trait_item(
cx: &LateContext<'tcx>,
item: &'tcx hir::TraitItem<'_>,
too_many_arguments_threshold: u64,
) {
if let hir::TraitItemKind::Fn(ref sig, _) = item.kind {
// don't lint extern functions decls, it's not their fault
if sig.header.abi == Abi::Rust {
check_arg_number(
cx,
sig.decl,
item.span.with_hi(sig.decl.output.span().hi()),
too_many_arguments_threshold,
);
}
}
}
fn check_arg_number(cx: &LateContext<'_>, decl: &hir::FnDecl<'_>, fn_span: Span, too_many_arguments_threshold: u64) {
let args = decl.inputs.len() as u64;
if args > too_many_arguments_threshold {
span_lint(
cx,
TOO_MANY_ARGUMENTS,
fn_span,
&format!(
"this function has too many arguments ({}/{})",
args, too_many_arguments_threshold
),
);
}
}

View file

@ -0,0 +1,68 @@
use rustc_hir as hir;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::Span;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet;
use super::TOO_MANY_LINES;
pub(super) fn check_fn(cx: &LateContext<'_>, span: Span, body: &'tcx hir::Body<'_>, too_many_lines_threshold: u64) {
if in_external_macro(cx.sess(), span) {
return;
}
let code_snippet = snippet(cx, body.value.span, "..");
let mut line_count: u64 = 0;
let mut in_comment = false;
let mut code_in_line;
// Skip the surrounding function decl.
let start_brace_idx = code_snippet.find('{').map_or(0, |i| i + 1);
let end_brace_idx = code_snippet.rfind('}').unwrap_or_else(|| code_snippet.len());
let function_lines = code_snippet[start_brace_idx..end_brace_idx].lines();
for mut line in function_lines {
code_in_line = false;
loop {
line = line.trim_start();
if line.is_empty() {
break;
}
if in_comment {
if let Some(i) = line.find("*/") {
line = &line[i + 2..];
in_comment = false;
continue;
}
} else {
let multi_idx = line.find("/*").unwrap_or_else(|| line.len());
let single_idx = line.find("//").unwrap_or_else(|| line.len());
code_in_line |= multi_idx > 0 && single_idx > 0;
// Implies multi_idx is below line.len()
if multi_idx < single_idx {
line = &line[multi_idx + 2..];
in_comment = true;
continue;
}
}
break;
}
if code_in_line {
line_count += 1;
}
}
if line_count > too_many_lines_threshold {
span_lint(
cx,
TOO_MANY_LINES,
span,
&format!(
"this function has too many lines ({}/{})",
line_count, too_many_lines_threshold
),
)
}
}

View file

@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// Is a method call // Is a method call
if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
// Method name is "get" // Method name is "get"
if path.ident.name == sym!(get); if path.ident.name == sym!(get);

View file

@ -35,7 +35,7 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
if e.span.from_expansion() { if e.span.from_expansion() {
return; return;
} }
if let ExprKind::Binary(cmp, ref left, ref right) = e.kind { if let ExprKind::Binary(cmp, left, right) = e.kind {
if is_allowed(cx, cmp, left, right) { if is_allowed(cx, cmp, left, right) {
return; return;
} }

View file

@ -55,8 +55,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
cx, cx,
}; };
if let ExprKind::Match( if let ExprKind::Match(
ref op, op,
ref arms, arms,
MatchSource::IfLetDesugar { MatchSource::IfLetDesugar {
contains_else_clause: true, contains_else_clause: true,
}, },
@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
{ {
op_visit.visit_expr(op); op_visit.visit_expr(op);
if op_visit.mutex_lock_called { if op_visit.mutex_lock_called {
for arm in *arms { for arm in arms {
arm_visit.visit_arm(arm); arm_visit.visit_arm(arm);
} }
@ -94,14 +94,11 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
then {
self.found_mutex = Some(mutex); self.found_mutex = Some(mutex);
self.mutex_lock_called = true; self.mutex_lock_called = true;
return; return;
} }
}
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }
@ -121,14 +118,11 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
type Map = Map<'tcx>; type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
if_chain! { if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
if let Some(mutex) = is_mutex_lock_call(self.cx, expr);
then {
self.found_mutex = Some(mutex); self.found_mutex = Some(mutex);
self.mutex_lock_called = true; self.mutex_lock_called = true;
return; return;
} }
}
visit::walk_expr(self, expr); visit::walk_expr(self, expr);
} }

View file

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

View file

@ -72,15 +72,15 @@ impl LateLintPass<'_> for IfThenSomeElseNone {
} }
if_chain! { if_chain! {
if let ExprKind::If(ref cond, ref then, Some(ref els)) = expr.kind; if let ExprKind::If(cond, then, Some(els)) = expr.kind;
if let ExprKind::Block(ref then_block, _) = then.kind; if let ExprKind::Block(then_block, _) = then.kind;
if let Some(ref then_expr) = then_block.expr; if let Some(then_expr) = then_block.expr;
if let ExprKind::Call(ref then_call, [then_arg]) = then_expr.kind; if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
if let ExprKind::Path(ref then_call_qpath) = then_call.kind; if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
if match_qpath(then_call_qpath, &clippy_utils::paths::OPTION_SOME); if match_qpath(then_call_qpath, &clippy_utils::paths::OPTION_SOME);
if let ExprKind::Block(ref els_block, _) = els.kind; if let ExprKind::Block(els_block, _) = els.kind;
if els_block.stmts.is_empty(); if els_block.stmts.is_empty();
if let Some(ref els_expr) = els_block.expr; if let Some(els_expr) = els_block.expr;
if let ExprKind::Path(ref els_call_qpath) = els_expr.kind; if let ExprKind::Path(ref els_call_qpath) = els_expr.kind;
if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE); if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE);
then { then {

View file

@ -0,0 +1,377 @@
#![allow(rustc::default_hash_types)]
use std::borrow::Cow;
use std::collections::BTreeMap;
use rustc_errors::DiagnosticBuilder;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, NestedVisitorMap, Visitor};
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{Ty, TyS, TypeckResults};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::sym;
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::paths;
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{differing_macro_contexts, match_path};
declare_clippy_lint! {
/// **What it does:** Checks for public `impl` or `fn` missing generalization
/// over different hashers and implicitly defaulting to the default hashing
/// algorithm (`SipHash`).
///
/// **Why is this bad?** `HashMap` or `HashSet` with custom hashers cannot be
/// used with them.
///
/// **Known problems:** Suggestions for replacing constructors can contain
/// false-positives. Also applying suggestions can require modification of other
/// pieces of code, possibly including external crates.
///
/// **Example:**
/// ```rust
/// # use std::collections::HashMap;
/// # use std::hash::{Hash, BuildHasher};
/// # trait Serialize {};
/// impl<K: Hash + Eq, V> Serialize for HashMap<K, V> { }
///
/// pub fn foo(map: &mut HashMap<i32, i32>) { }
/// ```
/// could be rewritten as
/// ```rust
/// # use std::collections::HashMap;
/// # use std::hash::{Hash, BuildHasher};
/// # trait Serialize {};
/// impl<K: Hash + Eq, V, S: BuildHasher> Serialize for HashMap<K, V, S> { }
///
/// pub fn foo<S: BuildHasher>(map: &mut HashMap<i32, i32, S>) { }
/// ```
pub IMPLICIT_HASHER,
pedantic,
"missing generalization over different hashers"
}
declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]);
impl<'tcx> LateLintPass<'tcx> for ImplicitHasher {
#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)]
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
use rustc_span::BytePos;
fn suggestion<'tcx>(
cx: &LateContext<'tcx>,
diag: &mut DiagnosticBuilder<'_>,
generics_span: Span,
generics_suggestion_span: Span,
target: &ImplicitHasherType<'_>,
vis: ImplicitHasherConstructorVisitor<'_, '_, '_>,
) {
let generics_snip = snippet(cx, generics_span, "");
// trim `<` `>`
let generics_snip = if generics_snip.is_empty() {
""
} else {
&generics_snip[1..generics_snip.len() - 1]
};
multispan_sugg(
diag,
"consider adding a type parameter",
vec![
(
generics_suggestion_span,
format!(
"<{}{}S: ::std::hash::BuildHasher{}>",
generics_snip,
if generics_snip.is_empty() { "" } else { ", " },
if vis.suggestions.is_empty() {
""
} else {
// request users to add `Default` bound so that generic constructors can be used
" + Default"
},
),
),
(
target.span(),
format!("{}<{}, S>", target.type_name(), target.type_arguments(),),
),
],
);
if !vis.suggestions.is_empty() {
multispan_sugg(diag, "...and use generic constructor", vis.suggestions);
}
}
if !cx.access_levels.is_exported(item.hir_id()) {
return;
}
match item.kind {
ItemKind::Impl(ref impl_) => {
let mut vis = ImplicitHasherTypeVisitor::new(cx);
vis.visit_ty(impl_.self_ty);
for target in &vis.found {
if differing_macro_contexts(item.span, target.span()) {
return;
}
let generics_suggestion_span = impl_.generics.span.substitute_dummy({
let pos = snippet_opt(cx, item.span.until(target.span()))
.and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4)));
if let Some(pos) = pos {
Span::new(pos, pos, item.span.data().ctxt)
} else {
return;
}
});
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
for item in impl_.items.iter().map(|item| cx.tcx.hir().impl_item(item.id)) {
ctr_vis.visit_impl_item(item);
}
span_lint_and_then(
cx,
IMPLICIT_HASHER,
target.span(),
&format!(
"impl for `{}` should be generalized over different hashers",
target.type_name()
),
move |diag| {
suggestion(cx, diag, impl_.generics.span, generics_suggestion_span, target, ctr_vis);
},
);
}
},
ItemKind::Fn(ref sig, ref generics, body_id) => {
let body = cx.tcx.hir().body(body_id);
for ty in sig.decl.inputs {
let mut vis = ImplicitHasherTypeVisitor::new(cx);
vis.visit_ty(ty);
for target in &vis.found {
if in_external_macro(cx.sess(), generics.span) {
continue;
}
let generics_suggestion_span = generics.span.substitute_dummy({
let pos = snippet_opt(cx, item.span.until(body.params[0].pat.span))
.and_then(|snip| {
let i = snip.find("fn")?;
Some(item.span.lo() + BytePos((i + (&snip[i..]).find('(')?) as u32))
})
.expect("failed to create span for type parameters");
Span::new(pos, pos, item.span.data().ctxt)
});
let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target);
ctr_vis.visit_body(body);
span_lint_and_then(
cx,
IMPLICIT_HASHER,
target.span(),
&format!(
"parameter of type `{}` should be generalized over different hashers",
target.type_name()
),
move |diag| {
suggestion(cx, diag, generics.span, generics_suggestion_span, target, ctr_vis);
},
);
}
}
},
_ => {},
}
}
}
enum ImplicitHasherType<'tcx> {
HashMap(Span, Ty<'tcx>, Cow<'static, str>, Cow<'static, str>),
HashSet(Span, Ty<'tcx>, Cow<'static, str>),
}
impl<'tcx> ImplicitHasherType<'tcx> {
/// Checks that `ty` is a target type without a `BuildHasher`.
fn new(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> Option<Self> {
if let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind {
let params: Vec<_> = path
.segments
.last()
.as_ref()?
.args
.as_ref()?
.args
.iter()
.filter_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
})
.collect();
let params_len = params.len();
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
if is_type_diagnostic_item(cx, ty, sym::hashmap_type) && params_len == 2 {
Some(ImplicitHasherType::HashMap(
hir_ty.span,
ty,
snippet(cx, params[0].span, "K"),
snippet(cx, params[1].span, "V"),
))
} else if is_type_diagnostic_item(cx, ty, sym::hashset_type) && params_len == 1 {
Some(ImplicitHasherType::HashSet(
hir_ty.span,
ty,
snippet(cx, params[0].span, "T"),
))
} else {
None
}
} else {
None
}
}
fn type_name(&self) -> &'static str {
match *self {
ImplicitHasherType::HashMap(..) => "HashMap",
ImplicitHasherType::HashSet(..) => "HashSet",
}
}
fn type_arguments(&self) -> String {
match *self {
ImplicitHasherType::HashMap(.., ref k, ref v) => format!("{}, {}", k, v),
ImplicitHasherType::HashSet(.., ref t) => format!("{}", t),
}
}
fn ty(&self) -> Ty<'tcx> {
match *self {
ImplicitHasherType::HashMap(_, ty, ..) | ImplicitHasherType::HashSet(_, ty, ..) => ty,
}
}
fn span(&self) -> Span {
match *self {
ImplicitHasherType::HashMap(span, ..) | ImplicitHasherType::HashSet(span, ..) => span,
}
}
}
struct ImplicitHasherTypeVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
found: Vec<ImplicitHasherType<'tcx>>,
}
impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self { cx, found: vec![] }
}
}
impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) {
if let Some(target) = ImplicitHasherType::new(self.cx, t) {
self.found.push(target);
}
walk_ty(self, t);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
/// Looks for default-hasher-dependent constructors like `HashMap::new`.
struct ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
cx: &'a LateContext<'tcx>,
maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>,
target: &'b ImplicitHasherType<'tcx>,
suggestions: BTreeMap<Span, String>,
}
impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
fn new(cx: &'a LateContext<'tcx>, target: &'b ImplicitHasherType<'tcx>) -> Self {
Self {
cx,
maybe_typeck_results: cx.maybe_typeck_results(),
target,
suggestions: BTreeMap::new(),
}
}
}
impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> {
type Map = Map<'tcx>;
fn visit_body(&mut self, body: &'tcx Body<'_>) {
let old_maybe_typeck_results = self.maybe_typeck_results.replace(self.cx.tcx.typeck_body(body.id()));
walk_body(self, body);
self.maybe_typeck_results = old_maybe_typeck_results;
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Call(fun, args) = e.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, method)) = fun.kind;
if let TyKind::Path(QPath::Resolved(None, ty_path)) = ty.kind;
then {
if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
return;
}
if match_path(ty_path, &paths::HASHMAP) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashMap::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashMap::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
} else if match_path(ty_path, &paths::HASHSET) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashSet::default()".to_string());
} else if method.ident.name == sym!(with_capacity) {
self.suggestions.insert(
e.span,
format!(
"HashSet::with_capacity_and_hasher({}, Default::default())",
snippet(self.cx, args[0].span, "capacity"),
),
);
}
}
}
}
walk_expr(self, e);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
}

View file

@ -100,10 +100,10 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
if check_all_arms { if check_all_arms {
for arm in arms { for arm in arms {
expr_match(cx, &arm.body); expr_match(cx, arm.body);
} }
} else { } else {
expr_match(cx, &arms.first().expect("`if let` doesn't have a single arm").body); expr_match(cx, arms.first().expect("`if let` doesn't have a single arm").body);
} }
}, },
// skip if it already has a return statement // skip if it already has a return statement

View file

@ -46,21 +46,21 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if let ExprKind::If(cond, then, None) = &expr.kind; if let ExprKind::If(cond, then, None) = &expr.kind;
// Check if the conditional expression is a binary operation // Check if the conditional expression is a binary operation
if let ExprKind::Binary(ref cond_op, ref cond_left, ref cond_right) = cond.kind; if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind;
// Ensure that the binary operator is >, != and < // Ensure that the binary operator is >, != and <
if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node;
// Check if the true condition block has only one statement // Check if the true condition block has only one statement
if let ExprKind::Block(ref block, _) = then.kind; if let ExprKind::Block(block, _) = then.kind;
if block.stmts.len() == 1 && block.expr.is_none(); if block.stmts.len() == 1 && block.expr.is_none();
// Check if assign operation is done // Check if assign operation is done
if let StmtKind::Semi(ref e) = block.stmts[0].kind; if let StmtKind::Semi(e) = block.stmts[0].kind;
if let Some(target) = subtracts_one(cx, e); if let Some(target) = subtracts_one(cx, e);
// Extracting out the variable name // Extracting out the variable name
if let ExprKind::Path(QPath::Resolved(_, ref ares_path)) = target.kind; if let ExprKind::Path(QPath::Resolved(_, ares_path)) = target.kind;
then { then {
// Handle symmetric conditions in the if statement // Handle symmetric conditions in the if statement
@ -104,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
print_lint_and_sugg(cx, &var_name, expr); print_lint_and_sugg(cx, &var_name, expr);
}; };
}, },
ExprKind::Call(ref func, _) => { ExprKind::Call(func, _) => {
if let ExprKind::Path(ref cond_num_path) = func.kind { if let ExprKind::Path(ref cond_num_path) = func.kind {
if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "min_value"])) { if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "min_value"])) {
print_lint_and_sugg(cx, &var_name, expr); print_lint_and_sugg(cx, &var_name, expr);
@ -120,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> { fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'a>> {
match expr.kind { match expr.kind {
ExprKind::AssignOp(ref op1, ref target, ref value) => { ExprKind::AssignOp(ref op1, target, value) => {
if_chain! { if_chain! {
if BinOpKind::Sub == op1.node; if BinOpKind::Sub == op1.node;
// Check if literal being subtracted is one // Check if literal being subtracted is one
@ -133,9 +133,9 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &Expr<'a>) -> Option<&'a Expr<'
} }
} }
}, },
ExprKind::Assign(ref target, ref value, _) => { ExprKind::Assign(target, value, _) => {
if_chain! { if_chain! {
if let ExprKind::Binary(ref op1, ref left1, ref right1) = value.kind; if let ExprKind::Binary(ref op1, left1, right1) = value.kind;
if BinOpKind::Sub == op1.node; if BinOpKind::Sub == op1.node;
if SpanlessEq::new(cx).eq_expr(left1, target); if SpanlessEq::new(cx).eq_expr(left1, target);

View file

@ -88,7 +88,7 @@ declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]
impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { impl<'tcx> LateLintPass<'tcx> for IndexingSlicing {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Index(ref array, ref index) = &expr.kind { if let ExprKind::Index(array, index) = &expr.kind {
let ty = cx.typeck_results().expr_ty(array).peel_refs(); let ty = cx.typeck_results().expr_ty(array).peel_refs();
if let Some(range) = higher::range(index) { if let Some(range) = higher::range(index) {
// Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..] // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]

View file

@ -139,7 +139,7 @@ const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
match expr.kind { match expr.kind {
ExprKind::MethodCall(ref method, _, ref args, _) => { ExprKind::MethodCall(method, _, args, _) => {
for &(name, len, heuristic, cap) in &HEURISTICS { for &(name, len, heuristic, cap) in &HEURISTICS {
if method.ident.name.as_str() == name && args.len() == len { if method.ident.name.as_str() == name && args.len() == len {
return (match heuristic { return (match heuristic {
@ -159,9 +159,9 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
} }
Finite Finite
}, },
ExprKind::Block(ref block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)), ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
ExprKind::Box(ref e) | ExprKind::AddrOf(BorrowKind::Ref, _, ref e) => is_infinite(cx, e), ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
ExprKind::Call(ref path, _) => { ExprKind::Call(path, _) => {
if let ExprKind::Path(ref qpath) = path.kind { if let ExprKind::Path(ref qpath) = path.kind {
match_qpath(qpath, &paths::REPEAT).into() match_qpath(qpath, &paths::REPEAT).into()
} else { } else {
@ -215,7 +215,7 @@ const INFINITE_COLLECTORS: [&[&str]; 8] = [
fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
match expr.kind { match expr.kind {
ExprKind::MethodCall(ref method, _, ref args, _) => { ExprKind::MethodCall(method, _, args, _) => {
for &(name, len) in &COMPLETING_METHODS { for &(name, len) in &COMPLETING_METHODS {
if method.ident.name.as_str() == name && args.len() == len { if method.ident.name.as_str() == name && args.len() == len {
return is_infinite(cx, &args[0]); return is_infinite(cx, &args[0]);
@ -240,7 +240,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
} }
} }
}, },
ExprKind::Binary(op, ref l, ref r) => { ExprKind::Binary(op, l, r) => {
if op.node.is_comparison() { if op.node.is_comparison() {
return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite); return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
} }

View file

@ -2,7 +2,7 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_macro; use clippy_utils::in_macro;
use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefIdMap;
use rustc_hir::{def_id, Crate, Impl, Item, ItemKind}; use rustc_hir::{def_id, Crate, Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -43,7 +43,7 @@ declare_clippy_lint! {
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
#[derive(Default)] #[derive(Default)]
pub struct MultipleInherentImpl { pub struct MultipleInherentImpl {
impls: FxHashMap<def_id::DefId, Span>, impls: DefIdMap<Span>,
} }
impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]);

View file

@ -0,0 +1,221 @@
use std::cmp::Ordering;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, IntTy, UintTy};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_target::abi::LayoutOf;
use crate::consts::{constant, Constant};
use clippy_utils::comparisons::Rel;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet;
use clippy_utils::{comparisons, sext};
declare_clippy_lint! {
/// **What it does:** Checks for comparisons where the relation is always either
/// true or false, but where one side has been upcast so that the comparison is
/// necessary. Only integer types are checked.
///
/// **Why is this bad?** An expression like `let x : u8 = ...; (x as u32) > 300`
/// will mistakenly imply that it is possible for `x` to be outside the range of
/// `u8`.
///
/// **Known problems:**
/// https://github.com/rust-lang/rust-clippy/issues/886
///
/// **Example:**
/// ```rust
/// let x: u8 = 1;
/// (x as u32) > 300;
/// ```
pub INVALID_UPCAST_COMPARISONS,
pedantic,
"a comparison involving an upcast which is always true or false"
}
declare_lint_pass!(InvalidUpcastComparisons => [INVALID_UPCAST_COMPARISONS]);
#[derive(Copy, Clone, Debug, Eq)]
enum FullInt {
S(i128),
U(u128),
}
impl FullInt {
#[allow(clippy::cast_sign_loss)]
#[must_use]
fn cmp_s_u(s: i128, u: u128) -> Ordering {
if s < 0 {
Ordering::Less
} else if u > (i128::MAX as u128) {
Ordering::Greater
} else {
(s as u128).cmp(&u)
}
}
}
impl PartialEq for FullInt {
#[must_use]
fn eq(&self, other: &Self) -> bool {
self.partial_cmp(other).expect("`partial_cmp` only returns `Some(_)`") == Ordering::Equal
}
}
impl PartialOrd for FullInt {
#[must_use]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(match (self, other) {
(&Self::S(s), &Self::S(o)) => s.cmp(&o),
(&Self::U(s), &Self::U(o)) => s.cmp(&o),
(&Self::S(s), &Self::U(o)) => Self::cmp_s_u(s, o),
(&Self::U(s), &Self::S(o)) => Self::cmp_s_u(o, s).reverse(),
})
}
}
impl Ord for FullInt {
#[must_use]
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other)
.expect("`partial_cmp` for FullInt can never return `None`")
}
}
fn numeric_cast_precast_bounds<'a>(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<(FullInt, FullInt)> {
if let ExprKind::Cast(cast_exp, _) = expr.kind {
let pre_cast_ty = cx.typeck_results().expr_ty(cast_exp);
let cast_ty = cx.typeck_results().expr_ty(expr);
// if it's a cast from i32 to u32 wrapping will invalidate all these checks
if cx.layout_of(pre_cast_ty).ok().map(|l| l.size) == cx.layout_of(cast_ty).ok().map(|l| l.size) {
return None;
}
match pre_cast_ty.kind() {
ty::Int(int_ty) => Some(match int_ty {
IntTy::I8 => (FullInt::S(i128::from(i8::MIN)), FullInt::S(i128::from(i8::MAX))),
IntTy::I16 => (FullInt::S(i128::from(i16::MIN)), FullInt::S(i128::from(i16::MAX))),
IntTy::I32 => (FullInt::S(i128::from(i32::MIN)), FullInt::S(i128::from(i32::MAX))),
IntTy::I64 => (FullInt::S(i128::from(i64::MIN)), FullInt::S(i128::from(i64::MAX))),
IntTy::I128 => (FullInt::S(i128::MIN), FullInt::S(i128::MAX)),
IntTy::Isize => (FullInt::S(isize::MIN as i128), FullInt::S(isize::MAX as i128)),
}),
ty::Uint(uint_ty) => Some(match uint_ty {
UintTy::U8 => (FullInt::U(u128::from(u8::MIN)), FullInt::U(u128::from(u8::MAX))),
UintTy::U16 => (FullInt::U(u128::from(u16::MIN)), FullInt::U(u128::from(u16::MAX))),
UintTy::U32 => (FullInt::U(u128::from(u32::MIN)), FullInt::U(u128::from(u32::MAX))),
UintTy::U64 => (FullInt::U(u128::from(u64::MIN)), FullInt::U(u128::from(u64::MAX))),
UintTy::U128 => (FullInt::U(u128::MIN), FullInt::U(u128::MAX)),
UintTy::Usize => (FullInt::U(usize::MIN as u128), FullInt::U(usize::MAX as u128)),
}),
_ => None,
}
} else {
None
}
}
fn node_as_const_fullint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<FullInt> {
let val = constant(cx, cx.typeck_results(), expr)?.0;
if let Constant::Int(const_int) = val {
match *cx.typeck_results().expr_ty(expr).kind() {
ty::Int(ity) => Some(FullInt::S(sext(cx.tcx, const_int, ity))),
ty::Uint(_) => Some(FullInt::U(const_int)),
_ => None,
}
} else {
None
}
}
fn err_upcast_comparison(cx: &LateContext<'_>, span: Span, expr: &Expr<'_>, always: bool) {
if let ExprKind::Cast(cast_val, _) = expr.kind {
span_lint(
cx,
INVALID_UPCAST_COMPARISONS,
span,
&format!(
"because of the numeric bounds on `{}` prior to casting, this expression is always {}",
snippet(cx, cast_val.span, "the expression"),
if always { "true" } else { "false" },
),
);
}
}
fn upcast_comparison_bounds_err<'tcx>(
cx: &LateContext<'tcx>,
span: Span,
rel: comparisons::Rel,
lhs_bounds: Option<(FullInt, FullInt)>,
lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>,
invert: bool,
) {
if let Some((lb, ub)) = lhs_bounds {
if let Some(norm_rhs_val) = node_as_const_fullint(cx, rhs) {
if rel == Rel::Eq || rel == Rel::Ne {
if norm_rhs_val < lb || norm_rhs_val > ub {
err_upcast_comparison(cx, span, lhs, rel == Rel::Ne);
}
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val < lb
} else {
ub < norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val <= lb
} else {
ub <= norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, true)
} else if match rel {
Rel::Lt => {
if invert {
norm_rhs_val >= ub
} else {
lb >= norm_rhs_val
}
},
Rel::Le => {
if invert {
norm_rhs_val > ub
} else {
lb > norm_rhs_val
}
},
Rel::Eq | Rel::Ne => unreachable!(),
} {
err_upcast_comparison(cx, span, lhs, false)
}
}
}
}
impl<'tcx> LateLintPass<'tcx> for InvalidUpcastComparisons {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref cmp, lhs, rhs) = expr.kind {
let normalized = comparisons::normalize_comparison(cmp.node, lhs, rhs);
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized {
val
} else {
return;
};
let lhs_bounds = numeric_cast_precast_bounds(cx, normalized_lhs);
let rhs_bounds = numeric_cast_precast_bounds(cx, normalized_rhs);
upcast_comparison_bounds_err(cx, expr.span, rel, lhs_bounds, normalized_lhs, normalized_rhs, false);
upcast_comparison_bounds_err(cx, expr.span, rel, rhs_bounds, normalized_rhs, normalized_lhs, true);
}
}
}

View file

@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
); );
if variant.fields.len() == 1 { if variant.fields.len() == 1 {
let span = match def.variants[i].data { let span = match def.variants[i].data {
VariantData::Struct(ref fields, ..) | VariantData::Tuple(ref fields, ..) => { VariantData::Struct(fields, ..) | VariantData::Tuple(fields, ..) => {
fields[0].ty.span fields[0].ty.span
}, },
VariantData::Unit(..) => unreachable!(), VariantData::Unit(..) => unreachable!(),

View file

@ -3,16 +3,19 @@ use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{get_item_name, get_parent_as_impl, is_allowed}; use clippy_utils::{get_item_name, get_parent_as_impl, is_allowed};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{ use rustc_hir::{
def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
ItemKind, Mutability, Node, TraitItemRef, TyKind, ItemKind, Mutability, Node, TraitItemRef, TyKind,
}; };
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, AssocKind, FnSig}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty, TyS};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::{Span, Spanned, Symbol}; use rustc_span::{
source_map::{Span, Spanned, Symbol},
symbol::sym,
};
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for getting the length of something via `.len()` /// **What it does:** Checks for getting the length of something via `.len()`
@ -118,7 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
return; return;
} }
if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind { if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind {
check_trait_items(cx, item, trait_items); check_trait_items(cx, item, trait_items);
} }
} }
@ -137,6 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
if let Some(local_id) = ty_id.as_local(); if let Some(local_id) = ty_id.as_local();
let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
if let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.def_id).skip_binder());
then { then {
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
@ -148,7 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
} }
_ => return, _ => return,
}; };
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind) check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind)
} }
} }
} }
@ -158,7 +162,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
return; return;
} }
if let ExprKind::Binary(Spanned { node: cmp, .. }, ref left, ref right) = expr.kind { if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind {
match cmp { match cmp {
BinOpKind::Eq => { BinOpKind::Eq => {
check_cmp(cx, expr.span, left, right, "", 0); // len == 0 check_cmp(cx, expr.span, left, right, "", 0); // len == 0
@ -195,7 +199,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
} }
// fill the set with current and super traits // fill the set with current and super traits
fn fill_trait_set(traitt: DefId, set: &mut FxHashSet<DefId>, cx: &LateContext<'_>) { fn fill_trait_set(traitt: DefId, set: &mut DefIdSet, cx: &LateContext<'_>) {
if set.insert(traitt) { if set.insert(traitt) {
for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) { for supertrait in rustc_trait_selection::traits::supertrait_def_ids(cx.tcx, traitt) {
fill_trait_set(supertrait, set, cx); fill_trait_set(supertrait, set, cx);
@ -204,7 +208,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
} }
if cx.access_levels.is_exported(visited_trait.hir_id()) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) { if cx.access_levels.is_exported(visited_trait.hir_id()) && trait_items.iter().any(|i| is_named_self(cx, i, "len")) {
let mut current_and_super_traits = FxHashSet::default(); let mut current_and_super_traits = DefIdSet::default();
fill_trait_set(visited_trait.def_id.to_def_id(), &mut current_and_super_traits, cx); fill_trait_set(visited_trait.def_id.to_def_id(), &mut current_and_super_traits, cx);
let is_empty_method_found = current_and_super_traits let is_empty_method_found = current_and_super_traits
@ -231,10 +235,62 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
} }
} }
#[derive(Debug, Clone, Copy)]
enum LenOutput<'tcx> {
Integral,
Option(DefId),
Result(DefId, Ty<'tcx>),
}
fn parse_len_output(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::option_type, adt.did) => {
subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did))
},
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::result_type, adt.did) => subs
.type_at(0)
.is_integral()
.then(|| LenOutput::Result(adt.did, subs.type_at(1))),
_ => None,
}
}
impl LenOutput<'_> {
fn matches_is_empty_output(self, ty: Ty<'_>) -> bool {
match (self, ty.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(),
(Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => {
subs.type_at(0).is_bool() && TyS::same_type(subs.type_at(1), err_ty)
},
_ => false,
}
}
fn expected_sig(self, self_kind: ImplicitSelfKind) -> String {
let self_ref = match self_kind {
ImplicitSelfKind::ImmRef => "&",
ImplicitSelfKind::MutRef => "&mut ",
_ => "",
};
match self {
Self::Integral => format!("expected signature: `({}self) -> bool`", self_ref),
Self::Option(_) => format!(
"expected signature: `({}self) -> bool` or `({}self) -> Option<bool>",
self_ref, self_ref
),
Self::Result(..) => format!(
"expected signature: `({}self) -> bool` or `({}self) -> Result<bool>",
self_ref, self_ref
),
}
}
}
/// Checks if the given signature matches the expectations for `is_empty` /// Checks if the given signature matches the expectations for `is_empty`
fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool { fn check_is_empty_sig(sig: FnSig<'_>, self_kind: ImplicitSelfKind, len_output: LenOutput<'_>) -> bool {
match &**sig.inputs_and_output { match &**sig.inputs_and_output {
[arg, res] if *res == cx.tcx.types.bool => { [arg, res] if len_output.matches_is_empty_output(res) => {
matches!( matches!(
(arg.kind(), self_kind), (arg.kind(), self_kind),
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
@ -250,6 +306,7 @@ fn check_for_is_empty(
cx: &LateContext<'_>, cx: &LateContext<'_>,
span: Span, span: Span,
self_kind: ImplicitSelfKind, self_kind: ImplicitSelfKind,
output: LenOutput<'_>,
impl_ty: DefId, impl_ty: DefId,
item_name: Symbol, item_name: Symbol,
item_kind: &str, item_kind: &str,
@ -289,7 +346,7 @@ fn check_for_is_empty(
}, },
Some(is_empty) Some(is_empty)
if !(is_empty.fn_has_self_parameter if !(is_empty.fn_has_self_parameter
&& check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) => && check_is_empty_sig(cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind, output)) =>
{ {
( (
format!( format!(
@ -309,21 +366,13 @@ fn check_for_is_empty(
db.span_note(span, "`is_empty` defined here"); db.span_note(span, "`is_empty` defined here");
} }
if let Some(self_kind) = self_kind { if let Some(self_kind) = self_kind {
db.note(&format!( db.note(&output.expected_sig(self_kind));
"expected signature: `({}self) -> bool`",
match self_kind {
ImplicitSelfKind::ImmRef => "&",
ImplicitSelfKind::MutRef => "&mut ",
_ => "",
}
));
} }
}); });
} }
fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
if let (&ExprKind::MethodCall(ref method_path, _, ref args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) if let (&ExprKind::MethodCall(method_path, _, args, _), &ExprKind::Lit(ref lit)) = (&method.kind, &lit.kind) {
{
// check if we are in an is_empty() method // check if we are in an is_empty() method
if let Some(name) = get_item_name(cx, method) { if let Some(name) = get_item_name(cx, method) {
if name.as_str() == "is_empty" { if name.as_str() == "is_empty" {
@ -401,7 +450,7 @@ fn is_empty_string(expr: &Expr<'_>) -> bool {
} }
fn is_empty_array(expr: &Expr<'_>) -> bool { fn is_empty_array(expr: &Expr<'_>) -> bool {
if let ExprKind::Array(ref arr) = expr.kind { if let ExprKind::Array(arr) = expr.kind {
return arr.is_empty(); return arr.is_empty();
} }
false false
@ -430,17 +479,17 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
cx.tcx cx.tcx
.associated_items(*imp) .associated_items(*imp)
.in_definition_order() .in_definition_order()
.any(|item| is_is_empty(cx, &item)) .any(|item| is_is_empty(cx, item))
}) })
} }
let ty = &cx.typeck_results().expr_ty(expr).peel_refs(); let ty = &cx.typeck_results().expr_ty(expr).peel_refs();
match ty.kind() { match ty.kind() {
ty::Dynamic(ref tt, ..) => tt.principal().map_or(false, |principal| { ty::Dynamic(tt, ..) => tt.principal().map_or(false, |principal| {
cx.tcx cx.tcx
.associated_items(principal.def_id()) .associated_items(principal.def_id())
.in_definition_order() .in_definition_order()
.any(|item| is_is_empty(cx, &item)) .any(|item| is_is_empty(cx, item))
}), }),
ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id), ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
ty::Adt(id, _) => has_is_empty_impl(cx, id.did), ty::Adt(id, _) => has_is_empty_impl(cx, id.did),

View file

@ -61,13 +61,13 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
while let Some(stmt) = it.next() { while let Some(stmt) = it.next() {
if_chain! { if_chain! {
if let Some(expr) = it.peek(); if let Some(expr) = it.peek();
if let hir::StmtKind::Local(ref local) = stmt.kind; if let hir::StmtKind::Local(local) = stmt.kind;
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind; if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(ref if_) = expr.kind; if let hir::StmtKind::Expr(if_) = expr.kind;
if let hir::ExprKind::If(ref cond, ref then, ref else_) = if_.kind; if let hir::ExprKind::If(cond, then, ref else_) = if_.kind;
let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id); let mut used_visitor = LocalUsedVisitor::new(cx, canonical_id);
if !used_visitor.check_expr(cond); if !used_visitor.check_expr(cond);
if let hir::ExprKind::Block(ref then, _) = then.kind; if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, &*then); if let Some(value) = check_assign(cx, canonical_id, &*then);
if !used_visitor.check_expr(value); if !used_visitor.check_expr(value);
then { then {
@ -79,20 +79,20 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
); );
if has_interior_mutability { return; } if has_interior_mutability { return; }
let (default_multi_stmts, default) = if let Some(ref else_) = *else_ { let (default_multi_stmts, default) = if let Some(else_) = *else_ {
if let hir::ExprKind::Block(ref else_, _) = else_.kind { if let hir::ExprKind::Block(else_, _) = else_.kind {
if let Some(default) = check_assign(cx, canonical_id, else_) { if let Some(default) = check_assign(cx, canonical_id, else_) {
(else_.stmts.len() > 1, default) (else_.stmts.len() > 1, default)
} else if let Some(ref default) = local.init { } else if let Some(default) = local.init {
(true, &**default) (true, default)
} else { } else {
continue; continue;
} }
} else { } else {
continue; continue;
} }
} else if let Some(ref default) = local.init { } else if let Some(default) = local.init {
(false, &**default) (false, default)
} else { } else {
continue; continue;
}; };
@ -144,8 +144,8 @@ fn check_assign<'tcx>(
if_chain! { if_chain! {
if block.expr.is_none(); if block.expr.is_none();
if let Some(expr) = block.stmts.iter().last(); if let Some(expr) = block.stmts.iter().last();
if let hir::StmtKind::Semi(ref expr) = expr.kind; if let hir::StmtKind::Semi(expr) = expr.kind;
if let hir::ExprKind::Assign(ref var, ref value, _) = expr.kind; if let hir::ExprKind::Assign(var, value, _) = expr.kind;
if path_to_local_id(var, decl); if path_to_local_id(var, decl);
then { then {
let mut v = LocalUsedVisitor::new(cx, decl); let mut v = LocalUsedVisitor::new(cx, decl);

View file

@ -116,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
if_chain! { if_chain! {
if let PatKind::Wild = local.pat.kind; if let PatKind::Wild = local.pat.kind;
if let Some(ref init) = local.init; if let Some(init) = local.init;
then { then {
let init_ty = cx.typeck_results().expr_ty(init); let init_ty = cx.typeck_results().expr_ty(init);
let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() {

File diff suppressed because it is too large Load diff

View file

@ -81,7 +81,7 @@ declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]);
impl<'tcx> LateLintPass<'tcx> for Lifetimes { impl<'tcx> LateLintPass<'tcx> for Lifetimes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Fn(ref sig, ref generics, id) = item.kind { if let ItemKind::Fn(ref sig, ref generics, id) = item.kind {
check_fn_inner(cx, &sig.decl, Some(id), generics, item.span, true); check_fn_inner(cx, sig.decl, Some(id), generics, item.span, true);
} }
} }
@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
let report_extra_lifetimes = trait_ref_of_method(cx, item.hir_id()).is_none(); let report_extra_lifetimes = trait_ref_of_method(cx, item.hir_id()).is_none();
check_fn_inner( check_fn_inner(
cx, cx,
&sig.decl, sig.decl,
Some(id), Some(id),
&item.generics, &item.generics,
item.span, item.span,
@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
TraitFn::Required(_) => None, TraitFn::Required(_) => None,
TraitFn::Provided(id) => Some(id), TraitFn::Provided(id) => Some(id),
}; };
check_fn_inner(cx, &sig.decl, body, &item.generics, item.span, true); check_fn_inner(cx, sig.decl, body, &item.generics, item.span, true);
} }
} }
} }
@ -149,7 +149,7 @@ fn check_fn_inner<'tcx>(
.last() .last()
.expect("a path must have at least one segment") .expect("a path must have at least one segment")
.args; .args;
if let Some(ref params) = *params { if let Some(params) = *params {
let lifetimes = params.args.iter().filter_map(|arg| match arg { let lifetimes = params.args.iter().filter_map(|arg| match arg {
GenericArg::Lifetime(lt) => Some(lt), GenericArg::Lifetime(lt) => Some(lt),
_ => None, _ => None,
@ -163,7 +163,7 @@ fn check_fn_inner<'tcx>(
} }
} }
} }
if could_use_elision(cx, decl, body, &generics.params) { if could_use_elision(cx, decl, body, generics.params) {
span_lint( span_lint(
cx, cx,
NEEDLESS_LIFETIMES, NEEDLESS_LIFETIMES,
@ -201,7 +201,7 @@ fn could_use_elision<'tcx>(
input_visitor.visit_ty(arg); input_visitor.visit_ty(arg);
} }
// extract lifetimes in output type // extract lifetimes in output type
if let Return(ref ty) = func.output { if let Return(ty) = func.output {
output_visitor.visit_ty(ty); output_visitor.visit_ty(ty);
} }
for lt in named_generics { for lt in named_generics {
@ -416,12 +416,12 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl
// a predicate like F: Trait or F: for<'a> Trait<'a> // a predicate like F: Trait or F: for<'a> Trait<'a>
let mut visitor = RefVisitor::new(cx); let mut visitor = RefVisitor::new(cx);
// walk the type F, it may not contain LT refs // walk the type F, it may not contain LT refs
walk_ty(&mut visitor, &pred.bounded_ty); walk_ty(&mut visitor, pred.bounded_ty);
if !visitor.all_lts().is_empty() { if !visitor.all_lts().is_empty() {
return true; return true;
} }
// if the bounds define new lifetimes, they are fine to occur // if the bounds define new lifetimes, they are fine to occur
let allowed_lts = allowed_lts_from(&pred.bound_generic_params); let allowed_lts = allowed_lts_from(pred.bound_generic_params);
// now walk the bounds // now walk the bounds
for bound in pred.bounds.iter() { for bound in pred.bounds.iter() {
walk_param_bound(&mut visitor, bound); walk_param_bound(&mut visitor, bound);
@ -433,8 +433,8 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl
}, },
WherePredicate::EqPredicate(ref pred) => { WherePredicate::EqPredicate(ref pred) => {
let mut visitor = RefVisitor::new(cx); let mut visitor = RefVisitor::new(cx);
walk_ty(&mut visitor, &pred.lhs_ty); walk_ty(&mut visitor, pred.lhs_ty);
walk_ty(&mut visitor, &pred.rhs_ty); walk_ty(&mut visitor, pred.rhs_ty);
if !visitor.lts.is_empty() { if !visitor.lts.is_empty() {
return true; return true;
} }

View file

@ -249,7 +249,7 @@ impl LiteralDigitGrouping {
fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) { fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
if_chain! { if_chain! {
if let Some(src) = snippet_opt(cx, lit.span); if let Some(src) = snippet_opt(cx, lit.span);
if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit); if let Some(mut num_lit) = NumericLiteral::from_lit(&src, lit);
then { then {
if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) { if !Self::check_for_mistyped_suffix(cx, lit.span, &mut num_lit) {
return; return;
@ -439,7 +439,7 @@ impl DecimalLiteralRepresentation {
if_chain! { if_chain! {
if let LitKind::Int(val, _) = lit.kind; if let LitKind::Int(val, _) = lit.kind;
if let Some(src) = snippet_opt(cx, lit.span); if let Some(src) = snippet_opt(cx, lit.span);
if let Some(num_lit) = NumericLiteral::from_lit(&src, &lit); if let Some(num_lit) = NumericLiteral::from_lit(&src, lit);
if num_lit.radix == Radix::Decimal; if num_lit.radix == Radix::Decimal;
if val >= u128::from(self.threshold); if val >= u128::from(self.threshold);
then { then {

View file

@ -26,7 +26,7 @@ pub(super) fn check<'tcx>(
// For each candidate, check the parent block to see if // For each candidate, check the parent block to see if
// it's initialized to zero at the start of the loop. // it's initialized to zero at the start of the loop.
if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { if let Some(block) = get_enclosing_block(cx, expr.hir_id) {
for id in increment_visitor.into_results() { for id in increment_visitor.into_results() {
let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); let mut initialize_visitor = InitializeVisitor::new(cx, expr, id);
walk_block(&mut initialize_visitor, block); walk_block(&mut initialize_visitor, block);

View file

@ -19,7 +19,7 @@ pub(super) fn check<'tcx>(
) { ) {
let pat_span = pat.span; let pat_span = pat.span;
if let PatKind::Tuple(ref pat, _) = pat.kind { if let PatKind::Tuple(pat, _) = pat.kind {
if pat.len() == 2 { if pat.len() == 2 {
let arg_span = arg.span; let arg_span = arg.span;
let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() {
@ -35,7 +35,7 @@ pub(super) fn check<'tcx>(
Mutability::Mut => "_mut", Mutability::Mut => "_mut",
}; };
let arg = match arg.kind { let arg = match arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr,
_ => arg, _ => arg,
}; };

View file

@ -18,7 +18,7 @@ pub(super) fn check<'tcx>(
body: &'tcx Expr<'_>, body: &'tcx Expr<'_>,
span: Span, span: Span,
) { ) {
if let ExprKind::Block(ref block, _) = body.kind { if let ExprKind::Block(block, _) = body.kind {
// Ensure the `if let` statement is the only expression or statement in the for-loop // Ensure the `if let` statement is the only expression or statement in the for-loop
let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() { let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() {
let match_stmt = &block.stmts[0]; let match_stmt = &block.stmts[0];
@ -36,7 +36,7 @@ pub(super) fn check<'tcx>(
if_chain! { if_chain! {
if let Some(inner_expr) = inner_expr; if let Some(inner_expr) = inner_expr;
if let ExprKind::Match( if let ExprKind::Match(
ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } match_expr, match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false }
) = inner_expr.kind; ) = inner_expr.kind;
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop // Ensure match_expr in `if let` statement is the same as the pat from the for-loop
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
@ -46,9 +46,8 @@ pub(super) fn check<'tcx>(
let some_ctor = is_some_ctor(cx, path.res); let some_ctor = is_some_ctor(cx, path.res);
let ok_ctor = is_ok_ctor(cx, path.res); let ok_ctor = is_ok_ctor(cx, path.res);
if some_ctor || ok_ctor; if some_ctor || ok_ctor;
let if_let_type = if some_ctor { "Some" } else { "Ok" };
then { then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };
// Prepare the error message // Prepare the error message
let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type);

View file

@ -61,10 +61,10 @@ pub(super) fn check<'tcx>(
if_chain! { if_chain! {
if let ExprKind::Index(base_left, idx_left) = lhs.kind; if let ExprKind::Index(base_left, idx_left) = lhs.kind;
if let ExprKind::Index(base_right, idx_right) = rhs.kind; if let ExprKind::Index(base_right, idx_right) = rhs.kind;
if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) if is_slice_like(cx, cx.typeck_results().expr_ty(base_left));
&& is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); if is_slice_like(cx, cx.typeck_results().expr_ty(base_right));
if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); if let Some((start_left, offset_left)) = get_details_from_idx(cx, idx_left, &starts);
if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); if let Some((start_right, offset_right)) = get_details_from_idx(cx, idx_right, &starts);
// Source and destination must be different // Source and destination must be different
if path_to_local(base_left) != path_to_local(base_right); if path_to_local(base_left) != path_to_local(base_right);
@ -168,8 +168,8 @@ fn build_manual_memcpy_suggestion<'tcx>(
}, },
}; };
let (dst_offset, dst_limit) = print_offset_and_limit(&dst); let (dst_offset, dst_limit) = print_offset_and_limit(dst);
let (src_offset, src_limit) = print_offset_and_limit(&src); let (src_offset, src_limit) = print_offset_and_limit(src);
let dst_base_str = snippet(cx, dst.base.span, "???"); let dst_base_str = snippet(cx, dst.base.span, "???");
let src_base_str = snippet(cx, src.base.span, "???"); let src_base_str = snippet(cx, src.base.span, "???");
@ -438,7 +438,7 @@ fn get_loop_counters<'a, 'tcx>(
// For each candidate, check the parent block to see if // For each candidate, check the parent block to see if
// it's initialized to zero at the start of the loop. // it's initialized to zero at the start of the loop.
get_enclosing_block(&cx, expr.hir_id).and_then(|block| { get_enclosing_block(cx, expr.hir_id).and_then(|block| {
increment_visitor increment_visitor
.into_results() .into_results()
.filter_map(move |var_id| { .filter_map(move |var_id| {

View file

@ -562,7 +562,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
// check for `loop { if let {} else break }` that could be `while let` // check for `loop { if let {} else break }` that could be `while let`
// (also matches an explicit "match" instead of "if let") // (also matches an explicit "match" instead of "if let")
// (even if the "match" or "if let" is used for declaration) // (even if the "match" or "if let" is used for declaration)
if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { if let ExprKind::Loop(block, _, LoopSource::Loop, _) = expr.kind {
// also check for empty `loop {}` statements, skipping those in #[panic_handler] // also check for empty `loop {}` statements, skipping those in #[panic_handler]
empty_loop::check(cx, expr, block); empty_loop::check(cx, expr, block);
while_let_loop::check(cx, expr, block); while_let_loop::check(cx, expr, block);
@ -570,7 +570,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
while_let_on_iterator::check(cx, expr); while_let_on_iterator::check(cx, expr);
if let Some((cond, body)) = higher::while_loop(&expr) { if let Some((cond, body)) = higher::while_loop(expr) {
while_immutable_condition::check(cx, cond, body); while_immutable_condition::check(cx, cond, body);
} }
@ -602,7 +602,7 @@ fn check_for_loop<'tcx>(
fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) { fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, expr: &Expr<'_>) {
let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used let mut next_loop_linted = false; // whether or not ITER_NEXT_LOOP lint was used
if let ExprKind::MethodCall(ref method, _, ref args, _) = arg.kind { if let ExprKind::MethodCall(method, _, args, _) = arg.kind {
// just the receiver, no arguments // just the receiver, no arguments
if args.len() == 1 { if args.len() == 1 {
let method_name = &*method.ident.as_str(); let method_name = &*method.ident.as_str();

View file

@ -10,8 +10,8 @@ use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, Ident}; use rustc_span::symbol::{sym, Ident};
use rustc_span::{MultiSpan, Span};
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
@ -21,88 +21,60 @@ pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
} }
fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; if let ExprKind::MethodCall(chain_method, method0_span, _, _) = args[0].kind;
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
if let Some(ref generic_args) = chain_method.args; if let Some(generic_args) = chain_method.args;
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
then {
let ty = cx.typeck_results().node_type(ty.hir_id); let ty = cx.typeck_results().node_type(ty.hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type) || if is_type_diagnostic_item(cx, ty, sym::vec_type)
is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
match_type(cx, ty, &paths::BTREEMAP) || || match_type(cx, ty, &paths::BTREEMAP)
is_type_diagnostic_item(cx, ty, sym::hashmap_type) { || is_type_diagnostic_item(cx, ty, sym::hashmap_type);
if method.ident.name == sym!(len) { if let Some(sugg) = match &*method.ident.name.as_str() {
let span = shorten_needless_collect_span(expr); "len" => Some("count()".to_string()),
span_lint_and_sugg( "is_empty" => Some("next().is_none()".to_string()),
cx, "contains" => {
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
"replace with",
"count()".to_string(),
Applicability::MachineApplicable,
);
}
if method.ident.name == sym!(is_empty) {
let span = shorten_needless_collect_span(expr);
span_lint_and_sugg(
cx,
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
"replace with",
"next().is_none()".to_string(),
Applicability::MachineApplicable,
);
}
if method.ident.name == sym!(contains) {
let contains_arg = snippet(cx, args[1].span, "??"); let contains_arg = snippet(cx, args[1].span, "??");
let span = shorten_needless_collect_span(expr);
span_lint_and_then(
cx,
NEEDLESS_COLLECT,
span,
NEEDLESS_COLLECT_MSG,
|diag| {
let (arg, pred) = contains_arg let (arg, pred) = contains_arg
.strip_prefix('&') .strip_prefix('&')
.map_or(("&x", &*contains_arg), |s| ("x", s)); .map_or(("&x", &*contains_arg), |s| ("x", s));
diag.span_suggestion( Some(format!("any(|{}| x == {})", arg, pred))
span, }
_ => None,
};
then {
span_lint_and_sugg(
cx,
NEEDLESS_COLLECT,
method0_span.with_hi(expr.span.hi()),
NEEDLESS_COLLECT_MSG,
"replace with", "replace with",
format!( sugg,
"any(|{}| x == {})",
arg, pred
),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
} }
);
}
}
}
} }
} }
fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
if let ExprKind::Block(ref block, _) = expr.kind { if let ExprKind::Block(block, _) = expr.kind {
for ref stmt in block.stmts { for stmt in block.stmts {
if_chain! { if_chain! {
if let StmtKind::Local( if let StmtKind::Local(
Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
init: Some(ref init_expr), .. } init: Some(init_expr), .. }
) = stmt.kind; ) = stmt.kind;
if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
if method_name.ident.name == sym!(collect) && is_trait_method(cx, &init_expr, sym::Iterator); if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
if let Some(ref generic_args) = method_name.args; if let Some(generic_args) = method_name.args;
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
if let ty = cx.typeck_results().node_type(ty.hir_id); if let ty = cx.typeck_results().node_type(ty.hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type) || if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
match_type(cx, ty, &paths::LINKED_LIST); match_type(cx, ty, &paths::LINKED_LIST);
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
if iter_calls.len() == 1; if let [iter_call] = &*iter_calls;
then { then {
let mut used_count_visitor = UsedCountVisitor { let mut used_count_visitor = UsedCountVisitor {
cx, cx,
@ -115,11 +87,12 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
} }
// Suggest replacing iter_call with iter_replacement, and removing stmt // Suggest replacing iter_call with iter_replacement, and removing stmt
let iter_call = &iter_calls[0]; let mut span = MultiSpan::from_span(collect_span);
span.push_span_label(iter_call.span, "the iterator could be used here instead".into());
span_lint_and_then( span_lint_and_then(
cx, cx,
super::NEEDLESS_COLLECT, super::NEEDLESS_COLLECT,
stmt.span.until(iter_call.span), span,
NEEDLESS_COLLECT_MSG, NEEDLESS_COLLECT_MSG,
|diag| { |diag| {
let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx));
@ -130,7 +103,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
(iter_call.span, iter_replacement) (iter_call.span, iter_replacement)
], ],
Applicability::MachineApplicable,// MaybeIncorrect, Applicability::MachineApplicable,// MaybeIncorrect,
).emit(); );
}, },
); );
} }
@ -192,8 +165,8 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// Check function calls on our collection // Check function calls on our collection
if_chain! { if_chain! {
if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; if let ExprKind::MethodCall(method_name, _, args, _) = &expr.kind;
if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. }) = args.get(0);
if let &[name] = &path.segments; if let &[name] = &path.segments;
if name.ident == self.target; if name.ident == self.target;
then { then {
@ -220,7 +193,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
} }
// Check if the collection is used for anything else // Check if the collection is used for anything else
if_chain! { if_chain! {
if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; if let Expr { kind: ExprKind::Path(QPath::Resolved(_, path)), .. } = expr;
if let &[name] = &path.segments; if let &[name] = &path.segments;
if name.ident == self.target; if name.ident == self.target;
then { then {
@ -270,14 +243,3 @@ fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident)
visitor.visit_block(block); visitor.visit_block(block);
if visitor.seen_other { None } else { Some(visitor.uses) } if visitor.seen_other { None } else { Some(visitor.uses) }
} }
fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span {
if_chain! {
if let ExprKind::MethodCall(.., args, _) = &expr.kind;
if let ExprKind::MethodCall(_, span, ..) = &args[0].kind;
then {
return expr.span.with_lo(span.lo());
}
}
unreachable!();
}

View file

@ -96,7 +96,7 @@ pub(super) fn check<'tcx>(
let take = if let Some(end) = *end { let take = if let Some(end) = *end {
let mut take_expr = end; let mut take_expr = end;
if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { if let ExprKind::Binary(ref op, left, right) = end.kind {
if let BinOpKind::Add = op.node { if let BinOpKind::Add = op.node {
let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left);
let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right);
@ -190,10 +190,10 @@ pub(super) fn check<'tcx>(
fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; if let ExprKind::MethodCall(method, _, len_args, _) = expr.kind;
if len_args.len() == 1; if len_args.len() == 1;
if method.ident.name == sym!(len); if method.ident.name == sym!(len);
if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; if let ExprKind::Path(QPath::Resolved(_, path)) = len_args[0].kind;
if path.segments.len() == 1; if path.segments.len() == 1;
if path.segments[0].ident.name == var; if path.segments[0].ident.name == var;
then { then {
@ -254,17 +254,16 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
if_chain! { if_chain! {
// the indexed container is referenced by a name // the indexed container is referenced by a name
if let ExprKind::Path(ref seqpath) = seqexpr.kind; if let ExprKind::Path(ref seqpath) = seqexpr.kind;
if let QPath::Resolved(None, ref seqvar) = *seqpath; if let QPath::Resolved(None, seqvar) = *seqpath;
if seqvar.segments.len() == 1; if seqvar.segments.len() == 1;
then {
let index_used_directly = path_to_local_id(idx, self.var); let index_used_directly = path_to_local_id(idx, self.var);
let indexed_indirectly = { let indexed_indirectly = {
let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var);
walk_expr(&mut used_visitor, idx); walk_expr(&mut used_visitor, idx);
used_visitor.used used_visitor.used
}; };
if indexed_indirectly || index_used_directly;
if indexed_indirectly || index_used_directly { then {
if self.prefer_mutable { if self.prefer_mutable {
self.indexed_mut.insert(seqvar.segments[0].ident.name); self.indexed_mut.insert(seqvar.segments[0].ident.name);
} }
@ -301,7 +300,6 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
} }
} }
} }
}
true true
} }
} }
@ -312,7 +310,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// a range index op // a range index op
if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; if let ExprKind::MethodCall(meth, _, args, _) = expr.kind;
if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX))
|| (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT));
if !self.check(&args[1], &args[0], expr); if !self.check(&args[1], &args[0], expr);
@ -321,7 +319,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
if_chain! { if_chain! {
// an index op // an index op
if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; if let ExprKind::Index(seqexpr, idx) = expr.kind;
if !self.check(idx, seqexpr, expr); if !self.check(idx, seqexpr, expr);
then { return } then { return }
} }
@ -342,19 +340,19 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
let old = self.prefer_mutable; let old = self.prefer_mutable;
match expr.kind { match expr.kind {
ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { ExprKind::AssignOp(_, lhs, rhs) | ExprKind::Assign(lhs, rhs, _) => {
self.prefer_mutable = true; self.prefer_mutable = true;
self.visit_expr(lhs); self.visit_expr(lhs);
self.prefer_mutable = false; self.prefer_mutable = false;
self.visit_expr(rhs); self.visit_expr(rhs);
}, },
ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
if mutbl == Mutability::Mut { if mutbl == Mutability::Mut {
self.prefer_mutable = true; self.prefer_mutable = true;
} }
self.visit_expr(expr); self.visit_expr(expr);
}, },
ExprKind::Call(ref f, args) => { ExprKind::Call(f, args) => {
self.visit_expr(f); self.visit_expr(f);
for expr in args { for expr in args {
let ty = self.cx.typeck_results().expr_ty_adjusted(expr); let ty = self.cx.typeck_results().expr_ty_adjusted(expr);

View file

@ -5,7 +5,7 @@ use rustc_lint::LateContext;
use std::iter::{once, Iterator}; use std::iter::{once, Iterator};
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Loop(ref block, _, _, _) = expr.kind { if let ExprKind::Loop(block, _, _, _) = expr.kind {
match never_loop_block(block, expr.hir_id) { match never_loop_block(block, expr.hir_id) {
NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"),
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
@ -76,36 +76,36 @@ fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_lo
fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match stmt.kind { match stmt.kind {
StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), StmtKind::Semi(e, ..) | StmtKind::Expr(e, ..) => Some(e),
StmtKind::Local(ref local) => local.init.as_deref(), StmtKind::Local(local) => local.init.as_deref(),
StmtKind::Item(..) => None, StmtKind::Item(..) => None,
} }
} }
fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
match expr.kind { match expr.kind {
ExprKind::Box(ref e) ExprKind::Box(e)
| ExprKind::Unary(_, ref e) | ExprKind::Unary(_, e)
| ExprKind::Cast(ref e, _) | ExprKind::Cast(e, _)
| ExprKind::Type(ref e, _) | ExprKind::Type(e, _)
| ExprKind::Field(ref e, _) | ExprKind::Field(e, _)
| ExprKind::AddrOf(_, _, ref e) | ExprKind::AddrOf(_, _, e)
| ExprKind::Struct(_, _, Some(ref e)) | ExprKind::Struct(_, _, Some(e))
| ExprKind::Repeat(ref e, _) | ExprKind::Repeat(e, _)
| ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id),
ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { ExprKind::Array(es) | ExprKind::MethodCall(_, _, es, _) | ExprKind::Tup(es) => {
never_loop_expr_all(&mut es.iter(), main_loop_id) never_loop_expr_all(&mut es.iter(), main_loop_id)
}, },
ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id),
ExprKind::Binary(_, ref e1, ref e2) ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(ref e1, ref e2, _) | ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, ref e1, ref e2) | ExprKind::AssignOp(_, e1, e2)
| ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), | ExprKind::Index(e1, e2) => never_loop_expr_all(&mut [e1, e2].iter().cloned(), main_loop_id),
ExprKind::Loop(ref b, _, _, _) => { ExprKind::Loop(b, _, _, _) => {
// Break can come from the inner loop so remove them. // Break can come from the inner loop so remove them.
absorb_break(&never_loop_block(b, main_loop_id)) absorb_break(&never_loop_block(b, main_loop_id))
}, },
ExprKind::If(ref e, ref e2, ref e3) => { ExprKind::If(e, e2, ref e3) => {
let e1 = never_loop_expr(e, main_loop_id); let e1 = never_loop_expr(e, main_loop_id);
let e2 = never_loop_expr(e2, main_loop_id); let e2 = never_loop_expr(e2, main_loop_id);
let e3 = e3 let e3 = e3
@ -113,7 +113,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
.map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id));
combine_seq(e1, combine_branches(e2, e3)) combine_seq(e1, combine_branches(e2, e3))
}, },
ExprKind::Match(ref e, ref arms, _) => { ExprKind::Match(e, arms, _) => {
let e = never_loop_expr(e, main_loop_id); let e = never_loop_expr(e, main_loop_id);
if arms.is_empty() { if arms.is_empty() {
e e
@ -122,7 +122,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
combine_seq(e, arms) combine_seq(e, arms)
} }
}, },
ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), ExprKind::Block(b, _) => never_loop_block(b, main_loop_id),
ExprKind::Continue(d) => { ExprKind::Continue(d) => {
let id = d let id = d
.target_id .target_id
@ -136,7 +136,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| {
combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak)
}), }),
ExprKind::InlineAsm(ref asm) => asm ExprKind::InlineAsm(asm) => asm
.operands .operands
.iter() .iter()
.map(|(o, _)| match o { .map(|(o, _)| match o {

View file

@ -1,11 +1,13 @@
use super::SAME_ITEM_PUSH; use super::SAME_ITEM_PUSH;
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind}; use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
@ -41,30 +43,27 @@ pub(super) fn check<'tcx>(
} }
// Determine whether it is safe to lint the body // Determine whether it is safe to lint the body
let mut same_item_push_visitor = SameItemPushVisitor { let mut same_item_push_visitor = SameItemPushVisitor::new(cx);
should_lint: true,
vec_push: None,
cx,
};
walk_expr(&mut same_item_push_visitor, body); walk_expr(&mut same_item_push_visitor, body);
if same_item_push_visitor.should_lint { if_chain! {
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { if same_item_push_visitor.should_lint();
if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push;
let vec_ty = cx.typeck_results().expr_ty(vec); let vec_ty = cx.typeck_results().expr_ty(vec);
let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
if cx if cx
.tcx .tcx
.lang_items() .lang_items()
.clone_trait() .clone_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[])) .map_or(false, |id| implements_trait(cx, ty, id, &[]));
{ then {
// Make sure that the push does not involve possibly mutating values // Make sure that the push does not involve possibly mutating values
match pushed_item.kind { match pushed_item.kind {
ExprKind::Path(ref qpath) => { ExprKind::Path(ref qpath) => {
match cx.qpath_res(qpath, pushed_item.hir_id) { match cx.qpath_res(qpath, pushed_item.hir_id) {
// immutable bindings that are initialized with literal or constant // immutable bindings that are initialized with literal or constant
Res::Local(hir_id) => { Res::Local(hir_id) => {
if_chain! {
let node = cx.tcx.hir().get(hir_id); let node = cx.tcx.hir().get(hir_id);
if_chain! {
if let Node::Binding(pat) = node; if let Node::Binding(pat) = node;
if let PatKind::Binding(bind_ann, ..) = pat.kind; if let PatKind::Binding(bind_ann, ..) = pat.kind;
if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable);
@ -96,15 +95,42 @@ pub(super) fn check<'tcx>(
} }
} }
} }
}
} }
// Scans the body of the for loop and determines whether lint should be given // Scans the body of the for loop and determines whether lint should be given
struct SameItemPushVisitor<'a, 'tcx> { struct SameItemPushVisitor<'a, 'tcx> {
should_lint: bool, non_deterministic_expr: bool,
multiple_pushes: bool,
// this field holds the last vec push operation visited, which should be the only push seen // this field holds the last vec push operation visited, which should be the only push seen
vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
used_locals: FxHashSet<HirId>,
}
impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
fn new(cx: &'a LateContext<'tcx>) -> Self {
Self {
non_deterministic_expr: false,
multiple_pushes: false,
vec_push: None,
cx,
used_locals: FxHashSet::default(),
}
}
fn should_lint(&self) -> bool {
if_chain! {
if !self.non_deterministic_expr;
if !self.multiple_pushes;
if let Some((vec, _)) = self.vec_push;
if let Some(hir_id) = path_to_local(vec);
then {
!self.used_locals.contains(&hir_id)
} else {
false
}
}
}
} }
impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
@ -113,9 +139,14 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
match &expr.kind { match &expr.kind {
// Non-determinism may occur ... don't give a lint // Non-determinism may occur ... don't give a lint
ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, ExprKind::Loop(..) | ExprKind::Match(..) | ExprKind::If(..) => self.non_deterministic_expr = true,
ExprKind::Block(block, _) => self.visit_block(block), ExprKind::Block(block, _) => self.visit_block(block),
_ => {}, _ => {
if let Some(hir_id) = path_to_local(expr) {
self.used_locals.insert(hir_id);
}
walk_expr(self, expr);
},
} }
} }
@ -130,7 +161,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
if vec_push_option.is_none() { if vec_push_option.is_none() {
// Current statement is not a push so visit inside // Current statement is not a push so visit inside
match &s.kind { match &s.kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(expr),
_ => {}, _ => {},
} }
} else { } else {
@ -140,7 +171,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
self.vec_push = vec_push_option; self.vec_push = vec_push_option;
} else { } else {
// There are multiple pushes ... don't lint // There are multiple pushes ... don't lint
self.should_lint = false; self.multiple_pushes = true;
} }
} }
} }

View file

@ -15,12 +15,12 @@ pub(super) fn check<'tcx>(
expr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>,
) { ) {
if_chain! { if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; if let ExprKind::AddrOf(BorrowKind::Ref, _, arg_expr) = arg.kind;
if let PatKind::Binding(.., target, _) = pat.kind; if let PatKind::Binding(.., target, _) = pat.kind;
if let ExprKind::Array([arg_expression]) = arg_expr.kind; if let ExprKind::Array([arg_expression]) = arg_expr.kind;
if let ExprKind::Path(ref list_item) = arg_expression.kind; if let 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 Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name);
if let ExprKind::Block(ref block, _) = body.kind; if let ExprKind::Block(block, _) = body.kind;
if !block.stmts.is_empty(); if !block.stmts.is_empty();
then { then {

View file

@ -1,9 +1,9 @@
use clippy_utils::ty::{has_iter_method, implements_trait}; use clippy_utils::ty::{has_iter_method, implements_trait};
use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg}; use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_local_id, sugg};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor};
use rustc_hir::HirIdMap;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind}; use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
@ -21,7 +21,7 @@ enum IncrementVisitorVarState {
/// Scan a for loop for variables that are incremented exactly once and not used after that. /// Scan a for loop for variables that are incremented exactly once and not used after that.
pub(super) struct IncrementVisitor<'a, 'tcx> { pub(super) struct IncrementVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, // context reference cx: &'a LateContext<'tcx>, // context reference
states: FxHashMap<HirId, IncrementVisitorVarState>, // incremented variables states: HirIdMap<IncrementVisitorVarState>, // incremented variables
depth: u32, // depth of conditional expressions depth: u32, // depth of conditional expressions
done: bool, done: bool,
} }
@ -30,7 +30,7 @@ impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> {
pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self { pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self {
Self { Self {
cx, cx,
states: FxHashMap::default(), states: HirIdMap::default(),
depth: 0, depth: 0,
done: false, done: false,
} }
@ -65,7 +65,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
} }
match parent.kind { match parent.kind {
ExprKind::AssignOp(op, ref lhs, ref rhs) => { ExprKind::AssignOp(op, lhs, rhs) => {
if lhs.hir_id == expr.hir_id { if lhs.hir_id == expr.hir_id {
*state = if op.node == BinOpKind::Add *state = if op.node == BinOpKind::Add
&& is_integer_const(self.cx, rhs, 1) && is_integer_const(self.cx, rhs, 1)
@ -79,7 +79,7 @@ impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> {
}; };
} }
}, },
ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { ExprKind::Assign(lhs, _, _) if lhs.hir_id == expr.hir_id => {
*state = IncrementVisitorVarState::DontWarn *state = IncrementVisitorVarState::DontWarn
}, },
ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => {
@ -153,7 +153,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
// Look for declarations of the variable // Look for declarations of the variable
if_chain! { if_chain! {
if let StmtKind::Local(ref local) = stmt.kind; if let StmtKind::Local(local) = stmt.kind;
if local.pat.hir_id == self.var_id; if local.pat.hir_id == self.var_id;
if let PatKind::Binding(.., ident, _) = local.pat.kind; if let PatKind::Binding(.., ident, _) = local.pat.kind;
then { then {
@ -191,10 +191,10 @@ impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> {
if let Some(parent) = get_parent_expr(self.cx, expr) { if let Some(parent) = get_parent_expr(self.cx, expr) {
match parent.kind { match parent.kind {
ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == expr.hir_id => {
self.state = InitializeVisitorState::DontWarn; self.state = InitializeVisitorState::DontWarn;
}, },
ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { ExprKind::Assign(lhs, rhs, _) if lhs.hir_id == expr.hir_id => {
self.state = if_chain! { self.state = if_chain! {
if self.depth == 0; if self.depth == 0;
if let InitializeVisitorState::Declared(name) if let InitializeVisitorState::Declared(name)
@ -273,7 +273,7 @@ impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
return; return;
} }
match expr.kind { match expr.kind {
ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { ExprKind::Assign(path, _, _) | ExprKind::AssignOp(_, path, _) => {
if path_to_local_id(path, self.iterator) { if path_to_local_id(path, self.iterator) {
self.nesting = RuledOut; self.nesting = RuledOut;
} }
@ -327,7 +327,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
// (&mut x).into_iter() ==> x.iter_mut() // (&mut x).into_iter() ==> x.iter_mut()
match &arg.kind { match &arg.kind {
ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner)
if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => if has_iter_method(cx, cx.typeck_results().expr_ty(arg_inner)).is_some() =>
{ {
let meth_name = match mutability { let meth_name = match mutability {
Mutability::Mut => "iter_mut", Mutability::Mut => "iter_mut",
@ -335,7 +335,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic
}; };
format!( format!(
"{}.{}()", "{}.{}()",
sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), sugg::Sugg::hir_with_applicability(cx, arg_inner, "_", applic_ref).maybe_par(),
meth_name, meth_name,
) )
} }

View file

@ -3,13 +3,13 @@ use crate::consts::constant;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::usage::mutated_variables; use clippy_utils::usage::mutated_variables;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefIdMap;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath}; use rustc_hir::HirIdSet;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use std::iter::Iterator;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) {
if constant(cx, cx.typeck_results(), cond).is_some() { if constant(cx, cx.typeck_results(), cond).is_some() {
@ -19,8 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'
let mut var_visitor = VarCollectorVisitor { let mut var_visitor = VarCollectorVisitor {
cx, cx,
ids: FxHashSet::default(), ids: HirIdSet::default(),
def_ids: FxHashMap::default(), def_ids: DefIdMap::default(),
skip: false, skip: false,
}; };
var_visitor.visit_expr(cond); var_visitor.visit_expr(cond);
@ -93,8 +93,8 @@ impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor {
/// All variables definition IDs are collected /// All variables definition IDs are collected
struct VarCollectorVisitor<'a, 'tcx> { struct VarCollectorVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>, cx: &'a LateContext<'tcx>,
ids: FxHashSet<HirId>, ids: HirIdSet,
def_ids: FxHashMap<def_id::DefId, bool>, def_ids: DefIdMap<bool>,
skip: bool, skip: bool,
} }
@ -103,9 +103,8 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> {
if_chain! { if_chain! {
if let ExprKind::Path(ref qpath) = ex.kind; if let ExprKind::Path(ref qpath) = ex.kind;
if let QPath::Resolved(None, _) = *qpath; if let QPath::Resolved(None, _) = *qpath;
let res = self.cx.qpath_res(qpath, ex.hir_id);
then { then {
match res { match self.cx.qpath_res(qpath, ex.hir_id) {
Res::Local(hir_id) => { Res::Local(hir_id) => {
self.ids.insert(hir_id); self.ids.insert(hir_id);
}, },

View file

@ -11,14 +11,14 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'
let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); let inner_stmt_expr = extract_expr_from_first_stmt(loop_block);
// or extract the first expression (if any) from the block // or extract the first expression (if any) from the block
if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) {
if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { if let ExprKind::Match(matchexpr, arms, ref source) = inner.kind {
// ensure "if let" compatible match structure // ensure "if let" compatible match structure
match *source { match *source {
MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { MatchSource::Normal | MatchSource::IfLetDesugar { .. } => {
if arms.len() == 2 if arms.len() == 2
&& arms[0].guard.is_none() && arms[0].guard.is_none()
&& arms[1].guard.is_none() && arms[1].guard.is_none()
&& is_simple_break_expr(&arms[1].body) && is_simple_break_expr(arms[1].body)
{ {
if in_external_macro(cx.sess(), expr.span) { if in_external_macro(cx.sess(), expr.span) {
return; return;
@ -57,7 +57,7 @@ fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<
if block.stmts.is_empty() { if block.stmts.is_empty() {
return None; return None;
} }
if let StmtKind::Local(ref local) = block.stmts[0].kind { if let StmtKind::Local(local) = block.stmts[0].kind {
local.init //.map(|expr| expr) local.init //.map(|expr| expr)
} else { } else {
None None
@ -67,9 +67,9 @@ fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<
/// If a block begins with an expression (with or without semicolon), return it. /// If a block begins with an expression (with or without semicolon), return it.
fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match block.expr { match block.expr {
Some(ref expr) if block.stmts.is_empty() => Some(expr), Some(expr) if block.stmts.is_empty() => Some(expr),
None if !block.stmts.is_empty() => match block.stmts[0].kind { None if !block.stmts.is_empty() => match block.stmts[0].kind {
StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), StmtKind::Expr(expr) | StmtKind::Semi(expr) => Some(expr),
StmtKind::Local(..) | StmtKind::Item(..) => None, StmtKind::Local(..) | StmtKind::Item(..) => None,
}, },
_ => None, _ => None,
@ -82,7 +82,7 @@ fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_simple_break_expr(expr: &Expr<'_>) -> bool { fn is_simple_break_expr(expr: &Expr<'_>) -> bool {
match expr.kind { match expr.kind {
ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true,
ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), ExprKind::Block(b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)),
_ => false, _ => false,
} }
} }

View file

@ -16,12 +16,10 @@ use rustc_middle::hir::map::Map;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { if let ExprKind::Match(match_expr, arms, MatchSource::WhileLetDesugar) = expr.kind {
let pat = &arms[0].pat.kind; let pat = &arms[0].pat.kind;
if let ( if let (&PatKind::TupleStruct(ref qpath, pat_args, _), &ExprKind::MethodCall(method_path, _, method_args, _)) =
&PatKind::TupleStruct(ref qpath, ref pat_args, _), (pat, &match_expr.kind)
&ExprKind::MethodCall(ref method_path, _, ref method_args, _),
) = (pat, &match_expr.kind)
{ {
let iter_expr = &method_args[0]; let iter_expr = &method_args[0];
@ -40,8 +38,8 @@ pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
&& is_trait_method(cx, match_expr, sym::Iterator) && is_trait_method(cx, match_expr, sym::Iterator)
&& lhs_constructor.ident.name == sym::Some && lhs_constructor.ident.name == sym::Some
&& (pat_args.is_empty() && (pat_args.is_empty()
|| !is_refutable(cx, &pat_args[0]) || !is_refutable(cx, pat_args[0])
&& !is_used_inside(cx, iter_expr, &arms[0].body) && !is_used_inside(cx, iter_expr, arms[0].body)
&& !is_iterator_used_after_while_let(cx, iter_expr) && !is_iterator_used_after_while_let(cx, iter_expr)
&& !is_nested(cx, expr, &method_args[0])) && !is_nested(cx, expr, &method_args[0]))
{ {

View file

@ -9,7 +9,7 @@ use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{edition::Edition, Span}; use rustc_span::{edition::Edition, sym, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for `#[macro_use] use...`. /// **What it does:** Checks for `#[macro_use] use...`.
@ -110,9 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
if cx.sess().opts.edition >= Edition::Edition2018; if cx.sess().opts.edition >= Edition::Edition2018;
if let hir::ItemKind::Use(path, _kind) = &item.kind; if let hir::ItemKind::Use(path, _kind) = &item.kind;
let attrs = cx.tcx.hir().attrs(item.hir_id()); let attrs = cx.tcx.hir().attrs(item.hir_id());
if let Some(mac_attr) = attrs if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
.iter()
.find(|attr| attr.ident().map(|s| s.to_string()) == Some("macro_use".to_string()));
if let Res::Def(DefKind::Mod, id) = path.res; if let Res::Def(DefKind::Mod, id) = path.res;
then { then {
for kid in cx.tcx.item_children(id).iter() { for kid in cx.tcx.item_children(id).iter() {

View file

@ -2,7 +2,7 @@ use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{can_partially_move_ty, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; use clippy_utils::ty::{can_partially_move_ty, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{is_allowed, is_else_clause_of_if_let_else, match_def_path, match_var, paths, peel_hir_expr_refs}; use clippy_utils::{in_constant, is_allowed, is_else_clause, match_def_path, match_var, paths, peel_hir_expr_refs};
use rustc_ast::util::parser::PREC_POSTFIX; use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{ use rustc_hir::{
@ -47,16 +47,16 @@ declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl LateLintPass<'_> for ManualMap { impl LateLintPass<'_> for ManualMap {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Match( if let ExprKind::Match(
scrutinee, scrutinee,
[arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }], [arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
match_kind, match_kind,
) = expr.kind ) = expr.kind
{ {
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
let (scrutinee_ty, ty_ref_count, ty_mutability) = let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type)
@ -181,8 +181,7 @@ impl LateLintPass<'_> for ManualMap {
expr.span, expr.span,
"manual implementation of `Option::map`", "manual implementation of `Option::map`",
"try this", "try this",
if matches!(match_kind, MatchSource::IfLetDesugar { .. }) && is_else_clause_of_if_let_else(cx.tcx, expr) if matches!(match_kind, MatchSource::IfLetDesugar { .. }) && is_else_clause(cx.tcx, expr) {
{
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str) format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
} else { } else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str) format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)

View file

@ -1,9 +1,9 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::meets_msrv; use clippy_utils::meets_msrv;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::{Attribute, FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind}; use rustc_ast::ast::{FieldDef, Item, ItemKind, Variant, VariantData, VisibilityKind};
use rustc_attr as attr;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
@ -102,19 +102,11 @@ fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants
fn is_non_exhaustive_marker(variant: &Variant) -> bool { fn is_non_exhaustive_marker(variant: &Variant) -> bool {
matches!(variant.data, VariantData::Unit(_)) matches!(variant.data, VariantData::Unit(_))
&& variant.ident.as_str().starts_with('_') && variant.ident.as_str().starts_with('_')
&& variant.attrs.iter().any(|a| is_doc_hidden(a)) && is_doc_hidden(&variant.attrs)
} }
fn is_doc_hidden(attr: &Attribute) -> bool {
attr.has_name(sym::doc)
&& match attr.meta_item_list() {
Some(l) => attr::list_contains_name(&l, sym::hidden),
None => false,
}
}
if_chain! {
let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v)); let mut markers = variants.iter().filter(|v| is_non_exhaustive_marker(v));
if_chain! {
if let Some(marker) = markers.next(); if let Some(marker) = markers.next();
if markers.count() == 0 && variants.len() > 1; if markers.count() == 0 && variants.len() > 1;
then { then {

View file

@ -91,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
} else { } else {
return; return;
}; };
let target_res = cx.qpath_res(&target_path, target_arg.hir_id); let target_res = cx.qpath_res(target_path, target_arg.hir_id);
if target_res == Res::Err { if target_res == Res::Err {
return; return;
}; };
@ -174,7 +174,7 @@ fn eq_pattern_length<'tcx>(cx: &LateContext<'tcx>, pattern: &Expr<'_>, expr: &'t
// Tests if `expr` is a `&str`. // Tests if `expr` is a `&str`.
fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_ref_str(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match cx.typeck_results().expr_ty_adjusted(&expr).kind() { match cx.typeck_results().expr_ty_adjusted(expr).kind() {
ty::Ref(_, ty, _) => ty.is_str(), ty::Ref(_, ty, _) => ty.is_str(),
_ => false, _ => false,
} }

View file

@ -52,17 +52,17 @@ impl<'tcx> LateLintPass<'tcx> for MapClone {
} }
if_chain! { if_chain! {
if let hir::ExprKind::MethodCall(ref method, _, ref args, _) = e.kind; if let hir::ExprKind::MethodCall(method, _, args, _) = e.kind;
if args.len() == 2; if args.len() == 2;
if method.ident.name == sym::map; if method.ident.name == sym::map;
let ty = cx.typeck_results().expr_ty(&args[0]); let ty = cx.typeck_results().expr_ty(&args[0]);
if is_type_diagnostic_item(cx, ty, sym::option_type) || is_trait_method(cx, e, sym::Iterator); if is_type_diagnostic_item(cx, ty, sym::option_type) || is_trait_method(cx, e, sym::Iterator);
if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind; if let hir::ExprKind::Closure(_, _, body_id, _, _) = args[1].kind;
then {
let closure_body = cx.tcx.hir().body(body_id); let closure_body = cx.tcx.hir().body(body_id);
let closure_expr = remove_blocks(&closure_body.value); let closure_expr = remove_blocks(&closure_body.value);
then {
match closure_body.params[0].pat.kind { match closure_body.params[0].pat.kind {
hir::PatKind::Ref(ref inner, hir::Mutability::Not) => if let hir::PatKind::Binding( hir::PatKind::Ref(inner, hir::Mutability::Not) => if let hir::PatKind::Binding(
hir::BindingAnnotation::Unannotated, .., name, None hir::BindingAnnotation::Unannotated, .., name, None
) = inner.kind { ) = inner.kind {
if ident_eq(name, closure_expr) { if ident_eq(name, closure_expr) {
@ -71,14 +71,14 @@ impl<'tcx> LateLintPass<'tcx> for MapClone {
}, },
hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => { hir::PatKind::Binding(hir::BindingAnnotation::Unannotated, .., name, None) => {
match closure_expr.kind { match closure_expr.kind {
hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) => { hir::ExprKind::Unary(hir::UnOp::Deref, inner) => {
if ident_eq(name, inner) { if ident_eq(name, inner) {
if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() {
lint(cx, e.span, args[0].span, true); lint(cx, e.span, args[0].span, true);
} }
} }
}, },
hir::ExprKind::MethodCall(ref method, _, [obj], _) => if_chain! { hir::ExprKind::MethodCall(method, _, [obj], _) => if_chain! {
if ident_eq(name, obj) && method.ident.name == sym::clone; if ident_eq(name, obj) && method.ident.name == sym::clone;
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id);
if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); if let Some(trait_id) = cx.tcx.trait_of_item(fn_id);
@ -109,7 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for MapClone {
} }
fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool { fn ident_eq(name: Ident, path: &hir::Expr<'_>) -> bool {
if let hir::ExprKind::Path(hir::QPath::Resolved(None, ref path)) = path.kind { if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = path.kind {
path.segments.len() == 1 && path.segments[0].ident == name path.segments.len() == 1 && path.segments[0].ident == name
} else { } else {
false false

View file

@ -112,7 +112,7 @@ impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
} }
// check if this is a method call (e.g. x.foo()) // check if this is a method call (e.g. x.foo())
if let ExprKind::MethodCall(ref method, _t_span, ref args, _) = e.kind { if let ExprKind::MethodCall(method, _t_span, args, _) = e.kind {
// only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1] // only work if the method name is `map_err` and there are only 2 arguments (e.g. x.map_err(|_|[1]
// Enum::Variant[2])) // Enum::Variant[2]))
if method.ident.as_str() == "map_err" && args.len() == 2 { if method.ident.as_str() == "map_err" && args.len() == 2 {

View file

@ -61,7 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for MapIdentity {
/// map(). Otherwise, returns None. /// map(). Otherwise, returns None.
fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a [Expr<'a>]> { fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a [Expr<'a>]> {
if_chain! { if_chain! {
if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; if let ExprKind::MethodCall(method, _, args, _) = expr.kind;
if args.len() == 2 && method.ident.name == sym::map; if args.len() == 2 && method.ident.name == sym::map;
let caller_ty = cx.typeck_results().expr_ty(&args[0]); let caller_ty = cx.typeck_results().expr_ty(&args[0]);
if is_trait_method(cx, expr, sym::Iterator) if is_trait_method(cx, expr, sym::Iterator)
@ -80,7 +80,7 @@ fn get_map_argument<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a
fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind { match expr.kind {
ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)), ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
ExprKind::Path(QPath::Resolved(_, ref path)) => match_path(path, &paths::STD_CONVERT_IDENTITY), ExprKind::Path(QPath::Resolved(_, path)) => match_path(path, &paths::STD_CONVERT_IDENTITY),
_ => false, _ => false,
} }
} }
@ -99,12 +99,12 @@ fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
match body.kind { match body.kind {
ExprKind::Path(QPath::Resolved(None, _)) => match_expr_param(cx, body, params[0].pat), ExprKind::Path(QPath::Resolved(None, _)) => match_expr_param(cx, body, params[0].pat),
ExprKind::Ret(Some(ref ret_val)) => match_expr_param(cx, ret_val, params[0].pat), ExprKind::Ret(Some(ret_val)) => match_expr_param(cx, ret_val, params[0].pat),
ExprKind::Block(ref block, _) => { ExprKind::Block(block, _) => {
if_chain! { if_chain! {
if block.stmts.len() == 1; if block.stmts.len() == 1;
if let StmtKind::Semi(ref expr) | StmtKind::Expr(ref expr) = block.stmts[0].kind; if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = block.stmts[0].kind;
if let ExprKind::Ret(Some(ref ret_val)) = expr.kind; if let ExprKind::Ret(Some(ret_val)) = expr.kind;
then { then {
match_expr_param(cx, ret_val, params[0].pat) match_expr_param(cx, ret_val, params[0].pat)
} else { } else {

View file

@ -133,7 +133,7 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) ->
// Calls can't be reduced any more // Calls can't be reduced any more
Some(expr.span) Some(expr.span)
}, },
hir::ExprKind::Block(ref block, _) => { hir::ExprKind::Block(block, _) => {
match (block.stmts, block.expr.as_ref()) { match (block.stmts, block.expr.as_ref()) {
(&[], Some(inner_expr)) => { (&[], Some(inner_expr)) => {
// If block only contains an expression, // If block only contains an expression,
@ -144,8 +144,8 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) ->
// If block only contains statements, // If block only contains statements,
// reduce `{ X; }` to `X` or `X;` // reduce `{ X; }` to `X` or `X;`
match inner_stmt.kind { match inner_stmt.kind {
hir::StmtKind::Local(ref local) => Some(local.span), hir::StmtKind::Local(local) => Some(local.span),
hir::StmtKind::Expr(ref e) => Some(e.span), hir::StmtKind::Expr(e) => Some(e.span),
hir::StmtKind::Semi(..) => Some(inner_stmt.span), hir::StmtKind::Semi(..) => Some(inner_stmt.span),
hir::StmtKind::Item(..) => None, hir::StmtKind::Item(..) => None,
} }
@ -168,19 +168,17 @@ fn unit_closure<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> { ) -> Option<(&'tcx hir::Param<'tcx>, &'tcx hir::Expr<'tcx>)> {
if let hir::ExprKind::Closure(_, ref decl, inner_expr_id, _, _) = expr.kind { if_chain! {
if let hir::ExprKind::Closure(_, decl, inner_expr_id, _, _) = expr.kind;
let body = cx.tcx.hir().body(inner_expr_id); let body = cx.tcx.hir().body(inner_expr_id);
let body_expr = &body.value; let body_expr = &body.value;
if_chain! {
if decl.inputs.len() == 1; if decl.inputs.len() == 1;
if is_unit_expression(cx, body_expr); if is_unit_expression(cx, body_expr);
if let Some(binding) = iter_input_pats(&decl, body).next(); if let Some(binding) = iter_input_pats(decl, body).next();
then { then {
return Some((binding, body_expr)); return Some((binding, body_expr));
} }
} }
}
None None
} }
@ -269,7 +267,7 @@ impl<'tcx> LateLintPass<'tcx> for MapUnit {
return; return;
} }
if let hir::StmtKind::Semi(ref expr) = stmt.kind { if let hir::StmtKind::Semi(expr) = stmt.kind {
if let Some(arglists) = method_chain_args(expr, &["map"]) { if let Some(arglists) = method_chain_args(expr, &["map"]) {
lint_map_unit_fn(cx, stmt, expr, arglists[0]); lint_map_unit_fn(cx, stmt, expr, arglists[0]);
} }

View file

@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchOnVecItems {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! { if_chain! {
if !in_external_macro(cx.sess(), expr.span); if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Match(ref match_expr, _, MatchSource::Normal) = expr.kind; if let ExprKind::Match(match_expr, _, MatchSource::Normal) = expr.kind;
if let Some(idx_expr) = is_vec_indexing(cx, match_expr); if let Some(idx_expr) = is_vec_indexing(cx, match_expr);
if let ExprKind::Index(vec, idx) = idx_expr.kind; if let ExprKind::Index(vec, idx) = idx_expr.kind;
@ -78,7 +78,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchOnVecItems {
fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if_chain! { if_chain! {
if let ExprKind::Index(ref array, ref index) = expr.kind; if let ExprKind::Index(array, index) = expr.kind;
if is_vector(cx, array); if is_vector(cx, array);
if !is_full_range(cx, index); if !is_full_range(cx, index);

View file

@ -13,13 +13,13 @@ use clippy_utils::{
use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash}; use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::{ use rustc_hir::{
self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource, self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind, Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
}; };
use rustc_hir::{HirIdMap, HirIdSet};
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty, TyS, VariantDef}; use rustc_middle::ty::{self, Ty, TyS, VariantDef};
@ -590,7 +590,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
lint_match_arms(cx, expr); lint_match_arms(cx, expr);
} }
if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind { if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
check_single_match(cx, ex, arms, expr); check_single_match(cx, ex, arms, expr);
check_match_bool(cx, ex, arms, expr); check_match_bool(cx, ex, arms, expr);
check_overlapping_arms(cx, ex, arms); check_overlapping_arms(cx, ex, arms);
@ -605,7 +605,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
check_match_single_binding(cx, ex, arms, expr); check_match_single_binding(cx, ex, arms, expr);
} }
} }
if let ExprKind::Match(ref ex, ref arms, _) = expr.kind { if let ExprKind::Match(ex, arms, _) = expr.kind {
check_match_ref_pats(cx, ex, arms, expr); check_match_ref_pats(cx, ex, arms, expr);
} }
} }
@ -614,14 +614,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if_chain! { if_chain! {
if !in_external_macro(cx.sess(), local.span); if !in_external_macro(cx.sess(), local.span);
if !in_macro(local.span); if !in_macro(local.span);
if let Some(ref expr) = local.init; if let Some(expr) = local.init;
if let ExprKind::Match(ref target, ref arms, MatchSource::Normal) = expr.kind; if let ExprKind::Match(target, arms, MatchSource::Normal) = expr.kind;
if arms.len() == 1 && arms[0].guard.is_none(); if arms.len() == 1 && arms[0].guard.is_none();
if let PatKind::TupleStruct( if let PatKind::TupleStruct(
QPath::Resolved(None, ref variant_name), ref args, _) = arms[0].pat.kind; QPath::Resolved(None, variant_name), args, _) = arms[0].pat.kind;
if args.len() == 1; if args.len() == 1;
if let PatKind::Binding(_, arg, ..) = strip_pat_refs(&args[0]).kind; if let PatKind::Binding(_, arg, ..) = strip_pat_refs(args[0]).kind;
let body = remove_blocks(&arms[0].body); let body = remove_blocks(arms[0].body);
if path_to_local_id(body, arg); if path_to_local_id(body, arg);
then { then {
@ -650,7 +650,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if_chain! { if_chain! {
if !in_external_macro(cx.sess(), pat.span); if !in_external_macro(cx.sess(), pat.span);
if !in_macro(pat.span); if !in_macro(pat.span);
if let PatKind::Struct(QPath::Resolved(_, ref path), fields, true) = pat.kind; if let PatKind::Struct(QPath::Resolved(_, path), fields, true) = pat.kind;
if let Some(def_id) = path.res.opt_def_id(); if let Some(def_id) = path.res.opt_def_id();
let ty = cx.tcx.type_of(def_id); let ty = cx.tcx.type_of(def_id);
if let ty::Adt(def, _) = ty.kind(); if let ty::Adt(def, _) = ty.kind();
@ -733,8 +733,8 @@ fn report_single_match_single_pattern(
format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
}); });
let (msg, sugg) = if_chain! {
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); 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; if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait(); if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
@ -762,7 +762,7 @@ fn report_single_match_single_pattern(
// PartialEq for different reference counts may not exist. // PartialEq for different reference counts may not exist.
"&".repeat(ref_count_diff), "&".repeat(ref_count_diff),
snippet(cx, arms[0].pat.span, ".."), snippet(cx, arms[0].pat.span, ".."),
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
els_str, els_str,
); );
(msg, sugg) (msg, sugg)
@ -772,7 +772,7 @@ fn report_single_match_single_pattern(
"if let {} = {} {}{}", "if let {} = {} {}{}",
snippet(cx, arms[0].pat.span, ".."), snippet(cx, arms[0].pat.span, ".."),
snippet(cx, ex.span, ".."), snippet(cx, ex.span, ".."),
expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
els_str, els_str,
); );
(msg, sugg) (msg, sugg)
@ -810,7 +810,7 @@ fn check_single_match_opt_like(
]; ];
let path = match arms[1].pat.kind { let path = match arms[1].pat.kind {
PatKind::TupleStruct(ref path, ref inner, _) => { PatKind::TupleStruct(ref path, inner, _) => {
// Contains any non wildcard patterns (e.g., `Err(err)`)? // Contains any non wildcard patterns (e.g., `Err(err)`)?
if !inner.iter().all(is_wild) { if !inner.iter().all(is_wild) {
return; return;
@ -842,7 +842,7 @@ fn check_match_bool(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
move |diag| { move |diag| {
if arms.len() == 2 { if arms.len() == 2 {
// no guards // no guards
let exprs = if let PatKind::Lit(ref arm_bool) = arms[0].pat.kind { let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
if let ExprKind::Lit(ref lit) = arm_bool.kind { if let ExprKind::Lit(ref lit) = arm_bool.kind {
match lit.node { match lit.node {
LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)), LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
@ -918,14 +918,14 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs(); let ex_ty = cx.typeck_results().expr_ty(ex).peel_refs();
if is_type_diagnostic_item(cx, ex_ty, sym::result_type) { if is_type_diagnostic_item(cx, ex_ty, sym::result_type) {
for arm in arms { for arm in arms {
if let PatKind::TupleStruct(ref path, ref inner, _) = arm.pat.kind { 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)); let path_str = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_qpath(path, false));
if path_str == "Err" { if path_str == "Err" {
let mut matching_wild = inner.iter().any(is_wild); let mut matching_wild = inner.iter().any(is_wild);
let mut ident_bind_name = String::from("_"); let mut ident_bind_name = String::from("_");
if !matching_wild { if !matching_wild {
// Looking for unused bindings (i.e.: `_e`) // Looking for unused bindings (i.e.: `_e`)
inner.iter().for_each(|pat| { for pat in inner.iter() {
if let PatKind::Binding(_, id, ident, None) = pat.kind { if let PatKind::Binding(_, id, ident, None) = pat.kind {
if ident.as_str().starts_with('_') if ident.as_str().starts_with('_')
&& !LocalUsedVisitor::new(cx, id).check_expr(arm.body) && !LocalUsedVisitor::new(cx, id).check_expr(arm.body)
@ -934,11 +934,11 @@ fn check_wild_err_arm<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm
matching_wild = true; matching_wild = true;
} }
} }
}); }
} }
if_chain! { if_chain! {
if matching_wild; if matching_wild;
if let ExprKind::Block(ref block, _) = arm.body.kind; if let ExprKind::Block(block, _) = arm.body.kind;
if is_panic_block(block); if is_panic_block(block);
then { then {
// `Err(_)` or `Err(_e)` arm with `panic!` found // `Err(_)` or `Err(_e)` arm with `panic!` found
@ -984,6 +984,11 @@ impl CommonPrefixSearcher<'a> {
} }
} }
fn is_doc_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool {
let attrs = cx.tcx.get_attrs(variant_def.def_id);
clippy_utils::attrs::is_doc_hidden(attrs)
}
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
let ty = cx.typeck_results().expr_ty(ex).peel_refs(); let ty = cx.typeck_results().expr_ty(ex).peel_refs();
@ -1042,17 +1047,19 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
path path
}, },
PatKind::TupleStruct(path, patterns, ..) => { 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)) { if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) {
let id = cx.qpath_res(path, pat.hir_id).def_id();
missing_variants.retain(|e| e.ctor_def_id != Some(id)); missing_variants.retain(|e| e.ctor_def_id != Some(id));
} }
}
path path
}, },
PatKind::Struct(path, patterns, ..) => { 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)) { if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) {
let id = cx.qpath_res(path, pat.hir_id).def_id();
missing_variants.retain(|e| e.def_id != id); missing_variants.retain(|e| e.def_id != id);
} }
}
path path
}, },
_ => return, _ => return,
@ -1103,7 +1110,7 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
match missing_variants.as_slice() { match missing_variants.as_slice() {
[] => (), [] => (),
[x] if !adt_def.is_variant_list_non_exhaustive() => span_lint_and_sugg( [x] if !adt_def.is_variant_list_non_exhaustive() && !is_doc_hidden(cx, x) => span_lint_and_sugg(
cx, cx,
MATCH_WILDCARD_FOR_SINGLE_VARIANTS, MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
wildcard_span, wildcard_span,
@ -1137,9 +1144,7 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
// If the block contains only a `panic!` macro (as expression or statement) // If the block contains only a `panic!` macro (as expression or statement)
fn is_panic_block(block: &Block<'_>) -> bool { fn is_panic_block(block: &Block<'_>) -> bool {
match (&block.expr, block.stmts.len(), block.stmts.first()) { match (&block.expr, block.stmts.len(), block.stmts.first()) {
(&Some(ref exp), 0, _) => { (&Some(exp), 0, _) => is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none(),
is_expn_of(exp.span, "panic").is_some() && is_expn_of(exp.span, "unreachable").is_none()
},
(&None, 1, Some(stmt)) => { (&None, 1, Some(stmt)) => {
is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none() is_expn_of(stmt.span, "panic").is_some() && is_expn_of(stmt.span, "unreachable").is_none()
}, },
@ -1150,7 +1155,7 @@ fn is_panic_block(block: &Block<'_>) -> bool {
fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) { fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if has_only_ref_pats(arms) { if has_only_ref_pats(arms) {
let mut suggs = Vec::with_capacity(arms.len() + 1); let mut suggs = Vec::with_capacity(arms.len() + 1);
let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, ref inner) = ex.kind { let (title, msg) = if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
let span = ex.span.source_callsite(); let span = ex.span.source_callsite();
suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string())); suggs.push((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
( (
@ -1167,7 +1172,7 @@ fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], e
}; };
suggs.extend(arms.iter().filter_map(|a| { suggs.extend(arms.iter().filter_map(|a| {
if let PatKind::Ref(ref refp, _) = a.pat.kind { if let PatKind::Ref(refp, _) = a.pat.kind {
Some((a.pat.span, snippet(cx, refp.span, "..").to_string())) Some((a.pat.span, snippet(cx, refp.span, "..").to_string()))
} else { } else {
None None
@ -1236,7 +1241,7 @@ fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], exp
fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) {
for arm in arms { for arm in arms {
if let PatKind::Or(ref fields) = arm.pat.kind { if let PatKind::Or(fields) = arm.pat.kind {
// look for multiple fields in this arm that contains at least one Wild pattern // look for multiple fields in this arm that contains at least one Wild pattern
if fields.len() > 1 && fields.iter().any(is_wild) { if fields.len() > 1 && fields.iter().any(is_wild) {
span_lint_and_help( span_lint_and_help(
@ -1302,7 +1307,7 @@ fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr
// strip potential borrows (#6503), but only if the type is a reference // strip potential borrows (#6503), but only if the type is a reference
let mut ex_new = ex; let mut ex_new = ex;
if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind {
if let ty::Ref(..) = cx.typeck_results().expr_ty(&ex_inner).kind() { if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() {
ex_new = ex_inner; ex_new = ex_inner;
} }
}; };
@ -1379,7 +1384,7 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A
let matched_vars = ex.span; let matched_vars = ex.span;
let bind_names = arms[0].pat.span; let bind_names = arms[0].pat.span;
let match_body = remove_blocks(&arms[0].body); let match_body = remove_blocks(arms[0].body);
let mut snippet_body = if match_body.span.from_expansion() { let mut snippet_body = if match_body.span.from_expansion() {
Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string() Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
} else { } else {
@ -1390,13 +1395,13 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A
match match_body.kind { match match_body.kind {
ExprKind::Block(block, _) => { ExprKind::Block(block, _) => {
// macro + expr_ty(body) == () // macro + expr_ty(body) == ()
if block.span.from_expansion() && cx.typeck_results().expr_ty(&match_body).is_unit() { if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
snippet_body.push(';'); snippet_body.push(';');
} }
}, },
_ => { _ => {
// expr_ty(body) == () // expr_ty(body) == ()
if cx.typeck_results().expr_ty(&match_body).is_unit() { if cx.typeck_results().expr_ty(match_body).is_unit() {
snippet_body.push(';'); snippet_body.push(';');
} }
}, },
@ -1481,8 +1486,8 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A
/// Returns true if the `ex` match expression is in a local (`let`) statement /// 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>> { fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'a>> {
if_chain! {
let map = &cx.tcx.hir(); 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::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)); if let Some(Node::Local(parent_let_expr)) = map.find(map.get_parent_node(parent_arm_expr.hir_id));
then { then {
@ -1496,10 +1501,7 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'
fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> { fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> {
arms.iter() arms.iter()
.flat_map(|arm| { .flat_map(|arm| {
if let Arm { if let Arm { pat, guard: None, .. } = *arm {
ref pat, guard: None, ..
} = *arm
{
if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind { if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
let lhs = match lhs { let lhs = match lhs {
Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0, Some(lhs) => constant(cx, cx.typeck_results(), lhs)?.0,
@ -1519,7 +1521,7 @@ fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>)
}); });
} }
if let PatKind::Lit(ref value) = pat.kind { if let PatKind::Lit(value) = pat.kind {
let value = constant(cx, cx.typeck_results(), value)?.0; let value = constant(cx, cx.typeck_results(), value)?.0;
return Some(SpannedRange { return Some(SpannedRange {
span: pat.span, span: pat.span,
@ -1566,8 +1568,8 @@ fn type_ranges(ranges: &[SpannedRange<Constant>]) -> TypedRanges {
fn is_unit_expr(expr: &Expr<'_>) -> bool { fn is_unit_expr(expr: &Expr<'_>) -> bool {
match expr.kind { match expr.kind {
ExprKind::Tup(ref v) if v.is_empty() => true, ExprKind::Tup(v) if v.is_empty() => true,
ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true, ExprKind::Block(b, _) if b.stmts.is_empty() && b.expr.is_none() => true,
_ => false, _ => false,
} }
} }
@ -1580,14 +1582,14 @@ fn is_none_arm(arm: &Arm<'_>) -> bool {
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> { fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> {
if_chain! { if_chain! {
if let PatKind::TupleStruct(ref path, ref pats, _) = arm.pat.kind; if let PatKind::TupleStruct(ref path, pats, _) = arm.pat.kind;
if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME); if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME);
if let PatKind::Binding(rb, .., ident, _) = pats[0].kind; if let PatKind::Binding(rb, .., ident, _) = pats[0].kind;
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind; if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind;
if let ExprKind::Path(ref some_path) = e.kind; if let ExprKind::Path(ref some_path) = e.kind;
if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1; if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
if let ExprKind::Path(QPath::Resolved(_, ref path2)) = args[0].kind; if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name; if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
then { then {
return Some(rb) return Some(rb)
@ -1669,7 +1671,7 @@ where
values.sort(); values.sort();
for (a, b) in iter::zip(&values, &values[1..]) { for (a, b) in iter::zip(&values, values.iter().skip(1)) {
match (a, b) { match (a, b) {
(&Kind::Start(_, ra), &Kind::End(_, rb)) => { (&Kind::Start(_, ra), &Kind::End(_, rb)) => {
if ra.node != rb.node { if ra.node != rb.node {
@ -1679,7 +1681,7 @@ where
(&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (), (&Kind::End(a, _), &Kind::Start(b, _)) if a != Bound::Included(b) => (),
_ => { _ => {
// skip if the range `a` is completely included into the range `b` // skip if the range `a` is completely included into the range `b`
if let Ordering::Equal | Ordering::Less = a.cmp(&b) { if let Ordering::Equal | Ordering::Less = a.cmp(b) {
let kind_a = Kind::End(a.range().node.1, a.range()); let kind_a = Kind::End(a.range().node.1, a.range());
let kind_b = Kind::End(b.range().node.1, b.range()); let kind_b = Kind::End(b.range().node.1, b.range());
if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) { if let Ordering::Equal | Ordering::Greater = kind_a.cmp(&kind_b) {
@ -1724,8 +1726,14 @@ mod redundant_pattern_match {
arms: &[Arm<'_>], arms: &[Arm<'_>],
keyword: &'static str, keyword: &'static str,
) { ) {
let good_method = match arms[0].pat.kind { // also look inside refs
PatKind::TupleStruct(ref path, ref patterns, _) if patterns.len() == 1 => { let mut kind = &arms[0].pat.kind;
// if we have &None for example, peel it so we can detect "if let None = x"
if let PatKind::Ref(inner, _mutability) = kind {
kind = &inner.kind;
}
let good_method = match kind {
PatKind::TupleStruct(ref path, patterns, _) if patterns.len() == 1 => {
if let PatKind::Wild = patterns[0].kind { if let PatKind::Wild = patterns[0].kind {
if match_qpath(path, &paths::RESULT_OK) { if match_qpath(path, &paths::RESULT_OK) {
"is_ok()" "is_ok()"
@ -1806,8 +1814,8 @@ mod redundant_pattern_match {
let found_good_method = match node_pair { let found_good_method = match node_pair {
( (
PatKind::TupleStruct(ref path_left, ref patterns_left, _), PatKind::TupleStruct(ref path_left, patterns_left, _),
PatKind::TupleStruct(ref path_right, ref patterns_right, _), PatKind::TupleStruct(ref path_right, patterns_right, _),
) if patterns_left.len() == 1 && patterns_right.len() == 1 => { ) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
find_good_method_for_match( find_good_method_for_match(
@ -1834,8 +1842,8 @@ mod redundant_pattern_match {
None None
} }
}, },
(PatKind::TupleStruct(ref path_left, ref patterns, _), PatKind::Path(ref path_right)) (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right))
| (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, ref patterns, _)) | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _))
if patterns.len() == 1 => if patterns.len() == 1 =>
{ {
if let PatKind::Wild = patterns[0].kind { if let PatKind::Wild = patterns[0].kind {
@ -1957,10 +1965,10 @@ fn test_overlapping() {
/// Implementation of `MATCH_SAME_ARMS`. /// Implementation of `MATCH_SAME_ARMS`.
fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { if let ExprKind::Match(_, arms, MatchSource::Normal) = expr.kind {
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
let mut h = SpanlessHash::new(cx); let mut h = SpanlessHash::new(cx);
h.hash_expr(&arm.body); h.hash_expr(arm.body);
h.finish() h.finish()
}; };
@ -1968,7 +1976,7 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
let min_index = usize::min(lindex, rindex); let min_index = usize::min(lindex, rindex);
let max_index = usize::max(lindex, rindex); let max_index = usize::max(lindex, rindex);
let mut local_map: FxHashMap<HirId, HirId> = FxHashMap::default(); let mut local_map: HirIdMap<HirId> = HirIdMap::default();
let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| {
if_chain! { if_chain! {
if let Some(a_id) = path_to_local(a); if let Some(a_id) = path_to_local(a);
@ -1996,7 +2004,7 @@ fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) {
(min_index..=max_index).all(|index| arms[index].guard.is_none()) (min_index..=max_index).all(|index| arms[index].guard.is_none())
&& SpanlessEq::new(cx) && SpanlessEq::new(cx)
.expr_fallback(eq_fallback) .expr_fallback(eq_fallback)
.eq_expr(&lhs.body, &rhs.body) .eq_expr(lhs.body, rhs.body)
// these checks could be removed to allow unused bindings // these checks could be removed to allow unused bindings
&& bindings_eq(lhs.pat, local_map.keys().copied().collect()) && bindings_eq(lhs.pat, local_map.keys().copied().collect())
&& bindings_eq(rhs.pat, local_map.values().copied().collect()) && bindings_eq(rhs.pat, local_map.values().copied().collect())
@ -2052,7 +2060,7 @@ fn pat_contains_local(pat: &Pat<'_>, id: HirId) -> bool {
} }
/// Returns true if all the bindings in the `Pat` are in `ids` and vice versa /// Returns true if all the bindings in the `Pat` are in `ids` and vice versa
fn bindings_eq(pat: &Pat<'_>, mut ids: FxHashSet<HirId>) -> bool { fn bindings_eq(pat: &Pat<'_>, mut ids: HirIdSet) -> bool {
let mut result = true; let mut result = true;
pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id)); pat.each_binding_or_first(&mut |_, id, _, _| result &= ids.remove(&id));
result && ids.is_empty() result && ids.is_empty()

View file

@ -34,7 +34,7 @@ declare_lint_pass!(MemDiscriminant => [MEM_DISCRIMINANT_NON_ENUM]);
impl<'tcx> LateLintPass<'tcx> for MemDiscriminant { impl<'tcx> LateLintPass<'tcx> for MemDiscriminant {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if let ExprKind::Call(ref func, ref func_args) = expr.kind; if let ExprKind::Call(func, func_args) = expr.kind;
// is `mem::discriminant` // is `mem::discriminant`
if let ExprKind::Path(ref func_qpath) = func.kind; if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
@ -59,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for MemDiscriminant {
let mut derefs_needed = ptr_depth; let mut derefs_needed = ptr_depth;
let mut cur_expr = param; let mut cur_expr = param;
while derefs_needed > 0 { while derefs_needed > 0 {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref inner_expr) = cur_expr.kind { if let ExprKind::AddrOf(BorrowKind::Ref, _, inner_expr) = cur_expr.kind {
derefs_needed -= 1; derefs_needed -= 1;
cur_expr = inner_expr; cur_expr = inner_expr;
} else { } else {

View file

@ -28,7 +28,7 @@ declare_lint_pass!(MemForget => [MEM_FORGET]);
impl<'tcx> LateLintPass<'tcx> for MemForget { impl<'tcx> LateLintPass<'tcx> for MemForget {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if let ExprKind::Call(ref path_expr, ref args) = e.kind { if let ExprKind::Call(path_expr, args) = e.kind {
if let ExprKind::Path(ref qpath) = path_expr.kind { if let ExprKind::Path(ref qpath) = path_expr.kind {
if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() { if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id() {
if match_def_path(cx, def_id, &paths::MEM_FORGET) { if match_def_path(cx, def_id, &paths::MEM_FORGET) {

View file

@ -109,14 +109,14 @@ fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &E
// argument's type. All that's left is to get // argument's type. All that's left is to get
// replacee's path. // replacee's path.
let replaced_path = match dest.kind { let replaced_path = match dest.kind {
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, ref replaced) => { ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
if let ExprKind::Path(QPath::Resolved(None, ref replaced_path)) = replaced.kind { if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
replaced_path replaced_path
} else { } else {
return; return;
} }
}, },
ExprKind::Path(QPath::Resolved(None, ref replaced_path)) => replaced_path, ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
_ => return, _ => return,
}; };
@ -161,7 +161,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'
} }
if_chain! { if_chain! {
if let ExprKind::Call(ref repl_func, ref repl_args) = src.kind; if let ExprKind::Call(repl_func, repl_args) = src.kind;
if repl_args.is_empty(); if repl_args.is_empty();
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
@ -214,7 +214,7 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
.iter() .iter()
.any(|symbol| is_diagnostic_assoc_item(cx, def_id, *symbol)) .any(|symbol| is_diagnostic_assoc_item(cx, def_id, *symbol))
{ {
if let QPath::TypeRelative(_, ref method) = path { if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new { if method.ident.name == sym::new {
return true; return true;
} }
@ -225,8 +225,8 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
} }
fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) { fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
if let ExprKind::Call(ref repl_func, _) = src.kind {
if_chain! { if_chain! {
if let ExprKind::Call(repl_func, _) = src.kind;
if !in_external_macro(cx.tcx.sess, expr_span); if !in_external_macro(cx.tcx.sess, expr_span);
if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind; if let ExprKind::Path(ref repl_func_qpath) = repl_func.kind;
if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id(); if let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id();
@ -254,7 +254,6 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
); );
} }
} }
}
} }
const MEM_REPLACE_WITH_DEFAULT_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); const MEM_REPLACE_WITH_DEFAULT_MSRV: RustcVersion = RustcVersion::new(1, 40, 0);
@ -274,11 +273,11 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
// Check that `expr` is a call to `mem::replace()` // Check that `expr` is a call to `mem::replace()`
if let ExprKind::Call(ref func, ref func_args) = expr.kind; if let ExprKind::Call(func, func_args) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind; if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::MEM_REPLACE); if match_def_path(cx, def_id, &paths::MEM_REPLACE);
if let [dest, src] = &**func_args; if let [dest, src] = func_args;
then { then {
check_replace_option_with_none(cx, src, dest, expr.span); check_replace_option_with_none(cx, src, dest, expr.span);
check_replace_with_uninit(cx, src, dest, expr.span); check_replace_with_uninit(cx, src, dest, expr.span);

View file

@ -1,101 +1,82 @@
use super::{contains_return, BIND_INSTEAD_OF_MAP}; use super::{contains_return, BIND_INSTEAD_OF_MAP};
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::{snippet, snippet_with_macro_callsite}; use clippy_utils::source::{snippet, snippet_with_macro_callsite};
use clippy_utils::ty::match_type; use clippy_utils::{in_macro, remove_blocks, visitors::find_all_ret_expressions};
use clippy_utils::{in_macro, match_qpath, method_calls, paths, remove_blocks, visitors::find_all_ret_expressions};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{LangItem, QPath};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::DefIdTree;
use rustc_span::Span; use rustc_span::Span;
pub(crate) struct OptionAndThenSome; pub(crate) struct OptionAndThenSome;
impl BindInsteadOfMap for OptionAndThenSome { impl BindInsteadOfMap for OptionAndThenSome {
const TYPE_NAME: &'static str = "Option"; const VARIANT_LANG_ITEM: LangItem = LangItem::OptionSome;
const TYPE_QPATH: &'static [&'static str] = &paths::OPTION;
const BAD_METHOD_NAME: &'static str = "and_then"; const BAD_METHOD_NAME: &'static str = "and_then";
const BAD_VARIANT_NAME: &'static str = "Some";
const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::OPTION_SOME;
const GOOD_METHOD_NAME: &'static str = "map"; const GOOD_METHOD_NAME: &'static str = "map";
} }
pub(crate) struct ResultAndThenOk; pub(crate) struct ResultAndThenOk;
impl BindInsteadOfMap for ResultAndThenOk { impl BindInsteadOfMap for ResultAndThenOk {
const TYPE_NAME: &'static str = "Result"; const VARIANT_LANG_ITEM: LangItem = LangItem::ResultOk;
const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
const BAD_METHOD_NAME: &'static str = "and_then"; const BAD_METHOD_NAME: &'static str = "and_then";
const BAD_VARIANT_NAME: &'static str = "Ok";
const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_OK;
const GOOD_METHOD_NAME: &'static str = "map"; const GOOD_METHOD_NAME: &'static str = "map";
} }
pub(crate) struct ResultOrElseErrInfo; pub(crate) struct ResultOrElseErrInfo;
impl BindInsteadOfMap for ResultOrElseErrInfo { impl BindInsteadOfMap for ResultOrElseErrInfo {
const TYPE_NAME: &'static str = "Result"; const VARIANT_LANG_ITEM: LangItem = LangItem::ResultErr;
const TYPE_QPATH: &'static [&'static str] = &paths::RESULT;
const BAD_METHOD_NAME: &'static str = "or_else"; const BAD_METHOD_NAME: &'static str = "or_else";
const BAD_VARIANT_NAME: &'static str = "Err";
const BAD_VARIANT_QPATH: &'static [&'static str] = &paths::RESULT_ERR;
const GOOD_METHOD_NAME: &'static str = "map_err"; const GOOD_METHOD_NAME: &'static str = "map_err";
} }
pub(crate) trait BindInsteadOfMap { pub(crate) trait BindInsteadOfMap {
const TYPE_NAME: &'static str; const VARIANT_LANG_ITEM: LangItem;
const TYPE_QPATH: &'static [&'static str];
const BAD_METHOD_NAME: &'static str; const BAD_METHOD_NAME: &'static str;
const BAD_VARIANT_NAME: &'static str;
const BAD_VARIANT_QPATH: &'static [&'static str];
const GOOD_METHOD_NAME: &'static str; const GOOD_METHOD_NAME: &'static str;
fn no_op_msg() -> String { fn no_op_msg(cx: &LateContext<'_>) -> Option<String> {
format!( let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
let item_id = cx.tcx.parent(variant_id)?;
Some(format!(
"using `{}.{}({})`, which is a no-op", "using `{}.{}({})`, which is a no-op",
Self::TYPE_NAME, cx.tcx.item_name(item_id),
Self::BAD_METHOD_NAME, Self::BAD_METHOD_NAME,
Self::BAD_VARIANT_NAME cx.tcx.item_name(variant_id),
) ))
} }
fn lint_msg() -> String { fn lint_msg(cx: &LateContext<'_>) -> Option<String> {
format!( let variant_id = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM).ok()?;
let item_id = cx.tcx.parent(variant_id)?;
Some(format!(
"using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`", "using `{}.{}(|x| {}(y))`, which is more succinctly expressed as `{}(|x| y)`",
Self::TYPE_NAME, cx.tcx.item_name(item_id),
Self::BAD_METHOD_NAME, Self::BAD_METHOD_NAME,
Self::BAD_VARIANT_NAME, cx.tcx.item_name(variant_id),
Self::GOOD_METHOD_NAME Self::GOOD_METHOD_NAME
) ))
} }
fn lint_closure_autofixable( fn lint_closure_autofixable(
cx: &LateContext<'_>, cx: &LateContext<'_>,
expr: &hir::Expr<'_>, expr: &hir::Expr<'_>,
args: &[hir::Expr<'_>], recv: &hir::Expr<'_>,
closure_expr: &hir::Expr<'_>, closure_expr: &hir::Expr<'_>,
closure_args_span: Span, closure_args_span: Span,
) -> bool { ) -> bool {
if_chain! { if_chain! {
if let hir::ExprKind::Call(ref some_expr, ref some_args) = closure_expr.kind; if let hir::ExprKind::Call(some_expr, [inner_expr]) = closure_expr.kind;
if let hir::ExprKind::Path(ref qpath) = some_expr.kind; if let hir::ExprKind::Path(QPath::Resolved(_, path)) = some_expr.kind;
if match_qpath(qpath, Self::BAD_VARIANT_QPATH); if Self::is_variant(cx, path.res);
if some_args.len() == 1; if !contains_return(inner_expr);
if let Some(msg) = Self::lint_msg(cx);
then { then {
let inner_expr = &some_args[0];
if contains_return(inner_expr) {
return false;
}
let some_inner_snip = if inner_expr.span.from_expansion() { let some_inner_snip = if inner_expr.span.from_expansion() {
snippet_with_macro_callsite(cx, inner_expr.span, "_") snippet_with_macro_callsite(cx, inner_expr.span, "_")
} else { } else {
@ -103,13 +84,13 @@ pub(crate) trait BindInsteadOfMap {
}; };
let closure_args_snip = snippet(cx, closure_args_span, ".."); let closure_args_snip = snippet(cx, closure_args_span, "..");
let option_snip = snippet(cx, args[0].span, ".."); let option_snip = snippet(cx, recv.span, "..");
let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip); let note = format!("{}.{}({} {})", option_snip, Self::GOOD_METHOD_NAME, closure_args_snip, some_inner_snip);
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
BIND_INSTEAD_OF_MAP, BIND_INSTEAD_OF_MAP,
expr.span, expr.span,
Self::lint_msg().as_ref(), &msg,
"try this", "try this",
note, note,
Applicability::MachineApplicable, Applicability::MachineApplicable,
@ -126,68 +107,84 @@ pub(crate) trait BindInsteadOfMap {
let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| { let can_sugg: bool = find_all_ret_expressions(cx, closure_expr, |ret_expr| {
if_chain! { if_chain! {
if !in_macro(ret_expr.span); if !in_macro(ret_expr.span);
if let hir::ExprKind::Call(ref func_path, ref args) = ret_expr.kind; if let hir::ExprKind::Call(func_path, [arg]) = ret_expr.kind;
if let hir::ExprKind::Path(ref qpath) = func_path.kind; if let hir::ExprKind::Path(QPath::Resolved(_, path)) = func_path.kind;
if match_qpath(qpath, Self::BAD_VARIANT_QPATH); if Self::is_variant(cx, path.res);
if args.len() == 1; if !contains_return(arg);
if !contains_return(&args[0]);
then { then {
suggs.push((ret_expr.span, args[0].span.source_callsite())); suggs.push((ret_expr.span, arg.span.source_callsite()));
true true
} else { } else {
false false
} }
} }
}); });
let (span, msg) = if_chain! {
if can_sugg { if can_sugg;
span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, Self::lint_msg().as_ref(), |diag| { if let hir::ExprKind::MethodCall(_, span, ..) = expr.kind;
if let Some(msg) = Self::lint_msg(cx);
then { (span, msg) } else { return false; }
};
span_lint_and_then(cx, BIND_INSTEAD_OF_MAP, expr.span, &msg, |diag| {
multispan_sugg_with_applicability( multispan_sugg_with_applicability(
diag, diag,
"try this", "try this",
Applicability::MachineApplicable, Applicability::MachineApplicable,
std::iter::once((*method_calls(expr, 1).2.get(0).unwrap(), Self::GOOD_METHOD_NAME.into())).chain( std::iter::once((span, Self::GOOD_METHOD_NAME.into())).chain(
suggs suggs
.into_iter() .into_iter()
.map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())), .map(|(span1, span2)| (span1, snippet(cx, span2, "_").into())),
), ),
) )
}); });
} true
can_sugg
} }
/// Lint use of `_.and_then(|x| Some(y))` for `Option`s /// Lint use of `_.and_then(|x| Some(y))` for `Option`s
fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool { fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>) -> bool {
if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) { if_chain! {
return false; if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
if Some(adt.did) == cx.tcx.parent(vid);
then {} else { return false; }
} }
match args[1].kind { match arg.kind {
hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => { hir::ExprKind::Closure(_, _, body_id, closure_args_span, _) => {
let closure_body = cx.tcx.hir().body(body_id); let closure_body = cx.tcx.hir().body(body_id);
let closure_expr = remove_blocks(&closure_body.value); let closure_expr = remove_blocks(&closure_body.value);
if Self::lint_closure_autofixable(cx, expr, args, closure_expr, closure_args_span) { if Self::lint_closure_autofixable(cx, expr, recv, closure_expr, closure_args_span) {
true true
} else { } else {
Self::lint_closure(cx, expr, closure_expr) Self::lint_closure(cx, expr, closure_expr)
} }
}, },
// `_.and_then(Some)` case, which is no-op. // `_.and_then(Some)` case, which is no-op.
hir::ExprKind::Path(ref qpath) if match_qpath(qpath, Self::BAD_VARIANT_QPATH) => { hir::ExprKind::Path(QPath::Resolved(_, path)) if Self::is_variant(cx, path.res) => {
if let Some(msg) = Self::no_op_msg(cx) {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
BIND_INSTEAD_OF_MAP, BIND_INSTEAD_OF_MAP,
expr.span, expr.span,
Self::no_op_msg().as_ref(), &msg,
"use the expression directly", "use the expression directly",
snippet(cx, args[0].span, "..").into(), snippet(cx, recv.span, "..").into(),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
}
true true
}, },
_ => false, _ => false,
} }
} }
fn is_variant(cx: &LateContext<'_>, res: Res) -> bool {
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
if let Ok(variant_id) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM) {
return cx.tcx.parent(id) == Some(variant_id);
}
}
false
}
} }

View file

@ -1,27 +1,22 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind}; use rustc_hir::Expr;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::sym; use rustc_span::sym;
use super::BYTES_NTH; use super::BYTES_NTH;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>]) { pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx Expr<'tcx>, n_arg: &'tcx Expr<'tcx>) {
if_chain! { let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind; let caller_type = if ty.is_str() {
let ty = cx.typeck_results().expr_ty(&iter_args[0]).peel_refs(); "str"
let caller_type = if is_type_diagnostic_item(cx, ty, sym::string_type) { } else if is_type_diagnostic_item(cx, ty, sym::string_type) {
Some("String") "String"
} else if ty.is_str() {
Some("str")
} else { } else {
None return;
}; };
if let Some(caller_type) = caller_type;
then {
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
@ -31,11 +26,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'
"try", "try",
format!( format!(
"{}.as_bytes().get({})", "{}.as_bytes().get({})",
snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), snippet_with_applicability(cx, recv.span, "..", &mut applicability),
snippet_with_applicability(cx, args[1].span, "..", &mut applicability) snippet_with_applicability(cx, n_arg.span, "..", &mut applicability)
), ),
applicability, applicability,
); );
}
}
} }

View file

@ -19,7 +19,7 @@ pub(super) fn check(
) -> bool { ) -> bool {
if_chain! { if_chain! {
if let Some(args) = method_chain_args(info.chain, chain_methods); if let Some(args) = method_chain_args(info.chain, chain_methods);
if let hir::ExprKind::Call(ref fun, ref arg_char) = info.other.kind; if let hir::ExprKind::Call(fun, arg_char) = info.other.kind;
if arg_char.len() == 1; if arg_char.len() == 1;
if let hir::ExprKind::Path(ref qpath) = fun.kind; if let hir::ExprKind::Path(ref qpath) = fun.kind;
if let Some(segment) = single_segment_path(qpath); if let Some(segment) = single_segment_path(qpath);

View file

@ -1,10 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::get_parent_node;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg; use clippy_utils::sugg;
use clippy_utils::ty::is_copy; use clippy_utils::ty::is_copy;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir::{BindingAnnotation, Expr, ExprKind, MatchSource, Node, PatKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty; use rustc_middle::ty::{self, adjustment::Adjust};
use rustc_span::symbol::{sym, Symbol}; use rustc_span::symbol::{sym, Symbol};
use std::iter; use std::iter;
@ -12,12 +14,26 @@ use super::CLONE_DOUBLE_REF;
use super::CLONE_ON_COPY; use super::CLONE_ON_COPY;
/// Checks for the `CLONE_ON_COPY` lint. /// Checks for the `CLONE_ON_COPY` lint.
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) { #[allow(clippy::too_many_lines)]
if !(args.len() == 1 && method_name == sym::clone) { pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, args: &[Expr<'_>]) {
let arg = match args {
[arg] if method_name == sym::clone => arg,
_ => return,
};
if cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.and_then(|id| cx.tcx.trait_of_item(id))
.zip(cx.tcx.lang_items().clone_trait())
.map_or(true, |(x, y)| x != y)
{
return; return;
} }
let arg = &args[0]; let arg_adjustments = cx.typeck_results().expr_adjustments(arg);
let arg_ty = cx.typeck_results().expr_ty_adjusted(&args[0]); let arg_ty = arg_adjustments
.last()
.map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target);
let ty = cx.typeck_results().expr_ty(expr); let ty = cx.typeck_results().expr_ty(expr);
if let ty::Ref(_, inner, _) = arg_ty.kind() { if let ty::Ref(_, inner, _) = arg_ty.kind() {
if let ty::Ref(_, innermost, _) = inner.kind() { if let ty::Ref(_, innermost, _) = inner.kind() {
@ -61,57 +77,57 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_name: Sym
} }
if is_copy(cx, ty) { if is_copy(cx, ty) {
let snip; let parent_is_suffix_expr = match get_parent_node(cx.tcx, expr.hir_id) {
if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { Some(Node::Expr(parent)) => match parent.kind {
let parent = cx.tcx.hir().get_parent_node(expr.hir_id);
match &cx.tcx.hir().get(parent) {
hir::Node::Expr(parent) => match parent.kind {
// &*x is a nop, &x.clone() is not // &*x is a nop, &x.clone() is not
hir::ExprKind::AddrOf(..) => return, ExprKind::AddrOf(..) => return,
// (*x).func() is useless, x.clone().func() can work in case func borrows mutably // (*x).func() is useless, x.clone().func() can work in case func borrows self
hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => { ExprKind::MethodCall(_, _, [self_arg, ..], _)
return; if expr.hir_id == self_arg.hir_id && ty != cx.typeck_results().expr_ty_adjusted(expr) =>
}, {
_ => {},
},
hir::Node::Stmt(stmt) => {
if let hir::StmtKind::Local(ref loc) = stmt.kind {
if let hir::PatKind::Ref(..) = loc.pat.kind {
// let ref y = *x borrows x, let ref y = x.clone() does not
return; return;
} }
} ExprKind::MethodCall(_, _, [self_arg, ..], _) if expr.hir_id == self_arg.hir_id => true,
ExprKind::Match(_, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar)
| ExprKind::Field(..)
| ExprKind::Index(..) => true,
_ => false,
}, },
_ => {}, // local binding capturing a reference
Some(Node::Local(l))
if matches!(
l.pat.kind,
PatKind::Binding(BindingAnnotation::Ref | BindingAnnotation::RefMut, ..)
) =>
{
return;
} }
_ => false,
};
// x.clone() might have dereferenced x, possibly through Deref impls let mut app = Applicability::MachineApplicable;
if cx.typeck_results().expr_ty(arg) == ty { let snip = snippet_with_context(cx, arg.span, expr.span.ctxt(), "_", &mut app).0;
snip = Some(("try removing the `clone` call", format!("{}", snippet)));
} else { let deref_count = arg_adjustments
let deref_count = cx
.typeck_results()
.expr_adjustments(arg)
.iter() .iter()
.filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) .take_while(|adj| matches!(adj.kind, Adjust::Deref(_)))
.count(); .count();
let derefs: String = iter::repeat('*').take(deref_count).collect(); let (help, sugg) = if deref_count == 0 {
snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); ("try removing the `clone` call", snip.into())
} } else if parent_is_suffix_expr {
("try dereferencing it", format!("({}{})", "*".repeat(deref_count), snip))
} else { } else {
snip = None; ("try dereferencing it", format!("{}{}", "*".repeat(deref_count), snip))
} };
span_lint_and_then(
span_lint_and_sugg(
cx, cx,
CLONE_ON_COPY, CLONE_ON_COPY,
expr.span, expr.span,
&format!("using `clone` on type `{}` which implements the `Copy` trait", ty), &format!("using `clone` on type `{}` which implements the `Copy` trait", ty),
|diag| { help,
if let Some((text, snip)) = snip { sugg,
diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable); app,
}
},
); );
} }
} }

View file

@ -100,9 +100,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Spa
applicability: &mut Applicability, applicability: &mut Applicability,
) -> Vec<String> { ) -> Vec<String> {
if_chain! { if_chain! {
if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, format_arg) = a.kind;
if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; if let hir::ExprKind::Match(format_arg_expr, _, _) = format_arg.kind;
if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; if let hir::ExprKind::Tup(format_arg_expr_tup) = format_arg_expr.kind;
then { then {
format_arg_expr_tup format_arg_expr_tup
@ -155,7 +155,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Spa
if block.stmts.len() == 1; if block.stmts.len() == 1;
if let hir::StmtKind::Local(local) = &block.stmts[0].kind; if let hir::StmtKind::Local(local) = &block.stmts[0].kind;
if let Some(arg_root) = &local.init; if let Some(arg_root) = &local.init;
if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; if let hir::ExprKind::Call(inner_fun, inner_args) = arg_root.kind;
if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1;
if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind;
then { then {

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