Merge commit '98e2b9f25b6db4b2680a3d388456d9f95cb28344' into clippyup

This commit is contained in:
flip1995 2021-04-22 11:31:13 +02:00
parent 419bf6bbd8
commit 02bf692169
186 changed files with 4668 additions and 1570 deletions

View File

@ -3,7 +3,9 @@ Thank you for making Clippy better!
We're collecting our changelog from pull request descriptions.
If your PR only includes internal changes, you can just write
`changelog: none`. Otherwise, please write a short comment
explaining your change.
explaining your change. Also, it's helpful for us that
the lint name is put into brackets `[]` and backticks `` ` ` ``,
e.g. ``[`lint_name`]``.
If your PR fixes an issue, you can add "fixes #issue_number" into this
PR description. This way the issue will be automatically closed when
@ -29,4 +31,5 @@ Delete this line and everything above before opening your PR.
---
*Please write a short comment explaining your change (or "none" for internal only changes)*
changelog:

View File

@ -34,15 +34,16 @@ jobs:
run: |
MESSAGE=$(git log --format=%B -n 1)
PR=$(echo "$MESSAGE" | grep -o "#[0-9]*" | head -1 | sed -e 's/^#//')
output=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR" | \
python -c "import sys, json; print(json.load(sys.stdin)['body'])" | \
grep "^changelog: " | \
sed "s/changelog: //g")
if [[ -z "$output" ]]; then
body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR" | \
python -c "import sys, json; print(json.load(sys.stdin)['body'])")
output=$(grep "^changelog:\s*\S" <<< "$body" | sed "s/changelog:\s*//g") || {
echo "ERROR: PR body must contain 'changelog: ...'"
exit 1
elif [[ "$output" = "none" ]]; then
}
if [[ "$output" = "none" ]]; then
echo "WARNING: changelog is 'none'"
else
echo "changelog: $output"
fi
env:
PYTHONIOENCODING: 'utf-8'

View File

@ -179,7 +179,7 @@ Current stable, released 2021-03-25
* Replace [`find_map`] with [`manual_find_map`]
[#6591](https://github.com/rust-lang/rust-clippy/pull/6591)
* [`unknown_clippy_lints`] Now integrated in the `unknown_lints` rustc lint
* `unknown_clippy_lints` Now integrated in the `unknown_lints` rustc lint
[#6653](https://github.com/rust-lang/rust-clippy/pull/6653)
### Enhancements
@ -280,7 +280,7 @@ Released 2021-02-11
* Previously deprecated [`str_to_string`] and [`string_to_string`] have been un-deprecated
as `restriction` lints [#6333](https://github.com/rust-lang/rust-clippy/pull/6333)
* Deprecate [`panic_params`] lint. This is now available in rustc as `panic_fmt`
* Deprecate `panic_params` lint. This is now available in rustc as `non_fmt_panic`
[#6351](https://github.com/rust-lang/rust-clippy/pull/6351)
* Move [`map_err_ignore`] to `restriction`
[#6416](https://github.com/rust-lang/rust-clippy/pull/6416)
@ -419,7 +419,7 @@ Released 2020-12-31
[#6037](https://github.com/rust-lang/rust-clippy/pull/6037)
* Rename `zero_width_space` to [`invisible_characters`]
[#6105](https://github.com/rust-lang/rust-clippy/pull/6105)
* Deprecate [`drop_bounds`] (uplifted)
* Deprecate `drop_bounds` (uplifted)
[#6111](https://github.com/rust-lang/rust-clippy/pull/6111)
* Move [`string_lit_as_bytes`] to `nursery`
[#6117](https://github.com/rust-lang/rust-clippy/pull/6117)
@ -1018,7 +1018,7 @@ Released 2020-03-12
[#5015](https://github.com/rust-lang/rust-clippy/pull/5015)
* Move [`range_plus_one`] to pedantic group [#5057](https://github.com/rust-lang/rust-clippy/pull/5057)
* Move [`debug_assert_with_mut_call`] to nursery group [#5106](https://github.com/rust-lang/rust-clippy/pull/5106)
* Deprecate [`unused_label`] [#4930](https://github.com/rust-lang/rust-clippy/pull/4930)
* Deprecate `unused_label` [#4930](https://github.com/rust-lang/rust-clippy/pull/4930)
### Enhancements
@ -1046,7 +1046,7 @@ Released 2020-03-12
* [`wildcard_enum_match_arm`] [#4934](https://github.com/rust-lang/rust-clippy/pull/4934)
* [`cognitive_complexity`] [#4935](https://github.com/rust-lang/rust-clippy/pull/4935)
* [`decimal_literal_representation`] [#4956](https://github.com/rust-lang/rust-clippy/pull/4956)
* [`unknown_clippy_lints`] [#4963](https://github.com/rust-lang/rust-clippy/pull/4963)
* `unknown_clippy_lints` [#4963](https://github.com/rust-lang/rust-clippy/pull/4963)
* [`explicit_into_iter_loop`] [#4978](https://github.com/rust-lang/rust-clippy/pull/4978)
* [`useless_attribute`] [#5022](https://github.com/rust-lang/rust-clippy/pull/5022)
* [`if_let_some_result`] [#5032](https://github.com/rust-lang/rust-clippy/pull/5032)
@ -1080,7 +1080,7 @@ Released 2020-01-30
[Inside Rust Blog](https://blog.rust-lang.org/inside-rust/2019/11/04/Clippy-removes-plugin-interface.html) for
details [#4714](https://github.com/rust-lang/rust-clippy/pull/4714)
* Move [`use_self`] to nursery group [#4863](https://github.com/rust-lang/rust-clippy/pull/4863)
* Deprecate [`into_iter_on_array`] [#4788](https://github.com/rust-lang/rust-clippy/pull/4788)
* Deprecate `into_iter_on_array` [#4788](https://github.com/rust-lang/rust-clippy/pull/4788)
* Expand [`string_lit_as_bytes`] to also trigger when literal has escapes
[#4808](https://github.com/rust-lang/rust-clippy/pull/4808)
* Fix false positive in `comparison_chain` [#4842](https://github.com/rust-lang/rust-clippy/pull/4842)
@ -1282,7 +1282,7 @@ Released 2019-05-20
[1fac380..37f5c1e](https://github.com/rust-lang/rust-clippy/compare/1fac380...37f5c1e)
* New lint: [`drop_bounds`] to detect `T: Drop` bounds
* New lint: `drop_bounds` to detect `T: Drop` bounds
* Split [`redundant_closure`] into [`redundant_closure`] and [`redundant_closure_for_method_calls`] [#4110](https://github.com/rust-lang/rust-clippy/pull/4101)
* Rename `cyclomatic_complexity` to [`cognitive_complexity`], start work on making lint more practical for Rust code
* Move [`get_unwrap`] to the restriction category
@ -1375,7 +1375,7 @@ Released 2019-01-17
* New lints: [`slow_vector_initialization`], [`mem_discriminant_non_enum`],
[`redundant_clone`], [`wildcard_dependencies`],
[`into_iter_on_ref`], [`into_iter_on_array`], [`deprecated_cfg_attr`],
[`into_iter_on_ref`], `into_iter_on_array`, [`deprecated_cfg_attr`],
[`mem_discriminant_non_enum`], [`cargo_common_metadata`]
* Add support for `u128` and `i128` to integer related lints
* Add float support to `mistyped_literal_suffixes`
@ -1649,7 +1649,7 @@ Released 2018-09-13
## 0.0.166
* Rustup to *rustc 1.22.0-nightly (b7960878b 2017-10-18)*
* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], [`invalid_ref`], [`option_map_or_none`],
* New lints: [`explicit_write`], `identity_conversion`, [`implicit_hasher`], `invalid_ref`, [`option_map_or_none`],
[`range_minus_one`], [`range_plus_one`], [`transmute_int_to_bool`], [`transmute_int_to_char`],
[`transmute_int_to_float`]
@ -2037,7 +2037,7 @@ Released 2018-09-13
## 0.0.64 — 2016-04-26
* Rustup to *rustc 1.10.0-nightly (645dd013a 2016-04-24)*
* New lints: [`temporary_cstring_as_ptr`], [`unsafe_removed_from_name`], and [`mem_forget`]
* New lints: `temporary_cstring_as_ptr`, [`unsafe_removed_from_name`], and [`mem_forget`]
## 0.0.63 — 2016-04-08
* Rustup to *rustc 1.9.0-nightly (7979dd608 2016-04-07)*
@ -2091,7 +2091,7 @@ Released 2018-09-13
## 0.0.49 — 2016-03-09
* Update to *rustc 1.9.0-nightly (eabfc160f 2016-03-08)*
* New lints: [`overflow_check_conditional`], [`unused_label`], [`new_without_default`]
* New lints: [`overflow_check_conditional`], `unused_label`, [`new_without_default`]
## 0.0.48 — 2016-03-07
* Fixed: ICE in [`needless_range_loop`] with globals
@ -2124,6 +2124,7 @@ Released 2018-09-13
[`blacklisted_name`]: https://rust-lang.github.io/rust-clippy/master/index.html#blacklisted_name
[`blanket_clippy_restriction_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#blanket_clippy_restriction_lints
[`blocks_in_if_conditions`]: https://rust-lang.github.io/rust-clippy/master/index.html#blocks_in_if_conditions
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
@ -2148,6 +2149,7 @@ Released 2018-09-13
[`clone_double_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_double_ref
[`clone_on_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy
[`clone_on_ref_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_ref_ptr
[`cloned_instead_of_copied`]: https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied
[`cmp_nan`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_nan
[`cmp_null`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_null
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
@ -2178,7 +2180,6 @@ Released 2018-09-13
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
[`double_neg`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_neg
[`double_parens`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_parens
[`drop_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_bounds
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
@ -2216,6 +2217,7 @@ Released 2018-09-13
[`filter_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_next
[`find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#find_map
[`flat_map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_identity
[`flat_map_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#flat_map_option
[`float_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_arithmetic
[`float_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
[`float_cmp_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp_const
@ -2264,10 +2266,9 @@ Released 2018-09-13
[`int_plus_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#int_plus_one
[`integer_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_arithmetic
[`integer_division`]: https://rust-lang.github.io/rust-clippy/master/index.html#integer_division
[`into_iter_on_array`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_array
[`into_iter_on_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref
[`invalid_atomic_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_atomic_ordering
[`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref
[`invalid_null_ptr_usage`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_null_ptr_usage
[`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex
[`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons
[`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters
@ -2402,7 +2403,6 @@ Released 2018-09-13
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
[`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn
[`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
@ -2488,7 +2488,6 @@ Released 2018-09-13
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
@ -2517,13 +2516,13 @@ Released 2018-09-13
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
[`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by
[`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap
[`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
@ -2541,7 +2540,6 @@ Released 2018-09-13
[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings

View File

@ -21,10 +21,10 @@ All contributors are expected to follow the [Rust Code of Conduct].
- [IntelliJ Rust](#intellij-rust)
- [Rust Analyzer](#rust-analyzer)
- [How Clippy works](#how-clippy-works)
- [Syncing changes between Clippy and [`rust-lang/rust`]](#syncing-changes-between-clippy-and-rust-langrust)
- [Syncing changes between Clippy and `rust-lang/rust`](#syncing-changes-between-clippy-and-rust-langrust)
- [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos)
- [Performing the sync from [`rust-lang/rust`] to Clippy](#performing-the-sync-from-rust-langrust-to-clippy)
- [Performing the sync from Clippy to [`rust-lang/rust`]](#performing-the-sync-from-clippy-to-rust-langrust)
- [Performing the sync from `rust-lang/rust` to Clippy](#performing-the-sync-from-rust-langrust-to-clippy)
- [Performing the sync from Clippy to `rust-lang/rust`](#performing-the-sync-from-clippy-to-rust-langrust)
- [Defining remotes](#defining-remotes)
- [Issue and PR triage](#issue-and-pr-triage)
- [Bors and Homu](#bors-and-homu)

View File

@ -127,10 +127,9 @@ fn match_assert_with_message<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>)
_ => &block.expr,
};
// function call
if let Some(args) = match_panic_call(cx, begin_panic_call);
if args.len() == 1;
if let Some(arg) = match_panic_call(cx, begin_panic_call);
// bind the second argument of the `assert!` macro if it exists
if let panic_message = snippet_opt(cx, args[0].span);
if let panic_message = snippet_opt(cx, arg.span);
// second argument of begin_panic is irrelevant
// as is the second match arm
then {

View File

@ -0,0 +1,75 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{ast_utils, is_direct_expn_of};
use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// **What it does:** This lint warns about boolean comparisons in assert-like macros.
///
/// **Why is this bad?** It is shorter to use the equivalent.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// // Bad
/// assert_eq!("a".is_empty(), false);
/// assert_ne!("a".is_empty(), true);
///
/// // Good
/// assert!(!"a".is_empty());
/// ```
pub BOOL_ASSERT_COMPARISON,
style,
"Using a boolean as comparison value in an assert_* macro when there is no need"
}
declare_lint_pass!(BoolAssertComparison => [BOOL_ASSERT_COMPARISON]);
fn is_bool_lit(e: &Expr) -> bool {
matches!(
e.kind,
ExprKind::Lit(Lit {
kind: LitKind::Bool(_),
..
})
) && !e.span.from_expansion()
}
impl EarlyLintPass for BoolAssertComparison {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
let macros = ["assert_eq", "debug_assert_eq"];
let inverted_macros = ["assert_ne", "debug_assert_ne"];
for mac in macros.iter().chain(inverted_macros.iter()) {
if let Some(span) = is_direct_expn_of(e.span, mac) {
if let Some([a, b]) = ast_utils::extract_assert_macro_args(e) {
let nb_bool_args = is_bool_lit(a) as usize + is_bool_lit(b) as usize;
if nb_bool_args != 1 {
// If there are two boolean arguments, we definitely don't understand
// what's going on, so better leave things as is...
//
// Or there is simply no boolean and then we can leave things as is!
return;
}
let non_eq_mac = &mac[..mac.len() - 3];
span_lint_and_sugg(
cx,
BOOL_ASSERT_COMPARISON,
span,
&format!("used `{}!` with a literal bool", mac),
"replace it with",
format!("{}!(..)", non_eq_mac),
Applicability::MaybeIncorrect,
);
return;
}
}
}
}
}

View File

@ -261,7 +261,7 @@ fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<String> {
}
METHODS_WITH_NEGATION
.iter()
.cloned()
.copied()
.flat_map(|(a, b)| vec![(a, b), (b, a)])
.find(|&(a, _)| {
let path: &str = &path.ident.name.as_str();

View File

@ -323,7 +323,7 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
if let [int] = &*tp.segments;
then {
let name = &int.ident.name.as_str();
candidates.iter().find(|c| name == *c).cloned()
candidates.iter().find(|c| name == *c).copied()
} else {
None
}
@ -337,7 +337,7 @@ fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if let [ty] = &*path.segments;
then {
let name = &ty.ident.name.as_str();
INTS.iter().find(|c| name == *c).cloned()
INTS.iter().find(|c| name == *c).copied()
} else {
None
}

View File

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{path_to_local, SpanlessEq};
use clippy_utils::{is_lang_ctor, path_to_local, SpanlessEq};
use if_chain::if_chain;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind, UnOp};
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, StmtKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{DefIdTree, TyCtxt, TypeckResults};
use rustc_middle::ty::TypeckResults;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span};
@ -52,7 +52,7 @@ declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Match(_expr, arms, _source) = expr.kind {
if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(arm, cx.tcx)) {
if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
for arm in arms {
check_arm(arm, wild_arm, cx);
}
@ -75,7 +75,7 @@ fn check_arm<'tcx>(arm: &Arm<'tcx>, wild_outer_arm: &Arm<'tcx>, cx: &LateContext
// match <local> { .. }
if let Some(binding_id) = path_to_local(strip_ref_operators(expr_in, cx.typeck_results()));
// one of the branches must be "wild-like"
if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(arm_inner, cx.tcx));
if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(cx, arm_inner));
let (wild_inner_arm, non_wild_inner_arm) =
(&arms_inner[wild_inner_arm_idx], &arms_inner[1 - wild_inner_arm_idx]);
if !pat_contains_or(non_wild_inner_arm.pat);
@ -126,13 +126,13 @@ fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir>
/// A "wild-like" pattern is wild ("_") or `None`.
/// For this lint to apply, both the outer and inner match expressions
/// must have "wild-like" branches that can be combined.
fn arm_is_wild_like(arm: &Arm<'_>, tcx: TyCtxt<'_>) -> bool {
fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if arm.guard.is_some() {
return false;
}
match arm.pat.kind {
PatKind::Binding(..) | PatKind::Wild => true,
PatKind::Path(QPath::Resolved(None, path)) if is_none_ctor(path.res, tcx) => true,
PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
_ => false,
}
}
@ -164,17 +164,6 @@ fn pat_contains_or(pat: &Pat<'_>) -> bool {
result
}
fn is_none_ctor(res: Res, tcx: TyCtxt<'_>) -> bool {
if let Some(none_id) = tcx.lang_items().option_none_variant() {
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = res {
if let Some(variant_id) = tcx.parent(id) {
return variant_id == none_id;
}
}
}
false
}
/// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is
/// dereferenced. An overloaded deref such as `Vec` to slice would not be removed.
fn strip_ref_operators<'hir>(mut expr: &'hir Expr<'hir>, typeck_results: &TypeckResults<'_>) -> &'hir Expr<'hir> {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, if_sequence, parent_node_is_if_expr, paths, SpanlessEq};
use clippy_utils::{get_trait_def_id, if_sequence, is_else_clause, paths, SpanlessEq};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
}
// We only care about the top-most `if` in the chain
if parent_node_is_if_expr(expr, cx) {
if is_else_clause(cx.tcx, expr) {
return;
}

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
use clippy_utils::{
both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, parent_node_is_if_expr,
both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, in_macro, is_else_clause,
run_lints, search_same, ContainsName, SpanlessEq, SpanlessHash,
};
use if_chain::if_chain;
@ -188,13 +188,18 @@ fn lint_same_then_else<'tcx>(
expr: &'tcx Expr<'_>,
) {
// We only lint ifs with multiple blocks
if blocks.len() < 2 || parent_node_is_if_expr(expr, cx) {
if blocks.len() < 2 || is_else_clause(cx.tcx, expr) {
return;
}
// 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);
let (start_eq, mut end_eq, expr_eq) = if let Some(block_eq) = scan_block_for_eq(cx, blocks) {
(block_eq.start_eq, block_eq.end_eq, block_eq.expr_eq)
} else {
return;
};
// BRANCHES_SHARING_CODE prerequisites
if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) {
@ -290,7 +295,19 @@ fn lint_same_then_else<'tcx>(
}
}
fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> (usize, usize, bool) {
struct BlockEqual {
/// The amount statements that are equal from the start
start_eq: usize,
/// The amount statements that are equal from the end
end_eq: usize,
/// An indication if the block expressions are the same. This will also be true if both are
/// `None`
expr_eq: bool,
}
/// This function can also trigger the `IF_SAME_THEN_ELSE` in which case it'll return `None` to
/// abort any further processing and avoid duplicate lint triggers.
fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> Option<BlockEqual> {
let mut start_eq = usize::MAX;
let mut end_eq = usize::MAX;
let mut expr_eq = true;
@ -332,7 +349,7 @@ fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> (usize,
"same as this",
);
return (0, 0, false);
return None;
}
}
@ -352,7 +369,11 @@ fn scan_block_for_eq(cx: &LateContext<'tcx>, blocks: &[&Block<'tcx>]) -> (usize,
end_eq = min_block_size - start_eq;
}
(start_eq, end_eq, expr_eq)
Some(BlockEqual {
start_eq,
end_eq,
expr_eq,
})
}
fn check_for_warn_of_moved_symbol(

View File

@ -93,15 +93,6 @@ declare_deprecated_lint! {
"the replacement suggested by this lint had substantially different behavior"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been superseded by the warn-by-default
/// `invalid_value` rustc lint.
pub INVALID_REF,
"superseded by rustc lint `invalid_value`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
@ -110,24 +101,6 @@ declare_deprecated_lint! {
"`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been uplifted to rustc and is now called
/// `array_into_iter`.
pub INTO_ITER_ON_ARRAY,
"this lint has been uplifted to rustc and is now called `array_into_iter`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been uplifted to rustc and is now called
/// `unused_labels`.
pub UNUSED_LABEL,
"this lint has been uplifted to rustc and is now called `unused_labels`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
@ -144,42 +117,6 @@ declare_deprecated_lint! {
"the regex! macro has been removed from the regex crate in 2018"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been uplifted to rustc and is now called
/// `drop_bounds`.
pub DROP_BOUNDS,
"this lint has been uplifted to rustc and is now called `drop_bounds`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been uplifted to rustc and is now called
/// `temporary_cstring_as_ptr`.
pub TEMPORARY_CSTRING_AS_PTR,
"this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been uplifted to rustc and is now called
/// `panic_fmt`.
pub PANIC_PARAMS,
"this lint has been uplifted to rustc and is now called `panic_fmt`"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been integrated into the `unknown_lints`
/// rustc lint.
pub UNKNOWN_CLIPPY_LINTS,
"this lint has been integrated into the `unknown_lints` rustc lint"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
@ -188,3 +125,12 @@ declare_deprecated_lint! {
pub FIND_MAP,
"this lint has been replaced by `manual_find_map`, a more specific lint"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This lint has been replaced by `manual_filter_map`, a
/// more specific lint.
pub FILTER_MAP,
"this lint has been replaced by `manual_filter_map`, a more specific lint"
}

View File

@ -1,17 +1,19 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability};
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
use clippy_utils::SpanlessEq;
use clippy_utils::{get_item_name, paths};
use if_chain::if_chain;
use clippy_utils::{
can_move_expr_to_closure_no_visit,
diagnostics::span_lint_and_sugg,
is_expr_final_block_expr, is_expr_used_or_unified, match_def_path, paths, peel_hir_expr_while,
source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context},
SpanlessEq,
};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BorrowKind, Expr, ExprKind, UnOp};
use rustc_hir::{
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
Block, Expr, ExprKind, Guard, HirId, Local, Stmt, StmtKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::sym;
use rustc_span::{Span, SyntaxContext, DUMMY_SP};
use std::fmt::Write;
declare_clippy_lint! {
/// **What it does:** Checks for uses of `contains_key` + `insert` on `HashMap`
@ -19,15 +21,14 @@ declare_clippy_lint! {
///
/// **Why is this bad?** Using `entry` is more efficient.
///
/// **Known problems:** Some false negatives, eg.:
/// **Known problems:** The suggestion may have type inference errors in some cases. e.g.
/// ```rust
/// # use std::collections::HashMap;
/// # let mut map = HashMap::new();
/// # let v = 1;
/// # let k = 1;
/// if !map.contains_key(&k) {
/// map.insert(k.clone(), v);
/// }
/// let mut map = std::collections::HashMap::new();
/// let _ = if !map.contains_key(&0) {
/// map.insert(0, 0)
/// } else {
/// None
/// };
/// ```
///
/// **Example:**
@ -56,132 +57,584 @@ declare_clippy_lint! {
declare_lint_pass!(HashMapPass => [MAP_ENTRY]);
impl<'tcx> LateLintPass<'tcx> for HashMapPass {
#[allow(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::If(check, then_block, ref else_block) = expr.kind {
if let ExprKind::Unary(UnOp::Not, check) = check.kind {
if let Some((ty, map, key)) = check_cond(cx, check) {
// in case of `if !m.contains_key(&k) { m.insert(k, v); }`
// we can give a better error message
let sole_expr = {
else_block.is_none()
&& if let ExprKind::Block(then_block, _) = then_block.kind {
(then_block.expr.is_some() as usize) + then_block.stmts.len() == 1
} else {
true
}
// XXXManishearth we can also check for if/else blocks containing `None`.
};
let (cond_expr, then_expr, else_expr) = match expr.kind {
ExprKind::If(c, t, e) => (c, t, e),
_ => return,
};
let (map_ty, contains_expr) = match try_parse_contains(cx, cond_expr) {
Some(x) => x,
None => return,
};
let mut visitor = InsertVisitor {
cx,
span: expr.span,
ty,
map,
key,
sole_expr,
};
let then_search = match find_insert_calls(cx, &contains_expr, then_expr) {
Some(x) => x,
None => return,
};
walk_expr(&mut visitor, then_block);
}
} else if let Some(else_block) = *else_block {
if let Some((ty, map, key)) = check_cond(cx, check) {
let mut visitor = InsertVisitor {
cx,
span: expr.span,
ty,
map,
key,
sole_expr: false,
};
walk_expr(&mut visitor, else_block);
}
}
}
}
}
fn check_cond<'a>(cx: &LateContext<'_>, check: &'a Expr<'a>) -> Option<(&'static str, &'a Expr<'a>, &'a Expr<'a>)> {
if_chain! {
if let ExprKind::MethodCall(path, _, params, _) = check.kind;
if params.len() >= 2;
if path.ident.name == sym!(contains_key);
if let ExprKind::AddrOf(BorrowKind::Ref, _, key) = params[1].kind;
then {
let map = &params[0];
let obj_ty = cx.typeck_results().expr_ty(map).peel_refs();
return if match_type(cx, obj_ty, &paths::BTREEMAP) {
Some(("BTreeMap", map, key))
}
else if is_type_diagnostic_item(cx, obj_ty, sym::hashmap_type) {
Some(("HashMap", map, key))
}
else {
None
let mut app = Applicability::MachineApplicable;
let map_str = snippet_with_context(cx, contains_expr.map.span, contains_expr.call_ctxt, "..", &mut app).0;
let key_str = snippet_with_context(cx, contains_expr.key.span, contains_expr.call_ctxt, "..", &mut app).0;
let sugg = if let Some(else_expr) = else_expr {
let else_search = match find_insert_calls(cx, &contains_expr, else_expr) {
Some(search) => search,
None => return,
};
}
}
None
}
struct InsertVisitor<'a, 'tcx, 'b> {
cx: &'a LateContext<'tcx>,
span: Span,
ty: &'static str,
map: &'b Expr<'b>,
key: &'b Expr<'b>,
sole_expr: bool,
}
impl<'a, 'tcx, 'b> Visitor<'tcx> for InsertVisitor<'a, 'tcx, 'b> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, _, params, _) = expr.kind;
if params.len() == 3;
if path.ident.name == sym!(insert);
if get_item_name(self.cx, self.map) == get_item_name(self.cx, &params[0]);
if SpanlessEq::new(self.cx).eq_expr(self.key, &params[1]);
if snippet_opt(self.cx, self.map.span) == snippet_opt(self.cx, params[0].span);
then {
span_lint_and_then(self.cx, MAP_ENTRY, self.span,
&format!("usage of `contains_key` followed by `insert` on a `{}`", self.ty), |diag| {
if self.sole_expr {
let mut app = Applicability::MachineApplicable;
let help = format!("{}.entry({}).or_insert({});",
snippet_with_applicability(self.cx, self.map.span, "map", &mut app),
snippet_with_applicability(self.cx, params[1].span, "..", &mut app),
snippet_with_applicability(self.cx, params[2].span, "..", &mut app));
diag.span_suggestion(
self.span,
"consider using",
help,
Applicability::MachineApplicable, // snippet
);
}
else {
let help = format!("consider using `{}.entry({})`",
snippet(self.cx, self.map.span, "map"),
snippet(self.cx, params[1].span, ".."));
diag.span_label(
self.span,
&help,
);
}
});
if then_search.edits.is_empty() && else_search.edits.is_empty() {
// No insertions
return;
} else if then_search.edits.is_empty() || else_search.edits.is_empty() {
// if .. { insert } else { .. } or if .. { .. } else { insert }
let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) {
(true, true) => (
then_search.snippet_vacant(cx, then_expr.span, &mut app),
snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
),
(true, false) => (
then_search.snippet_occupied(cx, then_expr.span, &mut app),
snippet_with_applicability(cx, else_expr.span, "{ .. }", &mut app),
),
(false, true) => (
else_search.snippet_occupied(cx, else_expr.span, &mut app),
snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
),
(false, false) => (
else_search.snippet_vacant(cx, else_expr.span, &mut app),
snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app),
),
};
format!(
"if let {}::{} = {}.entry({}) {} else {}",
map_ty.entry_path(),
entry_kind,
map_str,
key_str,
then_str,
else_str,
)
} else {
// if .. { insert } else { insert }
let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated {
(
then_search.snippet_vacant(cx, then_expr.span, &mut app),
else_search.snippet_occupied(cx, else_expr.span, &mut app),
)
} else {
(
then_search.snippet_occupied(cx, then_expr.span, &mut app),
else_search.snippet_vacant(cx, else_expr.span, &mut app),
)
};
let indent_str = snippet_indent(cx, expr.span);
let indent_str = indent_str.as_deref().unwrap_or("");
format!(
"match {}.entry({}) {{\n{indent} {entry}::{} => {}\n\
{indent} {entry}::{} => {}\n{indent}}}",
map_str,
key_str,
then_entry,
reindent_multiline(then_str.into(), true, Some(4 + indent_str.len())),
else_entry,
reindent_multiline(else_str.into(), true, Some(4 + indent_str.len())),
entry = map_ty.entry_path(),
indent = indent_str,
)
}
} else {
if then_search.edits.is_empty() {
// no insertions
return;
}
}
if !self.sole_expr {
walk_expr(self, expr);
// if .. { insert }
if !then_search.allow_insert_closure {
let (body_str, entry_kind) = if contains_expr.negated {
then_search.snippet_vacant(cx, then_expr.span, &mut app)
} else {
then_search.snippet_occupied(cx, then_expr.span, &mut app)
};
format!(
"if let {}::{} = {}.entry({}) {}",
map_ty.entry_path(),
entry_kind,
map_str,
key_str,
body_str,
)
} else if let Some(insertion) = then_search.as_single_insertion() {
let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0;
if contains_expr.negated {
if insertion.value.can_have_side_effects() {
format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, value_str)
} else {
format!("{}.entry({}).or_insert({});", map_str, key_str, value_str)
}
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
// This would need to be a different lint.
return;
}
} else {
let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app);
if contains_expr.negated {
format!("{}.entry({}).or_insert_with(|| {});", map_str, key_str, block_str)
} else {
// TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here.
// This would need to be a different lint.
return;
}
}
};
span_lint_and_sugg(
cx,
MAP_ENTRY,
expr.span,
&format!("usage of `contains_key` followed by `insert` on a `{}`", map_ty.name()),
"try this",
sugg,
app,
);
}
}
#[derive(Clone, Copy)]
enum MapType {
Hash,
BTree,
}
impl MapType {
fn name(self) -> &'static str {
match self {
Self::Hash => "HashMap",
Self::BTree => "BTreeMap",
}
}
fn entry_path(self) -> &'static str {
match self {
Self::Hash => "std::collections::hash_map::Entry",
Self::BTree => "std::collections::btree_map::Entry",
}
}
}
struct ContainsExpr<'tcx> {
negated: bool,
map: &'tcx Expr<'tcx>,
key: &'tcx Expr<'tcx>,
call_ctxt: SyntaxContext,
}
fn try_parse_contains(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(MapType, ContainsExpr<'tcx>)> {
let mut negated = false;
let expr = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::Unary(UnOp::Not, e) => {
negated = !negated;
Some(e)
},
_ => None,
});
match expr.kind {
ExprKind::MethodCall(
_,
_,
[map, Expr {
kind: ExprKind::AddrOf(_, _, key),
span: key_span,
..
}],
_,
) if key_span.ctxt() == expr.span.ctxt() => {
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
let expr = ContainsExpr {
negated,
map,
key,
call_ctxt: expr.span.ctxt(),
};
if match_def_path(cx, id, &paths::BTREEMAP_CONTAINS_KEY) {
Some((MapType::BTree, expr))
} else if match_def_path(cx, id, &paths::HASHMAP_CONTAINS_KEY) {
Some((MapType::Hash, expr))
} else {
None
}
},
_ => None,
}
}
struct InsertExpr<'tcx> {
map: &'tcx Expr<'tcx>,
key: &'tcx Expr<'tcx>,
value: &'tcx Expr<'tcx>,
}
fn try_parse_insert(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> {
if let ExprKind::MethodCall(_, _, [map, key, value], _) = expr.kind {
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
if match_def_path(cx, id, &paths::BTREEMAP_INSERT) || match_def_path(cx, id, &paths::HASHMAP_INSERT) {
Some(InsertExpr { map, key, value })
} else {
None
}
} else {
None
}
}
/// An edit that will need to be made to move the expression to use the entry api
#[derive(Clone, Copy)]
enum Edit<'tcx> {
/// A semicolon that needs to be removed. Used to create a closure for `insert_with`.
RemoveSemi(Span),
/// An insertion into the map.
Insertion(Insertion<'tcx>),
}
impl Edit<'tcx> {
fn as_insertion(self) -> Option<Insertion<'tcx>> {
if let Self::Insertion(i) = self { Some(i) } else { None }
}
}
#[derive(Clone, Copy)]
struct Insertion<'tcx> {
call: &'tcx Expr<'tcx>,
value: &'tcx Expr<'tcx>,
}
/// This visitor needs to do a multiple things:
/// * Find all usages of the map. An insertion can only be made before any other usages of the map.
/// * Determine if there's an insertion using the same key. There's no need for the entry api
/// otherwise.
/// * Determine if the final statement executed is an insertion. This is needed to use
/// `or_insert_with`.
/// * Determine if there's any sub-expression that can't be placed in a closure.
/// * Determine if there's only a single insert statement. `or_insert` can be used in this case.
#[allow(clippy::struct_excessive_bools)]
struct InsertSearcher<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
/// The map expression used in the contains call.
map: &'tcx Expr<'tcx>,
/// The key expression used in the contains call.
key: &'tcx Expr<'tcx>,
/// The context of the top level block. All insert calls must be in the same context.
ctxt: SyntaxContext,
/// Whether this expression can be safely moved into a closure.
allow_insert_closure: bool,
/// Whether this expression can use the entry api.
can_use_entry: bool,
/// Whether this expression is the final expression in this code path. This may be a statement.
in_tail_pos: bool,
// Is this expression a single insert. A slightly better suggestion can be made in this case.
is_single_insert: bool,
/// If the visitor has seen the map being used.
is_map_used: bool,
/// The locations where changes need to be made for the suggestion.
edits: Vec<Edit<'tcx>>,
/// A stack of loops the visitor is currently in.
loops: Vec<HirId>,
}
impl<'tcx> InsertSearcher<'_, 'tcx> {
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
/// only if they are on separate code paths. This will return whether the map was used in the
/// given expression.
fn visit_cond_arm(&mut self, e: &'tcx Expr<'_>) -> bool {
let is_map_used = self.is_map_used;
let in_tail_pos = self.in_tail_pos;
self.visit_expr(e);
let res = self.is_map_used;
self.is_map_used = is_map_used;
self.in_tail_pos = in_tail_pos;
res
}
/// Visits an expression which is not itself in a tail position, but other sibling expressions
/// may be. e.g. if conditions
fn visit_non_tail_expr(&mut self, e: &'tcx Expr<'_>) {
let in_tail_pos = self.in_tail_pos;
self.in_tail_pos = false;
self.visit_expr(e);
self.in_tail_pos = in_tail_pos;
}
}
impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) {
match stmt.kind {
StmtKind::Semi(e) => {
self.visit_expr(e);
if self.in_tail_pos && self.allow_insert_closure {
// The spans are used to slice the top level expression into multiple parts. This requires that
// they all come from the same part of the source code.
if stmt.span.ctxt() == self.ctxt && e.span.ctxt() == self.ctxt {
self.edits
.push(Edit::RemoveSemi(stmt.span.trim_start(e.span).unwrap_or(DUMMY_SP)));
} else {
self.allow_insert_closure = false;
}
}
},
StmtKind::Expr(e) => self.visit_expr(e),
StmtKind::Local(Local { init: Some(e), .. }) => {
self.allow_insert_closure &= !self.in_tail_pos;
self.in_tail_pos = false;
self.is_single_insert = false;
self.visit_expr(e);
},
_ => {
self.allow_insert_closure &= !self.in_tail_pos;
self.is_single_insert = false;
},
}
}
fn visit_block(&mut self, block: &'tcx Block<'_>) {
// If the block is in a tail position, then the last expression (possibly a statement) is in the
// tail position. The rest, however, are not.
match (block.stmts, block.expr) {
([], None) => {
self.allow_insert_closure &= !self.in_tail_pos;
},
([], Some(expr)) => self.visit_expr(expr),
(stmts, Some(expr)) => {
let in_tail_pos = self.in_tail_pos;
self.in_tail_pos = false;
for stmt in stmts {
self.visit_stmt(stmt);
}
self.in_tail_pos = in_tail_pos;
self.visit_expr(expr);
},
([stmts @ .., stmt], None) => {
let in_tail_pos = self.in_tail_pos;
self.in_tail_pos = false;
for stmt in stmts {
self.visit_stmt(stmt);
}
self.in_tail_pos = in_tail_pos;
self.visit_stmt(stmt);
},
}
}
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if !self.can_use_entry {
return;
}
match try_parse_insert(self.cx, expr) {
Some(insert_expr) if SpanlessEq::new(self.cx).eq_expr(self.map, insert_expr.map) => {
// Multiple inserts, inserts with a different key, and inserts from a macro can't use the entry api.
if self.is_map_used
|| !SpanlessEq::new(self.cx).eq_expr(self.key, insert_expr.key)
|| expr.span.ctxt() != self.ctxt
{
self.can_use_entry = false;
return;
}
self.edits.push(Edit::Insertion(Insertion {
call: expr,
value: insert_expr.value,
}));
self.is_map_used = true;
self.allow_insert_closure &= self.in_tail_pos;
// The value doesn't affect whether there is only a single insert expression.
let is_single_insert = self.is_single_insert;
self.visit_non_tail_expr(insert_expr.value);
self.is_single_insert = is_single_insert;
},
_ if SpanlessEq::new(self.cx).eq_expr(self.map, expr) => {
self.is_map_used = true;
},
_ => match expr.kind {
ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
self.is_single_insert = false;
self.visit_non_tail_expr(cond_expr);
// Each branch may contain it's own insert expression.
let mut is_map_used = self.visit_cond_arm(then_expr);
is_map_used |= self.visit_cond_arm(else_expr);
self.is_map_used = is_map_used;
},
ExprKind::Match(scrutinee_expr, arms, _) => {
self.is_single_insert = false;
self.visit_non_tail_expr(scrutinee_expr);
// Each branch may contain it's own insert expression.
let mut is_map_used = self.is_map_used;
for arm in arms {
if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard {
self.visit_non_tail_expr(guard)
}
is_map_used |= self.visit_cond_arm(arm.body);
}
self.is_map_used = is_map_used;
},
ExprKind::Loop(block, ..) => {
self.loops.push(expr.hir_id);
self.is_single_insert = false;
self.allow_insert_closure &= !self.in_tail_pos;
// Don't allow insertions inside of a loop.
let edit_len = self.edits.len();
self.visit_block(block);
if self.edits.len() != edit_len {
self.can_use_entry = false;
}
self.loops.pop();
},
ExprKind::Block(block, _) => self.visit_block(block),
ExprKind::InlineAsm(_) | ExprKind::LlvmInlineAsm(_) => {
self.can_use_entry = false;
},
_ => {
self.allow_insert_closure &= !self.in_tail_pos;
self.allow_insert_closure &= can_move_expr_to_closure_no_visit(self.cx, expr, &self.loops);
// Sub expressions are no longer in the tail position.
self.is_single_insert = false;
self.in_tail_pos = false;
walk_expr(self, expr);
},
},
}
}
}
struct InsertSearchResults<'tcx> {
edits: Vec<Edit<'tcx>>,
allow_insert_closure: bool,
is_single_insert: bool,
}
impl InsertSearchResults<'tcx> {
fn as_single_insertion(&self) -> Option<Insertion<'tcx>> {
self.is_single_insert.then(|| self.edits[0].as_insertion().unwrap())
}
fn snippet(
&self,
cx: &LateContext<'_>,
mut span: Span,
app: &mut Applicability,
write_wrapped: impl Fn(&mut String, Insertion<'_>, SyntaxContext, &mut Applicability),
) -> String {
let ctxt = span.ctxt();
let mut res = String::new();
for insertion in self.edits.iter().filter_map(|e| e.as_insertion()) {
res.push_str(&snippet_with_applicability(
cx,
span.until(insertion.call.span),
"..",
app,
));
if is_expr_used_or_unified(cx.tcx, insertion.call) {
write_wrapped(&mut res, insertion, ctxt, app);
} else {
let _ = write!(
res,
"e.insert({})",
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
);
}
span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
}
res.push_str(&snippet_with_applicability(cx, span, "..", app));
res
}
fn snippet_occupied(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
(
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
// Insertion into a map would return `Some(&mut value)`, but the entry returns `&mut value`
let _ = write!(
res,
"Some(e.insert({}))",
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0
);
}),
"Occupied(mut e)",
)
}
fn snippet_vacant(&self, cx: &LateContext<'_>, span: Span, app: &mut Applicability) -> (String, &'static str) {
(
self.snippet(cx, span, app, |res, insertion, ctxt, app| {
// Insertion into a map would return `None`, but the entry returns a mutable reference.
let _ = if is_expr_final_block_expr(cx.tcx, insertion.call) {
write!(
res,
"e.insert({});\n{}None",
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
snippet_indent(cx, insertion.call.span).as_deref().unwrap_or(""),
)
} else {
write!(
res,
"{{ e.insert({}); None }}",
snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0,
)
};
}),
"Vacant(e)",
)
}
fn snippet_closure(&self, cx: &LateContext<'_>, mut span: Span, app: &mut Applicability) -> String {
let ctxt = span.ctxt();
let mut res = String::new();
for edit in &self.edits {
match *edit {
Edit::Insertion(insertion) => {
// Cut out the value from `map.insert(key, value)`
res.push_str(&snippet_with_applicability(
cx,
span.until(insertion.call.span),
"..",
app,
));
res.push_str(&snippet_with_context(cx, insertion.value.span, ctxt, "..", app).0);
span = span.trim_start(insertion.call.span).unwrap_or(DUMMY_SP);
},
Edit::RemoveSemi(semi_span) => {
// Cut out the semicolon. This allows the value to be returned from the closure.
res.push_str(&snippet_with_applicability(cx, span.until(semi_span), "..", app));
span = span.trim_start(semi_span).unwrap_or(DUMMY_SP);
},
}
}
res.push_str(&snippet_with_applicability(cx, span, "..", app));
res
}
}
fn find_insert_calls(
cx: &LateContext<'tcx>,
contains_expr: &ContainsExpr<'tcx>,
expr: &'tcx Expr<'_>,
) -> Option<InsertSearchResults<'tcx>> {
let mut s = InsertSearcher {
cx,
map: contains_expr.map,
key: contains_expr.key,
ctxt: expr.span.ctxt(),
edits: Vec::new(),
is_map_used: false,
allow_insert_closure: true,
can_use_entry: true,
in_tail_pos: true,
is_single_insert: true,
loops: Vec::new(),
};
s.visit_expr(expr);
let allow_insert_closure = s.allow_insert_closure;
let is_single_insert = s.is_single_insert;
let edits = s.edits;
s.can_use_entry.then(|| InsertSearchResults {
edits,
allow_insert_closure,
is_single_insert,
})
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::{in_macro, match_path_ast};
use clippy_utils::in_macro;
use rustc_ast::ast::{AssocItemKind, Extern, FnKind, FnSig, ImplKind, Item, ItemKind, TraitKind, Ty, TyKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@ -126,7 +126,9 @@ impl_lint_pass!(ExcessiveBools => [STRUCT_EXCESSIVE_BOOLS, FN_PARAMS_EXCESSIVE_B
fn is_bool_ty(ty: &Ty) -> bool {
if let TyKind::Path(None, path) = &ty.kind {
return match_path_ast(path, &["bool"]);
if let [name] = path.segments.as_slice() {
return name.ident.name == sym::bool;
}
}
false
}

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::paths;
use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_expn_of, last_path_segment, match_def_path, match_function_call};
use if_chain::if_chain;
@ -100,15 +101,15 @@ fn on_argumentv1_new<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &
return Some(format!("{:?}.to_string()", s.as_str()));
}
} else {
let snip = snippet(cx, format_args.span, "<arg>");
let sugg = Sugg::hir(cx, format_args, "<arg>");
if let ExprKind::MethodCall(path, _, _, _) = format_args.kind {
if path.ident.name == sym!(to_string) {
return Some(format!("{}", snip));
return Some(format!("{}", sugg));
}
} else if let ExprKind::Binary(..) = format_args.kind {
return Some(format!("{}", snip));
return Some(format!("{}", sugg));
}
return Some(format!("{}.to_string()", snip));
return Some(format!("{}.to_string()", sugg.maybe_par()));
}
}
}
@ -136,7 +137,7 @@ fn on_new_v1<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Strin
if let Some(s_src) = snippet_opt(cx, lit.span) {
// Simulate macro expansion, converting {{ and }} to { and }.
let s_expand = s_src.replace("{{", "{").replace("}}", "}");
return Some(format!("{}.to_string()", s_expand))
return Some(format!("{}.to_string()", s_expand));
}
} else if s.as_str().is_empty() {
return on_argumentv1_new(cx, &tup[0], arms);

View File

@ -215,9 +215,22 @@ fn check_else(cx: &EarlyContext<'_>, expr: &Expr) {
// the snippet should look like " else \n " with maybe comments anywhere
// its bad when there is a \n after the “else”
if let Some(else_snippet) = snippet_opt(cx, else_span);
if let Some(else_pos) = else_snippet.find("else");
if else_snippet[else_pos..].contains('\n');
if let Some((pre_else, post_else)) = else_snippet.split_once("else");
if let Some((_, post_else_post_eol)) = post_else.split_once('\n');
then {
// Allow allman style braces `} \n else \n {`
if_chain! {
if is_block(else_);
if let Some((_, pre_else_post_eol)) = pre_else.split_once('\n');
// Exactly one eol before and after the else
if !pre_else_post_eol.contains('\n');
if !post_else_post_eol.contains('\n');
then {
return;
}
}
let else_desc = if is_if(else_) { "if" } else { "{..}" };
span_lint_and_note(
cx,

View File

@ -73,7 +73,7 @@ impl LateLintPass<'_> for FromOverInto {
cx.tcx.sess.source_map().guess_head_span(item.span),
"an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true",
None,
"consider to implement `From` instead",
&format!("consider to implement `From<{}>` instead", impl_trait_ref.self_ty()),
);
}
}

View File

@ -1,7 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::{match_qpath, meets_msrv, parent_node_is_if_expr};
use clippy_utils::{is_else_clause, is_lang_ctor, meets_msrv};
use if_chain::if_chain;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -67,7 +68,7 @@ impl LateLintPass<'_> for IfThenSomeElseNone {
}
// We only care about the top-most `if` in the chain
if parent_node_is_if_expr(expr, cx) {
if is_else_clause(cx.tcx, expr) {
return;
}
@ -77,12 +78,12 @@ impl LateLintPass<'_> for IfThenSomeElseNone {
if let Some(then_expr) = then_block.expr;
if let ExprKind::Call(then_call, [then_arg]) = then_expr.kind;
if let ExprKind::Path(ref then_call_qpath) = then_call.kind;
if match_qpath(then_call_qpath, &clippy_utils::paths::OPTION_SOME);
if is_lang_ctor(cx, then_call_qpath, OptionSome);
if let ExprKind::Block(els_block, _) = els.kind;
if els_block.stmts.is_empty();
if let Some(els_expr) = els_block.expr;
if let ExprKind::Path(ref els_call_qpath) = els_expr.kind;
if match_qpath(els_call_qpath, &clippy_utils::paths::OPTION_NONE);
if let ExprKind::Path(ref qpath) = els_expr.kind;
if is_lang_ctor(cx, qpath, OptionNone);
then {
let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {

View File

@ -22,7 +22,7 @@ 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};
use clippy_utils::{differing_macro_contexts, match_def_path};
declare_clippy_lint! {
/// **What it does:** Checks for public `impl` or `fn` missing generalization
@ -333,12 +333,13 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't
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;
if let Some(ty_did) = ty_path.res.opt_def_id();
then {
if !TyS::same_type(self.target.ty(), self.maybe_typeck_results.unwrap().expr_ty(e)) {
return;
}
if match_path(ty_path, &paths::HASHMAP) {
if match_def_path(self.cx, ty_did, &paths::HASHMAP) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashMap::default()".to_string());
@ -351,7 +352,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't
),
);
}
} else if match_path(ty_path, &paths::HASHSET) {
} else if match_def_path(self.cx, ty_did, &paths::HASHSET) {
if method.ident.name == sym::new {
self.suggestions
.insert(e.span, "HashSet::default()".to_string());

View File

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{in_macro, match_qpath, SpanlessEq};
use clippy_utils::{in_macro, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, StmtKind};
use rustc_hir::{lang_items::LangItem, BinOpKind, Expr, ExprKind, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -87,7 +87,13 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
// Get the variable name
let var_name = ares_path.segments[0].ident.name.as_str();
const INT_TYPES: [&str; 5] = ["i8", "i16", "i32", "i64", "i128"];
const INT_TYPES: [LangItem; 5] = [
LangItem::I8,
LangItem::I16,
LangItem::I32,
LangItem::I64,
LangItem::Isize
];
match cond_num_val.kind {
ExprKind::Lit(ref cond_lit) => {
@ -99,17 +105,30 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
};
}
},
ExprKind::Path(ref cond_num_path) => {
if INT_TYPES.iter().any(|int_type| match_qpath(cond_num_path, &[int_type, "MIN"])) {
print_lint_and_sugg(cx, &var_name, expr);
};
},
ExprKind::Call(func, _) => {
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"])) {
print_lint_and_sugg(cx, &var_name, expr);
ExprKind::Path(QPath::TypeRelative(_, name)) => {
if_chain! {
if name.ident.as_str() == "MIN";
if let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(const_id);
let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok());
if int_ids.any(|int_id| int_id == impl_id);
then {
print_lint_and_sugg(cx, &var_name, expr)
}
};
}
},
ExprKind::Call(func, []) => {
if_chain! {
if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind;
if name.ident.as_str() == "min_value";
if let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(func_id);
let mut int_ids = INT_TYPES.iter().filter_map(|&ty| cx.tcx.lang_items().require(ty).ok());
if int_ids.any(|int_id| int_id == impl_id);
then {
print_lint_and_sugg(cx, &var_name, expr)
}
}
},
_ => (),
}

View File

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::in_macro;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
@ -66,6 +67,7 @@ declare_lint_pass!(InconsistentStructConstructor => [INCONSISTENT_STRUCT_CONSTRU
impl LateLintPass<'_> for InconsistentStructConstructor {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if !in_macro(expr.span);
if let ExprKind::Struct(qpath, fields, base) = expr.kind;
let ty = cx.typeck_results().expr_ty(expr);
if let Some(adt_def) = ty.ty_adt_def();

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{implements_trait, match_type};
use clippy_utils::{get_trait_def_id, higher, match_qpath, paths};
use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths};
use rustc_hir::{BorrowKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -163,7 +163,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
ExprKind::Call(path, _) => {
if let ExprKind::Path(ref qpath) = path.kind {
match_qpath(qpath, &paths::REPEAT).into()
is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into()
} else {
Finite
}

View File

@ -179,6 +179,7 @@ mod await_holding_invalid;
mod bit_mask;
mod blacklisted_name;
mod blocks_in_if_conditions;
mod bool_assert_comparison;
mod booleans;
mod bytecount;
mod cargo_common_metadata;
@ -357,6 +358,7 @@ mod unicode;
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
mod unnecessary_self_imports;
mod unnecessary_sort_by;
mod unnecessary_wraps;
mod unnested_or_patterns;
@ -391,6 +393,7 @@ pub use crate::utils::conf::Conf;
///
/// Used in `./src/driver.rs`.
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
store.register_pre_expansion_pass(|| box write::Write::default());
store.register_pre_expansion_pass(|| box attrs::EarlyAttributes);
store.register_pre_expansion_pass(|| box dbg_macro::DbgMacro);
@ -494,22 +497,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
"clippy::unsafe_vector_initialization",
"the replacement suggested by this lint had substantially different behavior",
);
store.register_removed(
"clippy::invalid_ref",
"superseded by rustc lint `invalid_value`",
);
store.register_removed(
"clippy::unused_collect",
"`collect` has been marked as #[must_use] in rustc and that covers all cases of this lint",
);
store.register_removed(
"clippy::into_iter_on_array",
"this lint has been uplifted to rustc and is now called `array_into_iter`",
);
store.register_removed(
"clippy::unused_label",
"this lint has been uplifted to rustc and is now called `unused_labels`",
);
store.register_removed(
"clippy::replace_consts",
"associated-constants `MIN`/`MAX` of integers are preferred to `{min,max}_value()` and module constants",
@ -518,26 +509,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
"clippy::regex_macro",
"the regex! macro has been removed from the regex crate in 2018",
);
store.register_removed(
"clippy::drop_bounds",
"this lint has been uplifted to rustc and is now called `drop_bounds`",
);
store.register_removed(
"clippy::temporary_cstring_as_ptr",
"this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`",
);
store.register_removed(
"clippy::panic_params",
"this lint has been uplifted to rustc and is now called `panic_fmt`",
);
store.register_removed(
"clippy::unknown_clippy_lints",
"this lint has been integrated into the `unknown_lints` rustc lint",
);
store.register_removed(
"clippy::find_map",
"this lint has been replaced by `manual_find_map`, a more specific lint",
);
store.register_removed(
"clippy::filter_map",
"this lint has been replaced by `manual_filter_map`, a more specific lint",
);
// end deprecated lints, do not remove this comment, its used in `update_lints`
// begin register lints, do not remove this comment, its used in `update_lints`
@ -592,6 +571,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
bit_mask::VERBOSE_BIT_MASK,
blacklisted_name::BLACKLISTED_NAME,
blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS,
bool_assert_comparison::BOOL_ASSERT_COMPARISON,
booleans::LOGIC_BUG,
booleans::NONMINIMAL_BOOL,
bytecount::NAIVE_BYTECOUNT,
@ -783,17 +763,18 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
methods::BYTES_NTH,
methods::CHARS_LAST_CMP,
methods::CHARS_NEXT_CMP,
methods::CLONED_INSTEAD_OF_COPIED,
methods::CLONE_DOUBLE_REF,
methods::CLONE_ON_COPY,
methods::CLONE_ON_REF_PTR,
methods::EXPECT_FUN_CALL,
methods::EXPECT_USED,
methods::FILETYPE_IS_FILE,
methods::FILTER_MAP,
methods::FILTER_MAP_IDENTITY,
methods::FILTER_MAP_NEXT,
methods::FILTER_NEXT,
methods::FLAT_MAP_IDENTITY,
methods::FLAT_MAP_OPTION,
methods::FROM_ITER_INSTEAD_OF_COLLECT,
methods::GET_UNWRAP,
methods::IMPLICIT_CLONE,
@ -904,6 +885,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
precedence::PRECEDENCE,
ptr::CMP_NULL,
ptr::INVALID_NULL_PTR_USAGE,
ptr::MUT_FROM_REF,
ptr::PTR_ARG,
ptr_eq::PTR_EQ,
@ -988,6 +970,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
unit_types::UNIT_CMP,
unnamed_address::FN_ADDRESS_COMPARISONS,
unnamed_address::VTABLE_ADDRESS_COMPARISONS,
unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS,
unnecessary_sort_by::UNNECESSARY_SORT_BY,
unnecessary_wraps::UNNECESSARY_WRAPS,
unnested_or_patterns::UNNESTED_OR_PATTERNS,
@ -1073,6 +1056,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box default_numeric_fallback::DefaultNumericFallback);
store.register_late_pass(|| box inconsistent_struct_constructor::InconsistentStructConstructor);
store.register_late_pass(|| box non_octal_unix_permissions::NonOctalUnixPermissions);
store.register_early_pass(|| box unnecessary_self_imports::UnnecessarySelfImports);
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
@ -1295,6 +1279,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box from_str_radix_10::FromStrRadix10);
store.register_late_pass(|| box manual_map::ManualMap);
store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv));
store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(arithmetic::FLOAT_ARITHMETIC),
@ -1345,6 +1330,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(strings::STRING_TO_STRING),
LintId::of(strings::STR_TO_STRING),
LintId::of(types::RC_BUFFER),
LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
LintId::of(verbose_file_reads::VERBOSE_FILE_READS),
LintId::of(write::PRINT_STDERR),
@ -1404,8 +1390,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS),
LintId::of(matches::MATCH_WILD_ERR_ARM),
LintId::of(matches::SINGLE_MATCH_ELSE),
LintId::of(methods::FILTER_MAP),
LintId::of(methods::CLONED_INSTEAD_OF_COPIED),
LintId::of(methods::FILTER_MAP_NEXT),
LintId::of(methods::FLAT_MAP_OPTION),
LintId::of(methods::IMPLICIT_CLONE),
LintId::of(methods::INEFFICIENT_TO_STRING),
LintId::of(methods::MAP_FLATTEN),
@ -1428,6 +1415,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(strings::STRING_ADD_ASSIGN),
LintId::of(trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS),
LintId::of(trait_bounds::TYPE_REPETITION_IN_BOUNDS),
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
LintId::of(types::LINKEDLIST),
LintId::of(types::OPTION_OPTION),
LintId::of(unicode::NON_ASCII_LITERAL),
@ -1474,6 +1462,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CAST_REF_TO_MUT),
@ -1673,6 +1662,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(precedence::PRECEDENCE),
LintId::of(ptr::CMP_NULL),
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ptr::PTR_ARG),
LintId::of(ptr_eq::PTR_EQ),
@ -1715,7 +1705,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
@ -1759,6 +1748,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(blacklisted_name::BLACKLISTED_NAME),
LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(casts::FN_TO_NUMERIC_CAST),
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
@ -1949,7 +1939,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(transmute::TRANSMUTE_INT_TO_BOOL),
LintId::of(transmute::TRANSMUTE_INT_TO_CHAR),
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_PTR_TO_PTR),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(types::BORROWED_BOX),
LintId::of(types::TYPE_COMPLEXITY),
@ -2012,6 +2001,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(regex::INVALID_REGEX),
@ -2153,6 +2143,15 @@ pub fn register_renamed(ls: &mut rustc_lint::LintStore) {
ls.register_renamed("clippy::identity_conversion", "clippy::useless_conversion");
ls.register_renamed("clippy::zero_width_space", "clippy::invisible_characters");
ls.register_renamed("clippy::single_char_push_str", "clippy::single_char_add_str");
// uplifted lints
ls.register_renamed("clippy::invalid_ref", "invalid_value");
ls.register_renamed("clippy::into_iter_on_array", "array_into_iter");
ls.register_renamed("clippy::unused_label", "unused_labels");
ls.register_renamed("clippy::drop_bounds", "drop_bounds");
ls.register_renamed("clippy::temporary_cstring_as_ptr", "temporary_cstring_as_ptr");
ls.register_renamed("clippy::panic_params", "non_fmt_panic");
ls.register_renamed("clippy::unknown_clippy_lints", "unknown_lints");
}
// only exists to let the dogfood integration test works.

View File

@ -1,24 +1,25 @@
use super::EXPLICIT_INTO_ITER_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{match_trait_method, paths};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::TyS;
pub(super) fn check(cx: &LateContext<'_>, args: &'hir [Expr<'hir>], arg: &Expr<'_>) {
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
if !TyS::same_type(receiver_ty, receiver_ty_adjusted) {
pub(super) fn check(cx: &LateContext<'_>, self_arg: &'hir Expr<'hir>, call_expr: &Expr<'_>) {
let self_ty = cx.typeck_results().expr_ty(self_arg);
let self_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
if !(TyS::same_type(self_ty, self_ty_adjusted) && match_trait_method(cx, call_expr, &paths::INTO_ITERATOR)) {
return;
}
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
EXPLICIT_INTO_ITER_LOOP,
arg.span,
call_expr.span,
"it is more concise to loop over containers instead of using explicit \
iteration methods",
"to write this more concisely, try",

View File

@ -9,12 +9,12 @@ use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty, TyS};
use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) {
pub(super) fn check(cx: &LateContext<'_>, self_arg: &Expr<'_>, arg: &Expr<'_>, method_name: &str) {
let should_lint = match method_name {
"iter" | "iter_mut" => is_ref_iterable_type(cx, &args[0]),
"iter" | "iter_mut" => is_ref_iterable_type(cx, self_arg),
"into_iter" if match_trait_method(cx, arg, &paths::INTO_ITERATOR) => {
let receiver_ty = cx.typeck_results().expr_ty(&args[0]);
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(&args[0]);
let receiver_ty = cx.typeck_results().expr_ty(self_arg);
let receiver_ty_adjusted = cx.typeck_results().expr_ty_adjusted(self_arg);
let ref_receiver_ty = cx.tcx.mk_ref(
cx.tcx.lifetimes.re_erased,
ty::TypeAndMut {
@ -32,7 +32,7 @@ pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, met
}
let mut applicability = Applicability::MachineApplicable;
let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability);
let object = snippet_with_applicability(cx, self_arg.span, "_", &mut applicability);
let muta = if method_name == "iter_mut" { "mut " } else { "" };
span_lint_and_sugg(
cx,

View File

@ -1,10 +1,11 @@
use super::utils::make_iterator_snippet;
use super::MANUAL_FLATTEN;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{is_ok_ctor, is_some_ctor, path_to_local_id};
use clippy_utils::{is_lang_ctor, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, StmtKind};
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, StmtKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Span;
@ -42,9 +43,9 @@ pub(super) fn check<'tcx>(
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
if path_to_local_id(match_expr, pat_hir_id);
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
if let PatKind::TupleStruct(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind;
let some_ctor = is_some_ctor(cx, path.res);
let ok_ctor = is_ok_ctor(cx, path.res);
if let PatKind::TupleStruct(ref qpath, _, _) = match_arms[0].pat.kind;
let some_ctor = is_lang_ctor(cx, qpath, OptionSome);
let ok_ctor = is_lang_ctor(cx, qpath, ResultOk);
if some_ctor || ok_ctor;
then {
let if_let_type = if some_ctor { "Some" } else { "Ok" };

View File

@ -602,22 +602,19 @@ fn check_for_loop<'tcx>(
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
if let ExprKind::MethodCall(method, _, args, _) = arg.kind {
// just the receiver, no arguments
if args.len() == 1 {
let method_name = &*method.ident.as_str();
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
match method_name {
"iter" | "iter_mut" => explicit_iter_loop::check(cx, args, arg, method_name),
"into_iter" => {
explicit_iter_loop::check(cx, args, arg, method_name);
explicit_into_iter_loop::check(cx, args, arg);
},
"next" => {
next_loop_linted = iter_next_loop::check(cx, arg, expr);
},
_ => {},
}
if let ExprKind::MethodCall(method, _, [self_arg], _) = arg.kind {
let method_name = &*method.ident.as_str();
// check for looping over x.iter() or x.iter_mut(), could use &x or &mut x
match method_name {
"iter" | "iter_mut" => explicit_iter_loop::check(cx, self_arg, arg, method_name),
"into_iter" => {
explicit_iter_loop::check(cx, self_arg, arg, method_name);
explicit_into_iter_loop::check(cx, self_arg, arg);
},
"next" => {
next_loop_linted = iter_next_loop::check(cx, arg, expr);
},
_ => {},
}
}

View File

@ -100,7 +100,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, e1, e2)
| ExprKind::Index(e1, 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().copied(), main_loop_id),
ExprKind::Loop(b, _, _, _) => {
// Break can come from the inner loop so remove them.
absorb_break(&never_loop_block(b, main_loop_id))

View File

@ -112,6 +112,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports {
let attrs = cx.tcx.hir().attrs(item.hir_id());
if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use));
if let Res::Def(DefKind::Mod, id) = path.res;
if !id.is_local();
then {
for kid in cx.tcx.item_children(id).iter() {
if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res {

View File

@ -1,15 +1,14 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
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::{in_constant, is_allowed, is_else_clause, match_def_path, match_var, paths, peel_hir_expr_refs};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable};
use clippy_utils::{
can_move_expr_to_closure, in_constant, is_allowed, is_else_clause, is_lang_ctor, match_var, peel_hir_expr_refs,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::{
def::Res,
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath,
};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -103,12 +102,18 @@ impl LateLintPass<'_> for ManualMap {
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr) == cx.tcx.types.unit
&& !is_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr).is_empty() {
return;
}
if !can_move_expr_to_closure(cx, some_expr) {
return;
}
@ -192,51 +197,6 @@ impl LateLintPass<'_> for ManualMap {
}
}
// Checks if the expression can be moved into a closure as is.
fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
make_closure: bool,
}
impl Visitor<'tcx> for V<'_, 'tcx> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
match e.kind {
ExprKind::Break(..)
| ExprKind::Continue(_)
| ExprKind::Ret(_)
| ExprKind::Yield(..)
| ExprKind::InlineAsm(_)
| ExprKind::LlvmInlineAsm(_) => {
self.make_closure = false;
},
// Accessing a field of a local value can only be done if the type isn't
// partially moved.
ExprKind::Field(base_expr, _)
if matches!(
base_expr.kind,
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
) && can_partially_move_ty(self.cx, self.cx.typeck_results().expr_ty(base_expr)) =>
{
// TODO: check if the local has been partially moved. Assume it has for now.
self.make_closure = false;
return;
}
_ => (),
};
walk_expr(self, e);
}
}
let mut v = V { cx, make_closure: true };
v.visit_expr(expr);
v.make_closure
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func(cx: &LateContext<'tcx>, binding: Ident, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
@ -269,20 +229,9 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxCon
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(QPath::Resolved(None, path))
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)) =>
{
Some(OptionPat::None)
},
PatKind::TupleStruct(QPath::Resolved(None, path), [pattern], _)
if path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME))
&& pat.span.ctxt() == ctxt =>
PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
@ -298,17 +247,11 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxConte
match expr.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(QPath::Resolved(None, path)),
kind: ExprKind::Path(ref qpath),
..
},
[arg],
) if ctxt == expr.span.ctxt() => {
if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) {
Some(arg)
} else {
None
}
},
) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(arg),
ExprKind::Block(
Block {
stmts: [],
@ -324,10 +267,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxConte
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
match expr.kind {
ExprKind::Path(QPath::Resolved(None, path)) => path
.res
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, &paths::OPTION_NONE)),
ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
ExprKind::Block(
Block {
stmts: [],

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_qpath, path_to_local_id, paths};
use clippy_utils::{is_lang_ctor, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::{Expr, ExprKind, PatKind};
use rustc_lint::LintContext;
use rustc_lint::{LateContext, LateLintPass};
@ -54,7 +55,7 @@ impl LateLintPass<'_> for ManualOkOr {
let or_expr = &args[1];
if is_ok_wrapping(cx, &args[2]);
if let ExprKind::Call(Expr { kind: ExprKind::Path(err_path), .. }, &[ref err_arg]) = or_expr.kind;
if match_qpath(err_path, &paths::RESULT_ERR);
if is_lang_ctor(cx, err_path, ResultErr);
if let Some(method_receiver_snippet) = snippet_opt(cx, method_receiver.span);
if let Some(err_arg_snippet) = snippet_opt(cx, err_arg.span);
if let Some(indent) = indent_of(cx, scrutinee.span);
@ -81,7 +82,7 @@ impl LateLintPass<'_> for ManualOkOr {
fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
if let ExprKind::Path(ref qpath) = map_expr.kind {
if match_qpath(qpath, &paths::RESULT_OK) {
if is_lang_ctor(cx, qpath, ResultOk) {
return true;
}
}
@ -90,7 +91,7 @@ fn is_ok_wrapping(cx: &LateContext<'_>, map_expr: &Expr<'_>) -> bool {
let body = cx.tcx.hir().body(body_id);
if let PatKind::Binding(_, param_id, ..) = body.params[0].pat.kind;
if let ExprKind::Call(Expr { kind: ExprKind::Path(ok_path), .. }, &[ref ok_arg]) = body.value.kind;
if match_qpath(ok_path, &paths::RESULT_OK);
if is_lang_ctor(cx, ok_path, ResultOk);
then { path_to_local_id(ok_arg, param_id) } else { false }
}
}

View File

@ -3,10 +3,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{in_constant, match_qpath, path_to_local_id, paths, sugg};
use clippy_utils::{in_constant, is_lang_ctor, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{Arm, Expr, ExprKind, PatKind};
use rustc_lint::LintContext;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
@ -68,23 +69,21 @@ impl Case {
}
fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
fn applicable_or_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
if_chain! {
if arms.len() == 2;
if arms.iter().all(|arm| arm.guard.is_none());
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)|
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
match arm.pat.kind {
PatKind::Path(ref some_qpath) =>
match_qpath(some_qpath, &paths::OPTION_NONE),
PatKind::TupleStruct(ref err_qpath, &[Pat { kind: PatKind::Wild, .. }], _) =>
match_qpath(err_qpath, &paths::RESULT_ERR),
PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
PatKind::TupleStruct(ref qpath, &[pat], _) =>
matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
_ => false,
}
);
});
let unwrap_arm = &arms[1 - idx];
if let PatKind::TupleStruct(ref unwrap_qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind;
if match_qpath(unwrap_qpath, &paths::OPTION_SOME)
|| match_qpath(unwrap_qpath, &paths::RESULT_OK);
if let PatKind::TupleStruct(ref qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind;
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
if path_to_local_id(unwrap_arm.body, binding_hir_id);
if !contains_return_break_continue_macro(or_arm.body);
@ -106,7 +105,7 @@ fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
} else {
None
};
if let Some(or_arm) = applicable_or_arm(match_arms);
if let Some(or_arm) = applicable_or_arm(cx, match_arms);
if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
if let Some(indent) = indent_of(cx, expr.span);
if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_adjusted, is_trait_method, match_path, match_var, paths, remove_blocks};
use clippy_utils::{is_adjusted, is_qpath_def_path, is_trait_method, match_var, paths, remove_blocks};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Body, Expr, ExprKind, Pat, PatKind, QPath, StmtKind};
@ -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 {
match expr.kind {
ExprKind::Closure(_, _, body_id, _, _) => is_body_identity_function(cx, cx.tcx.hir().body(body_id)),
ExprKind::Path(QPath::Resolved(_, path)) => match_path(path, &paths::STD_CONVERT_IDENTITY),
ExprKind::Path(ref path) => is_qpath_def_path(cx, path, expr.hir_id, &paths::CONVERT_IDENTITY),
_ => false,
}
}

View File

@ -7,7 +7,7 @@ use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, match_type, peel_mid_ty_refs};
use clippy_utils::visitors::LocalUsedVisitor;
use clippy_utils::{
get_parent_expr, in_macro, is_allowed, is_expn_of, is_refutable, is_wild, match_qpath, meets_msrv, path_to_local,
get_parent_expr, in_macro, is_allowed, is_expn_of, is_lang_ctor, is_refutable, is_wild, meets_msrv, path_to_local,
path_to_local_id, peel_hir_pat_refs, peel_n_hir_expr_refs, recurse_or_patterns, remove_blocks, strip_pat_refs,
};
use clippy_utils::{paths, search_same, SpanlessEq, SpanlessHash};
@ -15,6 +15,7 @@ use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{
self as hir, Arm, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, Guard, HirId, Local, MatchSource,
Mutability, Node, Pat, PatKind, PathSegment, QPath, RangeEnd, TyKind,
@ -422,7 +423,12 @@ declare_clippy_lint! {
/// **Why is this bad?** It's more concise and clear to just use the proper
/// utility function
///
/// **Known problems:** None.
/// **Known problems:** This will change the drop order for the matched type. Both `if let` and
/// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
/// value before entering the block. For most types this change will not matter, but for a few
/// types this will not be an acceptable change (e.g. locks). See the
/// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
/// drop order.
///
/// **Example:**
///
@ -737,8 +743,11 @@ fn report_single_match_single_pattern(
let (msg, sugg) = if_chain! {
if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind;
let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex));
if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait();
if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]);
if let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait();
if let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait();
if ty.is_integral() || ty.is_char() || ty.is_str()
|| (implements_trait(cx, ty, spe_trait_id, &[])
&& implements_trait(cx, ty, pe_trait_id, &[ty.into()]));
then {
// scrutinee derives PartialEq and the pattern is a constant.
let pat_ref_count = match pat.kind {
@ -1120,7 +1129,7 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
Applicability::MaybeIncorrect,
),
variants => {
let mut suggestions: Vec<_> = variants.iter().cloned().map(format_suggestion).collect();
let mut suggestions: Vec<_> = variants.iter().copied().map(format_suggestion).collect();
let message = if adt_def.is_variant_list_non_exhaustive() {
suggestions.push("_".into());
"wildcard matches known variants and will also match future added variants"
@ -1189,10 +1198,10 @@ fn check_match_ref_pats(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], e
fn check_match_as_ref(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
if arms.len() == 2 && arms[0].guard.is_none() && arms[1].guard.is_none() {
let arm_ref: Option<BindingAnnotation> = if is_none_arm(&arms[0]) {
is_ref_some_arm(&arms[1])
} else if is_none_arm(&arms[1]) {
is_ref_some_arm(&arms[0])
let arm_ref: Option<BindingAnnotation> = if is_none_arm(cx, &arms[0]) {
is_ref_some_arm(cx, &arms[1])
} else if is_none_arm(cx, &arms[1]) {
is_ref_some_arm(cx, &arms[0])
} else {
None
};
@ -1500,7 +1509,7 @@ fn opt_parent_let<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<&'a Local<'
/// Gets all arms that are unbounded `PatRange`s.
fn all_ranges<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], ty: Ty<'tcx>) -> Vec<SpannedRange<Constant>> {
arms.iter()
.flat_map(|arm| {
.filter_map(|arm| {
if let Arm { pat, guard: None, .. } = *arm {
if let PatKind::Range(ref lhs, ref rhs, range_end) = pat.kind {
let lhs = match lhs {
@ -1575,20 +1584,20 @@ fn is_unit_expr(expr: &Expr<'_>) -> bool {
}
// Checks if arm has the form `None => None`
fn is_none_arm(arm: &Arm<'_>) -> bool {
matches!(arm.pat.kind, PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE))
fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
matches!(arm.pat.kind, PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
}
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> {
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
if_chain! {
if let PatKind::TupleStruct(ref path, pats, _) = arm.pat.kind;
if pats.len() == 1 && match_qpath(path, &paths::OPTION_SOME);
if let PatKind::TupleStruct(ref qpath, pats, _) = arm.pat.kind;
if is_lang_ctor(cx, qpath, OptionSome);
if let PatKind::Binding(rb, .., ident, _) = pats[0].kind;
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind;
if let ExprKind::Path(ref some_path) = e.kind;
if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
if is_lang_ctor(cx, some_path, OptionSome) && args.len() == 1;
if let ExprKind::Path(QPath::Resolved(_, path2)) = args[0].kind;
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
then {
@ -1699,54 +1708,206 @@ where
mod redundant_pattern_match {
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::{is_trait_method, match_qpath, paths};
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, is_type_lang_item, match_type};
use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind, QPath};
use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
use rustc_hir::{
intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor},
Arm, Block, Expr, ExprKind, LangItem, MatchSource, Node, PatKind, QPath,
};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
use rustc_span::sym;
pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Match(op, arms, ref match_source) = &expr.kind {
match match_source {
MatchSource::Normal => find_sugg_for_match(cx, expr, op, arms),
MatchSource::IfLetDesugar { .. } => find_sugg_for_if_let(cx, expr, op, arms, "if"),
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, arms, "while"),
MatchSource::IfLetDesugar { contains_else_clause } => {
find_sugg_for_if_let(cx, expr, op, &arms[0], "if", *contains_else_clause)
},
MatchSource::WhileLetDesugar => find_sugg_for_if_let(cx, expr, op, &arms[0], "while", false),
_ => {},
}
}
}
/// Checks if the drop order for a type matters. Some std types implement drop solely to
/// deallocate memory. For these types, and composites containing them, changing the drop order
/// won't result in any observable side effects.
fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if !ty.needs_drop(cx.tcx, cx.param_env) {
false
} else if !cx
.tcx
.lang_items()
.drop_trait()
.map_or(false, |id| implements_trait(cx, ty, id, &[]))
{
// This type doesn't implement drop, so no side effects here.
// Check if any component type has any.
match ty.kind() {
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)),
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty),
ty::Adt(adt, subs) => adt
.all_fields()
.map(|f| f.ty(cx.tcx, subs))
.any(|ty| type_needs_ordered_drop(cx, ty)),
_ => true,
}
}
// Check for std types which implement drop, but only for memory allocation.
else if is_type_diagnostic_item(cx, ty, sym::vec_type)
|| is_type_lang_item(cx, ty, LangItem::OwnedBox)
|| is_type_diagnostic_item(cx, ty, sym::Rc)
|| is_type_diagnostic_item(cx, ty, sym::Arc)
|| is_type_diagnostic_item(cx, ty, sym::cstring_type)
|| match_type(cx, ty, &paths::BTREEMAP)
|| match_type(cx, ty, &paths::LINKED_LIST)
|| match_type(cx, ty, &paths::WEAK_RC)
|| match_type(cx, ty, &paths::WEAK_ARC)
{
// Check all of the generic arguments.
if let ty::Adt(_, subs) = ty.kind() {
subs.types().any(|ty| type_needs_ordered_drop(cx, ty))
} else {
true
}
} else {
true
}
}
// Extract the generic arguments out of a type
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
if_chain! {
if let ty::Adt(_, subs) = ty.kind();
if let Some(sub) = subs.get(index);
if let GenericArgKind::Type(sub_ty) = sub.unpack();
then {
Some(sub_ty)
} else {
None
}
}
}
// Checks if there are any temporaries created in the given expression for which drop order
// matters.
fn temporaries_need_ordered_drop(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
struct V<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
res: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for V<'a, 'tcx> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
match expr.kind {
// Taking the reference of a value leaves a temporary
// e.g. In `&String::new()` the string is a temporary value.
// Remaining fields are temporary values
// e.g. In `(String::new(), 0).1` the string is a temporary value.
ExprKind::AddrOf(_, _, expr) | ExprKind::Field(expr, _) => {
if !matches!(expr.kind, ExprKind::Path(_)) {
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(expr)) {
self.res = true;
} else {
self.visit_expr(expr);
}
}
},
// the base type is alway taken by reference.
// e.g. In `(vec![0])[0]` the vector is a temporary value.
ExprKind::Index(base, index) => {
if !matches!(base.kind, ExprKind::Path(_)) {
if type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(base)) {
self.res = true;
} else {
self.visit_expr(base);
}
}
self.visit_expr(index);
},
// Method calls can take self by reference.
// e.g. In `String::new().len()` the string is a temporary value.
ExprKind::MethodCall(_, _, [self_arg, args @ ..], _) => {
if !matches!(self_arg.kind, ExprKind::Path(_)) {
let self_by_ref = self
.cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
.map_or(false, |id| self.cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref());
if self_by_ref
&& type_needs_ordered_drop(self.cx, self.cx.typeck_results().expr_ty(self_arg))
{
self.res = true;
} else {
self.visit_expr(self_arg)
}
}
args.iter().for_each(|arg| self.visit_expr(arg));
},
// Either explicitly drops values, or changes control flow.
ExprKind::DropTemps(_)
| ExprKind::Ret(_)
| ExprKind::Break(..)
| ExprKind::Yield(..)
| ExprKind::Block(Block { expr: None, .. }, _)
| ExprKind::Loop(..) => (),
// Only consider the final expression.
ExprKind::Block(Block { expr: Some(expr), .. }, _) => self.visit_expr(expr),
_ => walk_expr(self, expr),
}
}
}
let mut v = V { cx, res: false };
v.visit_expr(expr);
v.res
}
fn find_sugg_for_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
op: &Expr<'_>,
arms: &[Arm<'_>],
op: &'tcx Expr<'tcx>,
arm: &Arm<'_>,
keyword: &'static str,
has_else: bool,
) {
// also look inside refs
let mut kind = &arms[0].pat.kind;
let mut kind = &arm.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 match_qpath(path, &paths::RESULT_OK) {
"is_ok()"
} else if match_qpath(path, &paths::RESULT_ERR) {
"is_err()"
} else if match_qpath(path, &paths::OPTION_SOME) {
"is_some()"
} else if match_qpath(path, &paths::POLL_READY) {
"is_ready()"
} else if match_qpath(path, &paths::IPADDR_V4) {
"is_ipv4()"
} else if match_qpath(path, &paths::IPADDR_V6) {
"is_ipv6()"
let op_ty = cx.typeck_results().expr_ty(op);
// Determine which function should be used, and the type contained by the corresponding
// variant.
let (good_method, inner_ty) = match kind {
PatKind::TupleStruct(ref path, [sub_pat], _) => {
if let PatKind::Wild = sub_pat.kind {
if is_lang_ctor(cx, path, ResultOk) {
("is_ok()", try_get_generic_ty(op_ty, 0).unwrap_or(op_ty))
} else if is_lang_ctor(cx, path, ResultErr) {
("is_err()", try_get_generic_ty(op_ty, 1).unwrap_or(op_ty))
} else if is_lang_ctor(cx, path, OptionSome) {
("is_some()", op_ty)
} else if is_lang_ctor(cx, path, PollReady) {
("is_ready()", op_ty)
} else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V4) {
("is_ipv4()", op_ty)
} else if is_qpath_def_path(cx, path, sub_pat.hir_id, &paths::IPADDR_V6) {
("is_ipv6()", op_ty)
} else {
return;
}
@ -1755,17 +1916,36 @@ mod redundant_pattern_match {
}
},
PatKind::Path(ref path) => {
if match_qpath(path, &paths::OPTION_NONE) {
let method = if is_lang_ctor(cx, path, OptionNone) {
"is_none()"
} else if match_qpath(path, &paths::POLL_PENDING) {
} else if is_lang_ctor(cx, path, PollPending) {
"is_pending()"
} else {
return;
}
};
// `None` and `Pending` don't have an inner type.
(method, cx.tcx.types.unit)
},
_ => return,
};
// If this is the last expression in a block or there is an else clause then the whole
// type needs to be considered, not just the inner type of the branch being matched on.
// Note the last expression in a block is dropped after all local bindings.
let check_ty = if has_else
|| (keyword == "if" && matches!(cx.tcx.hir().parent_iter(expr.hir_id).next(), Some((_, Node::Block(..)))))
{
op_ty
} else {
inner_ty
};
// All temporaries created in the scrutinee expression are dropped at the same time as the
// scrutinee would be, so they have to be considered as well.
// e.g. in `if let Some(x) = foo.lock().unwrap().baz.as_ref() { .. }` the lock will be held
// for the duration if body.
let needs_drop = type_needs_ordered_drop(cx, check_ty) || temporaries_need_ordered_drop(cx, op);
// check that `while_let_on_iterator` lint does not trigger
if_chain! {
if keyword == "while";
@ -1784,7 +1964,7 @@ mod redundant_pattern_match {
span_lint_and_then(
cx,
REDUNDANT_PATTERN_MATCHING,
arms[0].pat.span,
arm.pat.span,
&format!("redundant pattern matching, consider using `{}`", good_method),
|diag| {
// while let ... = ... { ... }
@ -1798,12 +1978,20 @@ mod redundant_pattern_match {
// while let ... = ... { ... }
// ^^^^^^^^^^^^^^^^^^^
let span = expr_span.until(op_span.shrink_to_hi());
diag.span_suggestion(
span,
"try this",
format!("{} {}.{}", keyword, snippet(cx, op_span, "_"), good_method),
Applicability::MachineApplicable, // snippet
);
let mut app = if needs_drop {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
let sugg = snippet_with_applicability(cx, op_span, "_", &mut app);
diag.span_suggestion(span, "try this", format!("{} {}.{}", keyword, sugg, good_method), app);
if needs_drop {
diag.note("this will change drop order of the result, as well as all temporaries");
diag.note("add `#[allow(clippy::redundant_pattern_matching)]` if this is important");
}
},
);
}
@ -1819,6 +2007,7 @@ mod redundant_pattern_match {
) if patterns_left.len() == 1 && patterns_right.len() == 1 => {
if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) {
find_good_method_for_match(
cx,
arms,
path_left,
path_right,
@ -1829,6 +2018,7 @@ mod redundant_pattern_match {
)
.or_else(|| {
find_good_method_for_match(
cx,
arms,
path_left,
path_right,
@ -1848,6 +2038,7 @@ mod redundant_pattern_match {
{
if let PatKind::Wild = patterns[0].kind {
find_good_method_for_match(
cx,
arms,
path_left,
path_right,
@ -1858,6 +2049,7 @@ mod redundant_pattern_match {
)
.or_else(|| {
find_good_method_for_match(
cx,
arms,
path_left,
path_right,
@ -1898,7 +2090,9 @@ mod redundant_pattern_match {
}
}
#[allow(clippy::too_many_arguments)]
fn find_good_method_for_match<'a>(
cx: &LateContext<'_>,
arms: &[Arm<'_>],
path_left: &QPath<'_>,
path_right: &QPath<'_>,
@ -1907,9 +2101,13 @@ mod redundant_pattern_match {
should_be_left: &'a str,
should_be_right: &'a str,
) -> Option<&'a str> {
let body_node_pair = if match_qpath(path_left, expected_left) && match_qpath(path_right, expected_right) {
let body_node_pair = if is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_left)
&& is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_right)
{
(&(*arms[0].body).kind, &(*arms[1].body).kind)
} else if match_qpath(path_right, expected_left) && match_qpath(path_left, expected_right) {
} else if is_qpath_def_path(cx, path_right, arms[1].pat.hir_id, expected_left)
&& is_qpath_def_path(cx, path_left, arms[0].pat.hir_id, expected_right)
{
(&(*arms[1].body).kind, &(*arms[0].body).kind)
} else {
return None;

View File

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diagnostic_assoc_item;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::{in_macro, match_def_path, match_qpath, meets_msrv, paths};
use clippy_utils::{in_macro, is_diag_trait_item, is_lang_ctor, match_def_path, meets_msrv, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -102,7 +102,7 @@ impl_lint_pass!(MemReplace =>
fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
if let ExprKind::Path(ref replacement_qpath) = src.kind {
// Check that second argument is `Option::None`
if match_qpath(replacement_qpath, &paths::OPTION_NONE) {
if is_lang_ctor(cx, replacement_qpath, OptionNone) {
// Since this is a late pass (already type-checked),
// and we already know that the second argument is an
// `Option`, we do not need to check the first
@ -210,17 +210,17 @@ fn is_default_equivalent_ctor(cx: &LateContext<'_>, def_id: DefId, path: &QPath<
sym::BinaryHeap,
];
if std_types_symbols
.iter()
.any(|symbol| is_diagnostic_assoc_item(cx, def_id, *symbol))
{
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
return true;
if let QPath::TypeRelative(_, method) = path {
if method.ident.name == sym::new {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
.any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
}
}
}
}
false
}
@ -230,7 +230,7 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
if !in_external_macro(cx.tcx.sess, expr_span);
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 is_diagnostic_assoc_item(cx, repl_def_id, sym::Default)
if is_diag_trait_item(cx, repl_def_id, sym::Default)
|| is_default_equivalent_ctor(cx, repl_def_id, repl_func_qpath);
then {

View File

@ -0,0 +1,38 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use clippy_utils::ty::{get_iterator_item_ty, is_copy};
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{sym, Span};
use super::CLONED_INSTEAD_OF_COPIED;
pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) {
let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
let inner_ty = match recv_ty.kind() {
// `Option<T>` -> `T`
ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::option_type, adt.did) => subst.type_at(0),
_ if is_trait_method(cx, expr, sym::Iterator) => match get_iterator_item_ty(cx, recv_ty) {
// <T as Iterator>::Item
Some(ty) => ty,
_ => return,
},
_ => return,
};
match inner_ty.kind() {
// &T where T: Copy
ty::Ref(_, ty, _) if is_copy(cx, ty) => {},
_ => return,
};
span_lint_and_sugg(
cx,
CLONED_INSTEAD_OF_COPIED,
span,
"used `cloned` where `copied` could be used instead",
"try",
"copied".into(),
Applicability::MachineApplicable,
)
}

View File

@ -1,18 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_trait_method;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::FILTER_MAP;
/// lint use of `filter().flat_map()` for `Iterators`
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
// lint if caller of `.filter().flat_map()` is an Iterator
if is_trait_method(cx, expr, sym::Iterator) {
let msg = "called `filter(..).flat_map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
and filtering by returning `iter::empty()`";
span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
}
}

View File

@ -1,18 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_trait_method;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::FILTER_MAP;
/// lint use of `filter_map().flat_map()` for `Iterators`
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
// lint if caller of `.filter_map().flat_map()` is an Iterator
if is_trait_method(cx, expr, sym::Iterator) {
let msg = "called `filter_map(..).flat_map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by calling `.flat_map(..)` \
and filtering by returning `iter::empty()`";
span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_trait_method, match_qpath, path_to_local_id, paths};
use clippy_utils::{is_expr_path_def_path, is_trait_method, path_to_local_id, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -33,14 +33,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg:
}
}
if_chain! {
if let hir::ExprKind::Path(ref qpath) = filter_map_arg.kind;
if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY);
then {
apply_lint("called `filter_map(std::convert::identity)` on an `Iterator`");
}
if is_expr_path_def_path(cx, filter_map_arg, &paths::CONVERT_IDENTITY) {
apply_lint("called `filter_map(std::convert::identity)` on an `Iterator`");
}
}
}

View File

@ -1,17 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_trait_method;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::FILTER_MAP;
/// lint use of `filter_map().map()` for `Iterators`
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
// lint if caller of `.filter_map().map()` is an Iterator
if is_trait_method(cx, expr, sym::Iterator) {
let msg = "called `filter_map(..).map(..)` on an `Iterator`";
let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead";
span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint);
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_trait_method, match_qpath, paths};
use clippy_utils::{is_expr_path_def_path, is_trait_method, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -16,8 +16,6 @@ pub(super) fn check<'tcx>(
flat_map_span: Span,
) {
if is_trait_method(cx, expr, sym::Iterator) {
let arg_node = &flat_map_arg.kind;
let apply_lint = |message: &str| {
span_lint_and_sugg(
cx,
@ -31,8 +29,8 @@ pub(super) fn check<'tcx>(
};
if_chain! {
if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node;
let body = cx.tcx.hir().body(*body_id);
if let hir::ExprKind::Closure(_, _, body_id, _, _) = flat_map_arg.kind;
let body = cx.tcx.hir().body(body_id);
if let hir::PatKind::Binding(_, _, binding_ident, _) = body.params[0].pat.kind;
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = body.value.kind;
@ -45,14 +43,8 @@ pub(super) fn check<'tcx>(
}
}
if_chain! {
if let hir::ExprKind::Path(ref qpath) = arg_node;
if match_qpath(qpath, &paths::STD_CONVERT_IDENTITY);
then {
apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`");
}
if is_expr_path_def_path(cx, flat_map_arg, &paths::CONVERT_IDENTITY) {
apply_lint("called `flat_map(std::convert::identity)` on an `Iterator`");
}
}
}

View File

@ -0,0 +1,34 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_trait_method;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{source_map::Span, sym};
use super::FLAT_MAP_OPTION;
use clippy_utils::ty::is_type_diagnostic_item;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, span: Span) {
if !is_trait_method(cx, expr, sym::Iterator) {
return;
}
let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
let sig = match arg_ty.kind() {
ty::Closure(_, substs) => substs.as_closure().sig(),
_ if arg_ty.is_fn() => arg_ty.fn_sig(cx.tcx),
_ => return,
};
if !is_type_diagnostic_item(cx, sig.output().skip_binder(), sym::option_type) {
return;
}
span_lint_and_sugg(
cx,
FLAT_MAP_OPTION,
span,
"used `flat_map` where `filter_map` could be used instead",
"try",
"filter_map".into(),
Applicability::MachineApplicable,
)
}

View File

@ -1,26 +1,23 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, match_qpath, paths, sugg};
use clippy_utils::{is_expr_path_def_path, paths, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::ExprKind;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::Ty;
use rustc_span::sym;
use super::FROM_ITER_INSTEAD_OF_COLLECT;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func_kind: &ExprKind<'_>) {
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
if_chain! {
if let hir::ExprKind::Path(path) = func_kind;
if match_qpath(path, &["from_iter"]);
if is_expr_path_def_path(cx, func, &paths::FROM_ITERATOR_METHOD);
let ty = cx.typeck_results().expr_ty(expr);
let arg_ty = cx.typeck_results().expr_ty(&args[0]);
if let Some(from_iter_id) = get_trait_def_id(cx, &paths::FROM_ITERATOR);
if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]);
if implements_trait(cx, arg_ty, iter_id, &[]);
then {
// `expr` implements `FromIterator` trait
let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();

View File

@ -1,28 +1,36 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_diag_item_method, is_diag_trait_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::ExprKind;
use rustc_lint::LateContext;
use rustc_middle::ty::TyS;
use rustc_span::symbol::Symbol;
use rustc_span::{sym, Span};
use super::IMPLICIT_CLONE;
use clippy_utils::is_diagnostic_assoc_item;
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, trait_diagnostic: Symbol) {
pub fn check(cx: &LateContext<'_>, method_name: &str, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, span: Span) {
if_chain! {
if let ExprKind::MethodCall(method_path, _, [arg], _) = &expr.kind;
if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if match method_name {
"to_os_string" => is_diag_item_method(cx, method_def_id, sym::OsStr),
"to_owned" => is_diag_trait_item(cx, method_def_id, sym::ToOwned),
"to_path_buf" => is_diag_item_method(cx, method_def_id, sym::Path),
"to_vec" => cx.tcx.impl_of_method(method_def_id)
.map(|impl_did| Some(impl_did) == cx.tcx.lang_items().slice_alloc_impl())
== Some(true),
_ => false,
};
let return_type = cx.typeck_results().expr_ty(expr);
let input_type = cx.typeck_results().expr_ty(arg).peel_refs();
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
let input_type = cx.typeck_results().expr_ty(recv).peel_refs();
if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did));
if TyS::same_type(return_type, input_type);
if is_diagnostic_assoc_item(cx, expr_def_id, trait_diagnostic);
then {
span_lint_and_sugg(
cx,IMPLICIT_CLONE,method_path.ident.span,
&format!("implicitly cloning a `{}` by calling `{}` on its dereferenced type", ty_name, method_path.ident.name),
cx,
IMPLICIT_CLONE,
span,
&format!("implicitly cloning a `{}` by calling `{}` on its dereferenced type", ty_name, method_name),
"consider using",
"clone".to_string(),
Applicability::MachineApplicable

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::match_qpath;
use clippy_utils::is_qpath_def_path;
use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain;
use rustc_ast::ast;
@ -94,11 +94,11 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<M
// `std::T::MAX` `std::T::MIN` constants
if let hir::ExprKind::Path(path) = &expr.kind {
if match_qpath(path, &["core", &ty_str, "MAX"][..]) {
if is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MAX"][..]) {
return Some(MinMax::Max);
}
if match_qpath(path, &["core", &ty_str, "MIN"][..]) {
if is_qpath_def_path(cx, path, expr.hir_id, &["core", &ty_str, "MIN"][..]) {
return Some(MinMax::Min);
}
}

View File

@ -8,17 +8,16 @@ mod chars_next_cmp;
mod chars_next_cmp_with_unwrap;
mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
mod expect_fun_call;
mod expect_used;
mod filetype_is_file;
mod filter_flat_map;
mod filter_map;
mod filter_map_flat_map;
mod filter_map_identity;
mod filter_map_map;
mod filter_map_next;
mod filter_next;
mod flat_map_identity;
mod flat_map_option;
mod from_iter_instead_of_collect;
mod get_unwrap;
mod implicit_clone;
@ -76,6 +75,52 @@ use rustc_span::symbol::SymbolStr;
use rustc_span::{sym, Span};
use rustc_typeck::hir_ty_to_ty;
declare_clippy_lint! {
/// **What it does:** Checks for usages of `cloned()` on an `Iterator` or `Option` where
/// `copied()` could be used instead.
///
/// **Why is this bad?** `copied()` is better because it guarantees that the type being cloned
/// implements `Copy`.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// [1, 2, 3].iter().cloned();
/// ```
/// Use instead:
/// ```rust
/// [1, 2, 3].iter().copied();
/// ```
pub CLONED_INSTEAD_OF_COPIED,
pedantic,
"used `cloned` where `copied` could be used instead"
}
declare_clippy_lint! {
/// **What it does:** Checks for usages of `Iterator::flat_map()` where `filter_map()` could be
/// used instead.
///
/// **Why is this bad?** When applicable, `filter_map()` is more clear since it shows that
/// `Option` is used to produce 0 or 1 items.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// let nums: Vec<i32> = ["1", "2", "whee!"].iter().flat_map(|x| x.parse().ok()).collect();
/// ```
/// Use instead:
/// ```rust
/// let nums: Vec<i32> = ["1", "2", "whee!"].iter().filter_map(|x| x.parse().ok()).collect();
/// ```
pub FLAT_MAP_OPTION,
pedantic,
"used `flat_map` where `filter_map` could be used instead"
}
declare_clippy_lint! {
/// **What it does:** Checks for `.unwrap()` calls on `Option`s and on `Result`s.
///
@ -472,35 +517,6 @@ declare_clippy_lint! {
"using combinations of `flatten` and `map` which can usually be written as a single method call"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.filter(_).map(_)`,
/// `_.filter(_).flat_map(_)`, `_.filter_map(_).flat_map(_)` and similar.
///
/// **Why is this bad?** Readability, this can be written more concisely as
/// `_.filter_map(_)`.
///
/// **Known problems:** Often requires a condition + Option/Iterator creation
/// inside the closure.
///
/// **Example:**
/// ```rust
/// let vec = vec![1];
///
/// // Bad
/// vec.iter().filter(|x| **x == 0).map(|x| *x * 2);
///
/// // Good
/// vec.iter().filter_map(|x| if *x == 0 {
/// Some(*x * 2)
/// } else {
/// None
/// });
/// ```
pub FILTER_MAP,
pedantic,
"using combinations of `filter`, `map`, `filter_map` and `flat_map` which can usually be written as a single method call"
}
declare_clippy_lint! {
/// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply
/// as `filter_map(_)`.
@ -1670,6 +1686,8 @@ impl_lint_pass!(Methods => [
CLONE_ON_COPY,
CLONE_ON_REF_PTR,
CLONE_DOUBLE_REF,
CLONED_INSTEAD_OF_COPIED,
FLAT_MAP_OPTION,
INEFFICIENT_TO_STRING,
NEW_RET_NO_SELF,
SINGLE_CHAR_PATTERN,
@ -1677,7 +1695,6 @@ impl_lint_pass!(Methods => [
SEARCH_IS_SOME,
FILTER_NEXT,
SKIP_WHILE_NEXT,
FILTER_MAP,
FILTER_MAP_IDENTITY,
MANUAL_FILTER_MAP,
MANUAL_FIND_MAP,
@ -1741,7 +1758,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
match expr.kind {
hir::ExprKind::Call(func, args) => {
from_iter_instead_of_collect::check(cx, expr, args, &func.kind);
from_iter_instead_of_collect::check(cx, expr, args, func);
},
hir::ExprKind::MethodCall(method_call, ref method_span, args, _) => {
or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args);
@ -1942,6 +1959,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
("cloned", []) => cloned_instead_of_copied::check(cx, expr, recv, span),
("collect", []) => match method_call!(recv) {
Some(("cloned", [recv2], _)) => iter_cloned_collect::check(cx, expr, recv2),
Some(("map", [m_recv, m_arg], _)) => {
@ -1965,10 +1983,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
unnecessary_filter_map::check(cx, expr, arg);
filter_map_identity::check(cx, expr, arg, span);
},
("flat_map", [flm_arg]) => match method_call!(recv) {
Some(("filter", [_, _], _)) => filter_flat_map::check(cx, expr),
Some(("filter_map", [_, _], _)) => filter_map_flat_map::check(cx, expr),
_ => flat_map_identity::check(cx, expr, flm_arg, span),
("flat_map", [arg]) => {
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);
},
("flatten", []) => {
if let Some(("map", [recv, map_arg], _)) = method_call!(recv) {
@ -1993,7 +2010,6 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
("filter", [f_arg]) => {
filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false)
},
("filter_map", [_]) => filter_map_map::check(cx, expr),
("find", [f_arg]) => filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, true),
_ => {},
}
@ -2025,10 +2041,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
}
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("to_os_string", []) => implicit_clone::check(cx, expr, sym::OsStr),
("to_owned", []) => implicit_clone::check(cx, expr, sym::ToOwned),
("to_path_buf", []) => implicit_clone::check(cx, expr, sym::Path),
("to_vec", []) => implicit_clone::check(cx, expr, sym::slice),
("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => {
implicit_clone::check(cx, name, expr, recv, span);
},
("unwrap", []) => match method_call!(recv) {
Some(("get", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, false),
Some(("get_mut", [recv, get_arg], _)) => get_unwrap::check(cx, expr, recv, get_arg, true),

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lang_ctor;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{match_qpath, paths};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
@ -32,7 +33,7 @@ pub(super) fn check<'tcx>(
let (lint_name, msg, instead, hint) = {
let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = def_arg.kind {
match_qpath(qpath, &paths::OPTION_NONE)
is_lang_ctor(cx, qpath, OptionNone)
} else {
return;
};
@ -43,7 +44,7 @@ pub(super) fn check<'tcx>(
}
let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_arg.kind {
match_qpath(qpath, &paths::OPTION_SOME)
is_lang_ctor(cx, qpath, OptionSome)
} else {
false
};

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{match_def_path, match_qpath, paths};
use clippy_utils::{is_expr_path_def_path, match_def_path, paths};
use if_chain::if_chain;
use rustc_hir as hir;
use rustc_lint::LateContext;
@ -12,8 +12,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
if_chain! {
if let hir::ExprKind::Call(callee, args) = recv.kind;
if args.is_empty();
if let hir::ExprKind::Path(ref path) = callee.kind;
if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT);
if is_expr_path_def_path(cx, callee, &paths::MEM_MAYBEUNINIT_UNINIT);
if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(expr));
then {
span_lint(

View File

@ -1,8 +1,9 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{is_trait_method, match_qpath, path_to_local_id, paths};
use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
use rustc_span::sym;
@ -54,14 +55,12 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
match &expr.kind {
hir::ExprKind::Call(func, args) => {
if let hir::ExprKind::Path(ref path) = func.kind {
if match_qpath(path, &paths::OPTION_SOME) {
if is_lang_ctor(cx, path, OptionSome) {
if path_to_local_id(&args[0], arg_id) {
return (false, false);
}
return (true, false);
}
// We don't know. It might do anything.
return (true, true);
}
(true, true)
},
@ -85,7 +84,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc
let else_check = check_expression(cx, arg_id, else_arm);
(if_check.0 | else_check.0, if_check.1 | else_check.1)
},
hir::ExprKind::Path(path) if match_qpath(path, &paths::OPTION_NONE) => (false, true),
hir::ExprKind::Path(path) if is_lang_ctor(cx, path, OptionNone) => (false, true),
_ => (true, true),
}
}

View File

@ -102,6 +102,14 @@ pub(super) fn check<'tcx>(
.iter()
.all(|conv| conv.check(cx, self_ty, item_name, implements_trait, is_trait_item))
}) {
// don't lint if it implements a trait but not willing to check `Copy` types conventions (see #7032)
if implements_trait
&& !conventions
.iter()
.any(|conv| matches!(conv, Convention::IsSelfTypeCopy(_)))
{
return;
}
if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) {
let suggestion = {
if conventions.len() > 1 {

View File

@ -20,8 +20,8 @@ use rustc_span::symbol::sym;
use crate::consts::{constant, Constant};
use clippy_utils::sugg::Sugg;
use clippy_utils::{
get_item_name, get_parent_expr, higher, in_constant, is_diagnostic_assoc_item, is_integer_const, iter_input_pats,
last_path_segment, match_qpath, unsext, SpanlessEq,
expr_path_res, get_item_name, get_parent_expr, higher, in_constant, is_diag_trait_item, is_integer_const,
iter_input_pats, last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq,
};
declare_clippy_lint! {
@ -555,8 +555,8 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
ExprKind::MethodCall(.., args, _) if args.len() == 1 => {
if_chain!(
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diagnostic_assoc_item(cx, expr_def_id, sym::ToString)
|| is_diagnostic_assoc_item(cx, expr_def_id, sym::ToOwned);
if is_diag_trait_item(cx, expr_def_id, sym::ToString)
|| is_diag_trait_item(cx, expr_def_id, sym::ToOwned);
then {
(cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, ".."))
} else {
@ -564,13 +564,13 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left:
}
)
},
ExprKind::Call(path, v) if v.len() == 1 => {
if let ExprKind::Path(ref path) = path.kind {
if match_qpath(path, &["String", "from_str"]) || match_qpath(path, &["String", "from"]) {
(cx.typeck_results().expr_ty(&v[0]), snippet(cx, v[0].span, ".."))
} else {
return;
}
ExprKind::Call(path, [arg]) => {
if expr_path_res(cx, path)
.opt_def_id()
.and_then(|id| match_any_def_paths(cx, id, &[&paths::FROM_STR_METHOD, &paths::FROM_FROM]))
.is_some()
{
(cx.typeck_results().expr_ty(arg), snippet(cx, arg.span, ".."))
} else {
return;
}

View File

@ -138,7 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn {
let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir) {
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
if rustc_mir::const_eval::is_min_const_fn(cx.tcx, def_id.to_def_id()) {
cx.tcx.sess.span_err(span, &err);
}

View File

@ -4,7 +4,7 @@ use clippy_utils::sext;
use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use std::fmt::Display;

View File

@ -5,7 +5,7 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{is_expn_of, parent_node_is_if_expr};
use clippy_utils::{is_else_clause, is_expn_of};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool {
snip = snip.make_return();
}
if parent_node_is_if_expr(e, cx) {
if is_else_clause(cx.tcx, e) {
snip = snip.blockify()
}

View File

@ -279,7 +279,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
spans.extend(
deref_span
.iter()
.cloned()
.copied()
.map(|span| (span, format!("*{}", snippet(cx, span, "<expr>")))),
);
spans.sort_by_key(|&(span, _)| span);

View File

@ -1,9 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lang_ctor;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{differing_macro_contexts, is_ok_ctor, is_some_ctor, meets_msrv};
use clippy_utils::{differing_macro_contexts, meets_msrv};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_semver::RustcVersion;
@ -159,8 +161,8 @@ fn is_some_or_ok_call<'a>(
if_chain! {
// Check outer expression matches CALL_IDENT(ARGUMENT) format
if let ExprKind::Call(path, args) = &expr.kind;
if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind;
if is_some_ctor(cx, path.res) || is_ok_ctor(cx, path.res);
if let ExprKind::Path(ref qpath) = &path.kind;
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
// Extract inner expression from ARGUMENT
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;

View File

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{eager_or_lazy, get_enclosing_block, in_macro, match_qpath};
use clippy_utils::{eager_or_lazy, get_enclosing_block, in_macro, is_lang_ctor};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionSome;
use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, MatchSource, Mutability, PatKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -164,7 +164,7 @@ fn detect_option_if_let_else<'tcx>(
if arms.len() == 2;
if !is_result_ok(cx, cond_expr); // Don't lint on Result::ok because a different lint does it already
if let PatKind::TupleStruct(struct_qpath, &[inner_pat], _) = &arms[0].pat.kind;
if match_qpath(struct_qpath, &paths::OPTION_SOME);
if is_lang_ctor(cx, struct_qpath, OptionSome);
if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind;
if !contains_return_break_continue_macro(arms[0].body);
if !contains_return_break_continue_macro(arms[1].body);

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{find_macro_calls, return_ty};
use clippy_utils::{find_macro_calls, is_expn_of, return_ty};
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind;
use rustc_lint::{LateContext, LateLintPass};
@ -52,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicInResultFn {
}
fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir::Body<'tcx>) {
let panics = find_macro_calls(
let mut panics = find_macro_calls(
&[
"unimplemented",
"unreachable",
@ -61,12 +61,10 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir
"assert",
"assert_eq",
"assert_ne",
"debug_assert",
"debug_assert_eq",
"debug_assert_ne",
],
body,
);
panics.retain(|span| is_expn_of(*span, "debug_assert").is_none());
if !panics.is_empty() {
span_lint_and_then(
cx,

View File

@ -74,7 +74,7 @@ declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANI
impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if match_panic_call(cx, expr).is_some() {
if match_panic_call(cx, expr).is_some() && is_expn_of(expr.span, "debug_assert").is_none() {
let span = get_outer_span(expr);
if is_expn_of(expr.span, "unimplemented").is_some() {
span_lint(

View File

@ -42,6 +42,14 @@ declare_clippy_lint! {
/// false positives in cases involving multiple lifetimes that are bounded by
/// each other.
///
/// Also, it does not take account of other similar cases where getting memory addresses
/// matters; namely, returning the pointer to the argument in question,
/// and passing the argument, as both references and pointers,
/// to a function that needs the memory address. For further details, refer to
/// [this issue](https://github.com/rust-lang/rust-clippy/issues/5953)
/// that explains a real case in which this false positive
/// led to an **undefined behaviour** introduced with unsafe code.
///
/// **Example:**
///
/// ```rust

View File

@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
use clippy_utils::ptr::get_spans;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{is_type_diagnostic_item, match_type, walk_ptrs_hir_ty};
use clippy_utils::{is_allowed, match_qpath, paths};
use clippy_utils::{expr_path_res, is_allowed, match_any_def_paths, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{
@ -15,6 +15,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::Symbol;
use rustc_span::{sym, MultiSpan};
use std::borrow::Cow;
@ -94,7 +95,7 @@ declare_clippy_lint! {
/// ```
pub CMP_NULL,
style,
"comparing a pointer to a null pointer, suggesting to use `.is_null()` instead."
"comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
}
declare_clippy_lint! {
@ -119,7 +120,28 @@ declare_clippy_lint! {
"fns that create mutable refs from immutable ref args"
}
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF]);
declare_clippy_lint! {
/// **What it does:** This lint checks for invalid usages of `ptr::null`.
///
/// **Why is this bad?** This causes undefined behavior.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```ignore
/// // Bad. Undefined behavior
/// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
/// ```
///
/// // Good
/// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
/// ```
pub INVALID_NULL_PTR_USAGE,
correctness,
"invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
}
declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
impl<'tcx> LateLintPass<'tcx> for Ptr {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
@ -153,7 +175,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let ExprKind::Binary(ref op, l, r) = expr.kind {
if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(l) || is_null_path(r)) {
if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
span_lint(
cx,
CMP_NULL,
@ -161,6 +183,55 @@ impl<'tcx> LateLintPass<'tcx> for Ptr {
"comparing with null is better expressed by the `.is_null()` method",
);
}
} else {
check_invalid_ptr_usage(cx, expr);
}
}
}
fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
(&paths::SLICE_FROM_RAW_PARTS, &[0]),
(&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
(&paths::PTR_COPY, &[0, 1]),
(&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
(&paths::PTR_READ, &[0]),
(&paths::PTR_READ_UNALIGNED, &[0]),
(&paths::PTR_READ_VOLATILE, &[0]),
(&paths::PTR_REPLACE, &[0]),
(&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
(&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
(&paths::PTR_SWAP, &[0, 1]),
(&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
(&paths::PTR_WRITE, &[0]),
(&paths::PTR_WRITE_UNALIGNED, &[0]),
(&paths::PTR_WRITE_VOLATILE, &[0]),
(&paths::PTR_WRITE_BYTES, &[0]),
];
if_chain! {
if let ExprKind::Call(ref fun, ref args) = expr.kind;
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
.iter()
.find(|&&(fn_path, _)| fn_path == fun_def_path);
then {
for &arg_idx in arg_indices {
if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
span_lint_and_sugg(
cx,
INVALID_NULL_PTR_USAGE,
arg.span,
"pointer must be non-null",
"change this to",
"core::ptr::NonNull::dangling().as_ptr()".to_string(),
Applicability::MachineApplicable,
);
}
}
}
}
}
@ -345,13 +416,12 @@ fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability,
}
}
fn is_null_path(expr: &Expr<'_>) -> bool {
if let ExprKind::Call(pathexp, args) = expr.kind {
if args.is_empty() {
if let ExprKind::Path(ref path) = pathexp.kind {
return match_qpath(path, &paths::PTR_NULL) || match_qpath(path, &paths::PTR_NULL_MUT);
}
}
fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let ExprKind::Call(pathexp, []) = expr.kind {
expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
match_any_def_paths(cx, id, &[&paths::PTR_NULL, &paths::PTR_NULL_MUT]).is_some()
})
} else {
false
}
false
}

View File

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_lang_ctor;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, match_def_path, match_qpath, paths};
use clippy_utils::{eq_expr_value, path_to_local_id};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{def, BindingAnnotation, Block, Expr, ExprKind, MatchSource, PatKind, StmtKind};
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, MatchSource, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@ -100,15 +101,14 @@ impl QuestionMark {
if Self::is_option(cx, subject);
if let PatKind::TupleStruct(path1, fields, None) = &arms[0].pat.kind;
if match_qpath(path1, &["Some"]);
if let PatKind::Binding(annot, _, bind, _) = &fields[0].kind;
if is_lang_ctor(cx, path1, OptionSome);
if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind;
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Block(block, None) = &arms[0].body.kind;
if block.stmts.is_empty();
if let Some(trailing_expr) = &block.expr;
if let ExprKind::Path(path) = &trailing_expr.kind;
if match_qpath(path, &[&bind.as_str()]);
if path_to_local_id(trailing_expr, bind_id);
if let PatKind::Wild = arms[1].pat.kind;
if Self::expression_returns_none(cx, arms[1].body);
@ -156,15 +156,7 @@ impl QuestionMark {
false
},
ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr),
ExprKind::Path(ref qp) => {
if let Res::Def(DefKind::Ctor(def::CtorOf::Variant, def::CtorKind::Const), def_id) =
cx.qpath_res(qp, expression.hir_id)
{
return match_def_path(cx, def_id, &paths::OPTION_NONE);
}
false
},
ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
_ => false,
}
}

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::{fn_def_id, in_macro, match_qpath};
use clippy_utils::{fn_def_id, in_macro, path_to_local_id};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
@ -84,9 +84,8 @@ impl<'tcx> LateLintPass<'tcx> for Return {
if local.ty.is_none();
if cx.tcx.hir().attrs(local.hir_id).is_empty();
if let Some(initexpr) = &local.init;
if let PatKind::Binding(.., ident, _) = local.pat.kind;
if let ExprKind::Path(qpath) = &retexpr.kind;
if match_qpath(qpath, &[&*ident.name.as_str()]);
if let PatKind::Binding(_, local_id, _, _) = local.pat.kind;
if path_to_local_id(retexpr, local_id);
if !last_statement_borrows(cx, initexpr);
if !in_external_macro(cx.sess(), initexpr.span);
if !in_external_macro(cx.sess(), retexpr.span);
@ -223,6 +222,7 @@ fn check_final_expr<'tcx>(
},
_ => (),
},
ExprKind::DropTemps(expr) => check_final_expr(cx, expr, None, RetReplacement::Empty),
_ => (),
}
}

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use clippy_utils::in_macro;
use if_chain::if_chain;
use rustc_ast::{Item, ItemKind, UseTreeKind};
use rustc_ast::{ptr::P, Crate, Item, ItemKind, ModKind, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::edition::Edition;
use rustc_span::{edition::Edition, symbol::kw, Span, Symbol};
declare_clippy_lint! {
/// **What it does:** Checking for imports with single component use path.
@ -38,26 +37,120 @@ declare_clippy_lint! {
declare_lint_pass!(SingleComponentPathImports => [SINGLE_COMPONENT_PATH_IMPORTS]);
impl EarlyLintPass for SingleComponentPathImports {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! {
if !in_macro(item.span);
if cx.sess.opts.edition >= Edition::Edition2018;
if !item.vis.kind.is_pub();
if let ItemKind::Use(use_tree) = &item.kind;
if let segments = &use_tree.prefix.segments;
if segments.len() == 1;
if let UseTreeKind::Simple(None, _, _) = use_tree.kind;
then {
fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) {
if cx.sess.opts.edition < Edition::Edition2018 {
return;
}
check_mod(cx, &krate.items);
}
}
fn check_mod(cx: &EarlyContext<'_>, items: &[P<Item>]) {
// keep track of imports reused with `self` keyword,
// such as `self::crypto_hash` in the example below
// ```rust,ignore
// use self::crypto_hash::{Algorithm, Hasher};
// ```
let mut imports_reused_with_self = Vec::new();
// keep track of single use statements
// such as `crypto_hash` in the example below
// ```rust,ignore
// use crypto_hash;
// ```
let mut single_use_usages = Vec::new();
for item in items {
track_uses(cx, &item, &mut imports_reused_with_self, &mut single_use_usages);
}
for single_use in &single_use_usages {
if !imports_reused_with_self.contains(&single_use.0) {
let can_suggest = single_use.2;
if can_suggest {
span_lint_and_sugg(
cx,
SINGLE_COMPONENT_PATH_IMPORTS,
item.span,
single_use.1,
"this import is redundant",
"remove it entirely",
String::new(),
Applicability::MachineApplicable
Applicability::MachineApplicable,
);
} else {
span_lint_and_help(
cx,
SINGLE_COMPONENT_PATH_IMPORTS,
single_use.1,
"this import is redundant",
None,
"remove this import",
);
}
}
}
}
fn track_uses(
cx: &EarlyContext<'_>,
item: &Item,
imports_reused_with_self: &mut Vec<Symbol>,
single_use_usages: &mut Vec<(Symbol, Span, bool)>,
) {
if in_macro(item.span) || item.vis.kind.is_pub() {
return;
}
match &item.kind {
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
check_mod(cx, &items);
},
ItemKind::Use(use_tree) => {
let segments = &use_tree.prefix.segments;
// keep track of `use some_module;` usages
if segments.len() == 1 {
if let UseTreeKind::Simple(None, _, _) = use_tree.kind {
let ident = &segments[0].ident;
single_use_usages.push((ident.name, item.span, true));
}
return;
}
if segments.is_empty() {
// keep track of `use {some_module, some_other_module};` usages
if let UseTreeKind::Nested(trees) = &use_tree.kind {
for tree in trees {
let segments = &tree.0.prefix.segments;
if segments.len() == 1 {
if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
let ident = &segments[0].ident;
single_use_usages.push((ident.name, tree.0.span, false));
}
}
}
}
} else {
// keep track of `use self::some_module` usages
if segments[0].ident.name == kw::SelfLower {
// simple case such as `use self::module::SomeStruct`
if segments.len() > 1 {
imports_reused_with_self.push(segments[1].ident.name);
return;
}
// nested case such as `use self::{module1::Struct1, module2::Struct2}`
if let UseTreeKind::Nested(trees) = &use_tree.kind {
for tree in trees {
let segments = &tree.0.prefix.segments;
if !segments.is_empty() {
imports_reused_with_self.push(segments[0].ident.name);
}
}
}
}
}
},
_ => {},
}
}

View File

@ -65,8 +65,8 @@ fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool)
fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> {
const FUNCTIONS: [&[&str]; 8] = [
&paths::COPY_NONOVERLAPPING,
&paths::COPY,
&paths::PTR_COPY_NONOVERLAPPING,
&paths::PTR_COPY,
&paths::WRITE_BYTES,
&paths::PTR_SWAP_NONOVERLAPPING,
&paths::PTR_SLICE_FROM_RAW_PARTS,

View File

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_enclosing_block, match_qpath, SpanlessEq};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_enclosing_block, is_expr_path_def_path, path_to_local, path_to_local_id, paths, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
@ -9,7 +10,7 @@ use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath,
use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use rustc_span::symbol::sym;
declare_clippy_lint! {
/// **What it does:** Checks slow zero-filled vector initialization
@ -46,8 +47,8 @@ declare_lint_pass!(SlowVectorInit => [SLOW_VECTOR_INITIALIZATION]);
/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or
/// `vec = Vec::with_capacity(0)`
struct VecAllocation<'tcx> {
/// Symbol of the local variable name
variable_name: Symbol,
/// HirId of the variable
local_id: HirId,
/// Reference to the expression which allocates the vector
allocation_expr: &'tcx Expr<'tcx>,
@ -72,16 +73,15 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
if_chain! {
if let ExprKind::Assign(left, right, _) = expr.kind;
// Extract variable name
if let ExprKind::Path(QPath::Resolved(_, path)) = left.kind;
if let Some(variable_name) = path.segments.get(0);
// Extract variable
if let Some(local_id) = path_to_local(left);
// Extract len argument
if let Some(len_arg) = Self::is_vec_with_capacity(right);
if let Some(len_arg) = Self::is_vec_with_capacity(cx, right);
then {
let vi = VecAllocation {
variable_name: variable_name.ident.name,
local_id,
allocation_expr: right,
len_expr: len_arg,
};
@ -95,13 +95,13 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
// Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
if_chain! {
if let StmtKind::Local(local) = stmt.kind;
if let PatKind::Binding(BindingAnnotation::Mutable, .., variable_name, None) = local.pat.kind;
if let PatKind::Binding(BindingAnnotation::Mutable, local_id, _, None) = local.pat.kind;
if let Some(init) = local.init;
if let Some(len_arg) = Self::is_vec_with_capacity(init);
if let Some(len_arg) = Self::is_vec_with_capacity(cx, init);
then {
let vi = VecAllocation {
variable_name: variable_name.name,
local_id,
allocation_expr: init,
len_expr: len_arg,
};
@ -115,19 +115,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
impl SlowVectorInit {
/// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
/// of the first argument of `with_capacity` call if it matches or `None` if it does not.
fn is_vec_with_capacity<'tcx>(expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::Call(func, args) = expr.kind;
if let ExprKind::Path(ref path) = func.kind;
if match_qpath(path, &["Vec", "with_capacity"]);
if args.len() == 1;
if let ExprKind::Call(func, [arg]) = expr.kind;
if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind;
if name.ident.as_str() == "with_capacity";
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::vec_type);
then {
return Some(&args[0]);
Some(arg)
} else {
None
}
}
None
}
/// Search initialization for the given vector
@ -208,11 +207,9 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if self.initialization_found;
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
if let ExprKind::Path(ref qpath_subj) = args[0].kind;
if match_qpath(qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]);
if let ExprKind::MethodCall(path, _, [self_arg, extend_arg], _) = expr.kind;
if path_to_local_id(self_arg, self.vec_alloc.local_id);
if path.ident.name == sym!(extend);
if let Some(extend_arg) = args.get(1);
if self.is_repeat_take(extend_arg);
then {
@ -225,11 +222,9 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
if_chain! {
if self.initialization_found;
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
if let ExprKind::Path(ref qpath_subj) = args[0].kind;
if match_qpath(qpath_subj, &[&*self.vec_alloc.variable_name.as_str()]);
if let ExprKind::MethodCall(path, _, [self_arg, len_arg, fill_arg], _) = expr.kind;
if path_to_local_id(self_arg, self.vec_alloc.local_id);
if path.ident.name == sym!(resize);
if let (Some(len_arg), Some(fill_arg)) = (args.get(1), args.get(2));
// Check that is filled with 0
if let ExprKind::Lit(ref lit) = fill_arg.kind;
@ -252,7 +247,7 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
// Check that take is applied to `repeat(0)`
if let Some(repeat_expr) = take_args.get(0);
if Self::is_repeat_zero(repeat_expr);
if self.is_repeat_zero(repeat_expr);
// Check that len expression is equals to `with_capacity` expression
if let Some(len_arg) = take_args.get(1);
@ -267,21 +262,19 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
}
/// Returns `true` if given expression is `repeat(0)`
fn is_repeat_zero(expr: &Expr<'_>) -> bool {
fn is_repeat_zero(&self, expr: &Expr<'_>) -> bool {
if_chain! {
if let ExprKind::Call(fn_expr, repeat_args) = expr.kind;
if let ExprKind::Path(ref qpath_repeat) = fn_expr.kind;
if match_qpath(qpath_repeat, &["repeat"]);
if let Some(repeat_arg) = repeat_args.get(0);
if let ExprKind::Call(fn_expr, [repeat_arg]) = expr.kind;
if is_expr_path_def_path(self.cx, fn_expr, &paths::ITER_REPEAT);
if let ExprKind::Lit(ref lit) = repeat_arg.kind;
if let LitKind::Int(0, _) = lit.node;
then {
return true
true
} else {
false
}
}
false
}
}

View File

@ -195,7 +195,7 @@ fn attempt_to_emit_no_difference_lint(
i: usize,
expected_loc: IdentLocation,
) {
if let Some(binop) = binops.get(i).cloned() {
if let Some(binop) = binops.get(i).copied() {
// We need to try and figure out which identifier we should
// suggest using instead. Since there could be multiple
// replacement candidates in a given expression, and we're

View File

@ -104,30 +104,32 @@ fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
// tracker to decide if the last group of tabs is not closed by a non-tab character
let mut is_active = false;
let chars_array: Vec<_> = the_str.chars().collect();
// Note that we specifically need the char _byte_ indices here, not the positional indexes
// within the char array to deal with multi-byte characters properly. `char_indices` does
// exactly that. It provides an iterator over tuples of the form `(byte position, char)`.
let char_indices: Vec<_> = the_str.char_indices().collect();
if chars_array == vec!['\t'] {
if let [(_, '\t')] = char_indices.as_slice() {
return vec![(0, 1)];
}
for (index, arr) in chars_array.windows(2).enumerate() {
let index = u32::try_from(index).expect(line_length_way_to_long);
match arr {
['\t', '\t'] => {
for entry in char_indices.windows(2) {
match entry {
[(_, '\t'), (_, '\t')] => {
// either string starts with double tab, then we have to set it active,
// otherwise is_active is true anyway
is_active = true;
},
[_, '\t'] => {
[(_, _), (index_b, '\t')] => {
// as ['\t', '\t'] is excluded, this has to be a start of a tab group,
// set indices accordingly
is_active = true;
current_start = index + 1;
current_start = u32::try_from(*index_b).unwrap();
},
['\t', _] => {
[(_, '\t'), (index_b, _)] => {
// this now has to be an end of the group, hence we have to push a new tuple
is_active = false;
spans.push((current_start, index + 1));
spans.push((current_start, u32::try_from(*index_b).unwrap()));
},
_ => {},
}
@ -137,7 +139,7 @@ fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
if is_active {
spans.push((
current_start,
u32::try_from(the_str.chars().count()).expect(line_length_way_to_long),
u32::try_from(char_indices.last().unwrap().0 + 1).expect(line_length_way_to_long),
));
}
@ -148,6 +150,13 @@ fn get_chunks_of_tabs(the_str: &str) -> Vec<(u32, u32)> {
mod tests_for_get_chunks_of_tabs {
use super::get_chunks_of_tabs;
#[test]
fn test_unicode_han_string() {
let res = get_chunks_of_tabs(" \u{4f4d}\t");
assert_eq!(res, vec![(4, 5)]);
}
#[test]
fn test_empty_string() {
let res = get_chunks_of_tabs("");

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_diagnostic_assoc_item, match_def_path, path_to_local_id, paths};
use clippy_utils::{is_diag_trait_item, match_def_path, path_to_local_id, paths};
use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -95,7 +95,7 @@ impl LateLintPass<'_> for ToStringInDisplay {
if let ExprKind::MethodCall(path, _, args, _) = expr.kind;
if path.ident.name == sym!(to_string);
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if is_diagnostic_assoc_item(cx, expr_def_id, sym::ToString);
if is_diag_trait_item(cx, expr_def_id, sym::ToString);
if path_to_local_id(&args[0], self_hir_id);
then {
span_lint(

View File

@ -274,7 +274,7 @@ declare_clippy_lint! {
/// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) };
/// ```
pub TRANSMUTE_PTR_TO_PTR,
complexity,
pedantic,
"transmutes from a pointer to a pointer / a reference to a reference"
}

View File

@ -1,6 +1,6 @@
use crate::consts::{constant_context, Constant};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{match_qpath, paths};
use clippy_utils::{is_expr_path_def_path, paths};
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_hir::{Expr, ExprKind};
@ -37,18 +37,15 @@ impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
}
if_chain! {
if let ExprKind::Call(func, args) = expr.kind;
if let ExprKind::Path(ref path) = func.kind;
if match_qpath(path, &paths::STD_MEM_TRANSMUTE);
if args.len() == 1;
if let ExprKind::Call(func, [arg]) = expr.kind;
if is_expr_path_def_path(cx, func, &paths::TRANSMUTE);
then {
// Catching transmute over constants that resolve to `null`.
let mut const_eval_context = constant_context(cx, cx.typeck_results());
if_chain! {
if let ExprKind::Path(ref _qpath) = args[0].kind;
let x = const_eval_context.expr(&args[0]);
if let ExprKind::Path(ref _qpath) = arg.kind;
let x = const_eval_context.expr(arg);
if let Some(Constant::RawPtr(0)) = x;
then {
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
@ -58,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
// Catching:
// `std::mem::transmute(0 as *const i32)`
if_chain! {
if let ExprKind::Cast(inner_expr, _cast_ty) = args[0].kind;
if let ExprKind::Cast(inner_expr, _cast_ty) = arg.kind;
if let ExprKind::Lit(ref lit) = inner_expr.kind;
if let LitKind::Int(0, _) = lit.node;
then {
@ -69,10 +66,8 @@ impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
// Catching:
// `std::mem::transmute(std::ptr::null::<i32>())`
if_chain! {
if let ExprKind::Call(func1, args1) = args[0].kind;
if let ExprKind::Path(ref path1) = func1.kind;
if match_qpath(path1, &paths::STD_PTR_NULL);
if args1.is_empty();
if let ExprKind::Call(func1, []) = arg.kind;
if is_expr_path_def_path(cx, func1, &paths::PTR_NULL);
then {
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
}

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{snippet, snippet_with_macro_callsite};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{differing_macro_contexts, in_macro, match_def_path, match_qpath, paths};
use clippy_utils::{differing_macro_contexts, get_parent_expr, in_macro, is_lang_ctor, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
@ -68,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for TryErr {
if let ExprKind::Call(err_fun, err_args) = try_arg.kind;
if let Some(err_arg) = err_args.get(0);
if let ExprKind::Path(ref err_fun_path) = err_fun.kind;
if match_qpath(err_fun_path, &paths::RESULT_ERR);
if is_lang_ctor(cx, err_fun_path, ResultErr);
if let Some(return_ty) = find_return_type(cx, &expr.kind);
then {
let prefix;
@ -101,10 +102,15 @@ impl<'tcx> LateLintPass<'tcx> for TryErr {
} else {
snippet(cx, err_arg.span, "_")
};
let suggestion = if err_ty == expr_err_ty {
format!("return {}{}{}", prefix, origin_snippet, suffix)
let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
"" // already returns
} else {
format!("return {}{}.into(){}", prefix, origin_snippet, suffix)
"return "
};
let suggestion = if err_ty == expr_err_ty {
format!("{}{}{}{}", ret_prefix, prefix, origin_snippet, suffix)
} else {
format!("{}{}{}.into(){}", ret_prefix, prefix, origin_snippet, suffix)
};
span_lint_and_sugg(

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{match_path, paths};
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{
@ -28,7 +28,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m
_ => None,
});
then {
if is_any_trait(inner) {
if is_any_trait(cx, inner) {
// Ignore `Box<Any>` types; see issue #1884 for details.
return false;
}
@ -84,13 +84,14 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m
}
// Returns true if given type is `Any` trait.
fn is_any_trait(t: &hir::Ty<'_>) -> bool {
fn is_any_trait(cx: &LateContext<'_>, t: &hir::Ty<'_>) -> bool {
if_chain! {
if let TyKind::TraitObject(traits, ..) = t.kind;
if !traits.is_empty();
if let Some(trait_did) = traits[0].trait_ref.trait_def_id();
// Only Send/Sync can be used as additional traits, so it is enough to
// check only the first trait.
if match_path(traits[0].trait_ref.path, &paths::ANY_TRAIT);
if match_def_path(cx, trait_did, &paths::ANY_TRAIT);
then {
return true;
}

View File

@ -0,0 +1,67 @@
use clippy_utils::diagnostics::span_lint_and_then;
use if_chain::if_chain;
use rustc_ast::{Item, ItemKind, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::kw;
declare_clippy_lint! {
/// **What it does:** Checks for imports ending in `::{self}`.
///
/// **Why is this bad?** In most cases, this can be written much more cleanly by omitting `::{self}`.
///
/// **Known problems:** Removing `::{self}` will cause any non-module items at the same path to also be imported.
/// This might cause a naming conflict (https://github.com/rust-lang/rustfmt/issues/3568). This lint makes no attempt
/// to detect this scenario and that is why it is a restriction lint.
///
/// **Example:**
///
/// ```rust
/// use std::io::{self};
/// ```
/// Use instead:
/// ```rust
/// use std::io;
/// ```
pub UNNECESSARY_SELF_IMPORTS,
restriction,
"imports ending in `::{self}`, which can be omitted"
}
declare_lint_pass!(UnnecessarySelfImports => [UNNECESSARY_SELF_IMPORTS]);
impl EarlyLintPass for UnnecessarySelfImports {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if_chain! {
if let ItemKind::Use(use_tree) = &item.kind;
if let UseTreeKind::Nested(nodes) = &use_tree.kind;
if let [(self_tree, _)] = &**nodes;
if let [self_seg] = &*self_tree.prefix.segments;
if self_seg.ident.name == kw::SelfLower;
if let Some(last_segment) = use_tree.prefix.segments.last();
then {
span_lint_and_then(
cx,
UNNECESSARY_SELF_IMPORTS,
item.span,
"import ending with `::{self}`",
|diag| {
diag.span_suggestion(
last_segment.span().with_hi(item.span.hi()),
"consider omitting `::{self}`",
format!(
"{}{};",
last_segment.ident,
if let UseTreeKind::Simple(Some(alias), ..) = self_tree.kind { format!(" as {}", alias) } else { String::new() },
),
Applicability::MaybeIncorrect,
);
diag.note("this will slightly change semantics; any non-module items at the same path will also be imported");
},
);
}
}
}
}

View File

@ -1,9 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::{contains_return, in_macro, match_qpath, paths, return_ty, visitors::find_all_ret_expressions};
use clippy_utils::{contains_return, in_macro, is_lang_ctor, return_ty, visitors::find_all_ret_expressions};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::LangItem::{OptionSome, ResultOk};
use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
@ -85,11 +86,11 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
}
// Get the wrapper and inner types, if can't, abort.
let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) {
("Option", &paths::OPTION_SOME, subst.type_at(0))
("Option", OptionSome, subst.type_at(0))
} else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) {
("Result", &paths::RESULT_OK, subst.type_at(0))
("Result", ResultOk, subst.type_at(0))
} else {
return;
}
@ -103,14 +104,12 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
if_chain! {
if !in_macro(ret_expr.span);
// Check if a function call.
if let ExprKind::Call(func, args) = ret_expr.kind;
// Get the Path of the function call.
if let ExprKind::Path(ref qpath) = func.kind;
if let ExprKind::Call(func, [arg]) = ret_expr.kind;
// Check if OPTION_SOME or RESULT_OK, depending on return type.
if match_qpath(qpath, path);
if args.len() == 1;
if let ExprKind::Path(qpath) = &func.kind;
if is_lang_ctor(cx, qpath, lang_item);
// Make sure the function argument does not contain a return expression.
if !contains_return(&args[0]);
if !contains_return(arg);
then {
suggs.push(
(
@ -118,7 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
if inner_type.is_unit() {
"".to_string()
} else {
snippet(cx, args[0].span.source_callsite(), "..").to_string()
snippet(cx, arg.span.source_callsite(), "..").to_string()
}
)
);

View File

@ -41,7 +41,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
};
match expr.kind {
hir::ExprKind::Match(res, _, _) if is_try(expr).is_some() => {
hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
if let hir::ExprKind::Call(func, args) = res.kind {
if matches!(
func.kind,

View File

@ -3,7 +3,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sug
use clippy_utils::source::snippet;
use clippy_utils::ty::match_type;
use clippy_utils::{
is_else_clause, is_expn_of, match_def_path, match_qpath, method_calls, path_to_res, paths, run_lints, SpanlessEq,
is_else_clause, is_expn_of, is_expr_path_def_path, match_def_path, method_calls, path_to_res, paths, run_lints,
SpanlessEq,
};
use if_chain::if_chain;
use rustc_ast::ast::{Crate as AstCrate, ItemKind, LitKind, ModKind, NodeId};
@ -578,8 +579,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
if_chain! {
if let ExprKind::Call(func, and_then_args) = expr.kind;
if let ExprKind::Path(ref path) = func.kind;
if match_qpath(path, &["span_lint_and_then"]);
if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]);
if and_then_args.len() == 5;
if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind;
let body = cx.tcx.hir().body(*body_id);
@ -761,8 +761,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
if_chain! {
// Check if this is a call to utils::match_type()
if let ExprKind::Call(fn_path, [context, ty, ty_path]) = expr.kind;
if let ExprKind::Path(fn_qpath) = &fn_path.kind;
if match_qpath(fn_qpath, &["utils", "match_type"]);
if is_expr_path_def_path(cx, fn_path, &["clippy_utils", "ty", "match_type"]);
// Extract the path to the matched type
if let Some(segments) = path_to_matched_type(cx, ty_path);
let segments: Vec<&str> = segments.iter().map(|sym| &**sym).collect();
@ -771,6 +770,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
let diag_items = cx.tcx.diagnostic_items(ty_did.krate);
if let Some(item_name) = diag_items.iter().find_map(|(k, v)| if *v == ty_did { Some(k) } else { None });
then {
// TODO: check paths constants from external crates.
let cx_snippet = snippet(cx, context.span, "_");
let ty_snippet = snippet(cx, ty.span, "_");
@ -778,9 +778,9 @@ impl<'tcx> LateLintPass<'tcx> for MatchTypeOnDiagItem {
cx,
MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
expr.span,
"usage of `utils::match_type()` on a type diagnostic item",
"usage of `clippy_utils::ty::match_type()` on a type diagnostic item",
"try",
format!("utils::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name),
format!("clippy_utils::ty::is_type_diagnostic_item({}, {}, sym::{})", cx_snippet, ty_snippet, item_name),
Applicability::MaybeIncorrect,
);
}

View File

@ -573,7 +573,7 @@ impl Write {
diag.multipart_suggestion(
"try this",
iter::once((comma_span.to(token_expr.span), String::new()))
.chain(fmt_spans.iter().cloned().zip(iter::repeat(replacement)))
.chain(fmt_spans.iter().copied().zip(iter::repeat(replacement)))
.collect(),
Applicability::MachineApplicable,
);

View File

@ -5,6 +5,7 @@
#![allow(clippy::similar_names, clippy::wildcard_imports, clippy::enum_glob_use)]
use crate::{both, over};
use if_chain::if_chain;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
@ -571,3 +572,34 @@ pub fn eq_mac_args(l: &MacArgs, r: &MacArgs) -> bool {
_ => false,
}
}
/// Extract args from an assert-like macro.
///
/// Currently working with:
/// - `assert_eq!` and `assert_ne!`
/// - `debug_assert_eq!` and `debug_assert_ne!`
///
/// For example:
///
/// `debug_assert_eq!(a, b)` will return Some([a, b])
pub fn extract_assert_macro_args(mut expr: &Expr) -> Option<[&Expr; 2]> {
if_chain! {
if let ExprKind::If(_, ref block, _) = expr.kind;
if let StmtKind::Semi(ref e) = block.stmts.get(0)?.kind;
then {
expr = e;
}
}
if_chain! {
if let ExprKind::Block(ref block, _) = expr.kind;
if let StmtKind::Expr(ref expr) = block.stmts.get(0)?.kind;
if let ExprKind::Match(ref match_expr, _) = expr.kind;
if let ExprKind::Tup(ref tup) = match_expr.kind;
if let [a, b, ..] = tup.as_slice();
if let (&ExprKind::AddrOf(_, _, ref a), &ExprKind::AddrOf(_, _, ref b)) = (&a.kind, &b.kind);
then {
return Some([&*a, &*b]);
}
}
None
}

View File

@ -151,10 +151,9 @@ pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {
/// Return true if the attributes contain `#[doc(hidden)]`
pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool {
#[allow(clippy::filter_map)]
attrs
.iter()
.filter(|attr| attr.has_name(sym::doc))
.flat_map(ast::Attribute::meta_item_list)
.filter_map(ast::Attribute::meta_item_list)
.any(|l| attr::list_contains_name(&l, sym::hidden))
}

View File

@ -96,6 +96,16 @@ impl HirEqInterExpr<'_, '_, '_> {
pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool {
match (&left.kind, &right.kind) {
(&StmtKind::Local(ref l), &StmtKind::Local(ref r)) => {
// This additional check ensures that the type of the locals are equivalent even if the init
// expression or type have some inferred parts.
if let Some(typeck) = self.inner.maybe_typeck_results {
let l_ty = typeck.pat_ty(&l.pat);
let r_ty = typeck.pat_ty(&r.pat);
if !rustc_middle::ty::TyS::same_type(l_ty, r_ty) {
return false;
}
}
// eq_pat adds the HirIds to the locals map. We therefor call it last to make sure that
// these only get added if the init and type is equal.
both(&l.init, &r.init, |l, r| self.eq_expr(l, r))
@ -424,7 +434,7 @@ fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'
TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace
)
})
.ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().cloned()) =>
.ne([TokenKind::OpenBrace, TokenKind::CloseBrace].iter().copied()) =>
{
kind
},

View File

@ -10,6 +10,7 @@
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_data_structures;
extern crate rustc_errors;
extern crate rustc_hir;
@ -57,13 +58,14 @@ use if_chain::if_chain;
use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, ImplItem,
ImplItemKind, Item, ItemKind, LangItem, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, QPath,
TraitItem, TraitItemKind, TraitRef, TyKind,
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment,
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
};
use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export;
@ -80,7 +82,7 @@ use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::Integer;
use crate::consts::{constant, Constant};
use crate::ty::is_recursively_primitive_type;
use crate::ty::{can_partially_move_ty, is_recursively_primitive_type};
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
@ -222,6 +224,19 @@ pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool {
}
}
/// Checks if a `QPath` resolves to a constructor of a `LangItem`.
/// For example, use this to check whether a function call or a pattern is `Some(..)`.
pub fn is_lang_ctor(cx: &LateContext<'_>, qpath: &QPath<'_>, lang_item: LangItem) -> bool {
if let QPath::Resolved(_, path) = qpath {
if let Res::Def(DefKind::Ctor(..), ctor_id) = path.res {
if let Ok(item_id) = cx.tcx.lang_items().require(lang_item) {
return cx.tcx.parent(ctor_id) == Some(item_id);
}
}
}
false
}
/// Returns `true` if this `span` was expanded by any macro.
#[must_use]
pub fn in_macro(span: Span) -> bool {
@ -279,27 +294,29 @@ pub fn match_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, path: &[&str])
trt_id.map_or(false, |trt_id| match_def_path(cx, trt_id, path))
}
/// Checks if the method call given in `def_id` belongs to a trait or other container with a given
/// diagnostic item
pub fn is_diagnostic_assoc_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
cx.tcx
.opt_associated_item(def_id)
.and_then(|associated_item| match associated_item.container {
rustc_ty::TraitContainer(assoc_def_id) => Some(assoc_def_id),
rustc_ty::ImplContainer(assoc_def_id) => match cx.tcx.type_of(assoc_def_id).kind() {
rustc_ty::Adt(adt, _) => Some(adt.did),
rustc_ty::Slice(_) => cx.tcx.get_diagnostic_item(sym::slice), // this isn't perfect but it works
_ => None,
},
})
.map_or(false, |assoc_def_id| cx.tcx.is_diagnostic_item(diag_item, assoc_def_id))
/// Checks if a method is defined in an impl of a diagnostic item
pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return cx.tcx.is_diagnostic_item(diag_item, adt.did);
}
}
false
}
/// Checks if a method is in a diagnostic item trait
pub fn is_diag_trait_item(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(trait_did) = cx.tcx.trait_of_item(def_id) {
return cx.tcx.is_diagnostic_item(diag_item, trait_did);
}
false
}
/// Checks if the method call given in `expr` belongs to the given trait.
pub fn is_trait_method(cx: &LateContext<'_>, expr: &Expr<'_>, diag_item: Symbol) -> bool {
cx.typeck_results()
.type_dependent_def_id(expr.hir_id)
.map_or(false, |did| is_diagnostic_assoc_item(cx, did, diag_item))
.map_or(false, |did| is_diag_trait_item(cx, did, diag_item))
}
/// Checks if an expression references a variable of the given name.
@ -380,6 +397,29 @@ pub fn match_qpath(path: &QPath<'_>, segments: &[&str]) -> bool {
}
}
/// If the expression is a path, resolve it. Otherwise, return `Res::Err`.
pub fn expr_path_res(cx: &LateContext<'_>, expr: &Expr<'_>) -> Res {
if let ExprKind::Path(p) = &expr.kind {
cx.qpath_res(p, expr.hir_id)
} else {
Res::Err
}
}
/// Resolves the path to a `DefId` and checks if it matches the given path.
pub fn is_qpath_def_path(cx: &LateContext<'_>, path: &QPath<'_>, hir_id: HirId, segments: &[&str]) -> bool {
cx.qpath_res(path, hir_id)
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, segments))
}
/// If the expression is a path, resolves it to a `DefId` and checks if it matches the given path.
pub fn is_expr_path_def_path(cx: &LateContext<'_>, expr: &Expr<'_>, segments: &[&str]) -> bool {
expr_path_res(cx, expr)
.opt_def_id()
.map_or(false, |id| match_def_path(cx, id, segments))
}
/// THIS METHOD IS DEPRECATED and will eventually be removed since it does not match against the
/// entire path or resolved `DefId`. Prefer using `match_def_path`. Consider getting a `DefId` from
/// `QPath::Resolved.1.res.opt_def_id()`.
@ -408,20 +448,6 @@ pub fn match_path(path: &Path<'_>, segments: &[&str]) -> bool {
.all(|(a, b)| a.ident.name.as_str() == *b)
}
/// Matches a `Path` against a slice of segment string literals, e.g.
///
/// # Examples
/// ```rust,ignore
/// match_path_ast(path, &["std", "rt", "begin_unwind"])
/// ```
pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
path.segments
.iter()
.rev()
.zip(segments.iter().rev())
.all(|(a, b)| a.ident.name.as_str() == *b)
}
/// If the expression is a path to a local, returns the canonical `HirId` of the local.
pub fn path_to_local(expr: &Expr<'_>) -> Option<HirId> {
if let ExprKind::Path(QPath::Resolved(None, ref path)) = expr.kind {
@ -522,6 +548,73 @@ pub fn trait_ref_of_method<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
None
}
/// Checks if the top level expression can be moved into a closure as is.
pub fn can_move_expr_to_closure_no_visit(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, jump_targets: &[HirId]) -> bool {
match expr.kind {
ExprKind::Break(Destination { target_id: Ok(id), .. }, _)
| ExprKind::Continue(Destination { target_id: Ok(id), .. })
if jump_targets.contains(&id) =>
{
true
},
ExprKind::Break(..)
| ExprKind::Continue(_)
| ExprKind::Ret(_)
| ExprKind::Yield(..)
| ExprKind::InlineAsm(_)
| ExprKind::LlvmInlineAsm(_) => false,
// Accessing a field of a local value can only be done if the type isn't
// partially moved.
ExprKind::Field(base_expr, _)
if matches!(
base_expr.kind,
ExprKind::Path(QPath::Resolved(_, Path { res: Res::Local(_), .. }))
) && can_partially_move_ty(cx, cx.typeck_results().expr_ty(base_expr)) =>
{
// TODO: check if the local has been partially moved. Assume it has for now.
false
}
_ => true,
}
}
/// Checks if the expression can be moved into a closure as is.
pub fn can_move_expr_to_closure(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool {
struct V<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
loops: Vec<HirId>,
allow_closure: bool,
}
impl Visitor<'tcx> for V<'_, 'tcx> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if !self.allow_closure {
return;
}
if let ExprKind::Loop(b, ..) = e.kind {
self.loops.push(e.hir_id);
self.visit_block(b);
self.loops.pop();
} else {
self.allow_closure &= can_move_expr_to_closure_no_visit(self.cx, e, &self.loops);
walk_expr(self, e);
}
}
}
let mut v = V {
cx,
allow_closure: true,
loops: Vec::new(),
};
v.visit_expr(expr);
v.allow_closure
}
/// Returns the method names and argument list of nested method call expressions that make up
/// `expr`. method/span lists are sorted with the most recent call first.
pub fn method_calls<'tcx>(
@ -960,7 +1053,7 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool {
/// the function once on the given pattern.
pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx>, mut f: F) {
if let PatKind::Or(pats) = pat.kind {
pats.iter().cloned().for_each(f)
pats.iter().copied().for_each(f)
} else {
f(pat)
}
@ -1011,11 +1104,11 @@ pub fn iter_input_pats<'tcx>(decl: &FnDecl<'_>, body: &'tcx Body<'_>) -> impl It
/// Checks if a given expression is a match expression expanded from the `?`
/// operator or the `try` macro.
pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_ok(arm: &Arm<'_>) -> bool {
pub fn is_try<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
fn is_ok(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if_chain! {
if let PatKind::TupleStruct(ref path, ref pat, None) = arm.pat.kind;
if match_qpath(path, &paths::RESULT_OK[1..]);
if is_lang_ctor(cx, path, ResultOk);
if let PatKind::Binding(_, hir_id, _, None) = pat[0].kind;
if path_to_local_id(arm.body, hir_id);
then {
@ -1025,9 +1118,9 @@ pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
false
}
fn is_err(arm: &Arm<'_>) -> bool {
fn is_err(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
if let PatKind::TupleStruct(ref path, _, _) = arm.pat.kind {
match_qpath(path, &paths::RESULT_ERR[1..])
is_lang_ctor(cx, path, ResultErr)
} else {
false
}
@ -1043,8 +1136,8 @@ pub fn is_try<'tcx>(expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if arms.len() == 2;
if arms[0].guard.is_none();
if arms[1].guard.is_none();
if (is_ok(&arms[0]) && is_err(&arms[1])) ||
(is_ok(&arms[1]) && is_err(&arms[0]));
if (is_ok(cx, &arms[0]) && is_err(cx, &arms[1])) ||
(is_ok(cx, &arms[1]) && is_err(cx, &arms[0]));
then {
return Some(expr);
}
@ -1131,29 +1224,47 @@ pub fn match_function_call<'tcx>(
None
}
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
// We have to convert `syms` to `&[Symbol]` here because rustc's `match_def_path`
// accepts only that. We should probably move to Symbols in Clippy as well.
let syms = syms.iter().map(|p| Symbol::intern(p)).collect::<Vec<Symbol>>();
cx.match_def_path(did, &syms)
/// Checks if the given `DefId` matches any of the paths. Returns the index of matching path, if
/// any.
pub fn match_any_def_paths(cx: &LateContext<'_>, did: DefId, paths: &[&[&str]]) -> Option<usize> {
let search_path = cx.get_def_path(did);
paths
.iter()
.position(|p| p.iter().map(|x| Symbol::intern(x)).eq(search_path.iter().copied()))
}
pub fn match_panic_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx [Expr<'tcx>]> {
match_function_call(cx, expr, &paths::BEGIN_PANIC)
.or_else(|| match_function_call(cx, expr, &paths::BEGIN_PANIC_FMT))
.or_else(|| match_function_call(cx, expr, &paths::PANIC_ANY))
.or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC))
.or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_FMT))
.or_else(|| match_function_call(cx, expr, &paths::PANICKING_PANIC_STR))
/// Checks if the given `DefId` matches the path.
pub fn match_def_path<'tcx>(cx: &LateContext<'tcx>, did: DefId, syms: &[&str]) -> bool {
// We should probably move to Symbols in Clippy as well rather than interning every time.
let path = cx.get_def_path(did);
syms.iter().map(|x| Symbol::intern(x)).eq(path.iter().copied())
}
pub fn match_panic_call(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Call(func, [arg]) = expr.kind {
expr_path_res(cx, func)
.opt_def_id()
.map_or(false, |id| match_panic_def_id(cx, id))
.then(|| arg)
} else {
None
}
}
pub fn match_panic_def_id(cx: &LateContext<'_>, did: DefId) -> bool {
match_def_path(cx, did, &paths::BEGIN_PANIC)
|| match_def_path(cx, did, &paths::BEGIN_PANIC_FMT)
|| match_def_path(cx, did, &paths::PANIC_ANY)
|| match_def_path(cx, did, &paths::PANICKING_PANIC)
|| match_def_path(cx, did, &paths::PANICKING_PANIC_FMT)
|| match_def_path(cx, did, &paths::PANICKING_PANIC_STR)
match_any_def_paths(
cx,
did,
&[
&paths::BEGIN_PANIC,
&paths::BEGIN_PANIC_FMT,
&paths::PANIC_ANY,
&paths::PANICKING_PANIC,
&paths::PANICKING_PANIC_FMT,
&paths::PANICKING_PANIC_STR,
],
)
.is_some()
}
/// Returns the list of condition expressions and the list of blocks in a
@ -1189,21 +1300,6 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
(conds, blocks)
}
/// This function returns true if the given expression is the `else` or `if else` part of an if
/// statement
pub fn parent_node_is_if_expr(expr: &Expr<'_>, cx: &LateContext<'_>) -> bool {
let map = cx.tcx.hir();
let parent_id = map.get_parent_node(expr.hir_id);
let parent_node = map.get(parent_id);
matches!(
parent_node,
Node::Expr(Expr {
kind: ExprKind::If(_, _, _),
..
})
)
}
// Finds the `#[must_use]` attribute, if any
pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
attrs.iter().find(|a| a.has_name(sym::must_use))
@ -1228,6 +1324,51 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
did.map_or(false, |did| must_use_attr(&cx.tcx.get_attrs(did)).is_some())
}
/// Gets the node where an expression is either used, or it's type is unified with another branch.
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
let map = tcx.hir();
let mut child_id = expr.hir_id;
let mut iter = map.parent_iter(child_id);
loop {
match iter.next() {
None => break None,
Some((id, Node::Block(_))) => child_id = id,
Some((id, Node::Arm(arm))) if arm.body.hir_id == child_id => child_id = id,
Some((_, Node::Expr(expr))) => match expr.kind {
ExprKind::Match(_, [arm], _) if arm.hir_id == child_id => child_id = expr.hir_id,
ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = expr.hir_id,
ExprKind::If(_, then_expr, None) if then_expr.hir_id == child_id => break None,
_ => break Some(Node::Expr(expr)),
},
Some((_, node)) => break Some(node),
}
}
}
/// Checks if the result of an expression is used, or it's type is unified with another branch.
pub fn is_expr_used_or_unified(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
!matches!(
get_expr_use_or_unification_node(tcx, expr),
None | Some(Node::Stmt(Stmt {
kind: StmtKind::Expr(_)
| StmtKind::Semi(_)
| StmtKind::Local(Local {
pat: Pat {
kind: PatKind::Wild,
..
},
..
}),
..
}))
)
}
/// Checks if the expression is the final expression returned from a block.
pub fn is_expr_final_block_expr(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
matches!(get_parent_node(tcx, expr.hir_id), Some(Node::Block(..)))
}
pub fn is_no_std_crate(cx: &LateContext<'_>) -> bool {
cx.tcx.hir().attrs(hir::CRATE_HIR_ID).iter().any(|attr| {
if let ast::AttrKind::Normal(ref attr, _) = attr.kind {
@ -1397,28 +1538,43 @@ pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) {
peel(pat, 0)
}
/// Peels of expressions while the given closure returns `Some`.
pub fn peel_hir_expr_while<'tcx>(
mut expr: &'tcx Expr<'tcx>,
mut f: impl FnMut(&'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>>,
) -> &'tcx Expr<'tcx> {
while let Some(e) = f(expr) {
expr = e;
}
expr
}
/// Peels off up to the given number of references on the expression. Returns the underlying
/// expression and the number of references removed.
pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) {
match expr.kind {
ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target),
_ => (expr, count),
}
}
f(expr, 0, count)
let mut remaining = count;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) if remaining != 0 => {
remaining -= 1;
Some(e)
},
_ => None,
});
(e, count - remaining)
}
/// Peels off all references on the expression. Returns the underlying expression and the number of
/// references removed.
pub fn peel_hir_expr_refs(expr: &'a Expr<'a>) -> (&'a Expr<'a>, usize) {
fn f(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) {
match expr.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, expr) => f(expr, count + 1),
_ => (expr, count),
}
}
f(expr, 0)
let mut count = 0;
let e = peel_hir_expr_while(expr, |e| match e.kind {
ExprKind::AddrOf(BorrowKind::Ref, _, e) => {
count += 1;
Some(e)
},
_ => None,
});
(e, count)
}
#[macro_export]
@ -1450,27 +1606,3 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
}
}
}
/// Check if the resolution of a given path is an `Ok` variant of `Result`.
pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
if let Some(variant_id) = cx.tcx.parent(id) {
return variant_id == ok_id;
}
}
}
false
}
/// Check if the resolution of a given path is a `Some` variant of `Option`.
pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
if let Some(variant_id) = cx.tcx.parent(id) {
return variant_id == some_id;
}
}
}
false
}

View File

@ -4,7 +4,7 @@
//! Whenever possible, please consider diagnostic items over hardcoded paths.
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
pub const ANY_TRAIT: [&str; 3] = ["std", "any", "Any"];
pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
@ -13,13 +13,13 @@ pub(super) const BEGIN_PANIC_FMT: [&str; 3] = ["std", "panicking", "begin_panic_
pub const BINARY_HEAP: [&str; 4] = ["alloc", "collections", "binary_heap", "BinaryHeap"];
pub const BORROW_TRAIT: [&str; 3] = ["core", "borrow", "Borrow"];
pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeMap"];
pub const BTREEMAP_CONTAINS_KEY: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "contains_key"];
pub const BTREEMAP_ENTRY: [&str; 6] = ["alloc", "collections", "btree", "map", "entry", "Entry"];
pub const BTREEMAP_INSERT: [&str; 6] = ["alloc", "collections", "btree", "map", "BTreeMap", "insert"];
pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
pub const COPY: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
pub const COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy"];
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
@ -44,10 +44,14 @@ pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
pub const FROM_ITERATOR_METHOD: [&str; 6] = ["core", "iter", "traits", "collect", "FromIterator", "from_iter"];
pub const FROM_STR_METHOD: [&str; 5] = ["core", "str", "traits", "FromStr", "from_str"];
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
pub const HASHMAP_CONTAINS_KEY: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "contains_key"];
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
pub const HASHMAP_INSERT: [&str; 6] = ["std", "collections", "hash", "map", "HashMap", "insert"];
pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"];
#[cfg(feature = "internal-lints")]
pub const IDENT: [&str; 3] = ["rustc_span", "symbol", "Ident"];
@ -60,8 +64,9 @@ pub const INTO: [&str; 3] = ["core", "convert", "Into"];
pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
pub const IPADDR_V4: [&str; 5] = ["std", "net", "ip", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 5] = ["std", "net", "ip", "IpAddr", "V6"];
pub const ITER_REPEAT: [&str; 5] = ["core", "iter", "sources", "repeat", "repeat"];
#[cfg(feature = "internal-lints")]
pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")]
@ -100,12 +105,23 @@ pub const PERMISSIONS_FROM_MODE: [&str; 7] = ["std", "sys", "unix", "ext", "fs",
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
pub const PTR_COPY: [&str; 4] = ["core", "intrinsics", "", "copy"];
pub const PTR_COPY_NONOVERLAPPING: [&str; 4] = ["core", "intrinsics", "", "copy_nonoverlapping"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
pub const PTR_READ: [&str; 3] = ["core", "ptr", "read"];
pub const PTR_READ_UNALIGNED: [&str; 3] = ["core", "ptr", "read_unaligned"];
pub const PTR_READ_VOLATILE: [&str; 3] = ["core", "ptr", "read_volatile"];
pub const PTR_REPLACE: [&str; 3] = ["core", "ptr", "replace"];
pub const PTR_SWAP: [&str; 3] = ["core", "ptr", "swap"];
pub const PTR_WRITE: [&str; 3] = ["core", "ptr", "write"];
pub const PTR_WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];
pub const PTR_WRITE_UNALIGNED: [&str; 3] = ["core", "ptr", "write_unaligned"];
pub const PTR_WRITE_VOLATILE: [&str; 3] = ["core", "ptr", "write_volatile"];
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
@ -117,7 +133,6 @@ pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
pub const REPEAT: [&str; 3] = ["core", "iter", "repeat"];
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
@ -131,10 +146,8 @@ pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec
pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
pub const STDOUT: [&str; 4] = ["std", "io", "stdio", "stdout"];
pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
pub const CONVERT_IDENTITY: [&str; 3] = ["core", "convert", "identity"];
pub const STD_FS_CREATE_DIR: [&str; 3] = ["std", "fs", "create_dir"];
pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];

View File

@ -1,3 +1,8 @@
// This code used to be a part of `rustc` but moved to Clippy as a result of
// https://github.com/rust-lang/rust/issues/76618. Because of that, it contains unused code and some
// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
// differ from the time of `rustc` even if the name stays the same.
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::{
@ -6,6 +11,7 @@ use rustc_middle::mir::{
};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt};
use rustc_semver::RustcVersion;
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_target::spec::abi::Abi::RustIntrinsic;
@ -13,7 +19,7 @@ use std::borrow::Cow;
type McfResult = Result<(), (Span, Cow<'static, str>)>;
pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, msrv: Option<&RustcVersion>) -> McfResult {
let def_id = body.source.def_id();
let mut current = def_id;
loop {
@ -70,7 +76,7 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>) -> McfResult {
)?;
for bb in body.basic_blocks() {
check_terminator(tcx, body, bb.terminator())?;
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt)?;
}
@ -268,7 +274,12 @@ fn check_place(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'t
Ok(())
}
fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Terminator<'tcx>) -> McfResult {
fn check_terminator(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
terminator: &Terminator<'tcx>,
msrv: Option<&RustcVersion>,
) -> McfResult {
let span = terminator.source_info.span;
match &terminator.kind {
TerminatorKind::FalseEdge { .. }
@ -305,7 +316,7 @@ fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Termin
} => {
let fn_ty = func.ty(body, tcx);
if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() {
if !rustc_mir::const_eval::is_min_const_fn(tcx, fn_def_id) {
if !is_const_fn(tcx, fn_def_id, msrv) {
return Err((
span,
format!(
@ -350,3 +361,24 @@ fn check_terminator(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, terminator: &Termin
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
}
}
fn is_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: Option<&RustcVersion>) -> bool {
rustc_mir::const_eval::is_const_fn(tcx, def_id)
&& if let Some(const_stab) = tcx.lookup_const_stability(def_id) {
if let rustc_attr::StabilityLevel::Stable { since } = const_stab.level {
// Checking MSRV is manually necessary because `rustc` has no such concept. This entire
// function could be removed if `rustc` provided a MSRV-aware version of `is_const_fn`.
// as a part of an unimplemented MSRV check https://github.com/rust-lang/rust/issues/65262.
crate::meets_msrv(
msrv,
&RustcVersion::parse(&since.as_str())
.expect("`rustc_attr::StabilityLevel::Stable::since` is ill-formatted"),
)
} else {
// `rustc_mir::const_eval::is_const_fn` should return false for unstably const functions.
unreachable!();
}
} else {
true
}
}

View File

@ -66,6 +66,15 @@ pub fn indent_of<T: LintContext>(cx: &T, span: Span) -> Option<usize> {
snippet_opt(cx, line_span(cx, span)).and_then(|snip| snip.find(|c: char| !c.is_whitespace()))
}
/// Gets a snippet of the indentation of the line of a span
pub fn snippet_indent<T: LintContext>(cx: &T, span: Span) -> Option<String> {
snippet_opt(cx, line_span(cx, span)).map(|mut s| {
let len = s.len() - s.trim_start().len();
s.truncate(len);
s
})
}
// If the snippet is empty, it's an attribute that was inserted during macro
// expansion and we want to ignore those, because they could come from external
// sources that the user has no control over.

View File

@ -13,7 +13,7 @@ use rustc_lint::LateContext;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TypeFoldable, UintTy};
use rustc_span::sym;
use rustc_span::symbol::Symbol;
use rustc_span::symbol::{Ident, Symbol};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits::query::normalize::AtExt;
@ -52,6 +52,25 @@ pub fn contains_adt_constructor(ty: Ty<'_>, adt: &AdtDef) -> bool {
})
}
/// Resolves `<T as Iterator>::Item` for `T`
/// Do not invoke without first verifying that the type implements `Iterator`
pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
cx.tcx
.get_diagnostic_item(sym::Iterator)
.and_then(|iter_did| {
cx.tcx.associated_items(iter_did).find_by_name_and_kind(
cx.tcx,
Ident::from_str("Item"),
ty::AssocKind::Type,
iter_did,
)
})
.map(|assoc| {
let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[]));
cx.tcx.normalize_erasing_regions(cx.param_env, proj)
})
}
/// Returns true if ty has `iter` or `iter_mut` methods
pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option<Symbol> {
// FIXME: instead of this hard-coded list, we should check if `<adt>::iter`

View File

@ -625,7 +625,7 @@ in the following steps:
Here are some pointers to things you are likely going to need for every lint:
* [Clippy utils][utils] - Various helper functions. Maybe the function you need
is already in here (`implements_trait`, `match_path`, `snippet`, etc)
is already in here (`implements_trait`, `match_def_path`, `snippet`, etc)
* [Clippy diagnostics][diagnostics]
* [The `if_chain` macro][if_chain]
* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro]

View File

@ -5,7 +5,7 @@
// When a new lint is introduced, we can search the results for new warnings and check for false
// positives.
#![allow(clippy::filter_map, clippy::collapsible_else_if)]
#![allow(clippy::collapsible_else_if)]
use std::ffi::OsStr;
use std::process::Command;

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2021-04-08"
channel = "nightly-2021-04-22"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"]

View File

@ -2,58 +2,18 @@
#![deny(clippy::internal)]
#![feature(rustc_private)]
extern crate clippy_utils;
extern crate rustc_ast;
extern crate rustc_errors;
extern crate rustc_lint;
extern crate rustc_session;
extern crate rustc_span;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use rustc_ast::ast::Expr;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
#[allow(unused_variables)]
pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
where
F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
{
}
#[allow(unused_variables)]
fn span_lint_and_help<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: Span,
msg: &str,
option_span: Option<Span>,
help: &str,
) {
}
#[allow(unused_variables)]
fn span_lint_and_note<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: Span,
msg: &str,
note_span: Option<Span>,
note: &str,
) {
}
#[allow(unused_variables)]
fn span_lint_and_sugg<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
sp: Span,
msg: &str,
help: &str,
sugg: String,
applicability: Applicability,
) {
}
declare_tool_lint! {
pub clippy::TEST_LINT,

View File

@ -2,58 +2,18 @@
#![deny(clippy::internal)]
#![feature(rustc_private)]
extern crate clippy_utils;
extern crate rustc_ast;
extern crate rustc_errors;
extern crate rustc_lint;
extern crate rustc_session;
extern crate rustc_span;
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use rustc_ast::ast::Expr;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
#[allow(unused_variables)]
pub fn span_lint_and_then<'a, T: LintContext, F>(cx: &'a T, lint: &'static Lint, sp: Span, msg: &str, f: F)
where
F: for<'b> FnOnce(&mut DiagnosticBuilder<'b>),
{
}
#[allow(unused_variables)]
fn span_lint_and_help<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: Span,
msg: &str,
option_span: Option<Span>,
help: &str,
) {
}
#[allow(unused_variables)]
fn span_lint_and_note<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
span: Span,
msg: &str,
note_span: Option<Span>,
note: &str,
) {
}
#[allow(unused_variables)]
fn span_lint_and_sugg<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,
sp: Span,
msg: &str,
help: &str,
sugg: String,
applicability: Applicability,
) {
}
declare_tool_lint! {
pub clippy::TEST_LINT,

View File

@ -1,5 +1,5 @@
error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:75:9
--> $DIR/collapsible_span_lint_calls.rs:35:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_suggestion(expr.span, help_msg, sugg.to_string(), Applicability::MachineApplicable);
@ -14,7 +14,7 @@ LL | #![deny(clippy::internal)]
= note: `#[deny(clippy::collapsible_span_lint_calls)]` implied by `#[deny(clippy::internal)]`
error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:78:9
--> $DIR/collapsible_span_lint_calls.rs:38:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_help(expr.span, help_msg);
@ -22,7 +22,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg)`
error: this call is collapsible
--> $DIR/collapsible_span_lint_calls.rs:81:9
--> $DIR/collapsible_span_lint_calls.rs:41:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.help(help_msg);
@ -30,7 +30,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg)`
error: this call is collspible
--> $DIR/collapsible_span_lint_calls.rs:84:9
--> $DIR/collapsible_span_lint_calls.rs:44:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.span_note(expr.span, note_msg);
@ -38,7 +38,7 @@ LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg)`
error: this call is collspible
--> $DIR/collapsible_span_lint_calls.rs:87:9
--> $DIR/collapsible_span_lint_calls.rs:47:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | | db.note(note_msg);

View File

@ -1,29 +1,18 @@
#![deny(clippy::internal)]
#![feature(rustc_private)]
extern crate clippy_utils;
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
#[macro_use]
extern crate rustc_session;
use clippy_utils::{paths, ty::match_type};
use rustc_hir::Expr;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
mod paths {
pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
}
mod utils {
use super::*;
pub fn match_type(_cx: &LateContext<'_>, _ty: Ty<'_>, _path: &[&str]) -> bool {
false
}
}
use utils::match_type;
declare_lint! {
pub TEST_LINT,
Warn,
@ -38,12 +27,12 @@ impl<'tcx> LateLintPass<'tcx> for Pass {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr) {
let ty = cx.typeck_results().expr_ty(expr);
let _ = match_type(cx, ty, &paths::VEC);
let _ = match_type(cx, ty, &paths::VEC); // FIXME: Doesn't lint external paths
let _ = match_type(cx, ty, &OPTION);
let _ = match_type(cx, ty, &["core", "result", "Result"]);
let rc_path = &["alloc", "rc", "Rc"];
let _ = utils::match_type(cx, ty, rc_path);
let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
}
}

View File

@ -1,8 +1,8 @@
error: usage of `utils::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:41:17
error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:31:17
|
LL | let _ = match_type(cx, ty, &paths::VEC);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::vec_type)`
LL | let _ = match_type(cx, ty, &OPTION);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::option_type)`
|
note: the lint level is defined here
--> $DIR/match_type_on_diag_item.rs:1:9
@ -11,23 +11,17 @@ LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::match_type_on_diagnostic_item)]` implied by `#[deny(clippy::internal)]`
error: usage of `utils::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:42:17
|
LL | let _ = match_type(cx, ty, &OPTION);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::option_type)`
error: usage of `utils::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:43:17
error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:32:17
|
LL | let _ = match_type(cx, ty, &["core", "result", "Result"]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::result_type)`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::result_type)`
error: usage of `utils::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:46:17
error: usage of `clippy_utils::ty::match_type()` on a type diagnostic item
--> $DIR/match_type_on_diag_item.rs:35:17
|
LL | let _ = utils::match_type(cx, ty, rc_path);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `utils::is_type_diagnostic_item(cx, ty, sym::Rc)`
LL | let _ = clippy_utils::ty::match_type(cx, ty, rc_path);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `clippy_utils::ty::is_type_diagnostic_item(cx, ty, sym::Rc)`
error: aborting due to 4 previous errors
error: aborting due to 3 previous errors

View File

@ -1,5 +1,7 @@
#![feature(asm)]
// only-x86_64
// ignore-aarch64
#![feature(asm)]
#[warn(clippy::inline_asm_x86_intel_syntax)]
mod warn_intel {
@ -23,6 +25,7 @@ mod warn_att {
}
}
#[cfg(target_arch = "x86_64")]
fn main() {
unsafe {
warn_att::use_asm();

View File

@ -1,5 +1,5 @@
error: Intel x86 assembly syntax used
--> $DIR/asm_syntax.rs:7:9
--> $DIR/asm_syntax.rs:9:9
|
LL | asm!("");
| ^^^^^^^^^
@ -8,7 +8,7 @@ LL | asm!("");
= help: use AT&T x86 assembly syntax
error: Intel x86 assembly syntax used
--> $DIR/asm_syntax.rs:8:9
--> $DIR/asm_syntax.rs:10:9
|
LL | asm!("", options());
| ^^^^^^^^^^^^^^^^^^^^
@ -16,7 +16,7 @@ LL | asm!("", options());
= help: use AT&T x86 assembly syntax
error: Intel x86 assembly syntax used
--> $DIR/asm_syntax.rs:9:9
--> $DIR/asm_syntax.rs:11:9
|
LL | asm!("", options(nostack));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -24,7 +24,7 @@ LL | asm!("", options(nostack));
= help: use AT&T x86 assembly syntax
error: AT&T x86 assembly syntax used
--> $DIR/asm_syntax.rs:21:9
--> $DIR/asm_syntax.rs:23:9
|
LL | asm!("", options(att_syntax));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -33,7 +33,7 @@ LL | asm!("", options(att_syntax));
= help: use Intel x86 assembly syntax
error: AT&T x86 assembly syntax used
--> $DIR/asm_syntax.rs:22:9
--> $DIR/asm_syntax.rs:24:9
|
LL | asm!("", options(nostack, att_syntax));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,59 @@
#![warn(clippy::bool_assert_comparison)]
macro_rules! a {
() => {
true
};
}
macro_rules! b {
() => {
true
};
}
fn main() {
assert_eq!("a".len(), 1);
assert_eq!("a".is_empty(), false);
assert_eq!("".is_empty(), true);
assert_eq!(true, "".is_empty());
assert_eq!(a!(), b!());
assert_eq!(a!(), "".is_empty());
assert_eq!("".is_empty(), b!());
assert_ne!("a".len(), 1);
assert_ne!("a".is_empty(), false);
assert_ne!("".is_empty(), true);
assert_ne!(true, "".is_empty());
assert_ne!(a!(), b!());
assert_ne!(a!(), "".is_empty());
assert_ne!("".is_empty(), b!());
debug_assert_eq!("a".len(), 1);
debug_assert_eq!("a".is_empty(), false);
debug_assert_eq!("".is_empty(), true);
debug_assert_eq!(true, "".is_empty());
debug_assert_eq!(a!(), b!());
debug_assert_eq!(a!(), "".is_empty());
debug_assert_eq!("".is_empty(), b!());
debug_assert_ne!("a".len(), 1);
debug_assert_ne!("a".is_empty(), false);
debug_assert_ne!("".is_empty(), true);
debug_assert_ne!(true, "".is_empty());
debug_assert_ne!(a!(), b!());
debug_assert_ne!(a!(), "".is_empty());
debug_assert_ne!("".is_empty(), b!());
// assert with error messages
assert_eq!("a".len(), 1, "tadam {}", 1);
assert_eq!("a".len(), 1, "tadam {}", true);
assert_eq!("a".is_empty(), false, "tadam {}", 1);
assert_eq!("a".is_empty(), false, "tadam {}", true);
assert_eq!(false, "a".is_empty(), "tadam {}", true);
debug_assert_eq!("a".len(), 1, "tadam {}", 1);
debug_assert_eq!("a".len(), 1, "tadam {}", true);
debug_assert_eq!("a".is_empty(), false, "tadam {}", 1);
debug_assert_eq!("a".is_empty(), false, "tadam {}", true);
debug_assert_eq!(false, "a".is_empty(), "tadam {}", true);
}

View File

@ -0,0 +1,112 @@
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:16:5
|
LL | assert_eq!("a".is_empty(), false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
|
= note: `-D clippy::bool-assert-comparison` implied by `-D warnings`
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:17:5
|
LL | assert_eq!("".is_empty(), true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:18:5
|
LL | assert_eq!(true, "".is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:24:5
|
LL | assert_ne!("a".is_empty(), false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:25:5
|
LL | assert_ne!("".is_empty(), true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:26:5
|
LL | assert_ne!(true, "".is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:32:5
|
LL | debug_assert_eq!("a".is_empty(), false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:33:5
|
LL | debug_assert_eq!("".is_empty(), true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:34:5
|
LL | debug_assert_eq!(true, "".is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:40:5
|
LL | debug_assert_ne!("a".is_empty(), false);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:41:5
|
LL | debug_assert_ne!("".is_empty(), true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_ne!` with a literal bool
--> $DIR/bool_assert_comparison.rs:42:5
|
LL | debug_assert_ne!(true, "".is_empty());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:50:5
|
LL | assert_eq!("a".is_empty(), false, "tadam {}", 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:51:5
|
LL | assert_eq!("a".is_empty(), false, "tadam {}", true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:52:5
|
LL | assert_eq!(false, "a".is_empty(), "tadam {}", true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:56:5
|
LL | debug_assert_eq!("a".is_empty(), false, "tadam {}", 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:57:5
|
LL | debug_assert_eq!("a".is_empty(), false, "tadam {}", true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: used `debug_assert_eq!` with a literal bool
--> $DIR/bool_assert_comparison.rs:58:5
|
LL | debug_assert_eq!(false, "a".is_empty(), "tadam {}", true);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `debug_assert!(..)`
error: aborting due to 18 previous errors

View File

@ -206,4 +206,18 @@ fn fp_test() {
}
}
fn fp_if_let_issue7054() {
// This shouldn't trigger the lint
let string;
let _x = if let true = true {
""
} else if true {
string = "x".to_owned();
&string
} else {
string = "y".to_owned();
&string
};
}
fn main() {}

View File

@ -100,4 +100,15 @@ fn check_if_same_than_else_mask() {
}
}
#[allow(clippy::vec_init_then_push)]
fn pf_local_with_inferred_type_issue7053() {
if true {
let mut v = Vec::new();
v.push(0);
} else {
let mut v = Vec::new();
v.push("");
};
}
fn main() {}

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