diff options
| author | flip1995 <philipp.krones@embecosm.com> | 2021-03-12 15:30:50 +0100 |
|---|---|---|
| committer | flip1995 <philipp.krones@embecosm.com> | 2021-03-12 15:30:50 +0100 |
| commit | f2f2a005b4efd3e44ac6a02ea2b9660d28401679 (patch) | |
| tree | c4ece65dffee2aa79eaa3b7f190765a95055f815 /clippy_lints/src | |
| parent | 36a27ecaacad74f69b21a12bc66b826f11f2d44e (diff) | |
| download | rust-f2f2a005b4efd3e44ac6a02ea2b9660d28401679.tar.gz rust-f2f2a005b4efd3e44ac6a02ea2b9660d28401679.zip | |
Merge commit '6ed6f1e6a1a8f414ba7e6d9b8222e7e5a1686e42' into clippyup
Diffstat (limited to 'clippy_lints/src')
155 files changed, 9051 insertions, 7619 deletions
diff --git a/clippy_lints/src/assign_ops.rs b/clippy_lints/src/assign_ops.rs index b3185b88840..e13f62d0428 100644 --- a/clippy_lints/src/assign_ops.rs +++ b/clippy_lints/src/assign_ops.rs @@ -209,7 +209,7 @@ fn lint_misrefactored_assign_op( diag.span_suggestion( expr.span, &format!( - "Did you mean `{} = {} {} {}` or `{}`? Consider replacing it with", + "did you mean `{} = {} {} {}` or `{}`? Consider replacing it with", snip_a, snip_a, op.node.as_str(), diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index 88d9d3b5a26..869a5c28d05 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -50,8 +50,7 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { let body_id = BodyId { hir_id: body.value.hir_id, }; - let def_id = cx.tcx.hir().body_owner_def_id(body_id); - let typeck_results = cx.tcx.typeck(def_id); + let typeck_results = cx.tcx.typeck_body(body_id); let expr_ty = typeck_results.expr_ty(&body.value); if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 78f0846e88e..6250810bc42 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -640,7 +640,7 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) { diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); if !unix_suggested && is_unix(os) { - diag.help("Did you mean `unix`?"); + diag.help("did you mean `unix`?"); unix_suggested = true; } } diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index ae64c688744..14b6a156c62 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -97,8 +97,7 @@ impl LateLintPass<'_> for AwaitHolding { let body_id = BodyId { hir_id: body.value.hir_id, }; - let def_id = cx.tcx.hir().body_owner_def_id(body_id); - let typeck_results = cx.tcx.typeck(def_id); + let typeck_results = cx.tcx.typeck_body(body_id); check_interior_types( cx, &typeck_results.generator_interior_types.as_ref().skip_binder(), @@ -116,20 +115,20 @@ fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorType cx, AWAIT_HOLDING_LOCK, ty_cause.span, - "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.", + "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await", ty_cause.scope_span.or(Some(span)), "these are all the await points this lock is held through", ); } if is_refcell_ref(cx, adt.did) { span_lint_and_note( - cx, - AWAIT_HOLDING_REFCELL_REF, - ty_cause.span, - "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await.", - ty_cause.scope_span.or(Some(span)), - "these are all the await points this ref is held through", - ); + cx, + AWAIT_HOLDING_REFCELL_REF, + ty_cause.span, + "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await", + ty_cause.scope_span.or(Some(span)), + "these are all the await points this ref is held through", + ); } } } diff --git a/clippy_lints/src/bytecount.rs b/clippy_lints/src/bytecount.rs index b8828719f62..eb5dc7ceecd 100644 --- a/clippy_lints/src/bytecount.rs +++ b/clippy_lints/src/bytecount.rs @@ -28,7 +28,7 @@ declare_clippy_lint! { /// &vec.iter().filter(|x| **x == 0u8).count(); // use bytecount::count instead /// ``` pub NAIVE_BYTECOUNT, - perf, + pedantic, "use of naive `<slice>.filter(|&x| x == y).count()` to count byte values" } diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs index 6969ac949d8..b15fe65352a 100644 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -1,12 +1,11 @@ -use crate::utils::paths::STRING; -use crate::utils::{match_def_path, span_lint_and_help}; +use crate::utils::span_lint_and_help; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind, PathSegment}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{source_map::Spanned, Span}; +use rustc_span::{source_map::Spanned, symbol::sym, Span}; declare_clippy_lint! { /// **What it does:** @@ -59,7 +58,7 @@ fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: & return Some(span); }, ty::Adt(&ty::AdtDef { did, .. }, _) => { - if match_def_path(ctx, did, &STRING) { + if ctx.tcx.is_diagnostic_item(sym::string_type, did) { return Some(span); } }, diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs new file mode 100644 index 00000000000..478832a5164 --- /dev/null +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -0,0 +1,87 @@ +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{in_constant, is_isize_or_usize, snippet_opt, span_lint_and_sugg}; + +use super::{utils, CAST_LOSSLESS}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !should_lint(cx, expr, cast_from, cast_to) { + return; + } + + // The suggestion is to use a function call, so if the original expression + // has parens on the outside, they are no longer needed. + let mut applicability = Applicability::MachineApplicable; + let opt = snippet_opt(cx, cast_op.span); + let sugg = opt.as_ref().map_or_else( + || { + applicability = Applicability::HasPlaceholders; + ".." + }, + |snip| { + if should_strip_parens(cast_op, snip) { + &snip[1..snip.len() - 1] + } else { + snip.as_str() + } + }, + ); + + span_lint_and_sugg( + cx, + CAST_LOSSLESS, + expr.span, + &format!( + "casting `{}` to `{}` may become silently lossy if you later change the type", + cast_from, cast_to + ), + "try", + format!("{}::from({})", cast_to, sugg), + applicability, + ); +} + +fn should_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { + // Do not suggest using From in consts/statics until it is valid to do so (see #2267). + if in_constant(cx, expr.hir_id) { + return false; + } + + match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + !is_isize_or_usize(cast_from) + && !is_isize_or_usize(cast_to) + && from_nbits < to_nbits + && !cast_signed_to_unsigned + }, + + (true, false) => { + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { + 32 + } else { + 64 + }; + from_nbits < to_nbits + }, + + (_, _) => { + matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) + }, + } +} + +fn should_strip_parens(cast_expr: &Expr<'_>, snip: &str) -> bool { + if let ExprKind::Binary(_, _, _) = cast_expr.kind { + if snip.starts_with('(') && snip.ends_with(')') { + return true; + } + } + false +} diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs new file mode 100644 index 00000000000..33b06b8fe7c --- /dev/null +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -0,0 +1,54 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_POSSIBLE_TRUNCATION}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let msg = match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + (true, true) | (false, false) => (to_nbits < from_nbits, ""), + (true, false) => ( + to_nbits <= 32, + if to_nbits == 32 { + " on targets with 64-bit wide pointers" + } else { + "" + }, + ), + (false, true) => (from_nbits == 64, " on targets with 32-bit wide pointers"), + }; + + if !should_lint { + return; + } + + format!( + "casting `{}` to `{}` may truncate the value{}", + cast_from, cast_to, suffix, + ) + }, + + (false, true) => { + format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to) + }, + + (_, _) => { + if matches!(cast_from.kind(), &ty::Float(FloatTy::F64)) + && matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) + { + "casting `f64` to `f32` may truncate the value".to_string() + } else { + return; + } + }, + }; + + span_lint(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg); +} diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs new file mode 100644 index 00000000000..56d301ed3e1 --- /dev/null +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -0,0 +1,44 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_POSSIBLE_WRAP}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !(cast_from.is_integral() && cast_to.is_integral()) { + return; + } + + let arch_64_suffix = " on targets with 64-bit wide pointers"; + let arch_32_suffix = " on targets with 32-bit wide pointers"; + let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed(); + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { + (true, true) | (false, false) => (to_nbits == from_nbits && cast_unsigned_to_signed, ""), + (true, false) => (to_nbits <= 32 && cast_unsigned_to_signed, arch_32_suffix), + (false, true) => ( + cast_unsigned_to_signed, + if from_nbits == 64 { + arch_64_suffix + } else { + arch_32_suffix + }, + ), + }; + + if should_lint { + span_lint( + cx, + CAST_POSSIBLE_WRAP, + expr.span, + &format!( + "casting `{}` to `{}` may wrap around the value{}", + cast_from, cast_to, suffix, + ), + ); + } +} diff --git a/clippy_lints/src/casts/cast_precision_loss.rs b/clippy_lints/src/casts/cast_precision_loss.rs new file mode 100644 index 00000000000..a1c3900ce1f --- /dev/null +++ b/clippy_lints/src/casts/cast_precision_loss.rs @@ -0,0 +1,51 @@ +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, FloatTy, Ty}; + +use crate::utils::{is_isize_or_usize, span_lint}; + +use super::{utils, CAST_PRECISION_LOSS}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if !cast_from.is_integral() || cast_to.is_integral() { + return; + } + + let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { + 32 + } else { + 64 + }; + + if !(is_isize_or_usize(cast_from) || from_nbits >= to_nbits) { + return; + } + + let cast_to_f64 = to_nbits == 64; + let mantissa_nbits = if cast_to_f64 { 52 } else { 23 }; + let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64; + let arch_dependent_str = "on targets with 64-bit wide pointers "; + let from_nbits_str = if arch_dependent { + "64".to_owned() + } else if is_isize_or_usize(cast_from) { + "32 or 64".to_owned() + } else { + utils::int_ty_to_nbits(cast_from, cx.tcx).to_string() + }; + + span_lint( + cx, + CAST_PRECISION_LOSS, + expr.span, + &format!( + "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \ + but `{1}`'s mantissa is only {4} bits wide)", + cast_from, + if cast_to_f64 { "f64" } else { "f32" }, + if arch_dependent { arch_dependent_str } else { "" }, + from_nbits_str, + mantissa_nbits + ), + ); +} diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs new file mode 100644 index 00000000000..87fb5557be0 --- /dev/null +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -0,0 +1,81 @@ +use rustc_hir::{Expr, ExprKind, GenericArg}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; + +use if_chain::if_chain; + +use crate::utils::{is_hir_ty_cfg_dependant, span_lint}; + +use super::CAST_PTR_ALIGNMENT; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to) { + return; + } + let (cast_from, cast_to) = ( + cx.typeck_results().expr_ty(cast_expr), + cx.typeck_results().expr_ty(expr), + ); + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); + } else if let ExprKind::MethodCall(method_path, _, args, _) = expr.kind { + if_chain! { + if method_path.ident.name == sym!(cast); + if let Some(generic_args) = method_path.args; + if let [GenericArg::Type(cast_to)] = generic_args.args; + // There probably is no obvious reason to do this, just to be consistent with `as` cases. + if !is_hir_ty_cfg_dependant(cx, cast_to); + then { + let (cast_from, cast_to) = + (cx.typeck_results().expr_ty(&args[0]), cx.typeck_results().expr_ty(expr)); + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); + } + } + } +} + +fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + if_chain! { + if let ty::RawPtr(from_ptr_ty) = &cast_from.kind(); + if let ty::RawPtr(to_ptr_ty) = &cast_to.kind(); + if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty); + if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty); + if from_layout.align.abi < to_layout.align.abi; + // with c_void, we inherently need to trust the user + if !is_c_void(cx, from_ptr_ty.ty); + // when casting from a ZST, we don't know enough to properly lint + if !from_layout.is_zst(); + then { + span_lint( + cx, + CAST_PTR_ALIGNMENT, + expr.span, + &format!( + "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)", + cast_from, + cast_to, + from_layout.align.abi.bytes(), + to_layout.align.abi.bytes(), + ), + ); + } + } +} + +/// Check if the given type is either `core::ffi::c_void` or +/// one of the platform specific `libc::<platform>::c_void` of libc. +fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, _) = ty.kind() { + let names = cx.get_def_path(adt.did); + + if names.is_empty() { + return false; + } + if names[0] == sym::libc || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) { + return true; + } + } + false +} diff --git a/clippy_lints/src/casts/cast_ref_to_mut.rs b/clippy_lints/src/casts/cast_ref_to_mut.rs new file mode 100644 index 00000000000..3fdc1c6168b --- /dev/null +++ b/clippy_lints/src/casts/cast_ref_to_mut.rs @@ -0,0 +1,28 @@ +use rustc_hir::{Expr, ExprKind, MutTy, Mutability, TyKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use if_chain::if_chain; + +use crate::utils::span_lint; + +use super::CAST_REF_TO_MUT; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Unary(UnOp::Deref, e) = &expr.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind; + if let ExprKind::Cast(e, t) = &e.kind; + if let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind; + if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind(); + then { + span_lint( + cx, + CAST_REF_TO_MUT, + expr.span, + "casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell`", + ); + } + } +} diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs new file mode 100644 index 00000000000..9656fbebd77 --- /dev/null +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -0,0 +1,70 @@ +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use if_chain::if_chain; + +use crate::consts::{constant, Constant}; +use crate::utils::{method_chain_args, sext, span_lint}; + +use super::CAST_SIGN_LOSS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + if should_lint(cx, cast_op, cast_from, cast_to) { + span_lint( + cx, + CAST_SIGN_LOSS, + expr.span, + &format!( + "casting `{}` to `{}` may lose the sign of the value", + cast_from, cast_to + ), + ); + } +} + +fn should_lint(cx: &LateContext<'_>, cast_op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) -> bool { + match (cast_from.is_integral(), cast_to.is_integral()) { + (true, true) => { + if !cast_from.is_signed() || cast_to.is_signed() { + return false; + } + + // Don't lint for positive constants. + let const_val = constant(cx, &cx.typeck_results(), cast_op); + if_chain! { + if let Some((Constant::Int(n), _)) = const_val; + if let ty::Int(ity) = *cast_from.kind(); + if sext(cx.tcx, n, ity) >= 0; + then { + return false; + } + } + + // Don't lint for the result of methods that always return non-negative values. + if let ExprKind::MethodCall(ref path, _, _, _) = cast_op.kind { + let mut method_name = path.ident.name.as_str(); + let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; + + if_chain! { + if method_name == "unwrap"; + if let Some(arglist) = method_chain_args(cast_op, &["unwrap"]); + if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind; + then { + method_name = inner_path.ident.name.as_str(); + } + } + + if allowed_methods.iter().any(|&name| method_name == name) { + return false; + } + } + + true + }, + + (false, true) => !cast_to.is_signed(), + + (_, _) => false, + } +} diff --git a/clippy_lints/src/casts/char_lit_as_u8.rs b/clippy_lints/src/casts/char_lit_as_u8.rs new file mode 100644 index 00000000000..ccaad1b8f2a --- /dev/null +++ b/clippy_lints/src/casts/char_lit_as_u8.rs @@ -0,0 +1,42 @@ +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, UintTy}; + +use if_chain::if_chain; + +use crate::utils::{snippet_with_applicability, span_lint_and_then}; + +use super::CHAR_LIT_AS_U8; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Cast(e, _) = &expr.kind; + if let ExprKind::Lit(l) = &e.kind; + if let LitKind::Char(c) = l.node; + if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind(); + then { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability); + + span_lint_and_then( + cx, + CHAR_LIT_AS_U8, + expr.span, + "casting a character literal to `u8` truncates", + |diag| { + diag.note("`char` is four bytes wide, but `u8` is a single byte"); + + if c.is_ascii() { + diag.span_suggestion( + expr.span, + "use a byte literal instead", + format!("b{}", snippet), + applicability, + ); + } + }); + } + } +} diff --git a/clippy_lints/src/casts/fn_to_numeric_cast.rs b/clippy_lints/src/casts/fn_to_numeric_cast.rs new file mode 100644 index 00000000000..a8d508585b5 --- /dev/null +++ b/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -0,0 +1,37 @@ +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, UintTy}; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; + +use super::{utils, FN_TO_NUMERIC_CAST}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + // We only want to check casts to `ty::Uint` or `ty::Int` + match cast_to.kind() { + ty::Uint(_) | ty::Int(..) => { /* continue on */ }, + _ => return, + } + + match cast_from.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + + if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && (*cast_to.kind() != ty::Uint(UintTy::Usize)) { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST, + expr.span, + &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } + }, + _ => {}, + } +} diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs new file mode 100644 index 00000000000..0085c7b27b2 --- /dev/null +++ b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -0,0 +1,39 @@ +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; + +use super::{utils, FN_TO_NUMERIC_CAST_WITH_TRUNCATION}; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { + // We only want to check casts to `ty::Uint` or `ty::Int` + match cast_to.kind() { + ty::Uint(_) | ty::Int(..) => { /* continue on */ }, + _ => return, + } + match cast_from.kind() { + ty::FnDef(..) | ty::FnPtr(_) => { + let mut applicability = Applicability::MaybeIncorrect; + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); + + let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); + if to_nbits < cx.tcx.data_layout.pointer_size.bits() { + span_lint_and_sugg( + cx, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + expr.span, + &format!( + "casting function pointer `{}` to `{}`, which truncates the value", + from_snippet, cast_to + ), + "try", + format!("{} as usize", from_snippet), + applicability, + ); + } + }, + _ => {}, + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs new file mode 100644 index 00000000000..b726bd75f1d --- /dev/null +++ b/clippy_lints/src/casts/mod.rs @@ -0,0 +1,407 @@ +mod cast_lossless; +mod cast_possible_truncation; +mod cast_possible_wrap; +mod cast_precision_loss; +mod cast_ptr_alignment; +mod cast_ref_to_mut; +mod cast_sign_loss; +mod char_lit_as_u8; +mod fn_to_numeric_cast; +mod fn_to_numeric_cast_with_truncation; +mod ptr_as_ptr; +mod unnecessary_cast; +mod utils; + +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + +use crate::utils::is_hir_ty_cfg_dependant; + +declare_clippy_lint! { + /// **What it does:** Checks for casts from any numerical to a float type where + /// the receiving type cannot store all values from the original type without + /// rounding errors. This possible rounding is to be expected, so this lint is + /// `Allow` by default. + /// + /// Basically, this warns on casting any integer with 32 or more bits to `f32` + /// or any 64-bit integer to `f64`. + /// + /// **Why is this bad?** It's not bad at all. But in some applications it can be + /// helpful to know where precision loss can take place. This lint can help find + /// those places in the code. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = u64::MAX; + /// x as f64; + /// ``` + pub CAST_PRECISION_LOSS, + pedantic, + "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from a signed to an unsigned numerical + /// type. In this case, negative values wrap around to large positive values, + /// which can be quite surprising in practice. However, as the cast works as + /// defined, this lint is `Allow` by default. + /// + /// **Why is this bad?** Possibly surprising results. You can activate this lint + /// as a one-time check to see where numerical wrapping can arise. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let y: i8 = -1; + /// y as u128; // will return 18446744073709551615 + /// ``` + pub CAST_SIGN_LOSS, + pedantic, + "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// truncate large values. This is expected behavior, so the cast is `Allow` by + /// default. + /// + /// **Why is this bad?** In some problem domains, it is good practice to avoid + /// truncation. This lint can be activated to help assess where additional + /// checks could be beneficial. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u8(x: u64) -> u8 { + /// x as u8 + /// } + /// ``` + pub CAST_POSSIBLE_TRUNCATION, + pedantic, + "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts from an unsigned type to a signed type of + /// the same size. Performing such a cast is a 'no-op' for the compiler, + /// i.e., nothing is changed at the bit level, and the binary representation of + /// the value is reinterpreted. This can cause wrapping if the value is too big + /// for the target signed type. However, the cast works as defined, so this lint + /// is `Allow` by default. + /// + /// **Why is this bad?** While such a cast is not bad in itself, the results can + /// be surprising when this is not the intended behavior, as demonstrated by the + /// example below. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// u32::MAX as i32; // will yield a value of `-1` + /// ``` + pub CAST_POSSIBLE_WRAP, + pedantic, + "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts between numerical types that may + /// be replaced by safe conversion functions. + /// + /// **Why is this bad?** Rust's `as` keyword will perform many kinds of + /// conversions, including silently lossy conversions. Conversion functions such + /// as `i32::from` will only perform lossless conversions. Using the conversion + /// functions prevents conversions from turning into silent lossy conversions if + /// the types of the input expressions ever change, and make it easier for + /// people reading the code to know that the conversion is lossless. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// x as u64 + /// } + /// ``` + /// + /// Using `::from` would look like this: + /// + /// ```rust + /// fn as_u64(x: u8) -> u64 { + /// u64::from(x) + /// } + /// ``` + pub CAST_LOSSLESS, + pedantic, + "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts to the same type, casts of int literals to integer types + /// and casts of float literals to float types. + /// + /// **Why is this bad?** It's just unnecessary. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; + /// ``` + /// + /// Better: + /// + /// ```rust + /// let _ = 2_i32; + /// let _ = 0.5_f32; + /// ``` + pub UNNECESSARY_CAST, + complexity, + "cast to the same type, e.g., `x as i32` where `x: i32`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts, using `as` or `pointer::cast`, + /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer + /// + /// **Why is this bad?** Dereferencing the resulting pointer may be undefined + /// behavior. + /// + /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar + /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like + /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. + /// + /// **Example:** + /// ```rust + /// let _ = (&1u8 as *const u8) as *const u16; + /// let _ = (&mut 1u8 as *mut u8) as *mut u16; + /// + /// (&1u8 as *const u8).cast::<u16>(); + /// (&mut 1u8 as *mut u8).cast::<u16>(); + /// ``` + pub CAST_PTR_ALIGNMENT, + pedantic, + "cast from a pointer to a more-strictly-aligned pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of function pointers to something other than usize + /// + /// **Why is this bad?** + /// Casting a function pointer to anything other than usize/isize is not portable across + /// architectures, because you end up losing bits if the target type is too small or end up with a + /// bunch of extra bits that waste space and add more instructions to the final binary than + /// strictly necessary for the problem + /// + /// Casting to isize also doesn't make sense since there are no signed addresses. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fun() -> i32 { 1 } + /// let a = fun as i64; + /// + /// // Good + /// fn fun2() -> i32 { 1 } + /// let a = fun2 as usize; + /// ``` + pub FN_TO_NUMERIC_CAST, + style, + "casting a function pointer to a numeric type other than usize" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of a function pointer to a numeric type not wide enough to + /// store address. + /// + /// **Why is this bad?** + /// Such a cast discards some bits of the function's address. If this is intended, it would be more + /// clearly expressed by casting to usize first, then casting the usize to the intended type (with + /// a comment) to perform the truncation. + /// + /// **Example** + /// + /// ```rust + /// // Bad + /// fn fn1() -> i16 { + /// 1 + /// }; + /// let _ = fn1 as i32; + /// + /// // Better: Cast to usize first, then comment with the reason for the truncation + /// fn fn2() -> i16 { + /// 1 + /// }; + /// let fn_ptr = fn2 as usize; + /// let fn_ptr_truncated = fn_ptr as i32; + /// ``` + pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + style, + "casting a function pointer to a numeric type not wide enough to store the address" +} + +declare_clippy_lint! { + /// **What it does:** Checks for casts of `&T` to `&mut T` anywhere in the code. + /// + /// **Why is this bad?** It’s basically guaranteed to be undefined behaviour. + /// `UnsafeCell` is the only way to obtain aliasable data that is considered + /// mutable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// fn x(r: &i32) { + /// unsafe { + /// *(r as *const _ as *mut _) += 1; + /// } + /// } + /// ``` + /// + /// Instead consider using interior mutability types. + /// + /// ```rust + /// use std::cell::UnsafeCell; + /// + /// fn x(r: &UnsafeCell<i32>) { + /// unsafe { + /// *r.get() += 1; + /// } + /// } + /// ``` + pub CAST_REF_TO_MUT, + correctness, + "a cast of reference to a mutable pointer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for expressions where a character literal is cast + /// to `u8` and suggests using a byte literal instead. + /// + /// **Why is this bad?** In general, casting values to smaller types is + /// error-prone and should be avoided where possible. In the particular case of + /// converting a character literal to u8, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter + /// than `'a' as u8`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// 'x' as u8 + /// ``` + /// + /// A better version, using the byte literal: + /// + /// ```rust,ignore + /// b'x' + /// ``` + pub CHAR_LIT_AS_U8, + complexity, + "casting a character literal to `u8` truncates" +} + +declare_clippy_lint! { + /// **What it does:** + /// Checks for `as` casts between raw pointers without changing its mutability, + /// namely `*const T` to `*const U` and `*mut T` to `*mut U`. + /// + /// **Why is this bad?** + /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because + /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let ptr: *const u32 = &42_u32; + /// let mut_ptr: *mut u32 = &mut 42_u32; + /// let _ = ptr as *const i32; + /// let _ = mut_ptr as *mut i32; + /// ``` + /// Use instead: + /// ```rust + /// let ptr: *const u32 = &42_u32; + /// let mut_ptr: *mut u32 = &mut 42_u32; + /// let _ = ptr.cast::<i32>(); + /// let _ = mut_ptr.cast::<i32>(); + /// ``` + pub PTR_AS_PTR, + pedantic, + "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" +} + +pub struct Casts { + msrv: Option<RustcVersion>, +} + +impl Casts { + #[must_use] + pub fn new(msrv: Option<RustcVersion>) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(Casts => [ + CAST_PRECISION_LOSS, + CAST_SIGN_LOSS, + CAST_POSSIBLE_TRUNCATION, + CAST_POSSIBLE_WRAP, + CAST_LOSSLESS, + CAST_REF_TO_MUT, + CAST_PTR_ALIGNMENT, + UNNECESSARY_CAST, + FN_TO_NUMERIC_CAST, + FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + CHAR_LIT_AS_U8, + PTR_AS_PTR, +]); + +impl<'tcx> LateLintPass<'tcx> for Casts { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let ExprKind::Cast(ref cast_expr, cast_to) = expr.kind { + if is_hir_ty_cfg_dependant(cx, cast_to) { + return; + } + let (cast_from, cast_to) = ( + cx.typeck_results().expr_ty(cast_expr), + cx.typeck_results().expr_ty(expr), + ); + + if unnecessary_cast::check(cx, expr, cast_expr, cast_from, cast_to) { + return; + } + + fn_to_numeric_cast::check(cx, expr, cast_expr, cast_from, cast_to); + fn_to_numeric_cast_with_truncation::check(cx, expr, cast_expr, cast_from, cast_to); + if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { + cast_possible_truncation::check(cx, expr, cast_from, cast_to); + cast_possible_wrap::check(cx, expr, cast_from, cast_to); + cast_precision_loss::check(cx, expr, cast_from, cast_to); + cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to); + cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); + } + } + + cast_ref_to_mut::check(cx, expr); + cast_ptr_alignment::check(cx, expr); + char_lit_as_u8::check(cx, expr); + ptr_as_ptr::check(cx, expr, &self.msrv); + } + + extract_msrv_attr!(LateContext); +} diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs new file mode 100644 index 00000000000..abfbadf3642 --- /dev/null +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -0,0 +1,52 @@ +use std::borrow::Cow; + +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, TypeAndMut}; +use rustc_semver::RustcVersion; + +use if_chain::if_chain; + +use crate::utils::sugg::Sugg; +use crate::utils::{meets_msrv, span_lint_and_sugg}; + +use super::PTR_AS_PTR; + +const PTR_AS_PTR_MSRV: RustcVersion = RustcVersion::new(1, 38, 0); + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: &Option<RustcVersion>) { + if !meets_msrv(msrv.as_ref(), &PTR_AS_PTR_MSRV) { + return; + } + + if_chain! { + if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind; + let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)); + if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind(); + if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind(); + if matches!((from_mutbl, to_mutbl), + (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)); + // The `U` in `pointer::cast` have to be `Sized` + // as explained here: https://github.com/rust-lang/rust/issues/60602. + if to_pointee_ty.is_sized(cx.tcx.at(expr.span), cx.param_env); + then { + let mut applicability = Applicability::MachineApplicable; + let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability); + let turbofish = match &cast_to_hir_ty.kind { + TyKind::Infer => Cow::Borrowed(""), + TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""), + _ => Cow::Owned(format!("::<{}>", to_pointee_ty)), + }; + span_lint_and_sugg( + cx, + PTR_AS_PTR, + expr.span, + "`as` casting between raw pointers without changing its mutability", + "try `pointer::cast`, a safer alternative", + format!("{}.cast{}()", cast_expr_sugg.maybe_par(), turbofish), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs new file mode 100644 index 00000000000..fa2a07ef1da --- /dev/null +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -0,0 +1,106 @@ +use rustc_ast::{LitFloatType, LitIntType, LitKind}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Lit, UnOp}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, FloatTy, InferTy, Ty}; + +use if_chain::if_chain; + +use crate::utils::{numeric_literal::NumericLiteral, snippet_opt, span_lint, span_lint_and_sugg}; + +use super::UNNECESSARY_CAST; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + cast_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to: Ty<'_>, +) -> bool { + if let Some(lit) = get_numeric_literal(cast_expr) { + let literal_str = snippet_opt(cx, cast_expr.span).unwrap_or_default(); + + if_chain! { + if let LitKind::Int(n, _) = lit.node; + if let Some(src) = snippet_opt(cx, lit.span); + if cast_to.is_floating_point(); + if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); + let from_nbits = 128 - n.leading_zeros(); + let to_nbits = fp_ty_mantissa_nbits(cast_to); + if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); + then { + let literal_str = if is_unary_neg(cast_expr) { format!("-{}", num_lit.integer) } else { num_lit.integer.into() }; + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + return true + } + } + + match lit.node { + LitKind::Int(_, LitIntType::Unsuffixed) if cast_to.is_integral() => { + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, + LitKind::Float(_, LitFloatType::Unsuffixed) if cast_to.is_floating_point() => { + lint_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, + LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {}, + _ => { + if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) { + span_lint( + cx, + UNNECESSARY_CAST, + expr.span, + &format!( + "casting to the same type is unnecessary (`{}` -> `{}`)", + cast_from, cast_to + ), + ); + return true; + } + }, + } + } + + false +} + +fn lint_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; + span_lint_and_sugg( + cx, + UNNECESSARY_CAST, + expr.span, + &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), + "try", + format!("{}_{}", literal_str.trim_end_matches('.'), cast_to), + Applicability::MachineApplicable, + ); +} + +fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> { + match expr.kind { + ExprKind::Lit(ref lit) => Some(lit), + ExprKind::Unary(UnOp::Neg, e) => { + if let ExprKind::Lit(ref lit) = e.kind { + Some(lit) + } else { + None + } + }, + _ => None, + } +} + +/// Returns the mantissa bits wide of a fp type. +/// Will return 0 if the type is not a fp +fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 { + match typ.kind() { + ty::Float(FloatTy::F32) => 23, + ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52, + _ => 0, + } +} + +fn is_unary_neg(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Unary(UnOp::Neg, _)) +} diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs new file mode 100644 index 00000000000..00fd0b3473b --- /dev/null +++ b/clippy_lints/src/casts/utils.rs @@ -0,0 +1,25 @@ +use rustc_middle::ty::{self, IntTy, Ty, TyCtxt, UintTy}; + +/// Returns the size in bits of an integral type. +/// Will return 0 if the type is not an int or uint variant +pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { + match typ.kind() { + ty::Int(i) => match i { + IntTy::Isize => tcx.data_layout.pointer_size.bits(), + IntTy::I8 => 8, + IntTy::I16 => 16, + IntTy::I32 => 32, + IntTy::I64 => 64, + IntTy::I128 => 128, + }, + ty::Uint(i) => match i { + UintTy::Usize => tcx.data_layout.pointer_size.bits(), + UintTy::U8 => 8, + UintTy::U16 => 16, + UintTy::U32 => 32, + UintTy::U64 => 64, + UintTy::U128 => 128, + }, + _ => 0, + } +} diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 90d31dece13..e309db25995 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -117,7 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { expr.span, "`if` chain can be rewritten with `match`", None, - "Consider rewriting the `if` chain to use `cmp` and `match`.", + "consider rewriting the `if` chain to use `cmp` and `match`", ) } } diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 6ace9aa6bdf..369efacc9bc 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -130,10 +130,9 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { } }, - ExprKind::Struct(qpath, fields, base) => { + ExprKind::Struct(_, fields, base) => { if_chain! { - if let Some(def_id) = self.cx.qpath_res(qpath, expr.hir_id).opt_def_id(); - let ty = self.cx.tcx.type_of(def_id); + let ty = self.cx.typeck_results().expr_ty(expr); if let Some(adt_def) = ty.ty_adt_def(); if adt_def.is_struct(); if let Some(variant) = adt_def.variants.iter().next(); diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 66cf6682f85..6d3094ed6bf 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,6 +1,6 @@ use crate::utils::paths; use crate::utils::{ - get_trait_def_id, is_allowed, is_automatically_derived, is_copy, match_def_path, match_path, span_lint_and_help, + get_trait_def_id, is_allowed, is_automatically_derived, is_copy, match_def_path, span_lint_and_help, span_lint_and_note, span_lint_and_then, }; use if_chain::if_chain; @@ -294,7 +294,12 @@ fn check_ord_partial_ord<'tcx>( /// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint. fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) { - if match_path(&trait_ref.path, &paths::CLONE_TRAIT) { + if cx + .tcx + .lang_items() + .clone_trait() + .map_or(false, |id| Some(id) == trait_ref.trait_def_id()) + { if !is_copy(cx, ty) { return; } diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 23c99e45ca7..90b02d52f8a 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -329,9 +329,9 @@ fn lint_for_missing_headers<'tcx>( if_chain! { if let Some(body_id) = body_id; if let Some(future) = cx.tcx.lang_items().future_trait(); - let def_id = cx.tcx.hir().body_owner_def_id(body_id); - let mir = cx.tcx.optimized_mir(def_id.to_def_id()); - let ret_ty = mir.return_ty(); + let typeck = cx.tcx.typeck_body(body_id); + let body = cx.tcx.hir().body(body_id); + let ret_ty = typeck.expr_ty(&body.value); if implements_trait(cx, ret_ty, future, &[]); if let ty::Opaque(_, subs) = ret_ty.kind(); if let Some(gen) = subs.types().next(); diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index a84f9c46287..2aea00d883c 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -98,13 +98,13 @@ declare_clippy_lint! { } const DROP_REF_SUMMARY: &str = "calls to `std::mem::drop` with a reference instead of an owned value. \ - Dropping a reference does nothing."; + Dropping a reference does nothing"; const FORGET_REF_SUMMARY: &str = "calls to `std::mem::forget` with a reference instead of an owned value. \ - Forgetting a reference does nothing."; + Forgetting a reference does nothing"; const DROP_COPY_SUMMARY: &str = "calls to `std::mem::drop` with a value that implements `Copy`. \ - Dropping a copy leaves the original intact."; + Dropping a copy leaves the original intact"; const FORGET_COPY_SUMMARY: &str = "calls to `std::mem::forget` with a value that implements `Copy`. \ - Forgetting a copy leaves the original intact."; + Forgetting a copy leaves the original intact"; declare_lint_pass!(DropForgetRef => [DROP_REF, FORGET_REF, DROP_COPY, FORGET_COPY]); diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 1a722d39f73..c461732fd36 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -10,6 +10,8 @@ use crate::utils::{ implements_trait, is_adjusted, iter_input_pats, snippet_opt, span_lint_and_sugg, span_lint_and_then, type_is_unsafe_function, }; +use clippy_utils::higher; +use clippy_utils::higher::VecArgs; declare_clippy_lint! { /// **What it does:** Checks for closures which just call another function where @@ -74,7 +76,10 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { match expr.kind { ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) => { for arg in args { - check_closure(cx, arg) + // skip `foo(macro!())` + if arg.span.ctxt() == expr.span.ctxt() { + check_closure(cx, arg) + } } }, _ => (), @@ -87,6 +92,23 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) { let body = cx.tcx.hir().body(eid); let ex = &body.value; + if ex.span.ctxt() != expr.span.ctxt() { + if let Some(VecArgs::Vec(&[])) = higher::vec_macro(cx, ex) { + // replace `|| vec![]` with `Vec::new` + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE, + expr.span, + "redundant closure", + "replace the closure with `Vec::new`", + "std::vec::Vec::new".into(), + Applicability::MachineApplicable, + ); + } + // skip `foo(|| macro!())` + return; + } + if_chain!( if let ExprKind::Call(ref caller, ref args) = ex.kind; @@ -107,11 +129,11 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) { if compare_inputs(&mut iter_input_pats(decl, body), &mut args.iter()); then { - span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure found", |diag| { + span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| { if let Some(snippet) = snippet_opt(cx, caller.span) { diag.span_suggestion( expr.span, - "remove closure as shown", + "replace the closure with the function itself", snippet, Applicability::MachineApplicable, ); @@ -141,8 +163,8 @@ fn check_closure(cx: &LateContext<'_>, expr: &Expr<'_>) { cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, - "redundant closure found", - "remove closure as shown", + "redundant closure", + "replace the closure with the method itself", format!("{}::{}", name, path.ident.name), Applicability::MachineApplicable, ); diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 6d522c7ef33..f466dddc13c 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -133,7 +133,7 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[h move |diag| { diag.help( "`From` is intended for infallible conversions only. \ - Use `TryFrom` if there's a possibility for the conversion to fail."); + Use `TryFrom` if there's a possibility for the conversion to fail"); diag.span_note(fpu.result, "potential failure(s)"); }); } diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index be646cbe4d0..8e256f34684 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -145,11 +145,7 @@ fn count_digits(s: &str) -> usize { .take_while(|c| *c != 'e' && *c != 'E') .fold(0, |count, c| { // leading zeros - if c == '0' && count == 0 { - count - } else { - count + 1 - } + if c == '0' && count == 0 { count } else { count + 1 } }) } diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 8e41e0e34da..fd6bf19db94 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -28,11 +28,11 @@ declare_clippy_lint! { /// ```rust /// /// // Bad - /// # let foo = "foo"; + /// let foo = "foo"; /// format!("{}", foo); /// /// // Good - /// format!("foo"); + /// foo.to_owned(); /// ``` pub USELESS_FORMAT, complexity, diff --git a/clippy_lints/src/functions.rs b/clippy_lints/src/functions.rs index 234cb0f53aa..c474db06fe3 100644 --- a/clippy_lints/src/functions.rs +++ b/clippy_lints/src/functions.rs @@ -317,9 +317,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { let attr = must_use_attr(attrs); if let Some(attr) = attr { check_needless_must_use(cx, &sig.decl, item.hir_id(), item.span, fn_header_span, attr); - } else if is_public - && !is_proc_macro(cx.sess(), attrs) - && trait_ref_of_method(cx, item.hir_id()).is_none() + } else if is_public && !is_proc_macro(cx.sess(), attrs) && trait_ref_of_method(cx, item.hir_id()).is_none() { check_must_use_candidate( cx, diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 109d90ff772..b4f814e1dcc 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,4 +1,4 @@ -use crate::utils::{fn_has_unsatisfiable_preds, match_panic_def_id, snippet_opt, span_lint_and_then}; +use crate::utils::{match_panic_def_id, snippet_opt, span_lint_and_then}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -133,19 +133,13 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { span: Span, _: HirId, ) { - let def_id = cx.tcx.hir().body_owner_def_id(body.id()); - - // Building MIR for `fn`s with unsatisfiable preds results in ICE. - if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { + if span.from_expansion() { return; } - - let mir = cx.tcx.optimized_mir(def_id.to_def_id()); - - // checking return type through MIR, HIR is not able to determine inferred closure return types - // make sure it's not a macro - if !mir.return_ty().is_unit() && !span.from_expansion() { - expr_match(cx, &body.value); + let body = cx.tcx.hir().body(body.id()); + if cx.typeck_results().expr_ty(&body.value).is_unit() { + return; } + expr_match(cx, &body.value); } } diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index c5afdf530eb..4f35e13c85a 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -66,8 +66,7 @@ impl LateLintPass<'_> for InconsistentStructConstructor { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if_chain! { if let ExprKind::Struct(qpath, fields, base) = expr.kind; - if let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id(); - let ty = cx.tcx.type_of(def_id); + let ty = cx.typeck_results().expr_ty(expr); if let Some(adt_def) = ty.ty_adt_def(); if adt_def.is_struct(); if let Some(variant) = adt_def.variants.iter().next(); diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 741195f3b10..c919ec097a2 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -132,13 +132,13 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { } let help_msg = match (range.start, range.end) { - (None, Some(_)) => "Consider using `.get(..n)`or `.get_mut(..n)` instead", - (Some(_), None) => "Consider using `.get(n..)` or .get_mut(n..)` instead", - (Some(_), Some(_)) => "Consider using `.get(n..m)` or `.get_mut(n..m)` instead", + (None, Some(_)) => "consider using `.get(..n)`or `.get_mut(..n)` instead", + (Some(_), None) => "consider using `.get(n..)` or .get_mut(n..)` instead", + (Some(_), Some(_)) => "consider using `.get(n..m)` or `.get_mut(n..m)` instead", (None, None) => return, // [..] is ok. }; - span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic.", None, help_msg); + span_lint_and_help(cx, INDEXING_SLICING, expr.span, "slicing may panic", None, help_msg); } else { // Catchall non-range index, i.e., [n] or [n << m] if let ty::Array(..) = ty.kind() { @@ -153,9 +153,9 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { cx, INDEXING_SLICING, expr.span, - "indexing may panic.", + "indexing may panic", None, - "Consider using `.get(n)` or `.get_mut(n)` instead", + "consider using `.get(n)` or `.get_mut(n)` instead", ); } } diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 129abd7d897..7040ac3191f 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -89,11 +89,7 @@ impl Finiteness { impl From<bool> for Finiteness { #[must_use] fn from(b: bool) -> Self { - if b { - Infinite - } else { - Finite - } + if b { Infinite } else { Finite } } } diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index a95321ea7e2..c1f3e1d9d68 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -139,7 +139,7 @@ fn show_lint(cx: &LateContext<'_>, item: &ImplItem<'_>) { self_type.to_string() ), None, - &format!("remove the inherent method from type `{}`", self_type.to_string()) + &format!("remove the inherent method from type `{}`", self_type.to_string()), ); } else { span_lint_and_help( diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs index 31181c10d23..39b4605e72f 100644 --- a/clippy_lints/src/integer_division.rs +++ b/clippy_lints/src/integer_division.rs @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for IntegerDivision { expr.span, "integer division", None, - "division of integers may cause loss of precision. consider using floats.", + "division of integers may cause loss of precision. consider using floats", ); } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index dab3e0565ca..1e1023b2743 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,11 +1,17 @@ -use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg}; +use crate::utils::{ + get_item_name, get_parent_as_impl, is_allowed, snippet_with_applicability, span_lint, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; -use rustc_hir::{AssocItemKind, BinOpKind, Expr, ExprKind, Impl, ImplItemRef, Item, ItemKind, TraitItemRef}; +use rustc_hir::{ + def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, + ItemKind, Mutability, Node, TraitItemRef, TyKind, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; +use rustc_middle::ty::{self, AssocKind, FnSig}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::{Span, Spanned, Symbol}; @@ -113,14 +119,38 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { return; } - match item.kind { - ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items), - ItemKind::Impl(Impl { - of_trait: None, - items: ref impl_items, - .. - }) => check_impl_items(cx, item, impl_items), - _ => (), + if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind { + check_trait_items(cx, item, trait_items); + } + } + + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { + if_chain! { + if item.ident.as_str() == "len"; + if let ImplItemKind::Fn(sig, _) = &item.kind; + if sig.decl.implicit_self.has_implicit_self(); + if cx.access_levels.is_exported(item.hir_id()); + if matches!(sig.decl.output, FnRetTy::Return(_)); + if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()); + if imp.of_trait.is_none(); + if let TyKind::Path(ty_path) = &imp.self_ty.kind; + if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id(); + if let Some(local_id) = ty_id.as_local(); + let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); + if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id); + then { + let (name, kind) = match cx.tcx.hir().find(ty_hir_id) { + Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"), + Some(Node::Item(x)) => match x.kind { + ItemKind::Struct(..) => (x.ident.name, "struct"), + ItemKind::Enum(..) => (x.ident.name, "enum"), + ItemKind::Union(..) => (x.ident.name, "union"), + _ => (x.ident.name, "type"), + } + _ => return, + }; + check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind) + } } } @@ -202,40 +232,94 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items } } -fn check_impl_items(cx: &LateContext<'_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) { - fn is_named_self(cx: &LateContext<'_>, item: &ImplItemRef<'_>, name: &str) -> bool { - item.ident.name.as_str() == name - && if let AssocItemKind::Fn { has_self } = item.kind { - has_self && cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 - } else { - false - } +/// Checks if the given signature matches the expectations for `is_empty` +fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool { + match &**sig.inputs_and_output { + [arg, res] if *res == cx.tcx.types.bool => { + matches!( + (arg.kind(), self_kind), + (ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef) + | (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef) + ) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut)) + }, + _ => false, } +} - let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) { - if cx.access_levels.is_exported(is_empty.id.hir_id()) { - return; - } - "a private" - } else { - "no corresponding" - }; - - if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) { - if cx.access_levels.is_exported(i.id.hir_id()) { - let ty = cx.tcx.type_of(item.def_id); +/// Checks if the given type has an `is_empty` method with the appropriate signature. +fn check_for_is_empty( + cx: &LateContext<'_>, + span: Span, + self_kind: ImplicitSelfKind, + impl_ty: DefId, + item_name: Symbol, + item_kind: &str, +) { + let is_empty = Symbol::intern("is_empty"); + let is_empty = cx + .tcx + .inherent_impls(impl_ty) + .iter() + .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty)) + .find(|item| item.kind == AssocKind::Fn); - span_lint( - cx, - LEN_WITHOUT_IS_EMPTY, - item.span, - &format!( - "item `{}` has a public `len` method but {} `is_empty` method", - ty, is_empty + let (msg, is_empty_span, self_kind) = match is_empty { + None => ( + format!( + "{} `{}` has a public `len` method, but no `is_empty` method", + item_kind, + item_name.as_str(), + ), + None, + None, + ), + Some(is_empty) + if !cx + .access_levels + .is_exported(cx.tcx.hir().local_def_id_to_hir_id(is_empty.def_id.expect_local())) => + { + ( + format!( + "{} `{}` has a public `len` method, but a private `is_empty` method", + item_kind, + item_name.as_str(), ), - ); + Some(cx.tcx.def_span(is_empty.def_id)), + None, + ) + }, + Some(is_empty) + if !(is_empty.fn_has_self_parameter + && check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) => + { + ( + format!( + "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature", + item_kind, + item_name.as_str(), + ), + Some(cx.tcx.def_span(is_empty.def_id)), + Some(self_kind), + ) + }, + Some(_) => return, + }; + + span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| { + if let Some(span) = is_empty_span { + db.span_note(span, "`is_empty` defined here"); } - } + if let Some(self_kind) = self_kind { + db.note(&format!( + "expected signature: `({}self) -> bool`", + match self_kind { + ImplicitSelfKind::ImmRef => "&", + ImplicitSelfKind::MutRef => "&mut ", + _ => "", + } + )); + } + }); } fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 176eeadcc63..04e151df8e8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -182,6 +182,7 @@ mod booleans; mod bytecount; mod cargo_common_metadata; mod case_sensitive_file_extension_comparisons; +mod casts; mod checked_conversions; mod cognitive_complexity; mod collapsible_if; @@ -586,6 +587,18 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &bytecount::NAIVE_BYTECOUNT, &cargo_common_metadata::CARGO_COMMON_METADATA, &case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + &casts::CAST_LOSSLESS, + &casts::CAST_POSSIBLE_TRUNCATION, + &casts::CAST_POSSIBLE_WRAP, + &casts::CAST_PRECISION_LOSS, + &casts::CAST_PTR_ALIGNMENT, + &casts::CAST_REF_TO_MUT, + &casts::CAST_SIGN_LOSS, + &casts::CHAR_LIT_AS_U8, + &casts::FN_TO_NUMERIC_CAST, + &casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION, + &casts::PTR_AS_PTR, + &casts::UNNECESSARY_CAST, &checked_conversions::CHECKED_CONVERSIONS, &cognitive_complexity::COGNITIVE_COMPLEXITY, &collapsible_if::COLLAPSIBLE_ELSE_IF, @@ -769,11 +782,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::FLAT_MAP_IDENTITY, &methods::FROM_ITER_INSTEAD_OF_COLLECT, &methods::GET_UNWRAP, + &methods::IMPLICIT_CLONE, &methods::INEFFICIENT_TO_STRING, &methods::INSPECT_FOR_EACH, &methods::INTO_ITER_ON_REF, &methods::ITERATOR_STEP_BY_ZERO, &methods::ITER_CLONED_COLLECT, + &methods::ITER_COUNT, &methods::ITER_NEXT_SLICE, &methods::ITER_NTH, &methods::ITER_NTH_ZERO, @@ -941,28 +956,16 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &types::ABSURD_EXTREME_COMPARISONS, &types::BORROWED_BOX, &types::BOX_VEC, - &types::CAST_LOSSLESS, - &types::CAST_POSSIBLE_TRUNCATION, - &types::CAST_POSSIBLE_WRAP, - &types::CAST_PRECISION_LOSS, - &types::CAST_PTR_ALIGNMENT, - &types::CAST_REF_TO_MUT, - &types::CAST_SIGN_LOSS, - &types::CHAR_LIT_AS_U8, - &types::FN_TO_NUMERIC_CAST, - &types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION, &types::IMPLICIT_HASHER, &types::INVALID_UPCAST_COMPARISONS, &types::LET_UNIT_VALUE, &types::LINKEDLIST, &types::OPTION_OPTION, - &types::PTR_AS_PTR, &types::RC_BUFFER, &types::REDUNDANT_ALLOCATION, &types::TYPE_COMPLEXITY, &types::UNIT_ARG, &types::UNIT_CMP, - &types::UNNECESSARY_CAST, &types::VEC_BOX, &undropped_manually_drops::UNDROPPED_MANUALLY_DROPS, &unicode::INVISIBLE_CHARACTERS, @@ -1073,6 +1076,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box use_self::UseSelf::new(msrv)); store.register_late_pass(move || box missing_const_for_fn::MissingConstForFn::new(msrv)); store.register_late_pass(move || box needless_question_mark::NeedlessQuestionMark::new(msrv)); + store.register_late_pass(move || box casts::Casts::new(msrv)); store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount); store.register_late_pass(|| box map_clone::MapClone); @@ -1084,7 +1088,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box main_recursion::MainRecursion::default()); store.register_late_pass(|| box lifetimes::Lifetimes); store.register_late_pass(|| box entry::HashMapPass); - store.register_late_pass(|| box types::Casts); let type_complexity_threshold = conf.type_complexity_threshold; store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold)); store.register_late_pass(|| box minmax::MinMaxPass); @@ -1105,7 +1108,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box panic_unimplemented::PanicUnimplemented); store.register_late_pass(|| box strings::StringLitAsBytes); store.register_late_pass(|| box derive::Derive); - store.register_late_pass(|| box types::CharLitAsU8); store.register_late_pass(|| box get_last_with_len::GetLastWithLen); store.register_late_pass(|| box drop_forget_ref::DropForgetRef); store.register_late_pass(|| box empty_enum::EmptyEnum); @@ -1173,7 +1175,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box slow_vector_initialization::SlowVectorInit); store.register_late_pass(|| box unnecessary_sort_by::UnnecessarySortBy); store.register_late_pass(|| box unnecessary_wraps::UnnecessaryWraps); - store.register_late_pass(|| box types::RefToMut); store.register_late_pass(|| box assertions_on_constants::AssertionsOnConstants); store.register_late_pass(|| box transmuting_null::TransmutingNull); store.register_late_pass(|| box path_buf_push_overwrite::PathBufPushOverwrite); @@ -1275,7 +1276,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box strings::StringToString); store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues); store.register_late_pass(|| box vec_init_then_push::VecInitThenPush::default()); - store.register_late_pass(move || box types::PtrAsPtr::new(msrv)); store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons); store.register_late_pass(|| box redundant_slicing::RedundantSlicing); store.register_late_pass(|| box from_str_radix_10::FromStrRadix10); @@ -1341,7 +1341,15 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(&await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(&bit_mask::VERBOSE_BIT_MASK), + LintId::of(&bytecount::NAIVE_BYTECOUNT), LintId::of(&case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), + LintId::of(&casts::CAST_LOSSLESS), + LintId::of(&casts::CAST_POSSIBLE_TRUNCATION), + LintId::of(&casts::CAST_POSSIBLE_WRAP), + LintId::of(&casts::CAST_PRECISION_LOSS), + LintId::of(&casts::CAST_PTR_ALIGNMENT), + LintId::of(&casts::CAST_SIGN_LOSS), + LintId::of(&casts::PTR_AS_PTR), LintId::of(&checked_conversions::CHECKED_CONVERSIONS), LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), LintId::of(©_iterator::COPY_ITERATOR), @@ -1380,6 +1388,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), + LintId::of(&methods::IMPLICIT_CLONE), LintId::of(&methods::INEFFICIENT_TO_STRING), LintId::of(&methods::MAP_FLATTEN), LintId::of(&methods::MAP_UNWRAP_OR), @@ -1400,18 +1409,11 @@ 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(&types::CAST_LOSSLESS), - LintId::of(&types::CAST_POSSIBLE_TRUNCATION), - LintId::of(&types::CAST_POSSIBLE_WRAP), - LintId::of(&types::CAST_PRECISION_LOSS), - LintId::of(&types::CAST_PTR_ALIGNMENT), - LintId::of(&types::CAST_SIGN_LOSS), LintId::of(&types::IMPLICIT_HASHER), LintId::of(&types::INVALID_UPCAST_COMPARISONS), LintId::of(&types::LET_UNIT_VALUE), LintId::of(&types::LINKEDLIST), LintId::of(&types::OPTION_OPTION), - LintId::of(&types::PTR_AS_PTR), LintId::of(&unicode::NON_ASCII_LITERAL), LintId::of(&unicode::UNICODE_NOT_NFC), LintId::of(&unnecessary_wraps::UNNECESSARY_WRAPS), @@ -1455,7 +1457,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(&booleans::LOGIC_BUG), LintId::of(&booleans::NONMINIMAL_BOOL), - LintId::of(&bytecount::NAIVE_BYTECOUNT), + LintId::of(&casts::CAST_REF_TO_MUT), + LintId::of(&casts::CHAR_LIT_AS_U8), + LintId::of(&casts::FN_TO_NUMERIC_CAST), + LintId::of(&casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), + LintId::of(&casts::UNNECESSARY_CAST), LintId::of(&collapsible_if::COLLAPSIBLE_ELSE_IF), LintId::of(&collapsible_if::COLLAPSIBLE_IF), LintId::of(&collapsible_match::COLLAPSIBLE_MATCH), @@ -1576,6 +1582,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITERATOR_STEP_BY_ZERO), LintId::of(&methods::ITER_CLONED_COLLECT), + LintId::of(&methods::ITER_COUNT), LintId::of(&methods::ITER_NEXT_SLICE), LintId::of(&methods::ITER_NTH), LintId::of(&methods::ITER_NTH_ZERO), @@ -1695,15 +1702,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::ABSURD_EXTREME_COMPARISONS), LintId::of(&types::BORROWED_BOX), LintId::of(&types::BOX_VEC), - LintId::of(&types::CAST_REF_TO_MUT), - LintId::of(&types::CHAR_LIT_AS_U8), - LintId::of(&types::FN_TO_NUMERIC_CAST), - LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), LintId::of(&types::REDUNDANT_ALLOCATION), LintId::of(&types::TYPE_COMPLEXITY), LintId::of(&types::UNIT_ARG), LintId::of(&types::UNIT_CMP), - LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(&unicode::INVISIBLE_CHARACTERS), @@ -1736,6 +1738,8 @@ 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(&casts::FN_TO_NUMERIC_CAST), + LintId::of(&casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), LintId::of(&collapsible_if::COLLAPSIBLE_ELSE_IF), LintId::of(&collapsible_if::COLLAPSIBLE_IF), LintId::of(&collapsible_match::COLLAPSIBLE_MATCH), @@ -1832,8 +1836,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME), LintId::of(&try_err::TRY_ERR), - LintId::of(&types::FN_TO_NUMERIC_CAST), - LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS), @@ -1849,6 +1851,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&assign_ops::MISREFACTORED_ASSIGN_OP), LintId::of(&attrs::DEPRECATED_CFG_ATTR), LintId::of(&booleans::NONMINIMAL_BOOL), + LintId::of(&casts::CHAR_LIT_AS_U8), + LintId::of(&casts::UNNECESSARY_CAST), LintId::of(&double_comparison::DOUBLE_COMPARISONS), LintId::of(&double_parens::DOUBLE_PARENS), LintId::of(&duration_subsec::DURATION_SUBSEC), @@ -1881,6 +1885,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::FILTER_NEXT), LintId::of(&methods::FLAT_MAP_IDENTITY), LintId::of(&methods::INSPECT_FOR_EACH), + LintId::of(&methods::ITER_COUNT), LintId::of(&methods::MANUAL_FILTER_MAP), LintId::of(&methods::MANUAL_FIND_MAP), LintId::of(&methods::OPTION_AS_REF_DEREF), @@ -1924,10 +1929,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&transmute::TRANSMUTE_PTR_TO_PTR), LintId::of(&transmute::TRANSMUTE_PTR_TO_REF), LintId::of(&types::BORROWED_BOX), - LintId::of(&types::CHAR_LIT_AS_U8), LintId::of(&types::TYPE_COMPLEXITY), LintId::of(&types::UNIT_ARG), - LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), LintId::of(&unnecessary_sort_by::UNNECESSARY_SORT_BY), LintId::of(&unwrap::UNNECESSARY_UNWRAP), @@ -1945,6 +1948,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&bit_mask::BAD_BIT_MASK), LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(&booleans::LOGIC_BUG), + LintId::of(&casts::CAST_REF_TO_MUT), LintId::of(&copies::IFS_SAME_COND), LintId::of(&copies::IF_SAME_THEN_ELSE), LintId::of(&derive::DERIVE_HASH_XOR_EQ), @@ -1997,7 +2001,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&transmute::WRONG_TRANSMUTE), LintId::of(&transmuting_null::TRANSMUTING_NULL), LintId::of(&types::ABSURD_EXTREME_COMPARISONS), - LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::UNIT_CMP), LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(&unicode::INVISIBLE_CHARACTERS), @@ -2010,7 +2013,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ]); store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ - LintId::of(&bytecount::NAIVE_BYTECOUNT), LintId::of(&entry::MAP_ENTRY), LintId::of(&escape::BOXED_LOCAL), LintId::of(&large_const_arrays::LARGE_CONST_ARRAYS), diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 50e6383263d..33ff01a30e8 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -1,5 +1,4 @@ -use crate::utils::paths; -use crate::utils::{get_trait_def_id, in_macro, span_lint, trait_ref_of_method}; +use crate::utils::{in_macro, span_lint, trait_ref_of_method}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::intravisit::{ walk_fn_decl, walk_generic_param, walk_generics, walk_item, walk_param_bound, walk_poly_trait_ref, walk_ty, @@ -8,8 +7,8 @@ use rustc_hir::intravisit::{ use rustc_hir::FnRetTy::Return; use rustc_hir::{ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, - ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, TraitFn, - TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, + TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereClause, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::map::Map; @@ -300,7 +299,7 @@ fn unique_lifetimes(lts: &[RefLt]) -> usize { lts.iter().collect::<FxHashSet<_>>().len() } -const CLOSURE_TRAIT_BOUNDS: [&[&str]; 3] = [&paths::FN, &paths::FN_MUT, &paths::FN_ONCE]; +const CLOSURE_TRAIT_BOUNDS: [LangItem; 3] = [LangItem::Fn, LangItem::FnMut, LangItem::FnOnce]; /// A visitor usable for `rustc_front::visit::walk_ty()`. struct RefVisitor<'a, 'tcx> { @@ -359,10 +358,13 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { fn visit_poly_trait_ref(&mut self, poly_tref: &'tcx PolyTraitRef<'tcx>, tbm: TraitBoundModifier) { let trait_ref = &poly_tref.trait_ref; - if CLOSURE_TRAIT_BOUNDS - .iter() - .any(|trait_path| trait_ref.trait_def_id() == get_trait_def_id(self.cx, trait_path)) - { + if CLOSURE_TRAIT_BOUNDS.iter().any(|&item| { + self.cx + .tcx + .lang_items() + .require(item) + .map_or(false, |id| Some(id) == trait_ref.trait_def_id()) + }) { let mut sub_visitor = RefVisitor::new(self.cx); sub_visitor.visit_trait_ref(trait_ref); self.nested_elision_site_lts.append(&mut sub_visitor.all_lts()); diff --git a/clippy_lints/src/loops.rs b/clippy_lints/src/loops.rs deleted file mode 100644 index 9b626d81ebd..00000000000 --- a/clippy_lints/src/loops.rs +++ /dev/null @@ -1,3182 +0,0 @@ -use crate::consts::constant; -use crate::utils::sugg::Sugg; -use crate::utils::usage::mutated_variables; -use crate::utils::visitors::LocalUsedVisitor; -use crate::utils::{ - contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, - indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_ok_ctor, is_refutable, is_some_ctor, - is_type_diagnostic_item, last_path_segment, match_trait_method, match_type, multispan_sugg, path_to_local, - path_to_local_id, paths, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq, -}; -use if_chain::if_chain; -use rustc_ast::ast; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; -use rustc_hir::{ - def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, - Local, LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, -}; -use rustc_infer::infer::TyCtxtInferExt; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::map::Map; -use rustc_middle::lint::in_external_macro; -use rustc_middle::middle::region; -use rustc_middle::ty::{self, Ty, TyS}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Ident, Symbol}; -use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; -use std::iter::{once, Iterator}; -use std::mem; - -declare_clippy_lint! { - /// **What it does:** Checks for for-loops that manually copy items between - /// slices that could be optimized by having a memcpy. - /// - /// **Why is this bad?** It is not as fast as a memcpy. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # let src = vec![1]; - /// # let mut dst = vec![0; 65]; - /// for i in 0..src.len() { - /// dst[i + 64] = src[i]; - /// } - /// ``` - /// Could be written as: - /// ```rust - /// # let src = vec![1]; - /// # let mut dst = vec![0; 65]; - /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); - /// ``` - pub MANUAL_MEMCPY, - perf, - "manually copying items between slices" -} - -declare_clippy_lint! { - /// **What it does:** Checks for looping over the range of `0..len` of some - /// collection just to get the values by index. - /// - /// **Why is this bad?** Just iterating the collection itself makes the intent - /// more clear and is probably faster. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let vec = vec!['a', 'b', 'c']; - /// for i in 0..vec.len() { - /// println!("{}", vec[i]); - /// } - /// ``` - /// Could be written as: - /// ```rust - /// let vec = vec!['a', 'b', 'c']; - /// for i in vec { - /// println!("{}", i); - /// } - /// ``` - pub NEEDLESS_RANGE_LOOP, - style, - "for-looping over a range of indices where an iterator over items would do" -} - -declare_clippy_lint! { - /// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and - /// suggests the latter. - /// - /// **Why is this bad?** Readability. - /// - /// **Known problems:** False negatives. We currently only warn on some known - /// types. - /// - /// **Example:** - /// ```rust - /// // with `y` a `Vec` or slice: - /// # let y = vec![1]; - /// for x in y.iter() { - /// // .. - /// } - /// ``` - /// can be rewritten to - /// ```rust - /// # let y = vec![1]; - /// for x in &y { - /// // .. - /// } - /// ``` - pub EXPLICIT_ITER_LOOP, - pedantic, - "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" -} - -declare_clippy_lint! { - /// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and - /// suggests the latter. - /// - /// **Why is this bad?** Readability. - /// - /// **Known problems:** None - /// - /// **Example:** - /// ```rust - /// # let y = vec![1]; - /// // with `y` a `Vec` or slice: - /// for x in y.into_iter() { - /// // .. - /// } - /// ``` - /// can be rewritten to - /// ```rust - /// # let y = vec![1]; - /// for x in y { - /// // .. - /// } - /// ``` - pub EXPLICIT_INTO_ITER_LOOP, - pedantic, - "for-looping over `_.into_iter()` when `_` would do" -} - -declare_clippy_lint! { - /// **What it does:** Checks for loops on `x.next()`. - /// - /// **Why is this bad?** `next()` returns either `Some(value)` if there was a - /// value, or `None` otherwise. The insidious thing is that `Option<_>` - /// implements `IntoIterator`, so that possibly one value will be iterated, - /// leading to some hard to find bugs. No one will want to write such code - /// [except to win an Underhanded Rust - /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr). - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```ignore - /// for x in y.next() { - /// .. - /// } - /// ``` - pub ITER_NEXT_LOOP, - correctness, - "for-looping over `_.next()` which is probably not intended" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `for` loops over `Option` or `Result` values. - /// - /// **Why is this bad?** Readability. This is more clearly expressed as an `if - /// let`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # let opt = Some(1); - /// - /// // Bad - /// for x in opt { - /// // .. - /// } - /// - /// // Good - /// if let Some(x) = opt { - /// // .. - /// } - /// ``` - /// - /// // or - /// - /// ```rust - /// # let res: Result<i32, std::io::Error> = Ok(1); - /// - /// // Bad - /// for x in &res { - /// // .. - /// } - /// - /// // Good - /// if let Ok(x) = res { - /// // .. - /// } - /// ``` - pub FOR_LOOPS_OVER_FALLIBLES, - correctness, - "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" -} - -declare_clippy_lint! { - /// **What it does:** Detects `loop + match` combinations that are easier - /// written as a `while let` loop. - /// - /// **Why is this bad?** The `while let` loop is usually shorter and more - /// readable. - /// - /// **Known problems:** Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)). - /// - /// **Example:** - /// ```rust,no_run - /// # let y = Some(1); - /// loop { - /// let x = match y { - /// Some(x) => x, - /// None => break, - /// }; - /// // .. do something with x - /// } - /// // is easier written as - /// while let Some(x) = y { - /// // .. do something with x - /// }; - /// ``` - pub WHILE_LET_LOOP, - complexity, - "`loop { if let { ... } else break }`, which can be written as a `while let` loop" -} - -declare_clippy_lint! { - /// **What it does:** Checks for functions collecting an iterator when collect - /// is not needed. - /// - /// **Why is this bad?** `collect` causes the allocation of a new data structure, - /// when this allocation may not be needed. - /// - /// **Known problems:** - /// None - /// - /// **Example:** - /// ```rust - /// # let iterator = vec![1].into_iter(); - /// let len = iterator.clone().collect::<Vec<_>>().len(); - /// // should be - /// let len = iterator.count(); - /// ``` - pub NEEDLESS_COLLECT, - perf, - "collecting an iterator when collect is not needed" -} - -declare_clippy_lint! { - /// **What it does:** Checks `for` loops over slices with an explicit counter - /// and suggests the use of `.enumerate()`. - /// - /// **Why is it bad?** Using `.enumerate()` makes the intent more clear, - /// declutters the code and may be faster in some instances. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # let v = vec![1]; - /// # fn bar(bar: usize, baz: usize) {} - /// let mut i = 0; - /// for item in &v { - /// bar(i, *item); - /// i += 1; - /// } - /// ``` - /// Could be written as - /// ```rust - /// # let v = vec![1]; - /// # fn bar(bar: usize, baz: usize) {} - /// for (i, item) in v.iter().enumerate() { bar(i, *item); } - /// ``` - pub EXPLICIT_COUNTER_LOOP, - complexity, - "for-looping with an explicit counter when `_.enumerate()` would do" -} - -declare_clippy_lint! { - /// **What it does:** Checks for empty `loop` expressions. - /// - /// **Why is this bad?** These busy loops burn CPU cycles without doing - /// anything. It is _almost always_ a better idea to `panic!` than to have - /// a busy loop. - /// - /// If panicking isn't possible, think of the environment and either: - /// - block on something - /// - sleep the thread for some microseconds - /// - yield or pause the thread - /// - /// For `std` targets, this can be done with - /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) - /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html). - /// - /// For `no_std` targets, doing this is more complicated, especially because - /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will - /// probably need to invoke some target-specific intrinsic. Examples include: - /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html) - /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html) - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```no_run - /// loop {} - /// ``` - pub EMPTY_LOOP, - style, - "empty `loop {}`, which should block or sleep" -} - -declare_clippy_lint! { - /// **What it does:** Checks for `while let` expressions on iterators. - /// - /// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys - /// the intent better. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```ignore - /// while let Some(val) = iter() { - /// .. - /// } - /// ``` - pub WHILE_LET_ON_ITERATOR, - style, - "using a `while let` loop instead of a for loop on an iterator" -} - -declare_clippy_lint! { - /// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and - /// ignoring either the keys or values. - /// - /// **Why is this bad?** Readability. There are `keys` and `values` methods that - /// can be used to express that don't need the values or keys. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```ignore - /// for (k, _) in &map { - /// .. - /// } - /// ``` - /// - /// could be replaced by - /// - /// ```ignore - /// for k in map.keys() { - /// .. - /// } - /// ``` - pub FOR_KV_MAP, - style, - "looping on a map using `iter` when `keys` or `values` would do" -} - -declare_clippy_lint! { - /// **What it does:** Checks for loops that will always `break`, `return` or - /// `continue` an outer loop. - /// - /// **Why is this bad?** This loop never loops, all it does is obfuscating the - /// code. - /// - /// **Known problems:** None - /// - /// **Example:** - /// ```rust - /// loop { - /// ..; - /// break; - /// } - /// ``` - pub NEVER_LOOP, - correctness, - "any loop that will always `break` or `return`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for loops which have a range bound that is a mutable variable - /// - /// **Why is this bad?** One might think that modifying the mutable variable changes the loop bounds - /// - /// **Known problems:** None - /// - /// **Example:** - /// ```rust - /// let mut foo = 42; - /// for i in 0..foo { - /// foo -= 1; - /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 - /// } - /// ``` - pub MUT_RANGE_BOUND, - complexity, - "for loop over a range where one of the bounds is a mutable variable" -} - -declare_clippy_lint! { - /// **What it does:** Checks whether variables used within while loop condition - /// can be (and are) mutated in the body. - /// - /// **Why is this bad?** If the condition is unchanged, entering the body of the loop - /// will lead to an infinite loop. - /// - /// **Known problems:** If the `while`-loop is in a closure, the check for mutation of the - /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is - /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger. - /// - /// **Example:** - /// ```rust - /// let i = 0; - /// while i > 10 { - /// println!("let me loop forever!"); - /// } - /// ``` - pub WHILE_IMMUTABLE_CONDITION, - correctness, - "variables used within while expression are not mutated in the body" -} - -declare_clippy_lint! { - /// **What it does:** Checks whether a for loop is being used to push a constant - /// value into a Vec. - /// - /// **Why is this bad?** This kind of operation can be expressed more succinctly with - /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also - /// have better performance. - /// **Known problems:** None - /// - /// **Example:** - /// ```rust - /// let item1 = 2; - /// let item2 = 3; - /// let mut vec: Vec<u8> = Vec::new(); - /// for _ in 0..20 { - /// vec.push(item1); - /// } - /// for _ in 0..30 { - /// vec.push(item2); - /// } - /// ``` - /// could be written as - /// ```rust - /// let item1 = 2; - /// let item2 = 3; - /// let mut vec: Vec<u8> = vec![item1; 20]; - /// vec.resize(20 + 30, item2); - /// ``` - pub SAME_ITEM_PUSH, - style, - "the same item is pushed inside of a for loop" -} - -declare_clippy_lint! { - /// **What it does:** Checks whether a for loop has a single element. - /// - /// **Why is this bad?** There is no reason to have a loop of a - /// single element. - /// **Known problems:** None - /// - /// **Example:** - /// ```rust - /// let item1 = 2; - /// for item in &[item1] { - /// println!("{}", item); - /// } - /// ``` - /// could be written as - /// ```rust - /// let item1 = 2; - /// let item = &item1; - /// println!("{}", item); - /// ``` - pub SINGLE_ELEMENT_LOOP, - complexity, - "there is no reason to have a single element loop" -} - -declare_clippy_lint! { - /// **What it does:** Check for unnecessary `if let` usage in a for loop - /// where only the `Some` or `Ok` variant of the iterator element is used. - /// - /// **Why is this bad?** It is verbose and can be simplified - /// by first calling the `flatten` method on the `Iterator`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// ```rust - /// let x = vec![Some(1), Some(2), Some(3)]; - /// for n in x { - /// if let Some(n) = n { - /// println!("{}", n); - /// } - /// } - /// ``` - /// Use instead: - /// ```rust - /// let x = vec![Some(1), Some(2), Some(3)]; - /// for n in x.into_iter().flatten() { - /// println!("{}", n); - /// } - /// ``` - pub MANUAL_FLATTEN, - complexity, - "for loops over `Option`s or `Result`s with a single expression can be simplified" -} - -declare_lint_pass!(Loops => [ - MANUAL_MEMCPY, - MANUAL_FLATTEN, - NEEDLESS_RANGE_LOOP, - EXPLICIT_ITER_LOOP, - EXPLICIT_INTO_ITER_LOOP, - ITER_NEXT_LOOP, - FOR_LOOPS_OVER_FALLIBLES, - WHILE_LET_LOOP, - NEEDLESS_COLLECT, - EXPLICIT_COUNTER_LOOP, - EMPTY_LOOP, - WHILE_LET_ON_ITERATOR, - FOR_KV_MAP, - NEVER_LOOP, - MUT_RANGE_BOUND, - WHILE_IMMUTABLE_CONDITION, - SAME_ITEM_PUSH, - SINGLE_ELEMENT_LOOP, -]); - -impl<'tcx> LateLintPass<'tcx> for Loops { - #[allow(clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some((pat, arg, body, span)) = higher::for_loop(expr) { - // we don't want to check expanded macros - // this check is not at the top of the function - // since higher::for_loop expressions are marked as expansions - if body.span.from_expansion() { - return; - } - check_for_loop(cx, pat, arg, body, expr, span); - } - - // we don't want to check expanded macros - if expr.span.from_expansion() { - return; - } - - // check for never_loop - if let ExprKind::Loop(ref block, _, _, _) = expr.kind { - match never_loop_block(block, expr.hir_id) { - NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), - NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), - } - } - - // check for `loop { if let {} else break }` that could be `while let` - // (also matches an explicit "match" instead of "if let") - // (even if the "match" or "if let" is used for declaration) - if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { - // also check for empty `loop {}` statements, skipping those in #[panic_handler] - if block.stmts.is_empty() && block.expr.is_none() && !is_in_panic_handler(cx, expr) { - let msg = "empty `loop {}` wastes CPU cycles"; - let help = if is_no_std_crate(cx) { - "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" - } else { - "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" - }; - span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); - } - - // extract the expression from the first statement (if any) in a block - let inner_stmt_expr = extract_expr_from_first_stmt(block); - // or extract the first expression (if any) from the block - if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(block)) { - if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { - // ensure "if let" compatible match structure - match *source { - MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { - if arms.len() == 2 - && arms[0].guard.is_none() - && arms[1].guard.is_none() - && is_simple_break_expr(&arms[1].body) - { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - // NOTE: we used to build a body here instead of using - // ellipsis, this was removed because: - // 1) it was ugly with big bodies; - // 2) it was not indented properly; - // 3) it wasn’t very smart (see #675). - let mut applicability = Applicability::HasPlaceholders; - span_lint_and_sugg( - cx, - WHILE_LET_LOOP, - expr.span, - "this loop could be written as a `while let` loop", - "try", - format!( - "while let {} = {} {{ .. }}", - snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), - snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), - ), - applicability, - ); - } - }, - _ => (), - } - } - } - } - if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { - let pat = &arms[0].pat.kind; - if let ( - &PatKind::TupleStruct(ref qpath, ref pat_args, _), - &ExprKind::MethodCall(ref method_path, _, ref method_args, _), - ) = (pat, &match_expr.kind) - { - let iter_expr = &method_args[0]; - - // Don't lint when the iterator is recreated on every iteration - if_chain! { - if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; - if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); - if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); - then { - return; - } - } - - let lhs_constructor = last_path_segment(qpath); - if method_path.ident.name == sym::next - && match_trait_method(cx, match_expr, &paths::ITERATOR) - && lhs_constructor.ident.name == sym::Some - && (pat_args.is_empty() - || !is_refutable(cx, &pat_args[0]) - && !is_used_inside(cx, iter_expr, &arms[0].body) - && !is_iterator_used_after_while_let(cx, iter_expr) - && !is_nested(cx, expr, &method_args[0])) - { - let mut applicability = Applicability::MachineApplicable; - let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); - let loop_var = if pat_args.is_empty() { - "_".to_string() - } else { - snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() - }; - span_lint_and_sugg( - cx, - WHILE_LET_ON_ITERATOR, - expr.span.with_hi(match_expr.span.hi()), - "this loop could be written as a `for` loop", - "try", - format!("for {} in {}", loop_var, iterator), - applicability, - ); - } - } - } - - if let Some((cond, body)) = higher::while_loop(&expr) { - check_infinite_loop(cx, cond, body); - } - - check_needless_collect(expr, cx); - } -} - -enum NeverLoopResult { - // A break/return always get triggered but not necessarily for the main loop. - AlwaysBreak, - // A continue may occur for the main loop. - MayContinueMainLoop, - Otherwise, -} - -#[must_use] -fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { - match *arg { - NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, - NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, - } -} - -// Combine two results for parts that are called in order. -#[must_use] -fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { - match first { - NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, - NeverLoopResult::Otherwise => second, - } -} - -// Combine two results where both parts are called but not necessarily in order. -#[must_use] -fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { - match (left, right) { - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop - }, - (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, - } -} - -// Combine two results where only one of the part may have been executed. -#[must_use] -fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { - match (b1, b2) { - (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, - (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { - NeverLoopResult::MayContinueMainLoop - }, - (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, - } -} - -fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { - let stmts = block.stmts.iter().map(stmt_to_expr); - let expr = once(block.expr.as_deref()); - let mut iter = stmts.chain(expr).flatten(); - never_loop_expr_seq(&mut iter, main_loop_id) -} - -fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match stmt.kind { - StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), - StmtKind::Local(ref local) => local.init.as_deref(), - _ => None, - } -} - -fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { - match expr.kind { - ExprKind::Box(ref e) - | ExprKind::Unary(_, ref e) - | ExprKind::Cast(ref e, _) - | ExprKind::Type(ref e, _) - | ExprKind::Field(ref e, _) - | ExprKind::AddrOf(_, _, ref e) - | ExprKind::Struct(_, _, Some(ref e)) - | ExprKind::Repeat(ref e, _) - | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), - ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { - never_loop_expr_all(&mut es.iter(), main_loop_id) - }, - ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), - ExprKind::Binary(_, ref e1, ref e2) - | ExprKind::Assign(ref e1, ref e2, _) - | ExprKind::AssignOp(_, ref e1, ref e2) - | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), - ExprKind::Loop(ref b, _, _, _) => { - // Break can come from the inner loop so remove them. - absorb_break(&never_loop_block(b, main_loop_id)) - }, - ExprKind::If(ref e, ref e2, ref e3) => { - let e1 = never_loop_expr(e, main_loop_id); - let e2 = never_loop_expr(e2, main_loop_id); - let e3 = e3 - .as_ref() - .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); - combine_seq(e1, combine_branches(e2, e3)) - }, - ExprKind::Match(ref e, ref arms, _) => { - let e = never_loop_expr(e, main_loop_id); - if arms.is_empty() { - e - } else { - let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); - combine_seq(e, arms) - } - }, - ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), - ExprKind::Continue(d) => { - let id = d - .target_id - .expect("target ID can only be missing in the presence of compilation errors"); - if id == main_loop_id { - NeverLoopResult::MayContinueMainLoop - } else { - NeverLoopResult::AlwaysBreak - } - }, - ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { - combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) - }), - ExprKind::InlineAsm(ref asm) => asm - .operands - .iter() - .map(|(o, _)| match o { - InlineAsmOperand::In { expr, .. } - | InlineAsmOperand::InOut { expr, .. } - | InlineAsmOperand::Const { expr } - | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), - InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), - InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { - never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) - }, - }) - .fold(NeverLoopResult::Otherwise, combine_both), - ExprKind::Struct(_, _, None) - | ExprKind::Yield(_, _) - | ExprKind::Closure(_, _, _, _, _) - | ExprKind::LlvmInlineAsm(_) - | ExprKind::Path(_) - | ExprKind::ConstBlock(_) - | ExprKind::Lit(_) - | ExprKind::Err => NeverLoopResult::Otherwise, - } -} - -fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { - es.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_seq) -} - -fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { - es.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::Otherwise, combine_both) -} - -fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { - e.map(|e| never_loop_expr(e, main_loop_id)) - .fold(NeverLoopResult::AlwaysBreak, combine_branches) -} - -fn check_for_loop<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, - span: Span, -) { - let is_manual_memcpy_triggered = detect_manual_memcpy(cx, pat, arg, body, expr); - if !is_manual_memcpy_triggered { - check_for_loop_range(cx, pat, arg, body, expr); - check_for_loop_explicit_counter(cx, pat, arg, body, expr); - } - check_for_loop_arg(cx, pat, arg, expr); - check_for_loop_over_map_kv(cx, pat, arg, body, expr); - check_for_mut_range_bound(cx, arg, body); - check_for_single_element_loop(cx, pat, arg, body, expr); - detect_same_item_push(cx, pat, arg, body, expr); - check_manual_flatten(cx, pat, arg, body, span); -} - -// this function assumes the given expression is a `for` loop. -fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { - // for some reason this is the only way to get the `Span` - // of the entire `for` loop - if let ExprKind::Match(_, arms, _) = &expr.kind { - arms[0].body.span - } else { - unreachable!() - } -} - -/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; -/// and also, it avoids subtracting a variable from the same one by replacing it with `0`. -/// it exists for the convenience of the overloaded operators while normal functions can do the -/// same. -#[derive(Clone)] -struct MinifyingSugg<'a>(Sugg<'a>); - -impl<'a> MinifyingSugg<'a> { - fn as_str(&self) -> &str { - let s = match &self.0 { - Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s) => s, - }; - s.as_ref() - } - - fn into_sugg(self) -> Sugg<'a> { - self.0 - } -} - -impl<'a> From<Sugg<'a>> for MinifyingSugg<'a> { - fn from(sugg: Sugg<'a>) -> Self { - Self(sugg) - } -} - -impl std::ops::Add for &MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - ("0", _) => rhs.clone(), - (_, "0") => self.clone(), - (_, _) => (&self.0 + &rhs.0).into(), - } - } -} - -impl std::ops::Sub for &MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - (_, "0") => self.clone(), - ("0", _) => (-rhs.0.clone()).into(), - (x, y) if x == y => sugg::ZERO.into(), - (_, _) => (&self.0 - &rhs.0).into(), - } - } -} - -impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - ("0", _) => rhs.clone(), - (_, "0") => self, - (_, _) => (self.0 + &rhs.0).into(), - } - } -} - -impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { - type Output = MinifyingSugg<'static>; - fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { - match (self.as_str(), rhs.as_str()) { - (_, "0") => self, - ("0", _) => (-rhs.0.clone()).into(), - (x, y) if x == y => sugg::ZERO.into(), - (_, _) => (self.0 - &rhs.0).into(), - } - } -} - -/// a wrapper around `MinifyingSugg`, which carries a operator like currying -/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). -struct Offset { - value: MinifyingSugg<'static>, - sign: OffsetSign, -} - -#[derive(Clone, Copy)] -enum OffsetSign { - Positive, - Negative, -} - -impl Offset { - fn negative(value: Sugg<'static>) -> Self { - Self { - value: value.into(), - sign: OffsetSign::Negative, - } - } - - fn positive(value: Sugg<'static>) -> Self { - Self { - value: value.into(), - sign: OffsetSign::Positive, - } - } - - fn empty() -> Self { - Self::positive(sugg::ZERO) - } -} - -fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> { - match rhs.sign { - OffsetSign::Positive => lhs + &rhs.value, - OffsetSign::Negative => lhs - &rhs.value, - } -} - -#[derive(Debug, Clone, Copy)] -enum StartKind<'hir> { - Range, - Counter { initializer: &'hir Expr<'hir> }, -} - -struct IndexExpr<'hir> { - base: &'hir Expr<'hir>, - idx: StartKind<'hir>, - idx_offset: Offset, -} - -struct Start<'hir> { - id: HirId, - kind: StartKind<'hir>, -} - -fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool { - let is_slice = match ty.kind() { - ty::Ref(_, subty, _) => is_slice_like(cx, subty), - ty::Slice(..) | ty::Array(..) => true, - _ => false, - }; - - is_slice || is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) -} - -fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { - if_chain! { - if let ExprKind::MethodCall(method, _, args, _) = expr.kind; - if method.ident.name == sym::clone; - if args.len() == 1; - if let Some(arg) = args.get(0); - then { arg } else { expr } - } -} - -fn get_details_from_idx<'tcx>( - cx: &LateContext<'tcx>, - idx: &Expr<'_>, - starts: &[Start<'tcx>], -) -> Option<(StartKind<'tcx>, Offset)> { - fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<StartKind<'tcx>> { - let id = path_to_local(e)?; - starts.iter().find(|start| start.id == id).map(|start| start.kind) - } - - fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<Sugg<'static>> { - match &e.kind { - ExprKind::Lit(l) => match l.node { - ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())), - _ => None, - }, - ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")), - _ => None, - } - } - - match idx.kind { - ExprKind::Binary(op, lhs, rhs) => match op.node { - BinOpKind::Add => { - let offset_opt = get_start(lhs, starts) - .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) - .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); - - offset_opt.map(|(s, o)| (s, Offset::positive(o))) - }, - BinOpKind::Sub => { - get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o)))) - }, - _ => None, - }, - ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())), - _ => None, - } -} - -fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { - if let ExprKind::Assign(lhs, rhs, _) = e.kind { - Some((lhs, rhs)) - } else { - None - } -} - -/// Get assignments from the given block. -/// The returned iterator yields `None` if no assignment expressions are there, -/// filtering out the increments of the given whitelisted loop counters; -/// because its job is to make sure there's nothing other than assignments and the increments. -fn get_assignments<'a, 'tcx>( - Block { stmts, expr, .. }: &'tcx Block<'tcx>, - loop_counters: &'a [Start<'tcx>], -) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'a { - // As the `filter` and `map` below do different things, I think putting together - // just increases complexity. (cc #3188 and #4193) - stmts - .iter() - .filter_map(move |stmt| match stmt.kind { - StmtKind::Local(..) | StmtKind::Item(..) => None, - StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), - }) - .chain((*expr).into_iter()) - .filter(move |e| { - if let ExprKind::AssignOp(_, place, _) = e.kind { - path_to_local(place).map_or(false, |id| { - !loop_counters - .iter() - // skip the first item which should be `StartKind::Range` - // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop. - .skip(1) - .any(|counter| counter.id == id) - }) - } else { - true - } - }) - .map(get_assignment) -} - -fn get_loop_counters<'a, 'tcx>( - cx: &'a LateContext<'tcx>, - body: &'tcx Block<'tcx>, - expr: &'tcx Expr<'_>, -) -> Option<impl Iterator<Item = Start<'tcx>> + 'a> { - // Look for variables that are incremented once per loop iteration. - let mut increment_visitor = IncrementVisitor::new(cx); - walk_block(&mut increment_visitor, body); - - // For each candidate, check the parent block to see if - // it's initialized to zero at the start of the loop. - get_enclosing_block(&cx, expr.hir_id).and_then(|block| { - increment_visitor - .into_results() - .filter_map(move |var_id| { - let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); - walk_block(&mut initialize_visitor, block); - - initialize_visitor.get_result().map(|(_, initializer)| Start { - id: var_id, - kind: StartKind::Counter { initializer }, - }) - }) - .into() - }) -} - -fn build_manual_memcpy_suggestion<'tcx>( - cx: &LateContext<'tcx>, - start: &Expr<'_>, - end: &Expr<'_>, - limits: ast::RangeLimits, - dst: &IndexExpr<'_>, - src: &IndexExpr<'_>, -) -> String { - fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> { - if offset.as_str() == "0" { - sugg::EMPTY.into() - } else { - offset - } - } - - let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { - if_chain! { - if let ExprKind::MethodCall(method, _, len_args, _) = end.kind; - if method.ident.name == sym!(len); - if len_args.len() == 1; - if let Some(arg) = len_args.get(0); - if path_to_local(arg) == path_to_local(base); - then { - if sugg.as_str() == end_str { - sugg::EMPTY.into() - } else { - sugg - } - } else { - match limits { - ast::RangeLimits::Closed => { - sugg + &sugg::ONE.into() - }, - ast::RangeLimits::HalfOpen => sugg, - } - } - } - }; - - let start_str = Sugg::hir(cx, start, "").into(); - let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into(); - - let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx { - StartKind::Range => ( - print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(), - print_limit( - end, - end_str.as_str(), - idx_expr.base, - apply_offset(&end_str, &idx_expr.idx_offset), - ) - .into_sugg(), - ), - StartKind::Counter { initializer } => { - let counter_start = Sugg::hir(cx, initializer, "").into(); - ( - print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(), - print_limit( - end, - end_str.as_str(), - idx_expr.base, - apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str, - ) - .into_sugg(), - ) - }, - }; - - let (dst_offset, dst_limit) = print_offset_and_limit(&dst); - let (src_offset, src_limit) = print_offset_and_limit(&src); - - let dst_base_str = snippet(cx, dst.base.span, "???"); - let src_base_str = snippet(cx, src.base.span, "???"); - - let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY { - dst_base_str - } else { - format!( - "{}[{}..{}]", - dst_base_str, - dst_offset.maybe_par(), - dst_limit.maybe_par() - ) - .into() - }; - - format!( - "{}.clone_from_slice(&{}[{}..{}]);", - dst, - src_base_str, - src_offset.maybe_par(), - src_limit.maybe_par() - ) -} - -/// Checks for for loops that sequentially copy items from one slice-like -/// object to another. -fn detect_manual_memcpy<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) -> bool { - if let Some(higher::Range { - start: Some(start), - end: Some(end), - limits, - }) = higher::range(arg) - { - // the var must be a single name - if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { - let mut starts = vec![Start { - id: canonical_id, - kind: StartKind::Range, - }]; - - // This is one of few ways to return different iterators - // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 - let mut iter_a = None; - let mut iter_b = None; - - if let ExprKind::Block(block, _) = body.kind { - if let Some(loop_counters) = get_loop_counters(cx, block, expr) { - starts.extend(loop_counters); - } - iter_a = Some(get_assignments(block, &starts)); - } else { - iter_b = Some(get_assignment(body)); - } - - let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter()); - - let big_sugg = assignments - // The only statements in the for loops can be indexed assignments from - // indexed retrievals (except increments of loop counters). - .map(|o| { - o.and_then(|(lhs, rhs)| { - let rhs = fetch_cloned_expr(rhs); - if_chain! { - if let ExprKind::Index(base_left, idx_left) = lhs.kind; - if let ExprKind::Index(base_right, idx_right) = rhs.kind; - if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) - && is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); - if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); - if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); - - // Source and destination must be different - if path_to_local(base_left) != path_to_local(base_right); - then { - Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, - IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) - } else { - None - } - } - }) - }) - .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src))) - .collect::<Option<Vec<_>>>() - .filter(|v| !v.is_empty()) - .map(|v| v.join("\n ")); - - if let Some(big_sugg) = big_sugg { - span_lint_and_sugg( - cx, - MANUAL_MEMCPY, - get_span_of_entire_for_loop(expr), - "it looks like you're manually copying between slices", - "try replacing the loop by", - big_sugg, - Applicability::Unspecified, - ); - return true; - } - } - } - false -} - -// Scans the body of the for loop and determines whether lint should be given -struct SameItemPushVisitor<'a, 'tcx> { - should_lint: bool, - // this field holds the last vec push operation visited, which should be the only push seen - vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, - cx: &'a LateContext<'tcx>, -} - -impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - match &expr.kind { - // Non-determinism may occur ... don't give a lint - ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, - ExprKind::Block(block, _) => self.visit_block(block), - _ => {}, - } - } - - fn visit_block(&mut self, b: &'tcx Block<'_>) { - for stmt in b.stmts.iter() { - self.visit_stmt(stmt); - } - } - - fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) { - let vec_push_option = get_vec_push(self.cx, s); - if vec_push_option.is_none() { - // Current statement is not a push so visit inside - match &s.kind { - StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), - _ => {}, - } - } else { - // Current statement is a push ...check whether another - // push had been previously done - if self.vec_push.is_none() { - self.vec_push = vec_push_option; - } else { - // There are multiple pushes ... don't lint - self.should_lint = false; - } - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -// Given some statement, determine if that statement is a push on a Vec. If it is, return -// the Vec being pushed into and the item being pushed -fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { - if_chain! { - // Extract method being called - if let StmtKind::Semi(semi_stmt) = &stmt.kind; - if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind; - // Figure out the parameters for the method call - if let Some(self_expr) = args.get(0); - if let Some(pushed_item) = args.get(1); - // Check that the method being called is push() on a Vec - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type); - if path.ident.name.as_str() == "push"; - then { - return Some((self_expr, pushed_item)) - } - } - None -} - -/// Detects for loop pushing the same item into a Vec -fn detect_same_item_push<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - _: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - _: &'tcx Expr<'_>, -) { - fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { - let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); - let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); - - span_lint_and_help( - cx, - SAME_ITEM_PUSH, - vec.span, - "it looks like the same item is being pushed into this Vec", - None, - &format!( - "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})", - item_str, vec_str, item_str - ), - ) - } - - if !matches!(pat.kind, PatKind::Wild) { - return; - } - - // Determine whether it is safe to lint the body - let mut same_item_push_visitor = SameItemPushVisitor { - should_lint: true, - vec_push: None, - cx, - }; - walk_expr(&mut same_item_push_visitor, body); - if same_item_push_visitor.should_lint { - if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { - let vec_ty = cx.typeck_results().expr_ty(vec); - let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); - if cx - .tcx - .lang_items() - .clone_trait() - .map_or(false, |id| implements_trait(cx, ty, id, &[])) - { - // Make sure that the push does not involve possibly mutating values - match pushed_item.kind { - ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, pushed_item.hir_id) { - // immutable bindings that are initialized with literal or constant - Res::Local(hir_id) => { - if_chain! { - let node = cx.tcx.hir().get(hir_id); - if let Node::Binding(pat) = node; - if let PatKind::Binding(bind_ann, ..) = pat.kind; - if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); - let parent_node = cx.tcx.hir().get_parent_node(hir_id); - if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); - if let Some(init) = parent_let_expr.init; - then { - match init.kind { - // immutable bindings that are initialized with literal - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), - // immutable bindings that are initialized with constant - ExprKind::Path(ref path) => { - if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { - emit_lint(cx, vec, pushed_item); - } - } - _ => {}, - } - } - } - }, - // constant - Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), - _ => {}, - } - }, - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), - _ => {}, - } - } - } - } -} - -/// Checks for looping over a range and then indexing a sequence with it. -/// The iteratee must be a range literal. -#[allow(clippy::too_many_lines)] -fn check_for_loop_range<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - if let Some(higher::Range { - start: Some(start), - ref end, - limits, - }) = higher::range(arg) - { - // the var must be a single name - if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { - let mut visitor = VarVisitor { - cx, - var: canonical_id, - indexed_mut: FxHashSet::default(), - indexed_indirectly: FxHashMap::default(), - indexed_directly: FxHashMap::default(), - referenced: FxHashSet::default(), - nonindex: false, - prefer_mutable: false, - }; - walk_expr(&mut visitor, body); - - // linting condition: we only indexed one variable, and indexed it directly - if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { - let (indexed, (indexed_extent, indexed_ty)) = visitor - .indexed_directly - .into_iter() - .next() - .expect("already checked that we have exactly 1 element"); - - // ensure that the indexed variable was declared before the loop, see #601 - if let Some(indexed_extent) = indexed_extent { - let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); - let parent_def_id = cx.tcx.hir().local_def_id(parent_id); - let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); - if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { - return; - } - } - - // don't lint if the container that is indexed does not have .iter() method - let has_iter = has_iter_method(cx, indexed_ty); - if has_iter.is_none() { - return; - } - - // don't lint if the container that is indexed into is also used without - // indexing - if visitor.referenced.contains(&indexed) { - return; - } - - let starts_at_zero = is_integer_const(cx, start, 0); - - let skip = if starts_at_zero { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) { - return; - } else { - format!(".skip({})", snippet(cx, start.span, "..")) - }; - - let mut end_is_start_plus_val = false; - - let take = if let Some(end) = *end { - let mut take_expr = end; - - if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { - if let BinOpKind::Add = op.node { - let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); - let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); - - if start_equal_left { - take_expr = right; - } else if start_equal_right { - take_expr = left; - } - - end_is_start_plus_val = start_equal_left | start_equal_right; - } - } - - if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) { - return; - } else { - match limits { - ast::RangeLimits::Closed => { - let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>"); - format!(".take({})", take_expr + sugg::ONE) - }, - ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), - } - } - } else { - String::new() - }; - - let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { - ("mut ", "iter_mut") - } else { - ("", "iter") - }; - - let take_is_empty = take.is_empty(); - let mut method_1 = take; - let mut method_2 = skip; - - if end_is_start_plus_val { - mem::swap(&mut method_1, &mut method_2); - } - - if visitor.nonindex { - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - expr.span, - &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), - |diag| { - multispan_sugg( - diag, - "consider using an iterator", - vec![ - (pat.span, format!("({}, <item>)", ident.name)), - ( - arg.span, - format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), - ), - ], - ); - }, - ); - } else { - let repl = if starts_at_zero && take_is_empty { - format!("&{}{}", ref_mut, indexed) - } else { - format!("{}.{}(){}{}", indexed, method, method_1, method_2) - }; - - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - expr.span, - &format!( - "the loop variable `{}` is only used to index `{}`.", - ident.name, indexed - ), - |diag| { - multispan_sugg( - diag, - "consider using an iterator", - vec![(pat.span, "<item>".to_string()), (arg.span, repl)], - ); - }, - ); - } - } - } - } -} - -fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { - if_chain! { - if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; - if len_args.len() == 1; - if method.ident.name == sym!(len); - if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; - if path.segments.len() == 1; - if path.segments[0].ident.name == var; - then { - return true; - } - } - - false -} - -fn is_end_eq_array_len<'tcx>( - cx: &LateContext<'tcx>, - end: &Expr<'_>, - limits: ast::RangeLimits, - indexed_ty: Ty<'tcx>, -) -> bool { - if_chain! { - if let ExprKind::Lit(ref lit) = end.kind; - if let ast::LitKind::Int(end_int, _) = lit.node; - if let ty::Array(_, arr_len_const) = indexed_ty.kind(); - if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); - then { - return match limits { - ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), - ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), - }; - } - } - - false -} - -fn lint_iter_method(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - let muta = if method_name == "iter_mut" { "mut " } else { "" }; - span_lint_and_sugg( - cx, - EXPLICIT_ITER_LOOP, - arg.span, - "it is more concise to loop over references to containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - format!("&{}{}", muta, object), - applicability, - ) -} - -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(ref method, _, ref 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 - if method_name == "iter" || method_name == "iter_mut" { - if is_ref_iterable_type(cx, &args[0]) { - lint_iter_method(cx, args, arg, method_name); - } - } else if method_name == "into_iter" && 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]); - if TyS::same_type(receiver_ty, receiver_ty_adjusted) { - let mut applicability = Applicability::MachineApplicable; - let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); - span_lint_and_sugg( - cx, - EXPLICIT_INTO_ITER_LOOP, - arg.span, - "it is more concise to loop over containers instead of using explicit \ - iteration methods", - "to write this more concisely, try", - object.to_string(), - applicability, - ); - } else { - let ref_receiver_ty = cx.tcx.mk_ref( - cx.tcx.lifetimes.re_erased, - ty::TypeAndMut { - ty: receiver_ty, - mutbl: Mutability::Not, - }, - ); - if TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) { - lint_iter_method(cx, args, arg, method_name) - } - } - } else if method_name == "next" && match_trait_method(cx, arg, &paths::ITERATOR) { - span_lint( - cx, - ITER_NEXT_LOOP, - expr.span, - "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ - probably not what you want", - ); - next_loop_linted = true; - } - } - } - if !next_loop_linted { - check_arg_type(cx, pat, arg); - } -} - -/// Checks for `for` loops over `Option`s and `Result`s. -fn check_arg_type(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { - let ty = cx.typeck_results().expr_ty(arg); - if is_type_diagnostic_item(cx, ty, sym::option_type) { - span_lint_and_help( - cx, - FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is an `Option`. This is more readably written as an \ - `if let` statement.", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } else if is_type_diagnostic_item(cx, ty, sym::result_type) { - span_lint_and_help( - cx, - FOR_LOOPS_OVER_FALLIBLES, - arg.span, - &format!( - "for loop over `{0}`, which is a `Result`. This is more readably written as an \ - `if let` statement.", - snippet(cx, arg.span, "_") - ), - None, - &format!( - "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), - ); - } -} - -// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be -// incremented exactly once in the loop body, and initialized to zero -// at the start of the loop. -fn check_for_loop_explicit_counter<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - // Look for variables that are incremented once per loop iteration. - let mut increment_visitor = IncrementVisitor::new(cx); - walk_expr(&mut increment_visitor, body); - - // For each candidate, check the parent block to see if - // it's initialized to zero at the start of the loop. - if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { - for id in increment_visitor.into_results() { - let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); - walk_block(&mut initialize_visitor, block); - - if_chain! { - if let Some((name, initializer)) = initialize_visitor.get_result(); - if is_integer_const(cx, initializer, 0); - then { - let mut applicability = Applicability::MachineApplicable; - - let for_span = get_span_of_entire_for_loop(expr); - - span_lint_and_sugg( - cx, - EXPLICIT_COUNTER_LOOP, - for_span.with_hi(arg.span.hi()), - &format!("the variable `{}` is used as a loop counter.", name), - "consider using", - format!( - "for ({}, {}) in {}.enumerate()", - name, - snippet_with_applicability(cx, pat.span, "item", &mut applicability), - make_iterator_snippet(cx, arg, &mut applicability), - ), - applicability, - ); - } - } - } - } -} - -/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the -/// actual `Iterator` that the loop uses. -fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { - let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |id| { - implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[]) - }); - if impls_iterator { - format!( - "{}", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() - ) - } else { - // (&x).into_iter() ==> x.iter() - // (&mut x).into_iter() ==> x.iter_mut() - match &arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) - if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => - { - let meth_name = match mutability { - Mutability::Mut => "iter_mut", - Mutability::Not => "iter", - }; - format!( - "{}.{}()", - sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), - meth_name, - ) - } - _ => format!( - "{}.into_iter()", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() - ), - } - } -} - -/// Checks for the `FOR_KV_MAP` lint. -fn check_for_loop_over_map_kv<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - let pat_span = pat.span; - - if let PatKind::Tuple(ref pat, _) = pat.kind { - if pat.len() == 2 { - let arg_span = arg.span; - let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { - ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { - (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), - (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), - _ => return, - }, - _ => return, - }; - let mutbl = match mutbl { - Mutability::Not => "", - Mutability::Mut => "_mut", - }; - let arg = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, - _ => arg, - }; - - if is_type_diagnostic_item(cx, ty, sym::hashmap_type) || match_type(cx, ty, &paths::BTREEMAP) { - span_lint_and_then( - cx, - FOR_KV_MAP, - expr.span, - &format!("you seem to want to iterate on a map's {}s", kind), - |diag| { - let map = sugg::Sugg::hir(cx, arg, "map"); - multispan_sugg( - diag, - "use the corresponding method", - vec![ - (pat_span, snippet(cx, new_pat_span, kind).into_owned()), - (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), - ], - ); - }, - ); - } - } - } -} - -fn check_for_single_element_loop<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - expr: &'tcx Expr<'_>, -) { - if_chain! { - if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; - if let PatKind::Binding(.., target, _) = pat.kind; - if let ExprKind::Array([arg_expression]) = arg_expr.kind; - if let ExprKind::Path(ref list_item) = arg_expression.kind; - if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); - if let ExprKind::Block(ref block, _) = body.kind; - if !block.stmts.is_empty(); - - then { - let for_span = get_span_of_entire_for_loop(expr); - let mut block_str = snippet(cx, block.span, "..").into_owned(); - block_str.remove(0); - block_str.pop(); - - - span_lint_and_sugg( - cx, - SINGLE_ELEMENT_LOOP, - for_span, - "for loop over a single element", - "try", - format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), - Applicability::MachineApplicable - ) - } - } -} - -/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the -/// iterator element is used. -fn check_manual_flatten<'tcx>( - cx: &LateContext<'tcx>, - pat: &'tcx Pat<'_>, - arg: &'tcx Expr<'_>, - body: &'tcx Expr<'_>, - span: Span, -) { - if let ExprKind::Block(ref block, _) = body.kind { - // Ensure the `if let` statement is the only expression or statement in the for-loop - let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() { - let match_stmt = &block.stmts[0]; - if let StmtKind::Semi(inner_expr) = match_stmt.kind { - Some(inner_expr) - } else { - None - } - } else if block.stmts.is_empty() { - block.expr - } else { - None - }; - - if_chain! { - if let Some(inner_expr) = inner_expr; - if let ExprKind::Match( - ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } - ) = inner_expr.kind; - // Ensure match_expr in `if let` statement is the same as the pat from the for-loop - if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; - if 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 some_ctor || ok_ctor; - let if_let_type = if some_ctor { "Some" } else { "Ok" }; - - then { - // Prepare the error message - let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); - - // Prepare the help message - let mut applicability = Applicability::MaybeIncorrect; - let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); - - span_lint_and_then( - cx, - MANUAL_FLATTEN, - span, - &msg, - |diag| { - let sugg = format!("{}.flatten()", arg_snippet); - diag.span_suggestion( - arg.span, - "try", - sugg, - Applicability::MaybeIncorrect, - ); - diag.span_help( - inner_expr.span, - "...and remove the `if let` statement in the for loop", - ); - } - ); - } - } - } -} - -struct MutatePairDelegate<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - hir_id_low: Option<HirId>, - hir_id_high: Option<HirId>, - span_low: Option<Span>, - span_high: Option<Span>, -} - -impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { - fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} - - fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { - if let ty::BorrowKind::MutBorrow = bk { - if let PlaceBase::Local(id) = cmt.place.base { - if Some(id) == self.hir_id_low { - self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - if Some(id) == self.hir_id_high { - self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - } - } - } - - fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { - if let PlaceBase::Local(id) = cmt.place.base { - if Some(id) == self.hir_id_low { - self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - if Some(id) == self.hir_id_high { - self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) - } - } - } -} - -impl MutatePairDelegate<'_, '_> { - fn mutation_span(&self) -> (Option<Span>, Option<Span>) { - (self.span_low, self.span_high) - } -} - -fn check_for_mut_range_bound(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { - if let Some(higher::Range { - start: Some(start), - end: Some(end), - .. - }) = higher::range(arg) - { - let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; - if mut_ids[0].is_some() || mut_ids[1].is_some() { - let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); - mut_warn_with_span(cx, span_low); - mut_warn_with_span(cx, span_high); - } - } -} - -fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) { - if let Some(sp) = span { - span_lint( - cx, - MUT_RANGE_BOUND, - sp, - "attempt to mutate range bound within loop; note that the range of the loop is unchanged", - ); - } -} - -fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> { - if_chain! { - if let Some(hir_id) = path_to_local(bound); - if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); - if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; - then { - return Some(hir_id); - } - } - None -} - -fn check_for_mutation<'tcx>( - cx: &LateContext<'tcx>, - body: &Expr<'_>, - bound_ids: &[Option<HirId>], -) -> (Option<Span>, Option<Span>) { - let mut delegate = MutatePairDelegate { - cx, - hir_id_low: bound_ids[0], - hir_id_high: bound_ids[1], - span_low: None, - span_high: None, - }; - cx.tcx.infer_ctxt().enter(|infcx| { - ExprUseVisitor::new( - &mut delegate, - &infcx, - body.hir_id.owner, - cx.param_env, - cx.typeck_results(), - ) - .walk_expr(body); - }); - delegate.mutation_span() -} - -/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. -fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { - match *pat { - PatKind::Wild => true, - PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { - !LocalUsedVisitor::new(cx, id).check_expr(body) - }, - _ => false, - } -} - -struct VarVisitor<'a, 'tcx> { - /// context reference - cx: &'a LateContext<'tcx>, - /// var name to look for as index - var: HirId, - /// indexed variables that are used mutably - indexed_mut: FxHashSet<Symbol>, - /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global - indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>, - /// subset of `indexed` of vars that are indexed directly: `v[i]` - /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` - indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>, - /// Any names that are used outside an index operation. - /// Used to detect things like `&mut vec` used together with `vec[i]` - referenced: FxHashSet<Symbol>, - /// has the loop variable been used in expressions other than the index of - /// an index op? - nonindex: bool, - /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar - /// takes `&mut self` - prefer_mutable: bool, -} - -impl<'a, 'tcx> VarVisitor<'a, 'tcx> { - fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { - if_chain! { - // the indexed container is referenced by a name - if let ExprKind::Path(ref seqpath) = seqexpr.kind; - if let QPath::Resolved(None, ref seqvar) = *seqpath; - if seqvar.segments.len() == 1; - then { - let index_used_directly = path_to_local_id(idx, self.var); - let indexed_indirectly = { - let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); - walk_expr(&mut used_visitor, idx); - used_visitor.used - }; - - if indexed_indirectly || index_used_directly { - if self.prefer_mutable { - self.indexed_mut.insert(seqvar.segments[0].ident.name); - } - let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); - match res { - Res::Local(hir_id) => { - let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); - let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); - let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); - } - if index_used_directly { - self.indexed_directly.insert( - seqvar.segments[0].ident.name, - (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), - ); - } - return false; // no need to walk further *on the variable* - } - Res::Def(DefKind::Static | DefKind::Const, ..) => { - if indexed_indirectly { - self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); - } - if index_used_directly { - self.indexed_directly.insert( - seqvar.segments[0].ident.name, - (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), - ); - } - return false; // no need to walk further *on the variable* - } - _ => (), - } - } - } - } - true - } -} - -impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if_chain! { - // a range index op - if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; - if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) - || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); - if !self.check(&args[1], &args[0], expr); - then { return } - } - - if_chain! { - // an index op - if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; - if !self.check(idx, seqexpr, expr); - then { return } - } - - if_chain! { - // directly using a variable - if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; - if let Res::Local(local_id) = path.res; - then { - if local_id == self.var { - self.nonindex = true; - } else { - // not the correct variable, but still a variable - self.referenced.insert(path.segments[0].ident.name); - } - } - } - - let old = self.prefer_mutable; - match expr.kind { - ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { - self.prefer_mutable = true; - self.visit_expr(lhs); - self.prefer_mutable = false; - self.visit_expr(rhs); - }, - ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - self.visit_expr(expr); - }, - ExprKind::Call(ref f, args) => { - self.visit_expr(f); - for expr in args { - let ty = self.cx.typeck_results().expr_ty_adjusted(expr); - self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - } - self.visit_expr(expr); - } - }, - ExprKind::MethodCall(_, _, args, _) => { - let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); - for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { - self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } - } - self.visit_expr(expr); - } - }, - ExprKind::Closure(_, _, body_id, ..) => { - let body = self.cx.tcx.hir().body(body_id); - self.visit_expr(&body.value); - }, - _ => walk_expr(self, expr), - } - self.prefer_mutable = old; - } - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(expr) { - Some(id) => id, - None => return false, - }; - if let Some(used_mutably) = mutated_variables(container, cx) { - if used_mutably.contains(&def_id) { - return true; - } - } - false -} - -fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(iter_expr) { - Some(id) => id, - None => return false, - }; - let mut visitor = VarUsedAfterLoopVisitor { - def_id, - iter_expr_id: iter_expr.hir_id, - past_while_let: false, - var_used_after_while_let: false, - }; - if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { - walk_block(&mut visitor, enclosing_block); - } - visitor.var_used_after_while_let -} - -struct VarUsedAfterLoopVisitor { - def_id: HirId, - iter_expr_id: HirId, - past_while_let: bool, - var_used_after_while_let: bool, -} - -impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.past_while_let { - if path_to_local_id(expr, self.def_id) { - self.var_used_after_while_let = true; - } - } else if self.iter_expr_id == expr.hir_id { - self.past_while_let = true; - } - walk_expr(self, expr); - } - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -/// Returns `true` if the type of expr is one that provides `IntoIterator` impls -/// for `&T` and `&mut T`, such as `Vec`. -#[rustfmt::skip] -fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - // no walk_ptrs_ty: calling iter() on a reference can make sense because it - // will allow further borrows afterwards - let ty = cx.typeck_results().expr_ty(e); - is_iterable_array(ty, cx) || - is_type_diagnostic_item(cx, ty, sym::vec_type) || - match_type(cx, ty, &paths::LINKED_LIST) || - is_type_diagnostic_item(cx, ty, sym::hashmap_type) || - is_type_diagnostic_item(cx, ty, sym::hashset_type) || - is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || - match_type(cx, ty, &paths::BINARY_HEAP) || - match_type(cx, ty, &paths::BTREEMAP) || - match_type(cx, ty, &paths::BTREESET) -} - -fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { - // IntoIterator is currently only implemented for array sizes <= 32 in rustc - match ty.kind() { - ty::Array(_, n) => n - .try_eval_usize(cx.tcx, cx.param_env) - .map_or(false, |val| (0..=32).contains(&val)), - _ => false, - } -} - -/// If a block begins with a statement (possibly a `let` binding) and has an -/// expression, return it. -fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if block.stmts.is_empty() { - return None; - } - if let StmtKind::Local(ref local) = block.stmts[0].kind { - local.init //.map(|expr| expr) - } else { - None - } -} - -/// If a block begins with an expression (with or without semicolon), return it. -fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { - match block.expr { - Some(ref expr) if block.stmts.is_empty() => Some(expr), - None if !block.stmts.is_empty() => match block.stmts[0].kind { - StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), - StmtKind::Local(..) | StmtKind::Item(..) => None, - }, - _ => None, - } -} - -/// Returns `true` if expr contains a single break expr without destination label -/// and -/// passed expression. The expression may be within a block. -fn is_simple_break_expr(expr: &Expr<'_>) -> bool { - match expr.kind { - ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, - ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), - _ => false, - } -} - -#[derive(Debug, PartialEq)] -enum IncrementVisitorVarState { - Initial, // Not examined yet - IncrOnce, // Incremented exactly once, may be a loop counter - DontWarn, -} - -/// Scan a for loop for variables that are incremented exactly once and not used after that. -struct IncrementVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, // context reference - states: FxHashMap<HirId, IncrementVisitorVarState>, // incremented variables - depth: u32, // depth of conditional expressions - done: bool, -} - -impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - Self { - cx, - states: FxHashMap::default(), - depth: 0, - done: false, - } - } - - fn into_results(self) -> impl Iterator<Item = HirId> { - self.states.into_iter().filter_map(|(id, state)| { - if state == IncrementVisitorVarState::IncrOnce { - Some(id) - } else { - None - } - }) - } -} - -impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.done { - return; - } - - // If node is a variable - if let Some(def_id) = path_to_local(expr) { - if let Some(parent) = get_parent_expr(self.cx, expr) { - let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); - if *state == IncrementVisitorVarState::IncrOnce { - *state = IncrementVisitorVarState::DontWarn; - return; - } - - match parent.kind { - ExprKind::AssignOp(op, ref lhs, ref rhs) => { - if lhs.hir_id == expr.hir_id { - *state = if op.node == BinOpKind::Add - && is_integer_const(self.cx, rhs, 1) - && *state == IncrementVisitorVarState::Initial - && self.depth == 0 - { - IncrementVisitorVarState::IncrOnce - } else { - // Assigned some other value or assigned multiple times - IncrementVisitorVarState::DontWarn - }; - } - }, - ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { - *state = IncrementVisitorVarState::DontWarn - }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { - *state = IncrementVisitorVarState::DontWarn - }, - _ => (), - } - } - - walk_expr(self, expr); - } else if is_loop(expr) || is_conditional(expr) { - self.depth += 1; - walk_expr(self, expr); - self.depth -= 1; - } else if let ExprKind::Continue(_) = expr.kind { - self.done = true; - } else { - walk_expr(self, expr); - } - } - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -enum InitializeVisitorState<'hir> { - Initial, // Not examined yet - Declared(Symbol), // Declared but not (yet) initialized - Initialized { - name: Symbol, - initializer: &'hir Expr<'hir>, - }, - DontWarn, -} - -/// Checks whether a variable is initialized at the start of a loop and not modified -/// and used after the loop. -struct InitializeVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, // context reference - end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. - var_id: HirId, - state: InitializeVisitorState<'tcx>, - depth: u32, // depth of conditional expressions - past_loop: bool, -} - -impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { - Self { - cx, - end_expr, - var_id, - state: InitializeVisitorState::Initial, - depth: 0, - past_loop: false, - } - } - - fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { - if let InitializeVisitorState::Initialized { name, initializer } = self.state { - Some((name, initializer)) - } else { - None - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { - // Look for declarations of the variable - if_chain! { - if let StmtKind::Local(ref local) = stmt.kind; - if local.pat.hir_id == self.var_id; - if let PatKind::Binding(.., ident, _) = local.pat.kind; - then { - self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { - InitializeVisitorState::Initialized { - initializer: init, - name: ident.name, - } - }) - } - } - walk_stmt(self, stmt); - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if matches!(self.state, InitializeVisitorState::DontWarn) { - return; - } - if expr.hir_id == self.end_expr.hir_id { - self.past_loop = true; - return; - } - // No need to visit expressions before the variable is - // declared - if matches!(self.state, InitializeVisitorState::Initial) { - return; - } - - // If node is the desired variable, see how it's used - if path_to_local_id(expr, self.var_id) { - if self.past_loop { - self.state = InitializeVisitorState::DontWarn; - return; - } - - if let Some(parent) = get_parent_expr(self.cx, expr) { - match parent.kind { - ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { - self.state = InitializeVisitorState::DontWarn; - }, - ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { - self.state = if_chain! { - if self.depth == 0; - if let InitializeVisitorState::Declared(name) - | InitializeVisitorState::Initialized { name, ..} = self.state; - then { - InitializeVisitorState::Initialized { initializer: rhs, name } - } else { - InitializeVisitorState::DontWarn - } - } - }, - ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { - self.state = InitializeVisitorState::DontWarn - }, - _ => (), - } - } - - walk_expr(self, expr); - } else if !self.past_loop && is_loop(expr) { - self.state = InitializeVisitorState::DontWarn; - } else if is_conditional(expr) { - self.depth += 1; - walk_expr(self, expr); - self.depth -= 1; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) - } -} - -fn is_loop(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::Loop(..)) -} - -fn is_conditional(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) -} - -fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - if_chain! { - if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); - let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); - if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); - then { - return is_loop_nested(cx, loop_expr, iter_expr) - } - } - false -} - -fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - let mut id = loop_expr.hir_id; - let iter_id = if let Some(id) = path_to_local(iter_expr) { - id - } else { - return true; - }; - loop { - let parent = cx.tcx.hir().get_parent_node(id); - if parent == id { - return false; - } - match cx.tcx.hir().find(parent) { - Some(Node::Expr(expr)) => { - if let ExprKind::Loop(..) = expr.kind { - return true; - }; - }, - Some(Node::Block(block)) => { - let mut block_visitor = LoopNestVisitor { - hir_id: id, - iterator: iter_id, - nesting: Unknown, - }; - walk_block(&mut block_visitor, block); - if block_visitor.nesting == RuledOut { - return false; - } - }, - Some(Node::Stmt(_)) => (), - _ => { - return false; - }, - } - id = parent; - } -} - -#[derive(PartialEq, Eq)] -enum Nesting { - Unknown, // no nesting detected yet - RuledOut, // the iterator is initialized or assigned within scope - LookFurther, // no nesting detected, no further walk required -} - -use self::Nesting::{LookFurther, RuledOut, Unknown}; - -struct LoopNestVisitor { - hir_id: HirId, - iterator: HirId, - nesting: Nesting, -} - -impl<'tcx> Visitor<'tcx> for LoopNestVisitor { - type Map = Map<'tcx>; - - fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { - if stmt.hir_id == self.hir_id { - self.nesting = LookFurther; - } else if self.nesting == Unknown { - walk_stmt(self, stmt); - } - } - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.nesting != Unknown { - return; - } - if expr.hir_id == self.hir_id { - self.nesting = LookFurther; - return; - } - match expr.kind { - ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { - if path_to_local_id(path, self.iterator) { - self.nesting = RuledOut; - } - }, - _ => walk_expr(self, expr), - } - } - - fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { - if self.nesting != Unknown { - return; - } - if let PatKind::Binding(_, id, ..) = pat.kind { - if id == self.iterator { - self.nesting = RuledOut; - return; - } - } - walk_pat(self, pat) - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -fn check_infinite_loop<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { - if constant(cx, cx.typeck_results(), cond).is_some() { - // A pure constant condition (e.g., `while false`) is not linted. - return; - } - - let mut var_visitor = VarCollectorVisitor { - cx, - ids: FxHashSet::default(), - def_ids: FxHashMap::default(), - skip: false, - }; - var_visitor.visit_expr(cond); - if var_visitor.skip { - return; - } - let used_in_condition = &var_visitor.ids; - let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { - used_in_condition.is_disjoint(&used_mutably) - } else { - return; - }; - let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); - - let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { - has_break_or_return: false, - }; - has_break_or_return_visitor.visit_expr(expr); - let has_break_or_return = has_break_or_return_visitor.has_break_or_return; - - if no_cond_variable_mutated && !mutable_static_in_cond { - span_lint_and_then( - cx, - WHILE_IMMUTABLE_CONDITION, - cond.span, - "variables in the condition are not mutated in the loop body", - |diag| { - diag.note("this may lead to an infinite or to a never running loop"); - - if has_break_or_return { - diag.note("this loop contains `return`s or `break`s"); - diag.help("rewrite it as `if cond { loop { } }`"); - } - }, - ); - } -} - -struct HasBreakOrReturnVisitor { - has_break_or_return: bool, -} - -impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.has_break_or_return { - return; - } - - match expr.kind { - ExprKind::Ret(_) | ExprKind::Break(_, _) => { - self.has_break_or_return = true; - return; - }, - _ => {}, - } - - walk_expr(self, expr); - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -/// Collects the set of variables in an expression -/// Stops analysis if a function call is found -/// Note: In some cases such as `self`, there are no mutable annotation, -/// All variables definition IDs are collected -struct VarCollectorVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - ids: FxHashSet<HirId>, - def_ids: FxHashMap<def_id::DefId, bool>, - skip: bool, -} - -impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { - fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::Path(ref qpath) = ex.kind; - if let QPath::Resolved(None, _) = *qpath; - let res = self.cx.qpath_res(qpath, ex.hir_id); - then { - match res { - Res::Local(hir_id) => { - self.ids.insert(hir_id); - }, - Res::Def(DefKind::Static, def_id) => { - let mutable = self.cx.tcx.is_mutable_static(def_id); - self.def_ids.insert(def_id, mutable); - }, - _ => {}, - } - } - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - match ex.kind { - ExprKind::Path(_) => self.insert_def_id(ex), - // If there is any function/method call… we just stop analysis - ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, - - _ => walk_expr(self, ex), - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; - -fn check_needless_collect<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - check_needless_collect_direct_usage(expr, cx); - check_needless_collect_indirect_usage(expr, cx); -} -fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - if_chain! { - if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; - if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; - if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); - if let Some(ref generic_args) = chain_method.args; - if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - then { - let ty = cx.typeck_results().node_type(ty.hir_id); - if is_type_diagnostic_item(cx, ty, sym::vec_type) || - is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || - match_type(cx, ty, &paths::BTREEMAP) || - is_type_diagnostic_item(cx, ty, sym::hashmap_type) { - if method.ident.name == sym!(len) { - let span = shorten_needless_collect_span(expr); - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - "replace with", - "count()".to_string(), - Applicability::MachineApplicable, - ); - } - if method.ident.name == sym!(is_empty) { - let span = shorten_needless_collect_span(expr); - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - "replace with", - "next().is_none()".to_string(), - Applicability::MachineApplicable, - ); - } - if method.ident.name == sym!(contains) { - let contains_arg = snippet(cx, args[1].span, "??"); - let span = shorten_needless_collect_span(expr); - span_lint_and_then( - cx, - NEEDLESS_COLLECT, - span, - NEEDLESS_COLLECT_MSG, - |diag| { - let (arg, pred) = contains_arg - .strip_prefix('&') - .map_or(("&x", &*contains_arg), |s| ("x", s)); - diag.span_suggestion( - span, - "replace with", - format!( - "any(|{}| x == {})", - arg, pred - ), - Applicability::MachineApplicable, - ); - } - ); - } - } - } - } -} - -fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { - if let ExprKind::Block(ref block, _) = expr.kind { - for ref stmt in block.stmts { - if_chain! { - if let StmtKind::Local( - Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, - init: Some(ref init_expr), .. } - ) = stmt.kind; - if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; - if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR); - if let Some(ref generic_args) = method_name.args; - if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - if let ty = cx.typeck_results().node_type(ty.hir_id); - if is_type_diagnostic_item(cx, ty, sym::vec_type) || - is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || - match_type(cx, ty, &paths::LINKED_LIST); - if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); - if iter_calls.len() == 1; - then { - let mut used_count_visitor = UsedCountVisitor { - cx, - id: *pat_id, - count: 0, - }; - walk_block(&mut used_count_visitor, block); - if used_count_visitor.count > 1 { - return; - } - - // Suggest replacing iter_call with iter_replacement, and removing stmt - let iter_call = &iter_calls[0]; - span_lint_and_then( - cx, - NEEDLESS_COLLECT, - stmt.span.until(iter_call.span), - NEEDLESS_COLLECT_MSG, - |diag| { - let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); - diag.multipart_suggestion( - iter_call.get_suggestion_text(), - vec![ - (stmt.span, String::new()), - (iter_call.span, iter_replacement) - ], - Applicability::MachineApplicable,// MaybeIncorrect, - ).emit(); - }, - ); - } - } - } - } -} - -struct IterFunction { - func: IterFunctionKind, - span: Span, -} -impl IterFunction { - fn get_iter_method(&self, cx: &LateContext<'_>) -> String { - match &self.func { - IterFunctionKind::IntoIter => String::new(), - IterFunctionKind::Len => String::from(".count()"), - IterFunctionKind::IsEmpty => String::from(".next().is_none()"), - IterFunctionKind::Contains(span) => { - let s = snippet(cx, *span, ".."); - if let Some(stripped) = s.strip_prefix('&') { - format!(".any(|x| x == {})", stripped) - } else { - format!(".any(|x| x == *{})", s) - } - }, - } - } - fn get_suggestion_text(&self) -> &'static str { - match &self.func { - IterFunctionKind::IntoIter => { - "Use the original Iterator instead of collecting it and then producing a new one" - }, - IterFunctionKind::Len => { - "Take the original Iterator's count instead of collecting it and finding the length" - }, - IterFunctionKind::IsEmpty => { - "Check if the original Iterator has anything instead of collecting it and seeing if it's empty" - }, - IterFunctionKind::Contains(_) => { - "Check if the original Iterator contains an element instead of collecting then checking" - }, - } - } -} -enum IterFunctionKind { - IntoIter, - Len, - IsEmpty, - Contains(Span), -} - -struct IterFunctionVisitor { - uses: Vec<IterFunction>, - seen_other: bool, - target: Ident, -} -impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - // Check function calls on our collection - if_chain! { - if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; - if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); - if let &[name] = &path.segments; - if name.ident == self.target; - then { - let len = sym!(len); - let is_empty = sym!(is_empty); - let contains = sym!(contains); - match method_name.ident.name { - sym::into_iter => self.uses.push( - IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } - ), - name if name == len => self.uses.push( - IterFunction { func: IterFunctionKind::Len, span: expr.span } - ), - name if name == is_empty => self.uses.push( - IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } - ), - name if name == contains => self.uses.push( - IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span } - ), - _ => self.seen_other = true, - } - return - } - } - // Check if the collection is used for anything else - if_chain! { - if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; - if let &[name] = &path.segments; - if name.ident == self.target; - then { - self.seen_other = true; - } else { - walk_expr(self, expr); - } - } - } - - type Map = Map<'tcx>; - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::None - } -} - -struct UsedCountVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - id: HirId, - count: usize, -} - -impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if path_to_local_id(expr, self.id) { - self.count += 1; - } else { - walk_expr(self, expr); - } - } - - fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { - NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) - } -} - -/// Detect the occurrences of calls to `iter` or `into_iter` for the -/// given identifier -fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> { - let mut visitor = IterFunctionVisitor { - uses: Vec::new(), - target: identifier, - seen_other: false, - }; - visitor.visit_block(block); - if visitor.seen_other { - None - } else { - Some(visitor.uses) - } -} - -fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { - if_chain! { - if let ExprKind::MethodCall(.., args, _) = &expr.kind; - if let ExprKind::MethodCall(_, span, ..) = &args[0].kind; - then { - return expr.span.with_lo(span.lo()); - } - } - unreachable!(); -} diff --git a/clippy_lints/src/loops/empty_loop.rs b/clippy_lints/src/loops/empty_loop.rs new file mode 100644 index 00000000000..43e85538f28 --- /dev/null +++ b/clippy_lints/src/loops/empty_loop.rs @@ -0,0 +1,17 @@ +use super::EMPTY_LOOP; +use crate::utils::{is_in_panic_handler, is_no_std_crate, span_lint_and_help}; + +use rustc_hir::{Block, Expr}; +use rustc_lint::LateContext; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + if loop_block.stmts.is_empty() && loop_block.expr.is_none() && !is_in_panic_handler(cx, expr) { + let msg = "empty `loop {}` wastes CPU cycles"; + let help = if is_no_std_crate(cx) { + "you should either use `panic!()` or add a call pausing or sleeping the thread to the loop body" + } else { + "you should either use `panic!()` or add `std::thread::sleep(..);` to the loop body" + }; + span_lint_and_help(cx, EMPTY_LOOP, expr.span, msg, None, help); + } +} diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs new file mode 100644 index 00000000000..8d98b940c66 --- /dev/null +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -0,0 +1,58 @@ +use super::{ + get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP, +}; +use crate::utils::{get_enclosing_block, is_integer_const, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; + +// To trigger the EXPLICIT_COUNTER_LOOP lint, a variable must be +// incremented exactly once in the loop body, and initialized to zero +// at the start of the loop. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_expr(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + if let Some(block) = get_enclosing_block(&cx, expr.hir_id) { + for id in increment_visitor.into_results() { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, id); + walk_block(&mut initialize_visitor, block); + + if_chain! { + if let Some((name, initializer)) = initialize_visitor.get_result(); + if is_integer_const(cx, initializer, 0); + then { + let mut applicability = Applicability::MachineApplicable; + + let for_span = get_span_of_entire_for_loop(expr); + + span_lint_and_sugg( + cx, + EXPLICIT_COUNTER_LOOP, + for_span.with_hi(arg.span.hi()), + &format!("the variable `{}` is used as a loop counter", name), + "consider using", + format!( + "for ({}, {}) in {}.enumerate()", + name, + snippet_with_applicability(cx, pat.span, "item", &mut applicability), + make_iterator_snippet(cx, arg, &mut applicability), + ), + applicability, + ); + } + } + } + } +} diff --git a/clippy_lints/src/loops/explicit_into_iter_loop.rs b/clippy_lints/src/loops/explicit_into_iter_loop.rs new file mode 100644 index 00000000000..1d778205a2a --- /dev/null +++ b/clippy_lints/src/loops/explicit_into_iter_loop.rs @@ -0,0 +1,27 @@ +use super::EXPLICIT_INTO_ITER_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +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) { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + span_lint_and_sugg( + cx, + EXPLICIT_INTO_ITER_LOOP, + arg.span, + "it is more concise to loop over containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + object.to_string(), + applicability, + ); +} diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs new file mode 100644 index 00000000000..9683e59a396 --- /dev/null +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -0,0 +1,74 @@ +use super::EXPLICIT_ITER_LOOP; +use crate::utils::{match_trait_method, snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty, TyS}; +use rustc_span::sym; + +use crate::utils::{is_type_diagnostic_item, match_type, paths}; + +pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], arg: &Expr<'_>, method_name: &str) { + let should_lint = match method_name { + "iter" | "iter_mut" => is_ref_iterable_type(cx, &args[0]), + "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 ref_receiver_ty = cx.tcx.mk_ref( + cx.tcx.lifetimes.re_erased, + ty::TypeAndMut { + ty: receiver_ty, + mutbl: Mutability::Not, + }, + ); + TyS::same_type(receiver_ty_adjusted, ref_receiver_ty) + }, + _ => false, + }; + + if !should_lint { + return; + } + + let mut applicability = Applicability::MachineApplicable; + let object = snippet_with_applicability(cx, args[0].span, "_", &mut applicability); + let muta = if method_name == "iter_mut" { "mut " } else { "" }; + span_lint_and_sugg( + cx, + EXPLICIT_ITER_LOOP, + arg.span, + "it is more concise to loop over references to containers instead of using explicit \ + iteration methods", + "to write this more concisely, try", + format!("&{}{}", muta, object), + applicability, + ) +} + +/// Returns `true` if the type of expr is one that provides `IntoIterator` impls +/// for `&T` and `&mut T`, such as `Vec`. +#[rustfmt::skip] +fn is_ref_iterable_type(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { + // no walk_ptrs_ty: calling iter() on a reference can make sense because it + // will allow further borrows afterwards + let ty = cx.typeck_results().expr_ty(e); + is_iterable_array(ty, cx) || + is_type_diagnostic_item(cx, ty, sym::vec_type) || + match_type(cx, ty, &paths::LINKED_LIST) || + is_type_diagnostic_item(cx, ty, sym::hashmap_type) || + is_type_diagnostic_item(cx, ty, sym::hashset_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::BINARY_HEAP) || + match_type(cx, ty, &paths::BTREEMAP) || + match_type(cx, ty, &paths::BTREESET) +} + +fn is_iterable_array<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + // IntoIterator is currently only implemented for array sizes <= 32 in rustc + match ty.kind() { + ty::Array(_, n) => n + .try_eval_usize(cx.tcx, cx.param_env) + .map_or(false, |val| (0..=32).contains(&val)), + _ => false, + } +} diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs new file mode 100644 index 00000000000..6ee9b95a3b6 --- /dev/null +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -0,0 +1,71 @@ +use super::FOR_KV_MAP; +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{is_type_diagnostic_item, match_type, multispan_sugg, paths, snippet, span_lint_and_then, sugg}; +use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::sym; + +/// Checks for the `FOR_KV_MAP` lint. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + let pat_span = pat.span; + + if let PatKind::Tuple(ref pat, _) = pat.kind { + if pat.len() == 2 { + let arg_span = arg.span; + let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { + (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), + _ => return, + }, + _ => return, + }; + let mutbl = match mutbl { + Mutability::Not => "", + Mutability::Mut => "_mut", + }; + let arg = match arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, ref expr) => &**expr, + _ => arg, + }; + + if is_type_diagnostic_item(cx, ty, sym::hashmap_type) || match_type(cx, ty, &paths::BTREEMAP) { + span_lint_and_then( + cx, + FOR_KV_MAP, + expr.span, + &format!("you seem to want to iterate on a map's {}s", kind), + |diag| { + let map = sugg::Sugg::hir(cx, arg, "map"); + multispan_sugg( + diag, + "use the corresponding method", + vec![ + (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (arg_span, format!("{}.{}s{}()", map.maybe_par(), kind, mutbl)), + ], + ); + }, + ); + } + } + } +} + +/// Returns `true` if the pattern is a `PatWild` or an ident prefixed with `_`. +fn pat_is_wild<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool { + match *pat { + PatKind::Wild => true, + PatKind::Binding(_, id, ident, None) if ident.as_str().starts_with('_') => { + !LocalUsedVisitor::new(cx, id).check_expr(body) + }, + _ => false, + } +} diff --git a/clippy_lints/src/loops/for_loops_over_fallibles.rs b/clippy_lints/src/loops/for_loops_over_fallibles.rs new file mode 100644 index 00000000000..db22d90a304 --- /dev/null +++ b/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -0,0 +1,45 @@ +use super::FOR_LOOPS_OVER_FALLIBLES; +use crate::utils::{is_type_diagnostic_item, snippet, span_lint_and_help}; +use rustc_hir::{Expr, Pat}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +/// Checks for `for` loops over `Option`s and `Result`s. +pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { + let ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, ty, sym::option_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is an `Option`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } else if is_type_diagnostic_item(cx, ty, sym::result_type) { + span_lint_and_help( + cx, + FOR_LOOPS_OVER_FALLIBLES, + arg.span, + &format!( + "for loop over `{0}`, which is a `Result`. This is more readably written as an \ + `if let` statement", + snippet(cx, arg.span, "_") + ), + None, + &format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ), + ); + } +} diff --git a/clippy_lints/src/loops/iter_next_loop.rs b/clippy_lints/src/loops/iter_next_loop.rs new file mode 100644 index 00000000000..cf78bbc49a3 --- /dev/null +++ b/clippy_lints/src/loops/iter_next_loop.rs @@ -0,0 +1,19 @@ +use super::ITER_NEXT_LOOP; +use crate::utils::{match_trait_method, paths, span_lint}; +use rustc_hir::Expr; +use rustc_lint::LateContext; + +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, expr: &Expr<'_>) -> bool { + if match_trait_method(cx, arg, &paths::ITERATOR) { + span_lint( + cx, + ITER_NEXT_LOOP, + expr.span, + "you are iterating over `Iterator::next()` which is an Option; this will compile but is \ + probably not what you want", + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs new file mode 100644 index 00000000000..3d3ae6f3152 --- /dev/null +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -0,0 +1,79 @@ +use super::utils::make_iterator_snippet; +use super::MANUAL_FLATTEN; +use crate::utils::{is_ok_ctor, is_some_ctor, path_to_local_id, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the +/// iterator element is used. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, +) { + if let ExprKind::Block(ref block, _) = body.kind { + // Ensure the `if let` statement is the only expression or statement in the for-loop + let inner_expr = if block.stmts.len() == 1 && block.expr.is_none() { + let match_stmt = &block.stmts[0]; + if let StmtKind::Semi(inner_expr) = match_stmt.kind { + Some(inner_expr) + } else { + None + } + } else if block.stmts.is_empty() { + block.expr + } else { + None + }; + + if_chain! { + if let Some(inner_expr) = inner_expr; + if let ExprKind::Match( + ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false } + ) = inner_expr.kind; + // Ensure match_expr in `if let` statement is the same as the pat from the for-loop + if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind; + if 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 some_ctor || ok_ctor; + let if_let_type = if some_ctor { "Some" } else { "Ok" }; + + then { + // Prepare the error message + let msg = format!("unnecessary `if let` since only the `{}` variant of the iterator element is used", if_let_type); + + // Prepare the help message + let mut applicability = Applicability::MaybeIncorrect; + let arg_snippet = make_iterator_snippet(cx, arg, &mut applicability); + + span_lint_and_then( + cx, + MANUAL_FLATTEN, + span, + &msg, + |diag| { + let sugg = format!("{}.flatten()", arg_snippet); + diag.span_suggestion( + arg.span, + "try", + sugg, + Applicability::MaybeIncorrect, + ); + diag.span_help( + inner_expr.span, + "...and remove the `if let` statement in the for loop", + ); + } + ); + } + } + } +} diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs new file mode 100644 index 00000000000..fad96c2d5c0 --- /dev/null +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -0,0 +1,451 @@ +use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY}; +use crate::utils::sugg::Sugg; +use crate::utils::{ + get_enclosing_block, higher, is_type_diagnostic_item, path_to_local, snippet, span_lint_and_sugg, sugg, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::intravisit::walk_block; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Pat, PatKind, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Checks for for loops that sequentially copy items from one slice-like +/// object to another. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) -> bool { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { + let mut starts = vec![Start { + id: canonical_id, + kind: StartKind::Range, + }]; + + // This is one of few ways to return different iterators + // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 + let mut iter_a = None; + let mut iter_b = None; + + if let ExprKind::Block(block, _) = body.kind { + if let Some(loop_counters) = get_loop_counters(cx, block, expr) { + starts.extend(loop_counters); + } + iter_a = Some(get_assignments(block, &starts)); + } else { + iter_b = Some(get_assignment(body)); + } + + let assignments = iter_a.into_iter().flatten().chain(iter_b.into_iter()); + + let big_sugg = assignments + // The only statements in the for loops can be indexed assignments from + // indexed retrievals (except increments of loop counters). + .map(|o| { + o.and_then(|(lhs, rhs)| { + let rhs = fetch_cloned_expr(rhs); + if_chain! { + if let ExprKind::Index(base_left, idx_left) = lhs.kind; + if let ExprKind::Index(base_right, idx_right) = rhs.kind; + if is_slice_like(cx, cx.typeck_results().expr_ty(base_left)) + && is_slice_like(cx, cx.typeck_results().expr_ty(base_right)); + if let Some((start_left, offset_left)) = get_details_from_idx(cx, &idx_left, &starts); + if let Some((start_right, offset_right)) = get_details_from_idx(cx, &idx_right, &starts); + + // Source and destination must be different + if path_to_local(base_left) != path_to_local(base_right); + then { + Some((IndexExpr { base: base_left, idx: start_left, idx_offset: offset_left }, + IndexExpr { base: base_right, idx: start_right, idx_offset: offset_right })) + } else { + None + } + } + }) + }) + .map(|o| o.map(|(dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, &dst, &src))) + .collect::<Option<Vec<_>>>() + .filter(|v| !v.is_empty()) + .map(|v| v.join("\n ")); + + if let Some(big_sugg) = big_sugg { + span_lint_and_sugg( + cx, + MANUAL_MEMCPY, + get_span_of_entire_for_loop(expr), + "it looks like you're manually copying between slices", + "try replacing the loop by", + big_sugg, + Applicability::Unspecified, + ); + return true; + } + } + } + false +} + +fn build_manual_memcpy_suggestion<'tcx>( + cx: &LateContext<'tcx>, + start: &Expr<'_>, + end: &Expr<'_>, + limits: ast::RangeLimits, + dst: &IndexExpr<'_>, + src: &IndexExpr<'_>, +) -> String { + fn print_offset(offset: MinifyingSugg<'static>) -> MinifyingSugg<'static> { + if offset.as_str() == "0" { + sugg::EMPTY.into() + } else { + offset + } + } + + let print_limit = |end: &Expr<'_>, end_str: &str, base: &Expr<'_>, sugg: MinifyingSugg<'static>| { + if_chain! { + if let ExprKind::MethodCall(method, _, len_args, _) = end.kind; + if method.ident.name == sym!(len); + if len_args.len() == 1; + if let Some(arg) = len_args.get(0); + if path_to_local(arg) == path_to_local(base); + then { + if sugg.as_str() == end_str { + sugg::EMPTY.into() + } else { + sugg + } + } else { + match limits { + ast::RangeLimits::Closed => { + sugg + &sugg::ONE.into() + }, + ast::RangeLimits::HalfOpen => sugg, + } + } + } + }; + + let start_str = Sugg::hir(cx, start, "").into(); + let end_str: MinifyingSugg<'_> = Sugg::hir(cx, end, "").into(); + + let print_offset_and_limit = |idx_expr: &IndexExpr<'_>| match idx_expr.idx { + StartKind::Range => ( + print_offset(apply_offset(&start_str, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset), + ) + .into_sugg(), + ), + StartKind::Counter { initializer } => { + let counter_start = Sugg::hir(cx, initializer, "").into(); + ( + print_offset(apply_offset(&counter_start, &idx_expr.idx_offset)).into_sugg(), + print_limit( + end, + end_str.as_str(), + idx_expr.base, + apply_offset(&end_str, &idx_expr.idx_offset) + &counter_start - &start_str, + ) + .into_sugg(), + ) + }, + }; + + let (dst_offset, dst_limit) = print_offset_and_limit(&dst); + let (src_offset, src_limit) = print_offset_and_limit(&src); + + let dst_base_str = snippet(cx, dst.base.span, "???"); + let src_base_str = snippet(cx, src.base.span, "???"); + + let dst = if dst_offset == sugg::EMPTY && dst_limit == sugg::EMPTY { + dst_base_str + } else { + format!( + "{}[{}..{}]", + dst_base_str, + dst_offset.maybe_par(), + dst_limit.maybe_par() + ) + .into() + }; + + format!( + "{}.clone_from_slice(&{}[{}..{}]);", + dst, + src_base_str, + src_offset.maybe_par(), + src_limit.maybe_par() + ) +} + +/// a wrapper of `Sugg`. Besides what `Sugg` do, this removes unnecessary `0`; +/// and also, it avoids subtracting a variable from the same one by replacing it with `0`. +/// it exists for the convenience of the overloaded operators while normal functions can do the +/// same. +#[derive(Clone)] +struct MinifyingSugg<'a>(Sugg<'a>); + +impl<'a> MinifyingSugg<'a> { + fn as_str(&self) -> &str { + let (Sugg::NonParen(s) | Sugg::MaybeParen(s) | Sugg::BinOp(_, s)) = &self.0; + s.as_ref() + } + + fn into_sugg(self) -> Sugg<'a> { + self.0 + } +} + +impl<'a> From<Sugg<'a>> for MinifyingSugg<'a> { + fn from(sugg: Sugg<'a>) -> Self { + Self(sugg) + } +} + +impl std::ops::Add for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self.clone(), + (_, _) => (&self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub for &MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self.clone(), + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (&self.0 - &rhs.0).into(), + } + } +} + +impl std::ops::Add<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn add(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + ("0", _) => rhs.clone(), + (_, "0") => self, + (_, _) => (self.0 + &rhs.0).into(), + } + } +} + +impl std::ops::Sub<&MinifyingSugg<'static>> for MinifyingSugg<'static> { + type Output = MinifyingSugg<'static>; + fn sub(self, rhs: &MinifyingSugg<'static>) -> MinifyingSugg<'static> { + match (self.as_str(), rhs.as_str()) { + (_, "0") => self, + ("0", _) => (-rhs.0.clone()).into(), + (x, y) if x == y => sugg::ZERO.into(), + (_, _) => (self.0 - &rhs.0).into(), + } + } +} + +/// a wrapper around `MinifyingSugg`, which carries a operator like currying +/// so that the suggested code become more efficient (e.g. `foo + -bar` `foo - bar`). +struct Offset { + value: MinifyingSugg<'static>, + sign: OffsetSign, +} + +#[derive(Clone, Copy)] +enum OffsetSign { + Positive, + Negative, +} + +impl Offset { + fn negative(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Negative, + } + } + + fn positive(value: Sugg<'static>) -> Self { + Self { + value: value.into(), + sign: OffsetSign::Positive, + } + } + + fn empty() -> Self { + Self::positive(sugg::ZERO) + } +} + +fn apply_offset(lhs: &MinifyingSugg<'static>, rhs: &Offset) -> MinifyingSugg<'static> { + match rhs.sign { + OffsetSign::Positive => lhs + &rhs.value, + OffsetSign::Negative => lhs - &rhs.value, + } +} + +#[derive(Debug, Clone, Copy)] +enum StartKind<'hir> { + Range, + Counter { initializer: &'hir Expr<'hir> }, +} + +struct IndexExpr<'hir> { + base: &'hir Expr<'hir>, + idx: StartKind<'hir>, + idx_offset: Offset, +} + +struct Start<'hir> { + id: HirId, + kind: StartKind<'hir>, +} + +fn is_slice_like<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'_>) -> bool { + let is_slice = match ty.kind() { + ty::Ref(_, subty, _) => is_slice_like(cx, subty), + ty::Slice(..) | ty::Array(..) => true, + _ => false, + }; + + is_slice || is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) +} + +fn fetch_cloned_expr<'tcx>(expr: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { + if_chain! { + if let ExprKind::MethodCall(method, _, args, _) = expr.kind; + if method.ident.name == sym::clone; + if args.len() == 1; + if let Some(arg) = args.get(0); + then { arg } else { expr } + } +} + +fn get_details_from_idx<'tcx>( + cx: &LateContext<'tcx>, + idx: &Expr<'_>, + starts: &[Start<'tcx>], +) -> Option<(StartKind<'tcx>, Offset)> { + fn get_start<'tcx>(e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<StartKind<'tcx>> { + let id = path_to_local(e)?; + starts.iter().find(|start| start.id == id).map(|start| start.kind) + } + + fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>]) -> Option<Sugg<'static>> { + match &e.kind { + ExprKind::Lit(l) => match l.node { + ast::LitKind::Int(x, _ty) => Some(Sugg::NonParen(x.to_string().into())), + _ => None, + }, + ExprKind::Path(..) if get_start(e, starts).is_none() => Some(Sugg::hir(cx, e, "???")), + _ => None, + } + } + + match idx.kind { + ExprKind::Binary(op, lhs, rhs) => match op.node { + BinOpKind::Add => { + let offset_opt = get_start(lhs, starts) + .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) + .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); + + offset_opt.map(|(s, o)| (s, Offset::positive(o))) + }, + BinOpKind::Sub => { + get_start(lhs, starts).and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, Offset::negative(o)))) + }, + _ => None, + }, + ExprKind::Path(..) => get_start(idx, starts).map(|s| (s, Offset::empty())), + _ => None, + } +} + +fn get_assignment<'tcx>(e: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if let ExprKind::Assign(lhs, rhs, _) = e.kind { + Some((lhs, rhs)) + } else { + None + } +} + +/// Get assignments from the given block. +/// The returned iterator yields `None` if no assignment expressions are there, +/// filtering out the increments of the given whitelisted loop counters; +/// because its job is to make sure there's nothing other than assignments and the increments. +fn get_assignments<'a, 'tcx>( + Block { stmts, expr, .. }: &'tcx Block<'tcx>, + loop_counters: &'a [Start<'tcx>], +) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'a { + // As the `filter` and `map` below do different things, I think putting together + // just increases complexity. (cc #3188 and #4193) + stmts + .iter() + .filter_map(move |stmt| match stmt.kind { + StmtKind::Local(..) | StmtKind::Item(..) => None, + StmtKind::Expr(e) | StmtKind::Semi(e) => Some(e), + }) + .chain((*expr).into_iter()) + .filter(move |e| { + if let ExprKind::AssignOp(_, place, _) = e.kind { + path_to_local(place).map_or(false, |id| { + !loop_counters + .iter() + // skip the first item which should be `StartKind::Range` + // this makes it possible to use the slice with `StartKind::Range` in the same iterator loop. + .skip(1) + .any(|counter| counter.id == id) + }) + } else { + true + } + }) + .map(get_assignment) +} + +fn get_loop_counters<'a, 'tcx>( + cx: &'a LateContext<'tcx>, + body: &'tcx Block<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<impl Iterator<Item = Start<'tcx>> + 'a> { + // Look for variables that are incremented once per loop iteration. + let mut increment_visitor = IncrementVisitor::new(cx); + walk_block(&mut increment_visitor, body); + + // For each candidate, check the parent block to see if + // it's initialized to zero at the start of the loop. + get_enclosing_block(&cx, expr.hir_id).and_then(|block| { + increment_visitor + .into_results() + .filter_map(move |var_id| { + let mut initialize_visitor = InitializeVisitor::new(cx, expr, var_id); + walk_block(&mut initialize_visitor, block); + + initialize_visitor.get_result().map(|(_, initializer)| Start { + id: var_id, + kind: StartKind::Counter { initializer }, + }) + }) + .into() + }) +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs new file mode 100644 index 00000000000..2a372c6307e --- /dev/null +++ b/clippy_lints/src/loops/mod.rs @@ -0,0 +1,627 @@ +mod empty_loop; +mod explicit_counter_loop; +mod explicit_into_iter_loop; +mod explicit_iter_loop; +mod for_kv_map; +mod for_loops_over_fallibles; +mod iter_next_loop; +mod manual_flatten; +mod manual_memcpy; +mod mut_range_bound; +mod needless_collect; +mod needless_range_loop; +mod never_loop; +mod same_item_push; +mod single_element_loop; +mod utils; +mod while_immutable_condition; +mod while_let_loop; +mod while_let_on_iterator; + +use crate::utils::higher; +use rustc_hir::{Expr, ExprKind, LoopSource, Pat}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; +use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor}; + +declare_clippy_lint! { + /// **What it does:** Checks for for-loops that manually copy items between + /// slices that could be optimized by having a memcpy. + /// + /// **Why is this bad?** It is not as fast as a memcpy. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// for i in 0..src.len() { + /// dst[i + 64] = src[i]; + /// } + /// ``` + /// Could be written as: + /// ```rust + /// # let src = vec![1]; + /// # let mut dst = vec![0; 65]; + /// dst[64..(src.len() + 64)].clone_from_slice(&src[..]); + /// ``` + pub MANUAL_MEMCPY, + perf, + "manually copying items between slices" +} + +declare_clippy_lint! { + /// **What it does:** Checks for looping over the range of `0..len` of some + /// collection just to get the values by index. + /// + /// **Why is this bad?** Just iterating the collection itself makes the intent + /// more clear and is probably faster. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in 0..vec.len() { + /// println!("{}", vec[i]); + /// } + /// ``` + /// Could be written as: + /// ```rust + /// let vec = vec!['a', 'b', 'c']; + /// for i in vec { + /// println!("{}", i); + /// } + /// ``` + pub NEEDLESS_RANGE_LOOP, + style, + "for-looping over a range of indices where an iterator over items would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.iter()` where `&x` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** False negatives. We currently only warn on some known + /// types. + /// + /// **Example:** + /// ```rust + /// // with `y` a `Vec` or slice: + /// # let y = vec![1]; + /// for x in y.iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in &y { + /// // .. + /// } + /// ``` + pub EXPLICIT_ITER_LOOP, + pedantic, + "for-looping over `_.iter()` or `_.iter_mut()` when `&_` or `&mut _` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `y.into_iter()` where `y` will do, and + /// suggests the latter. + /// + /// **Why is this bad?** Readability. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// # let y = vec![1]; + /// // with `y` a `Vec` or slice: + /// for x in y.into_iter() { + /// // .. + /// } + /// ``` + /// can be rewritten to + /// ```rust + /// # let y = vec![1]; + /// for x in y { + /// // .. + /// } + /// ``` + pub EXPLICIT_INTO_ITER_LOOP, + pedantic, + "for-looping over `_.into_iter()` when `_` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops on `x.next()`. + /// + /// **Why is this bad?** `next()` returns either `Some(value)` if there was a + /// value, or `None` otherwise. The insidious thing is that `Option<_>` + /// implements `IntoIterator`, so that possibly one value will be iterated, + /// leading to some hard to find bugs. No one will want to write such code + /// [except to win an Underhanded Rust + /// Contest](https://www.reddit.com/r/rust/comments/3hb0wm/underhanded_rust_contest/cu5yuhr). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for x in y.next() { + /// .. + /// } + /// ``` + pub ITER_NEXT_LOOP, + correctness, + "for-looping over `_.next()` which is probably not intended" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `for` loops over `Option` or `Result` values. + /// + /// **Why is this bad?** Readability. This is more clearly expressed as an `if + /// let`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let opt = Some(1); + /// + /// // Bad + /// for x in opt { + /// // .. + /// } + /// + /// // Good + /// if let Some(x) = opt { + /// // .. + /// } + /// ``` + /// + /// // or + /// + /// ```rust + /// # let res: Result<i32, std::io::Error> = Ok(1); + /// + /// // Bad + /// for x in &res { + /// // .. + /// } + /// + /// // Good + /// if let Ok(x) = res { + /// // .. + /// } + /// ``` + pub FOR_LOOPS_OVER_FALLIBLES, + correctness, + "for-looping over an `Option` or a `Result`, which is more clearly expressed as an `if let`" +} + +declare_clippy_lint! { + /// **What it does:** Detects `loop + match` combinations that are easier + /// written as a `while let` loop. + /// + /// **Why is this bad?** The `while let` loop is usually shorter and more + /// readable. + /// + /// **Known problems:** Sometimes the wrong binding is displayed ([#383](https://github.com/rust-lang/rust-clippy/issues/383)). + /// + /// **Example:** + /// ```rust,no_run + /// # let y = Some(1); + /// loop { + /// let x = match y { + /// Some(x) => x, + /// None => break, + /// }; + /// // .. do something with x + /// } + /// // is easier written as + /// while let Some(x) = y { + /// // .. do something with x + /// }; + /// ``` + pub WHILE_LET_LOOP, + complexity, + "`loop { if let { ... } else break }`, which can be written as a `while let` loop" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions collecting an iterator when collect + /// is not needed. + /// + /// **Why is this bad?** `collect` causes the allocation of a new data structure, + /// when this allocation may not be needed. + /// + /// **Known problems:** + /// None + /// + /// **Example:** + /// ```rust + /// # let iterator = vec![1].into_iter(); + /// let len = iterator.clone().collect::<Vec<_>>().len(); + /// // should be + /// let len = iterator.count(); + /// ``` + pub NEEDLESS_COLLECT, + perf, + "collecting an iterator when collect is not needed" +} + +declare_clippy_lint! { + /// **What it does:** Checks `for` loops over slices with an explicit counter + /// and suggests the use of `.enumerate()`. + /// + /// **Why is it bad?** Using `.enumerate()` makes the intent more clear, + /// declutters the code and may be faster in some instances. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// let mut i = 0; + /// for item in &v { + /// bar(i, *item); + /// i += 1; + /// } + /// ``` + /// Could be written as + /// ```rust + /// # let v = vec![1]; + /// # fn bar(bar: usize, baz: usize) {} + /// for (i, item) in v.iter().enumerate() { bar(i, *item); } + /// ``` + pub EXPLICIT_COUNTER_LOOP, + complexity, + "for-looping with an explicit counter when `_.enumerate()` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for empty `loop` expressions. + /// + /// **Why is this bad?** These busy loops burn CPU cycles without doing + /// anything. It is _almost always_ a better idea to `panic!` than to have + /// a busy loop. + /// + /// If panicking isn't possible, think of the environment and either: + /// - block on something + /// - sleep the thread for some microseconds + /// - yield or pause the thread + /// + /// For `std` targets, this can be done with + /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) + /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html). + /// + /// For `no_std` targets, doing this is more complicated, especially because + /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will + /// probably need to invoke some target-specific intrinsic. Examples include: + /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html) + /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html) + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```no_run + /// loop {} + /// ``` + pub EMPTY_LOOP, + style, + "empty `loop {}`, which should block or sleep" +} + +declare_clippy_lint! { + /// **What it does:** Checks for `while let` expressions on iterators. + /// + /// **Why is this bad?** Readability. A simple `for` loop is shorter and conveys + /// the intent better. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// while let Some(val) = iter() { + /// .. + /// } + /// ``` + pub WHILE_LET_ON_ITERATOR, + style, + "using a `while let` loop instead of a for loop on an iterator" +} + +declare_clippy_lint! { + /// **What it does:** Checks for iterating a map (`HashMap` or `BTreeMap`) and + /// ignoring either the keys or values. + /// + /// **Why is this bad?** Readability. There are `keys` and `values` methods that + /// can be used to express that don't need the values or keys. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```ignore + /// for (k, _) in &map { + /// .. + /// } + /// ``` + /// + /// could be replaced by + /// + /// ```ignore + /// for k in map.keys() { + /// .. + /// } + /// ``` + pub FOR_KV_MAP, + style, + "looping on a map using `iter` when `keys` or `values` would do" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops that will always `break`, `return` or + /// `continue` an outer loop. + /// + /// **Why is this bad?** This loop never loops, all it does is obfuscating the + /// code. + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// loop { + /// ..; + /// break; + /// } + /// ``` + pub NEVER_LOOP, + correctness, + "any loop that will always `break` or `return`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for loops which have a range bound that is a mutable variable + /// + /// **Why is this bad?** One might think that modifying the mutable variable changes the loop bounds + /// + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let mut foo = 42; + /// for i in 0..foo { + /// foo -= 1; + /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 + /// } + /// ``` + pub MUT_RANGE_BOUND, + complexity, + "for loop over a range where one of the bounds is a mutable variable" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether variables used within while loop condition + /// can be (and are) mutated in the body. + /// + /// **Why is this bad?** If the condition is unchanged, entering the body of the loop + /// will lead to an infinite loop. + /// + /// **Known problems:** If the `while`-loop is in a closure, the check for mutation of the + /// condition variables in the body can cause false negatives. For example when only `Upvar` `a` is + /// in the condition and only `Upvar` `b` gets mutated in the body, the lint will not trigger. + /// + /// **Example:** + /// ```rust + /// let i = 0; + /// while i > 10 { + /// println!("let me loop forever!"); + /// } + /// ``` + pub WHILE_IMMUTABLE_CONDITION, + correctness, + "variables used within while expression are not mutated in the body" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether a for loop is being used to push a constant + /// value into a Vec. + /// + /// **Why is this bad?** This kind of operation can be expressed more succinctly with + /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also + /// have better performance. + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let item1 = 2; + /// let item2 = 3; + /// let mut vec: Vec<u8> = Vec::new(); + /// for _ in 0..20 { + /// vec.push(item1); + /// } + /// for _ in 0..30 { + /// vec.push(item2); + /// } + /// ``` + /// could be written as + /// ```rust + /// let item1 = 2; + /// let item2 = 3; + /// let mut vec: Vec<u8> = vec![item1; 20]; + /// vec.resize(20 + 30, item2); + /// ``` + pub SAME_ITEM_PUSH, + style, + "the same item is pushed inside of a for loop" +} + +declare_clippy_lint! { + /// **What it does:** Checks whether a for loop has a single element. + /// + /// **Why is this bad?** There is no reason to have a loop of a + /// single element. + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let item1 = 2; + /// for item in &[item1] { + /// println!("{}", item); + /// } + /// ``` + /// could be written as + /// ```rust + /// let item1 = 2; + /// let item = &item1; + /// println!("{}", item); + /// ``` + pub SINGLE_ELEMENT_LOOP, + complexity, + "there is no reason to have a single element loop" +} + +declare_clippy_lint! { + /// **What it does:** Check for unnecessary `if let` usage in a for loop + /// where only the `Some` or `Ok` variant of the iterator element is used. + /// + /// **Why is this bad?** It is verbose and can be simplified + /// by first calling the `flatten` method on the `Iterator`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x = vec![Some(1), Some(2), Some(3)]; + /// for n in x { + /// if let Some(n) = n { + /// println!("{}", n); + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// let x = vec![Some(1), Some(2), Some(3)]; + /// for n in x.into_iter().flatten() { + /// println!("{}", n); + /// } + /// ``` + pub MANUAL_FLATTEN, + complexity, + "for loops over `Option`s or `Result`s with a single expression can be simplified" +} + +declare_lint_pass!(Loops => [ + MANUAL_MEMCPY, + MANUAL_FLATTEN, + NEEDLESS_RANGE_LOOP, + EXPLICIT_ITER_LOOP, + EXPLICIT_INTO_ITER_LOOP, + ITER_NEXT_LOOP, + FOR_LOOPS_OVER_FALLIBLES, + WHILE_LET_LOOP, + NEEDLESS_COLLECT, + EXPLICIT_COUNTER_LOOP, + EMPTY_LOOP, + WHILE_LET_ON_ITERATOR, + FOR_KV_MAP, + NEVER_LOOP, + MUT_RANGE_BOUND, + WHILE_IMMUTABLE_CONDITION, + SAME_ITEM_PUSH, + SINGLE_ELEMENT_LOOP, +]); + +impl<'tcx> LateLintPass<'tcx> for Loops { + #[allow(clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some((pat, arg, body, span)) = higher::for_loop(expr) { + // we don't want to check expanded macros + // this check is not at the top of the function + // since higher::for_loop expressions are marked as expansions + if body.span.from_expansion() { + return; + } + check_for_loop(cx, pat, arg, body, expr, span); + } + + // we don't want to check expanded macros + if expr.span.from_expansion() { + return; + } + + // check for never_loop + never_loop::check(cx, expr); + + // check for `loop { if let {} else break }` that could be `while let` + // (also matches an explicit "match" instead of "if let") + // (even if the "match" or "if let" is used for declaration) + if let ExprKind::Loop(ref block, _, LoopSource::Loop, _) = expr.kind { + // also check for empty `loop {}` statements, skipping those in #[panic_handler] + empty_loop::check(cx, expr, block); + while_let_loop::check(cx, expr, block); + } + + while_let_on_iterator::check(cx, expr); + + if let Some((cond, body)) = higher::while_loop(&expr) { + while_immutable_condition::check(cx, cond, body); + } + + needless_collect::check(expr, cx); + } +} + +fn check_for_loop<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, + span: Span, +) { + let is_manual_memcpy_triggered = manual_memcpy::check(cx, pat, arg, body, expr); + if !is_manual_memcpy_triggered { + needless_range_loop::check(cx, pat, arg, body, expr); + explicit_counter_loop::check(cx, pat, arg, body, expr); + } + check_for_loop_arg(cx, pat, arg, expr); + for_kv_map::check(cx, pat, arg, body, expr); + mut_range_bound::check(cx, arg, body); + single_element_loop::check(cx, pat, arg, body, expr); + same_item_push::check(cx, pat, arg, body, expr); + manual_flatten::check(cx, pat, arg, body, span); +} + +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(ref method, _, ref 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 !next_loop_linted { + for_loops_over_fallibles::check(cx, pat, arg); + } +} diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs new file mode 100644 index 00000000000..3ae592950f1 --- /dev/null +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -0,0 +1,115 @@ +use super::MUT_RANGE_BOUND; +use crate::utils::{higher, path_to_local, span_lint}; +use if_chain::if_chain; +use rustc_hir::{BindingAnnotation, Expr, HirId, Node, PatKind}; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_typeck::expr_use_visitor::{ConsumeMode, Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; + +pub(super) fn check(cx: &LateContext<'_>, arg: &Expr<'_>, body: &Expr<'_>) { + if let Some(higher::Range { + start: Some(start), + end: Some(end), + .. + }) = higher::range(arg) + { + let mut_ids = vec![check_for_mutability(cx, start), check_for_mutability(cx, end)]; + if mut_ids[0].is_some() || mut_ids[1].is_some() { + let (span_low, span_high) = check_for_mutation(cx, body, &mut_ids); + mut_warn_with_span(cx, span_low); + mut_warn_with_span(cx, span_high); + } + } +} + +fn mut_warn_with_span(cx: &LateContext<'_>, span: Option<Span>) { + if let Some(sp) = span { + span_lint( + cx, + MUT_RANGE_BOUND, + sp, + "attempt to mutate range bound within loop; note that the range of the loop is unchanged", + ); + } +} + +fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId> { + if_chain! { + if let Some(hir_id) = path_to_local(bound); + if let Node::Binding(pat) = cx.tcx.hir().get(hir_id); + if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind; + then { + return Some(hir_id); + } + } + None +} + +fn check_for_mutation<'tcx>( + cx: &LateContext<'tcx>, + body: &Expr<'_>, + bound_ids: &[Option<HirId>], +) -> (Option<Span>, Option<Span>) { + let mut delegate = MutatePairDelegate { + cx, + hir_id_low: bound_ids[0], + hir_id_high: bound_ids[1], + span_low: None, + span_high: None, + }; + cx.tcx.infer_ctxt().enter(|infcx| { + ExprUseVisitor::new( + &mut delegate, + &infcx, + body.hir_id.owner, + cx.param_env, + cx.typeck_results(), + ) + .walk_expr(body); + }); + delegate.mutation_span() +} + +struct MutatePairDelegate<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + hir_id_low: Option<HirId>, + hir_id_high: Option<HirId>, + span_low: Option<Span>, + span_high: Option<Span>, +} + +impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { + fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ConsumeMode) {} + + fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { + if let ty::BorrowKind::MutBorrow = bk { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } + } + + fn mutate(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId) { + if let PlaceBase::Local(id) = cmt.place.base { + if Some(id) == self.hir_id_low { + self.span_low = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + if Some(id) == self.hir_id_high { + self.span_high = Some(self.cx.tcx.hir().span(diag_expr_id)) + } + } + } +} + +impl MutatePairDelegate<'_, '_> { + fn mutation_span(&self) -> (Option<Span>, Option<Span>) { + (self.span_low, self.span_high) + } +} diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs new file mode 100644 index 00000000000..92560c80629 --- /dev/null +++ b/clippy_lints/src/loops/needless_collect.rs @@ -0,0 +1,283 @@ +use super::NEEDLESS_COLLECT; +use crate::utils::sugg::Sugg; +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, match_type, path_to_local_id, paths, snippet, span_lint_and_sugg, + span_lint_and_then, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::{sym, Ident}; + +const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; + +pub(super) fn check<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + check_needless_collect_direct_usage(expr, cx); + check_needless_collect_indirect_usage(expr, cx); +} +fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref args, _) = expr.kind; + if let ExprKind::MethodCall(ref chain_method, _, _, _) = args[0].kind; + if chain_method.ident.name == sym!(collect) && match_trait_method(cx, &args[0], &paths::ITERATOR); + if let Some(ref generic_args) = chain_method.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + then { + let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::BTREEMAP) || + is_type_diagnostic_item(cx, ty, sym::hashmap_type) { + if method.ident.name == sym!(len) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "count()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(is_empty) { + let span = shorten_needless_collect_span(expr); + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + "replace with", + "next().is_none()".to_string(), + Applicability::MachineApplicable, + ); + } + if method.ident.name == sym!(contains) { + let contains_arg = snippet(cx, args[1].span, "??"); + let span = shorten_needless_collect_span(expr); + span_lint_and_then( + cx, + NEEDLESS_COLLECT, + span, + NEEDLESS_COLLECT_MSG, + |diag| { + let (arg, pred) = contains_arg + .strip_prefix('&') + .map_or(("&x", &*contains_arg), |s| ("x", s)); + diag.span_suggestion( + span, + "replace with", + format!( + "any(|{}| x == {})", + arg, pred + ), + Applicability::MachineApplicable, + ); + } + ); + } + } + } + } +} + +fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + if let ExprKind::Block(ref block, _) = expr.kind { + for ref stmt in block.stmts { + if_chain! { + if let StmtKind::Local( + Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, + init: Some(ref init_expr), .. } + ) = stmt.kind; + if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind; + if method_name.ident.name == sym!(collect) && match_trait_method(cx, &init_expr, &paths::ITERATOR); + if let Some(ref generic_args) = method_name.args; + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); + if let ty = cx.typeck_results().node_type(ty.hir_id); + if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + match_type(cx, ty, &paths::LINKED_LIST); + if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); + if iter_calls.len() == 1; + then { + let mut used_count_visitor = UsedCountVisitor { + cx, + id: *pat_id, + count: 0, + }; + walk_block(&mut used_count_visitor, block); + if used_count_visitor.count > 1 { + return; + } + + // Suggest replacing iter_call with iter_replacement, and removing stmt + let iter_call = &iter_calls[0]; + span_lint_and_then( + cx, + super::NEEDLESS_COLLECT, + stmt.span.until(iter_call.span), + NEEDLESS_COLLECT_MSG, + |diag| { + let iter_replacement = format!("{}{}", Sugg::hir(cx, iter_source, ".."), iter_call.get_iter_method(cx)); + diag.multipart_suggestion( + iter_call.get_suggestion_text(), + vec![ + (stmt.span, String::new()), + (iter_call.span, iter_replacement) + ], + Applicability::MachineApplicable,// MaybeIncorrect, + ).emit(); + }, + ); + } + } + } + } +} + +struct IterFunction { + func: IterFunctionKind, + span: Span, +} +impl IterFunction { + fn get_iter_method(&self, cx: &LateContext<'_>) -> String { + match &self.func { + IterFunctionKind::IntoIter => String::new(), + IterFunctionKind::Len => String::from(".count()"), + IterFunctionKind::IsEmpty => String::from(".next().is_none()"), + IterFunctionKind::Contains(span) => { + let s = snippet(cx, *span, ".."); + if let Some(stripped) = s.strip_prefix('&') { + format!(".any(|x| x == {})", stripped) + } else { + format!(".any(|x| x == *{})", s) + } + }, + } + } + fn get_suggestion_text(&self) -> &'static str { + match &self.func { + IterFunctionKind::IntoIter => { + "use the original Iterator instead of collecting it and then producing a new one" + }, + IterFunctionKind::Len => { + "take the original Iterator's count instead of collecting it and finding the length" + }, + IterFunctionKind::IsEmpty => { + "check if the original Iterator has anything instead of collecting it and seeing if it's empty" + }, + IterFunctionKind::Contains(_) => { + "check if the original Iterator contains an element instead of collecting then checking" + }, + } + } +} +enum IterFunctionKind { + IntoIter, + Len, + IsEmpty, + Contains(Span), +} + +struct IterFunctionVisitor { + uses: Vec<IterFunction>, + seen_other: bool, + target: Ident, +} +impl<'tcx> Visitor<'tcx> for IterFunctionVisitor { + fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { + // Check function calls on our collection + if_chain! { + if let ExprKind::MethodCall(method_name, _, ref args, _) = &expr.kind; + if let Some(Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. }) = args.get(0); + if let &[name] = &path.segments; + if name.ident == self.target; + then { + let len = sym!(len); + let is_empty = sym!(is_empty); + let contains = sym!(contains); + match method_name.ident.name { + sym::into_iter => self.uses.push( + IterFunction { func: IterFunctionKind::IntoIter, span: expr.span } + ), + name if name == len => self.uses.push( + IterFunction { func: IterFunctionKind::Len, span: expr.span } + ), + name if name == is_empty => self.uses.push( + IterFunction { func: IterFunctionKind::IsEmpty, span: expr.span } + ), + name if name == contains => self.uses.push( + IterFunction { func: IterFunctionKind::Contains(args[1].span), span: expr.span } + ), + _ => self.seen_other = true, + } + return + } + } + // Check if the collection is used for anything else + if_chain! { + if let Expr { kind: ExprKind::Path(QPath::Resolved(_, ref path)), .. } = expr; + if let &[name] = &path.segments; + if name.ident == self.target; + then { + self.seen_other = true; + } else { + walk_expr(self, expr); + } + } + } + + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +struct UsedCountVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + id: HirId, + count: usize, +} + +impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if path_to_local_id(expr, self.id) { + self.count += 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +/// Detect the occurrences of calls to `iter` or `into_iter` for the +/// given identifier +fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> { + let mut visitor = IterFunctionVisitor { + uses: Vec::new(), + target: identifier, + seen_other: false, + }; + visitor.visit_block(block); + if visitor.seen_other { None } else { Some(visitor.uses) } +} + +fn shorten_needless_collect_span(expr: &Expr<'_>) -> Span { + if_chain! { + if let ExprKind::MethodCall(.., args, _) = &expr.kind; + if let ExprKind::MethodCall(_, span, ..) = &args[0].kind; + then { + return expr.span.with_lo(span.lo()); + } + } + unreachable!(); +} diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs new file mode 100644 index 00000000000..5f02e4b9d87 --- /dev/null +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -0,0 +1,391 @@ +use super::NEEDLESS_RANGE_LOOP; +use crate::utils::visitors::LocalUsedVisitor; +use crate::utils::{ + contains_name, has_iter_method, higher, is_integer_const, match_trait_method, multispan_sugg, path_to_local_id, + paths, snippet, span_lint_and_then, sugg, SpanlessEq, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_middle::middle::region; +use rustc_middle::ty::{self, Ty}; +use rustc_span::symbol::{sym, Symbol}; +use std::iter::Iterator; +use std::mem; + +/// Checks for looping over a range and then indexing a sequence with it. +/// The iteratee must be a range literal. +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if let Some(higher::Range { + start: Some(start), + ref end, + limits, + }) = higher::range(arg) + { + // the var must be a single name + if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { + let mut visitor = VarVisitor { + cx, + var: canonical_id, + indexed_mut: FxHashSet::default(), + indexed_indirectly: FxHashMap::default(), + indexed_directly: FxHashMap::default(), + referenced: FxHashSet::default(), + nonindex: false, + prefer_mutable: false, + }; + walk_expr(&mut visitor, body); + + // linting condition: we only indexed one variable, and indexed it directly + if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + let (indexed, (indexed_extent, indexed_ty)) = visitor + .indexed_directly + .into_iter() + .next() + .expect("already checked that we have exactly 1 element"); + + // ensure that the indexed variable was declared before the loop, see #601 + if let Some(indexed_extent) = indexed_extent { + let parent_id = cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = cx.tcx.hir().local_def_id(parent_id); + let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id); + if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { + return; + } + } + + // don't lint if the container that is indexed does not have .iter() method + let has_iter = has_iter_method(cx, indexed_ty); + if has_iter.is_none() { + return; + } + + // don't lint if the container that is indexed into is also used without + // indexing + if visitor.referenced.contains(&indexed) { + return; + } + + let starts_at_zero = is_integer_const(cx, start, 0); + + let skip = if starts_at_zero { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start) { + return; + } else { + format!(".skip({})", snippet(cx, start.span, "..")) + }; + + let mut end_is_start_plus_val = false; + + let take = if let Some(end) = *end { + let mut take_expr = end; + + if let ExprKind::Binary(ref op, ref left, ref right) = end.kind { + if let BinOpKind::Add = op.node { + let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); + let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); + + if start_equal_left { + take_expr = right; + } else if start_equal_right { + take_expr = left; + } + + end_is_start_plus_val = start_equal_left | start_equal_right; + } + } + + if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) { + return; + } else { + match limits { + ast::RangeLimits::Closed => { + let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>"); + format!(".take({})", take_expr + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => format!(".take({})", snippet(cx, take_expr.span, "..")), + } + } + } else { + String::new() + }; + + let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { + ("mut ", "iter_mut") + } else { + ("", "iter") + }; + + let take_is_empty = take.is_empty(); + let mut method_1 = take; + let mut method_2 = skip; + + if end_is_start_plus_val { + mem::swap(&mut method_1, &mut method_2); + } + + if visitor.nonindex { + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![ + (pat.span, format!("({}, <item>)", ident.name)), + ( + arg.span, + format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2), + ), + ], + ); + }, + ); + } else { + let repl = if starts_at_zero && take_is_empty { + format!("&{}{}", ref_mut, indexed) + } else { + format!("{}.{}(){}{}", indexed, method, method_1, method_2) + }; + + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + expr.span, + &format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed), + |diag| { + multispan_sugg( + diag, + "consider using an iterator", + vec![(pat.span, "<item>".to_string()), (arg.span, repl)], + ); + }, + ); + } + } + } + } +} + +fn is_len_call(expr: &Expr<'_>, var: Symbol) -> bool { + if_chain! { + if let ExprKind::MethodCall(ref method, _, ref len_args, _) = expr.kind; + if len_args.len() == 1; + if method.ident.name == sym!(len); + if let ExprKind::Path(QPath::Resolved(_, ref path)) = len_args[0].kind; + if path.segments.len() == 1; + if path.segments[0].ident.name == var; + then { + return true; + } + } + + false +} + +fn is_end_eq_array_len<'tcx>( + cx: &LateContext<'tcx>, + end: &Expr<'_>, + limits: ast::RangeLimits, + indexed_ty: Ty<'tcx>, +) -> bool { + if_chain! { + if let ExprKind::Lit(ref lit) = end.kind; + if let ast::LitKind::Int(end_int, _) = lit.node; + if let ty::Array(_, arr_len_const) = indexed_ty.kind(); + if let Some(arr_len) = arr_len_const.try_eval_usize(cx.tcx, cx.param_env); + then { + return match limits { + ast::RangeLimits::Closed => end_int + 1 >= arr_len.into(), + ast::RangeLimits::HalfOpen => end_int >= arr_len.into(), + }; + } + } + + false +} + +struct VarVisitor<'a, 'tcx> { + /// context reference + cx: &'a LateContext<'tcx>, + /// var name to look for as index + var: HirId, + /// indexed variables that are used mutably + indexed_mut: FxHashSet<Symbol>, + /// indirectly indexed variables (`v[(i + 4) % N]`), the extend is `None` for global + indexed_indirectly: FxHashMap<Symbol, Option<region::Scope>>, + /// subset of `indexed` of vars that are indexed directly: `v[i]` + /// this will not contain cases like `v[calc_index(i)]` or `v[(i + 4) % N]` + indexed_directly: FxHashMap<Symbol, (Option<region::Scope>, Ty<'tcx>)>, + /// Any names that are used outside an index operation. + /// Used to detect things like `&mut vec` used together with `vec[i]` + referenced: FxHashSet<Symbol>, + /// has the loop variable been used in expressions other than the index of + /// an index op? + nonindex: bool, + /// Whether we are inside the `$` in `&mut $` or `$ = foo` or `$.bar`, where bar + /// takes `&mut self` + prefer_mutable: bool, +} + +impl<'a, 'tcx> VarVisitor<'a, 'tcx> { + fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { + if_chain! { + // the indexed container is referenced by a name + if let ExprKind::Path(ref seqpath) = seqexpr.kind; + if let QPath::Resolved(None, ref seqvar) = *seqpath; + if seqvar.segments.len() == 1; + then { + let index_used_directly = path_to_local_id(idx, self.var); + let indexed_indirectly = { + let mut used_visitor = LocalUsedVisitor::new(self.cx, self.var); + walk_expr(&mut used_visitor, idx); + used_visitor.used + }; + + if indexed_indirectly || index_used_directly { + if self.prefer_mutable { + self.indexed_mut.insert(seqvar.segments[0].ident.name); + } + let res = self.cx.qpath_res(seqpath, seqexpr.hir_id); + match res { + Res::Local(hir_id) => { + let parent_id = self.cx.tcx.hir().get_parent_item(expr.hir_id); + let parent_def_id = self.cx.tcx.hir().local_def_id(parent_id); + let extent = self.cx.tcx.region_scope_tree(parent_def_id).var_scope(hir_id.local_id); + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, Some(extent)); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (Some(extent), self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + Res::Def(DefKind::Static | DefKind::Const, ..) => { + if indexed_indirectly { + self.indexed_indirectly.insert(seqvar.segments[0].ident.name, None); + } + if index_used_directly { + self.indexed_directly.insert( + seqvar.segments[0].ident.name, + (None, self.cx.typeck_results().node_type(seqexpr.hir_id)), + ); + } + return false; // no need to walk further *on the variable* + } + _ => (), + } + } + } + } + true + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if_chain! { + // a range index op + if let ExprKind::MethodCall(ref meth, _, ref args, _) = expr.kind; + if (meth.ident.name == sym::index && match_trait_method(self.cx, expr, &paths::INDEX)) + || (meth.ident.name == sym::index_mut && match_trait_method(self.cx, expr, &paths::INDEX_MUT)); + if !self.check(&args[1], &args[0], expr); + then { return } + } + + if_chain! { + // an index op + if let ExprKind::Index(ref seqexpr, ref idx) = expr.kind; + if !self.check(idx, seqexpr, expr); + then { return } + } + + if_chain! { + // directly using a variable + if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind; + if let Res::Local(local_id) = path.res; + then { + if local_id == self.var { + self.nonindex = true; + } else { + // not the correct variable, but still a variable + self.referenced.insert(path.segments[0].ident.name); + } + } + } + + let old = self.prefer_mutable; + match expr.kind { + ExprKind::AssignOp(_, ref lhs, ref rhs) | ExprKind::Assign(ref lhs, ref rhs, _) => { + self.prefer_mutable = true; + self.visit_expr(lhs); + self.prefer_mutable = false; + self.visit_expr(rhs); + }, + ExprKind::AddrOf(BorrowKind::Ref, mutbl, ref expr) => { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + self.visit_expr(expr); + }, + ExprKind::Call(ref f, args) => { + self.visit_expr(f); + for expr in args { + let ty = self.cx.typeck_results().expr_ty_adjusted(expr); + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::MethodCall(_, _, args, _) => { + let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap(); + for (ty, expr) in self.cx.tcx.fn_sig(def_id).inputs().skip_binder().iter().zip(args) { + self.prefer_mutable = false; + if let ty::Ref(_, _, mutbl) = *ty.kind() { + if mutbl == Mutability::Mut { + self.prefer_mutable = true; + } + } + self.visit_expr(expr); + } + }, + ExprKind::Closure(_, _, body_id, ..) => { + let body = self.cx.tcx.hir().body(body_id); + self.visit_expr(&body.value); + }, + _ => walk_expr(self, expr), + } + self.prefer_mutable = old; + } + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs new file mode 100644 index 00000000000..45e1001d755 --- /dev/null +++ b/clippy_lints/src/loops/never_loop.rs @@ -0,0 +1,172 @@ +use super::NEVER_LOOP; +use crate::utils::span_lint; +use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Stmt, StmtKind}; +use rustc_lint::LateContext; +use std::iter::{once, Iterator}; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Loop(ref block, _, _, _) = expr.kind { + match never_loop_block(block, expr.hir_id) { + NeverLoopResult::AlwaysBreak => span_lint(cx, NEVER_LOOP, expr.span, "this loop never actually loops"), + NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (), + } + } +} + +enum NeverLoopResult { + // A break/return always get triggered but not necessarily for the main loop. + AlwaysBreak, + // A continue may occur for the main loop. + MayContinueMainLoop, + Otherwise, +} + +#[must_use] +fn absorb_break(arg: &NeverLoopResult) -> NeverLoopResult { + match *arg { + NeverLoopResult::AlwaysBreak | NeverLoopResult::Otherwise => NeverLoopResult::Otherwise, + NeverLoopResult::MayContinueMainLoop => NeverLoopResult::MayContinueMainLoop, + } +} + +// Combine two results for parts that are called in order. +#[must_use] +fn combine_seq(first: NeverLoopResult, second: NeverLoopResult) -> NeverLoopResult { + match first { + NeverLoopResult::AlwaysBreak | NeverLoopResult::MayContinueMainLoop => first, + NeverLoopResult::Otherwise => second, + } +} + +// Combine two results where both parts are called but not necessarily in order. +#[must_use] +fn combine_both(left: NeverLoopResult, right: NeverLoopResult) -> NeverLoopResult { + match (left, right) { + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::AlwaysBreak, _) | (_, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::Otherwise, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +// Combine two results where only one of the part may have been executed. +#[must_use] +fn combine_branches(b1: NeverLoopResult, b2: NeverLoopResult) -> NeverLoopResult { + match (b1, b2) { + (NeverLoopResult::AlwaysBreak, NeverLoopResult::AlwaysBreak) => NeverLoopResult::AlwaysBreak, + (NeverLoopResult::MayContinueMainLoop, _) | (_, NeverLoopResult::MayContinueMainLoop) => { + NeverLoopResult::MayContinueMainLoop + }, + (NeverLoopResult::Otherwise, _) | (_, NeverLoopResult::Otherwise) => NeverLoopResult::Otherwise, + } +} + +fn never_loop_block(block: &Block<'_>, main_loop_id: HirId) -> NeverLoopResult { + let stmts = block.stmts.iter().map(stmt_to_expr); + let expr = once(block.expr.as_deref()); + let mut iter = stmts.chain(expr).flatten(); + never_loop_expr_seq(&mut iter, main_loop_id) +} + +fn never_loop_expr_seq<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_seq) +} + +fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match stmt.kind { + StmtKind::Semi(ref e, ..) | StmtKind::Expr(ref e, ..) => Some(e), + StmtKind::Local(ref local) => local.init.as_deref(), + _ => None, + } +} + +fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { + match expr.kind { + ExprKind::Box(ref e) + | ExprKind::Unary(_, ref e) + | ExprKind::Cast(ref e, _) + | ExprKind::Type(ref e, _) + | ExprKind::Field(ref e, _) + | ExprKind::AddrOf(_, _, ref e) + | ExprKind::Struct(_, _, Some(ref e)) + | ExprKind::Repeat(ref e, _) + | ExprKind::DropTemps(ref e) => never_loop_expr(e, main_loop_id), + ExprKind::Array(ref es) | ExprKind::MethodCall(_, _, ref es, _) | ExprKind::Tup(ref es) => { + never_loop_expr_all(&mut es.iter(), main_loop_id) + }, + ExprKind::Call(ref e, ref es) => never_loop_expr_all(&mut once(&**e).chain(es.iter()), main_loop_id), + ExprKind::Binary(_, ref e1, ref e2) + | ExprKind::Assign(ref e1, ref e2, _) + | ExprKind::AssignOp(_, ref e1, ref e2) + | ExprKind::Index(ref e1, ref e2) => never_loop_expr_all(&mut [&**e1, &**e2].iter().cloned(), main_loop_id), + ExprKind::Loop(ref b, _, _, _) => { + // Break can come from the inner loop so remove them. + absorb_break(&never_loop_block(b, main_loop_id)) + }, + ExprKind::If(ref e, ref e2, ref e3) => { + let e1 = never_loop_expr(e, main_loop_id); + let e2 = never_loop_expr(e2, main_loop_id); + let e3 = e3 + .as_ref() + .map_or(NeverLoopResult::Otherwise, |e| never_loop_expr(e, main_loop_id)); + combine_seq(e1, combine_branches(e2, e3)) + }, + ExprKind::Match(ref e, ref arms, _) => { + let e = never_loop_expr(e, main_loop_id); + if arms.is_empty() { + e + } else { + let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id); + combine_seq(e, arms) + } + }, + ExprKind::Block(ref b, _) => never_loop_block(b, main_loop_id), + ExprKind::Continue(d) => { + let id = d + .target_id + .expect("target ID can only be missing in the presence of compilation errors"); + if id == main_loop_id { + NeverLoopResult::MayContinueMainLoop + } else { + NeverLoopResult::AlwaysBreak + } + }, + ExprKind::Break(_, ref e) | ExprKind::Ret(ref e) => e.as_ref().map_or(NeverLoopResult::AlwaysBreak, |e| { + combine_seq(never_loop_expr(e, main_loop_id), NeverLoopResult::AlwaysBreak) + }), + ExprKind::InlineAsm(ref asm) => asm + .operands + .iter() + .map(|(o, _)| match o { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr } + | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) + }, + }) + .fold(NeverLoopResult::Otherwise, combine_both), + ExprKind::Struct(_, _, None) + | ExprKind::Yield(_, _) + | ExprKind::Closure(_, _, _, _, _) + | ExprKind::LlvmInlineAsm(_) + | ExprKind::Path(_) + | ExprKind::ConstBlock(_) + | ExprKind::Lit(_) + | ExprKind::Err => NeverLoopResult::Otherwise, + } +} + +fn never_loop_expr_all<'a, T: Iterator<Item = &'a Expr<'a>>>(es: &mut T, main_loop_id: HirId) -> NeverLoopResult { + es.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::Otherwise, combine_both) +} + +fn never_loop_expr_branch<'a, T: Iterator<Item = &'a Expr<'a>>>(e: &mut T, main_loop_id: HirId) -> NeverLoopResult { + e.map(|e| never_loop_expr(e, main_loop_id)) + .fold(NeverLoopResult::AlwaysBreak, combine_branches) +} diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs new file mode 100644 index 00000000000..f3585830e4a --- /dev/null +++ b/clippy_lints/src/loops/same_item_push.rs @@ -0,0 +1,169 @@ +use super::SAME_ITEM_PUSH; +use crate::utils::{implements_trait, is_type_diagnostic_item, snippet_with_macro_callsite, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, Node, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::symbol::sym; +use std::iter::Iterator; + +/// Detects for loop pushing the same item into a Vec +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + _: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + _: &'tcx Expr<'_>, +) { + fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) { + let vec_str = snippet_with_macro_callsite(cx, vec.span, ""); + let item_str = snippet_with_macro_callsite(cx, pushed_item.span, ""); + + span_lint_and_help( + cx, + SAME_ITEM_PUSH, + vec.span, + "it looks like the same item is being pushed into this Vec", + None, + &format!( + "try using vec![{};SIZE] or {}.resize(NEW_SIZE, {})", + item_str, vec_str, item_str + ), + ) + } + + if !matches!(pat.kind, PatKind::Wild) { + return; + } + + // Determine whether it is safe to lint the body + let mut same_item_push_visitor = SameItemPushVisitor { + should_lint: true, + vec_push: None, + cx, + }; + walk_expr(&mut same_item_push_visitor, body); + if same_item_push_visitor.should_lint { + if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push { + let vec_ty = cx.typeck_results().expr_ty(vec); + let ty = vec_ty.walk().nth(1).unwrap().expect_ty(); + if cx + .tcx + .lang_items() + .clone_trait() + .map_or(false, |id| implements_trait(cx, ty, id, &[])) + { + // Make sure that the push does not involve possibly mutating values + match pushed_item.kind { + ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, pushed_item.hir_id) { + // immutable bindings that are initialized with literal or constant + Res::Local(hir_id) => { + if_chain! { + let node = cx.tcx.hir().get(hir_id); + if let Node::Binding(pat) = node; + if let PatKind::Binding(bind_ann, ..) = pat.kind; + if !matches!(bind_ann, BindingAnnotation::RefMut | BindingAnnotation::Mutable); + let parent_node = cx.tcx.hir().get_parent_node(hir_id); + if let Some(Node::Local(parent_let_expr)) = cx.tcx.hir().find(parent_node); + if let Some(init) = parent_let_expr.init; + then { + match init.kind { + // immutable bindings that are initialized with literal + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + // immutable bindings that are initialized with constant + ExprKind::Path(ref path) => { + if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { + emit_lint(cx, vec, pushed_item); + } + } + _ => {}, + } + } + } + }, + // constant + Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + }, + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item), + _ => {}, + } + } + } + } +} + +// Scans the body of the for loop and determines whether lint should be given +struct SameItemPushVisitor<'a, 'tcx> { + should_lint: bool, + // this field holds the last vec push operation visited, which should be the only push seen + vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>, + cx: &'a LateContext<'tcx>, +} + +impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + match &expr.kind { + // Non-determinism may occur ... don't give a lint + ExprKind::Loop(..) | ExprKind::Match(..) => self.should_lint = false, + ExprKind::Block(block, _) => self.visit_block(block), + _ => {}, + } + } + + fn visit_block(&mut self, b: &'tcx Block<'_>) { + for stmt in b.stmts.iter() { + self.visit_stmt(stmt); + } + } + + fn visit_stmt(&mut self, s: &'tcx Stmt<'_>) { + let vec_push_option = get_vec_push(self.cx, s); + if vec_push_option.is_none() { + // Current statement is not a push so visit inside + match &s.kind { + StmtKind::Expr(expr) | StmtKind::Semi(expr) => self.visit_expr(&expr), + _ => {}, + } + } else { + // Current statement is a push ...check whether another + // push had been previously done + if self.vec_push.is_none() { + self.vec_push = vec_push_option; + } else { + // There are multiple pushes ... don't lint + self.should_lint = false; + } + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +// Given some statement, determine if that statement is a push on a Vec. If it is, return +// the Vec being pushed into and the item being pushed +fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> { + if_chain! { + // Extract method being called + if let StmtKind::Semi(semi_stmt) = &stmt.kind; + if let ExprKind::MethodCall(path, _, args, _) = &semi_stmt.kind; + // Figure out the parameters for the method call + if let Some(self_expr) = args.get(0); + if let Some(pushed_item) = args.get(1); + // Check that the method being called is push() on a Vec + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::vec_type); + if path.ident.name.as_str() == "push"; + then { + return Some((self_expr, pushed_item)) + } + } + None +} diff --git a/clippy_lints/src/loops/single_element_loop.rs b/clippy_lints/src/loops/single_element_loop.rs new file mode 100644 index 00000000000..38400c93c9a --- /dev/null +++ b/clippy_lints/src/loops/single_element_loop.rs @@ -0,0 +1,42 @@ +use super::{get_span_of_entire_for_loop, SINGLE_ELEMENT_LOOP}; +use crate::utils::{indent_of, single_segment_path, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BorrowKind, Expr, ExprKind, Pat, PatKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; + if let PatKind::Binding(.., target, _) = pat.kind; + if let ExprKind::Array([arg_expression]) = arg_expr.kind; + if let ExprKind::Path(ref list_item) = arg_expression.kind; + if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); + if let ExprKind::Block(ref block, _) = body.kind; + if !block.stmts.is_empty(); + + then { + let for_span = get_span_of_entire_for_loop(expr); + let mut block_str = snippet(cx, block.span, "..").into_owned(); + block_str.remove(0); + block_str.pop(); + + + span_lint_and_sugg( + cx, + SINGLE_ELEMENT_LOOP, + for_span, + "for loop over a single element", + "try", + format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), + Applicability::MachineApplicable + ) + } + } +} diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs new file mode 100644 index 00000000000..9e38e17719a --- /dev/null +++ b/clippy_lints/src/loops/utils.rs @@ -0,0 +1,350 @@ +use crate::utils::{ + get_parent_expr, get_trait_def_id, has_iter_method, implements_trait, is_integer_const, path_to_local, + path_to_local_id, paths, sugg, +}; +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; +use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; +use std::iter::Iterator; + +#[derive(Debug, PartialEq)] +enum IncrementVisitorVarState { + Initial, // Not examined yet + IncrOnce, // Incremented exactly once, may be a loop counter + DontWarn, +} + +/// Scan a for loop for variables that are incremented exactly once and not used after that. +pub(super) struct IncrementVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + states: FxHashMap<HirId, IncrementVisitorVarState>, // incremented variables + depth: u32, // depth of conditional expressions + done: bool, +} + +impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>) -> Self { + Self { + cx, + states: FxHashMap::default(), + depth: 0, + done: false, + } + } + + pub(super) fn into_results(self) -> impl Iterator<Item = HirId> { + self.states.into_iter().filter_map(|(id, state)| { + if state == IncrementVisitorVarState::IncrOnce { + Some(id) + } else { + None + } + }) + } +} + +impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.done { + return; + } + + // If node is a variable + if let Some(def_id) = path_to_local(expr) { + if let Some(parent) = get_parent_expr(self.cx, expr) { + let state = self.states.entry(def_id).or_insert(IncrementVisitorVarState::Initial); + if *state == IncrementVisitorVarState::IncrOnce { + *state = IncrementVisitorVarState::DontWarn; + return; + } + + match parent.kind { + ExprKind::AssignOp(op, ref lhs, ref rhs) => { + if lhs.hir_id == expr.hir_id { + *state = if op.node == BinOpKind::Add + && is_integer_const(self.cx, rhs, 1) + && *state == IncrementVisitorVarState::Initial + && self.depth == 0 + { + IncrementVisitorVarState::IncrOnce + } else { + // Assigned some other value or assigned multiple times + IncrementVisitorVarState::DontWarn + }; + } + }, + ExprKind::Assign(ref lhs, _, _) if lhs.hir_id == expr.hir_id => { + *state = IncrementVisitorVarState::DontWarn + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + *state = IncrementVisitorVarState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if is_loop(expr) || is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else if let ExprKind::Continue(_) = expr.kind { + self.done = true; + } else { + walk_expr(self, expr); + } + } + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +enum InitializeVisitorState<'hir> { + Initial, // Not examined yet + Declared(Symbol), // Declared but not (yet) initialized + Initialized { + name: Symbol, + initializer: &'hir Expr<'hir>, + }, + DontWarn, +} + +/// Checks whether a variable is initialized at the start of a loop and not modified +/// and used after the loop. +pub(super) struct InitializeVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, // context reference + end_expr: &'tcx Expr<'tcx>, // the for loop. Stop scanning here. + var_id: HirId, + state: InitializeVisitorState<'tcx>, + depth: u32, // depth of conditional expressions + past_loop: bool, +} + +impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { + pub(super) fn new(cx: &'a LateContext<'tcx>, end_expr: &'tcx Expr<'tcx>, var_id: HirId) -> Self { + Self { + cx, + end_expr, + var_id, + state: InitializeVisitorState::Initial, + depth: 0, + past_loop: false, + } + } + + pub(super) fn get_result(&self) -> Option<(Symbol, &'tcx Expr<'tcx>)> { + if let InitializeVisitorState::Initialized { name, initializer } = self.state { + Some((name, initializer)) + } else { + None + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + // Look for declarations of the variable + if_chain! { + if let StmtKind::Local(ref local) = stmt.kind; + if local.pat.hir_id == self.var_id; + if let PatKind::Binding(.., ident, _) = local.pat.kind; + then { + self.state = local.init.map_or(InitializeVisitorState::Declared(ident.name), |init| { + InitializeVisitorState::Initialized { + initializer: init, + name: ident.name, + } + }) + } + } + walk_stmt(self, stmt); + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if matches!(self.state, InitializeVisitorState::DontWarn) { + return; + } + if expr.hir_id == self.end_expr.hir_id { + self.past_loop = true; + return; + } + // No need to visit expressions before the variable is + // declared + if matches!(self.state, InitializeVisitorState::Initial) { + return; + } + + // If node is the desired variable, see how it's used + if path_to_local_id(expr, self.var_id) { + if self.past_loop { + self.state = InitializeVisitorState::DontWarn; + return; + } + + if let Some(parent) = get_parent_expr(self.cx, expr) { + match parent.kind { + ExprKind::AssignOp(_, ref lhs, _) if lhs.hir_id == expr.hir_id => { + self.state = InitializeVisitorState::DontWarn; + }, + ExprKind::Assign(ref lhs, ref rhs, _) if lhs.hir_id == expr.hir_id => { + self.state = if_chain! { + if self.depth == 0; + if let InitializeVisitorState::Declared(name) + | InitializeVisitorState::Initialized { name, ..} = self.state; + then { + InitializeVisitorState::Initialized { initializer: rhs, name } + } else { + InitializeVisitorState::DontWarn + } + } + }, + ExprKind::AddrOf(BorrowKind::Ref, mutability, _) if mutability == Mutability::Mut => { + self.state = InitializeVisitorState::DontWarn + }, + _ => (), + } + } + + walk_expr(self, expr); + } else if !self.past_loop && is_loop(expr) { + self.state = InitializeVisitorState::DontWarn; + } else if is_conditional(expr) { + self.depth += 1; + walk_expr(self, expr); + self.depth -= 1; + } else { + walk_expr(self, expr); + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +fn is_loop(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Loop(..)) +} + +fn is_conditional(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::If(..) | ExprKind::Match(..)) +} + +#[derive(PartialEq, Eq)] +pub(super) enum Nesting { + Unknown, // no nesting detected yet + RuledOut, // the iterator is initialized or assigned within scope + LookFurther, // no nesting detected, no further walk required +} + +use self::Nesting::{LookFurther, RuledOut, Unknown}; + +pub(super) struct LoopNestVisitor { + pub(super) hir_id: HirId, + pub(super) iterator: HirId, + pub(super) nesting: Nesting, +} + +impl<'tcx> Visitor<'tcx> for LoopNestVisitor { + type Map = Map<'tcx>; + + fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { + if stmt.hir_id == self.hir_id { + self.nesting = LookFurther; + } else if self.nesting == Unknown { + walk_stmt(self, stmt); + } + } + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.nesting != Unknown { + return; + } + if expr.hir_id == self.hir_id { + self.nesting = LookFurther; + return; + } + match expr.kind { + ExprKind::Assign(ref path, _, _) | ExprKind::AssignOp(_, ref path, _) => { + if path_to_local_id(path, self.iterator) { + self.nesting = RuledOut; + } + }, + _ => walk_expr(self, expr), + } + } + + fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { + if self.nesting != Unknown { + return; + } + if let PatKind::Binding(_, id, ..) = pat.kind { + if id == self.iterator { + self.nesting = RuledOut; + return; + } + } + walk_pat(self, pat) + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +// this function assumes the given expression is a `for` loop. +pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span { + // for some reason this is the only way to get the `Span` + // of the entire `for` loop + if let ExprKind::Match(_, arms, _) = &expr.kind { + arms[0].body.span + } else { + unreachable!() + } +} + +/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the +/// actual `Iterator` that the loop uses. +pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String { + let impls_iterator = get_trait_def_id(cx, &paths::ITERATOR).map_or(false, |id| { + implements_trait(cx, cx.typeck_results().expr_ty(arg), id, &[]) + }); + if impls_iterator { + format!( + "{}", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ) + } else { + // (&x).into_iter() ==> x.iter() + // (&mut x).into_iter() ==> x.iter_mut() + match &arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, mutability, arg_inner) + if has_iter_method(cx, cx.typeck_results().expr_ty(&arg_inner)).is_some() => + { + let meth_name = match mutability { + Mutability::Mut => "iter_mut", + Mutability::Not => "iter", + }; + format!( + "{}.{}()", + sugg::Sugg::hir_with_applicability(cx, &arg_inner, "_", applic_ref).maybe_par(), + meth_name, + ) + } + _ => format!( + "{}.into_iter()", + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + ), + } + } +} diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs new file mode 100644 index 00000000000..05e0a722563 --- /dev/null +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -0,0 +1,139 @@ +use super::WHILE_IMMUTABLE_CONDITION; +use crate::consts::constant; +use crate::utils::span_lint_and_then; +use crate::utils::usage::mutated_variables; +use if_chain::if_chain; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{def_id, Expr, ExprKind, HirId, QPath}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; +use std::iter::Iterator; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) { + if constant(cx, cx.typeck_results(), cond).is_some() { + // A pure constant condition (e.g., `while false`) is not linted. + return; + } + + let mut var_visitor = VarCollectorVisitor { + cx, + ids: FxHashSet::default(), + def_ids: FxHashMap::default(), + skip: false, + }; + var_visitor.visit_expr(cond); + if var_visitor.skip { + return; + } + let used_in_condition = &var_visitor.ids; + let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { + used_in_condition.is_disjoint(&used_mutably) + } else { + return; + }; + let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); + + let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { + has_break_or_return: false, + }; + has_break_or_return_visitor.visit_expr(expr); + let has_break_or_return = has_break_or_return_visitor.has_break_or_return; + + if no_cond_variable_mutated && !mutable_static_in_cond { + span_lint_and_then( + cx, + WHILE_IMMUTABLE_CONDITION, + cond.span, + "variables in the condition are not mutated in the loop body", + |diag| { + diag.note("this may lead to an infinite or to a never running loop"); + + if has_break_or_return { + diag.note("this loop contains `return`s or `break`s"); + diag.help("rewrite it as `if cond { loop { } }`"); + } + }, + ); + } +} + +struct HasBreakOrReturnVisitor { + has_break_or_return: bool, +} + +impl<'tcx> Visitor<'tcx> for HasBreakOrReturnVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.has_break_or_return { + return; + } + + match expr.kind { + ExprKind::Ret(_) | ExprKind::Break(_, _) => { + self.has_break_or_return = true; + return; + }, + _ => {}, + } + + walk_expr(self, expr); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} + +/// Collects the set of variables in an expression +/// Stops analysis if a function call is found +/// Note: In some cases such as `self`, there are no mutable annotation, +/// All variables definition IDs are collected +struct VarCollectorVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + ids: FxHashSet<HirId>, + def_ids: FxHashMap<def_id::DefId, bool>, + skip: bool, +} + +impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { + fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Path(ref qpath) = ex.kind; + if let QPath::Resolved(None, _) = *qpath; + let res = self.cx.qpath_res(qpath, ex.hir_id); + then { + match res { + Res::Local(hir_id) => { + self.ids.insert(hir_id); + }, + Res::Def(DefKind::Static, def_id) => { + let mutable = self.cx.tcx.is_mutable_static(def_id); + self.def_ids.insert(def_id, mutable); + }, + _ => {}, + } + } + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + match ex.kind { + ExprKind::Path(_) => self.insert_def_id(ex), + // If there is any function/method call… we just stop analysis + ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + + _ => walk_expr(self, ex), + } + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/loops/while_let_loop.rs b/clippy_lints/src/loops/while_let_loop.rs new file mode 100644 index 00000000000..65d8f2f1111 --- /dev/null +++ b/clippy_lints/src/loops/while_let_loop.rs @@ -0,0 +1,87 @@ +use super::WHILE_LET_LOOP; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Block, Expr, ExprKind, MatchSource, StmtKind}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::lint::in_external_macro; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_block: &'tcx Block<'_>) { + // extract the expression from the first statement (if any) in a block + let inner_stmt_expr = extract_expr_from_first_stmt(loop_block); + // or extract the first expression (if any) from the block + if let Some(inner) = inner_stmt_expr.or_else(|| extract_first_expr(loop_block)) { + if let ExprKind::Match(ref matchexpr, ref arms, ref source) = inner.kind { + // ensure "if let" compatible match structure + match *source { + MatchSource::Normal | MatchSource::IfLetDesugar { .. } => { + if arms.len() == 2 + && arms[0].guard.is_none() + && arms[1].guard.is_none() + && is_simple_break_expr(&arms[1].body) + { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + // NOTE: we used to build a body here instead of using + // ellipsis, this was removed because: + // 1) it was ugly with big bodies; + // 2) it was not indented properly; + // 3) it wasn’t very smart (see #675). + let mut applicability = Applicability::HasPlaceholders; + span_lint_and_sugg( + cx, + WHILE_LET_LOOP, + expr.span, + "this loop could be written as a `while let` loop", + "try", + format!( + "while let {} = {} {{ .. }}", + snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), + snippet_with_applicability(cx, matchexpr.span, "..", &mut applicability), + ), + applicability, + ); + } + }, + _ => (), + } + } + } +} + +/// If a block begins with a statement (possibly a `let` binding) and has an +/// expression, return it. +fn extract_expr_from_first_stmt<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if block.stmts.is_empty() { + return None; + } + if let StmtKind::Local(ref local) = block.stmts[0].kind { + local.init //.map(|expr| expr) + } else { + None + } +} + +/// If a block begins with an expression (with or without semicolon), return it. +fn extract_first_expr<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match block.expr { + Some(ref expr) if block.stmts.is_empty() => Some(expr), + None if !block.stmts.is_empty() => match block.stmts[0].kind { + StmtKind::Expr(ref expr) | StmtKind::Semi(ref expr) => Some(expr), + StmtKind::Local(..) | StmtKind::Item(..) => None, + }, + _ => None, + } +} + +/// Returns `true` if expr contains a single break expr without destination label +/// and +/// passed expression. The expression may be within a block. +fn is_simple_break_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Break(dest, ref passed_expr) if dest.label.is_none() && passed_expr.is_none() => true, + ExprKind::Block(ref b, _) => extract_first_expr(b).map_or(false, |subexpr| is_simple_break_expr(subexpr)), + _ => false, + } +} diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs new file mode 100644 index 00000000000..e5a47694faa --- /dev/null +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -0,0 +1,171 @@ +use super::utils::{LoopNestVisitor, Nesting}; +use super::WHILE_LET_ON_ITERATOR; +use crate::utils::usage::mutated_variables; +use crate::utils::{ + get_enclosing_block, get_trait_def_id, implements_trait, is_refutable, last_path_segment, match_trait_method, + path_to_local, path_to_local_id, paths, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; +use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::hir::map::Map; + +use rustc_span::symbol::sym; + +pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind { + let pat = &arms[0].pat.kind; + if let ( + &PatKind::TupleStruct(ref qpath, ref pat_args, _), + &ExprKind::MethodCall(ref method_path, _, ref method_args, _), + ) = (pat, &match_expr.kind) + { + let iter_expr = &method_args[0]; + + // Don't lint when the iterator is recreated on every iteration + if_chain! { + if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; + if let Some(iter_def_id) = get_trait_def_id(cx, &paths::ITERATOR); + if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); + then { + return; + } + } + + let lhs_constructor = last_path_segment(qpath); + if method_path.ident.name == sym::next + && match_trait_method(cx, match_expr, &paths::ITERATOR) + && lhs_constructor.ident.name == sym::Some + && (pat_args.is_empty() + || !is_refutable(cx, &pat_args[0]) + && !is_used_inside(cx, iter_expr, &arms[0].body) + && !is_iterator_used_after_while_let(cx, iter_expr) + && !is_nested(cx, expr, &method_args[0])) + { + let mut applicability = Applicability::MachineApplicable; + let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); + let loop_var = if pat_args.is_empty() { + "_".to_string() + } else { + snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() + }; + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(match_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}", loop_var, iterator), + applicability, + ); + } + } + } +} + +fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(expr) { + Some(id) => id, + None => return false, + }; + if let Some(used_mutably) = mutated_variables(container, cx) { + if used_mutably.contains(&def_id) { + return true; + } + } + false +} + +fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { + let def_id = match path_to_local(iter_expr) { + Some(id) => id, + None => return false, + }; + let mut visitor = VarUsedAfterLoopVisitor { + def_id, + iter_expr_id: iter_expr.hir_id, + past_while_let: false, + var_used_after_while_let: false, + }; + if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { + walk_block(&mut visitor, enclosing_block); + } + visitor.var_used_after_while_let +} + +fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + if_chain! { + if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); + let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); + if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); + then { + return is_loop_nested(cx, loop_expr, iter_expr) + } + } + false +} + +fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { + let mut id = loop_expr.hir_id; + let iter_id = if let Some(id) = path_to_local(iter_expr) { + id + } else { + return true; + }; + loop { + let parent = cx.tcx.hir().get_parent_node(id); + if parent == id { + return false; + } + match cx.tcx.hir().find(parent) { + Some(Node::Expr(expr)) => { + if let ExprKind::Loop(..) = expr.kind { + return true; + }; + }, + Some(Node::Block(block)) => { + let mut block_visitor = LoopNestVisitor { + hir_id: id, + iterator: iter_id, + nesting: Nesting::Unknown, + }; + walk_block(&mut block_visitor, block); + if block_visitor.nesting == Nesting::RuledOut { + return false; + } + }, + Some(Node::Stmt(_)) => (), + _ => { + return false; + }, + } + id = parent; + } +} + +struct VarUsedAfterLoopVisitor { + def_id: HirId, + iter_expr_id: HirId, + past_while_let: bool, + var_used_after_while_let: bool, +} + +impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { + if self.past_while_let { + if path_to_local_id(expr, self.def_id) { + self.var_used_after_while_let = true; + } + } else if self.iter_expr_id == expr.hir_id { + self.past_while_let = true; + } + walk_expr(self, expr); + } + fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { + NestedVisitorMap::None + } +} diff --git a/clippy_lints/src/manual_map.rs b/clippy_lints/src/manual_map.rs index a50a3943bab..ac1d51e1993 100644 --- a/clippy_lints/src/manual_map.rs +++ b/clippy_lints/src/manual_map.rs @@ -2,17 +2,25 @@ use crate::{ map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF, utils::{ - is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, peel_hir_expr_refs, - peel_mid_ty_refs_is_mutable, snippet_with_applicability, span_lint_and_sugg, + can_partially_move_ty, is_allowed, is_type_diagnostic_item, match_def_path, match_var, paths, + peel_hir_expr_refs, peel_mid_ty_refs_is_mutable, snippet_with_applicability, snippet_with_context, + span_lint_and_sugg, }, }; use rustc_ast::util::parser::PREC_POSTFIX; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, QPath}; +use rustc_hir::{ + def::Res, + intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}, + Arm, BindingAnnotation, Block, Expr, ExprKind, Mutability, Pat, PatKind, Path, QPath, +}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::{sym, Ident}; +use rustc_span::{ + symbol::{sym, Ident}, + SyntaxContext, +}; declare_clippy_lint! { /// **What it does:** Checks for usages of `match` which could be implemented using `map` @@ -52,43 +60,46 @@ impl LateLintPass<'_> for ManualMap { { let (scrutinee_ty, ty_ref_count, ty_mutability) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee)); - if !is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) - || !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type) + if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::option_type) + && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::option_type)) { return; } - let (some_expr, some_pat, pat_ref_count, is_wild_none) = - match (try_parse_pattern(cx, arm1.pat), try_parse_pattern(cx, arm2.pat)) { - (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) - if is_none_expr(cx, arm1.body) => - { - (arm2.body, pattern, ref_count, true) - }, - (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) - if is_none_expr(cx, arm1.body) => - { - (arm2.body, pattern, ref_count, false) - }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) - if is_none_expr(cx, arm2.body) => - { - (arm1.body, pattern, ref_count, true) - }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) - if is_none_expr(cx, arm2.body) => - { - (arm1.body, pattern, ref_count, false) - }, - _ => return, - }; + let expr_ctxt = expr.span.ctxt(); + let (some_expr, some_pat, pat_ref_count, is_wild_none) = match ( + try_parse_pattern(cx, arm1.pat, expr_ctxt), + try_parse_pattern(cx, arm2.pat, expr_ctxt), + ) { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, true) + }, + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) + if is_none_expr(cx, arm1.body) => + { + (arm2.body, pattern, ref_count, false) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, true) + }, + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) + if is_none_expr(cx, arm2.body) => + { + (arm1.body, pattern, ref_count, false) + }, + _ => return, + }; // Top level or patterns aren't allowed in closures. if matches!(some_pat.kind, PatKind::Or(_)) { return; } - let some_expr = match get_some_expr(cx, some_expr) { + let some_expr = match get_some_expr(cx, some_expr, expr_ctxt) { Some(expr) => expr, None => return, }; @@ -99,6 +110,10 @@ impl LateLintPass<'_> for ManualMap { return; } + if !can_move_expr_to_closure(cx, some_expr) { + return; + } + // Determine which binding mode to use. let explicit_ref = some_pat.contains_explicit_ref_binding(); let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability)); @@ -111,47 +126,50 @@ impl LateLintPass<'_> for ManualMap { let mut app = Applicability::MachineApplicable; - // Remove address-of expressions from the scrutinee. `as_ref` will be called, - // the type is copyable, or the option is being passed by value. + // Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or + // it's being passed by value. let scrutinee = peel_hir_expr_refs(scrutinee).0; - let scrutinee_str = snippet_with_applicability(cx, scrutinee.span, "_", &mut app); - let scrutinee_str = if expr.precedence().order() < PREC_POSTFIX { - // Parens are needed to chain method calls. - format!("({})", scrutinee_str) - } else { - scrutinee_str.into() - }; + let scrutinee_str = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app); + let scrutinee_str = + if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX { + format!("({})", scrutinee_str) + } else { + scrutinee_str.into() + }; let body_str = if let PatKind::Binding(annotation, _, some_binding, None) = some_pat.kind { - if let Some(func) = can_pass_as_func(cx, some_binding, some_expr) { - snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() - } else { - if match_var(some_expr, some_binding.name) - && !is_allowed(cx, MATCH_AS_REF, expr.hir_id) - && binding_ref.is_some() - { - return; - } + match can_pass_as_func(cx, some_binding, some_expr) { + Some(func) if func.span.ctxt() == some_expr.span.ctxt() => { + snippet_with_applicability(cx, func.span, "..", &mut app).into_owned() + }, + _ => { + if match_var(some_expr, some_binding.name) + && !is_allowed(cx, MATCH_AS_REF, expr.hir_id) + && binding_ref.is_some() + { + return; + } - // `ref` and `ref mut` annotations were handled earlier. - let annotation = if matches!(annotation, BindingAnnotation::Mutable) { - "mut " - } else { - "" - }; - format!( - "|{}{}| {}", - annotation, - some_binding, - snippet_with_applicability(cx, some_expr.span, "..", &mut app) - ) + // `ref` and `ref mut` annotations were handled earlier. + let annotation = if matches!(annotation, BindingAnnotation::Mutable) { + "mut " + } else { + "" + }; + format!( + "|{}{}| {}", + annotation, + some_binding, + snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app) + ) + }, } } else if !is_wild_none && explicit_ref.is_none() { // TODO: handle explicit reference annotations. format!( "|{}| {}", - snippet_with_applicability(cx, some_pat.span, "..", &mut app), - snippet_with_applicability(cx, some_expr.span, "..", &mut app) + snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app), + snippet_with_context(cx, some_expr.span, expr_ctxt, "..", &mut app) ) } else { // Refutable bindings and mixed reference annotations can't be handled by `map`. @@ -171,6 +189,51 @@ 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>> { @@ -198,11 +261,11 @@ enum OptionPat<'a> { // Try to parse into a recognized `Option` pattern. // i.e. `_`, `None`, `Some(..)`, or a reference to any of those. -fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<OptionPat<'tcx>> { - fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize) -> Option<OptionPat<'tcx>> { +fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> { + fn f(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ref_count: usize, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> { match pat.kind { PatKind::Wild => Some(OptionPat::Wild), - PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1), + PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), PatKind::Path(QPath::Resolved(None, path)) if path .res @@ -215,18 +278,19 @@ fn try_parse_pattern(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) -> Option<Optio if path .res .opt_def_id() - .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) => + .map_or(false, |id| match_def_path(cx, id, &paths::OPTION_SOME)) + && pat.span.ctxt() == ctxt => { Some(OptionPat::Some { pattern, ref_count }) }, _ => None, } } - f(cx, pat, 0) + f(cx, pat, 0, ctxt) } // Checks for an expression wrapped by the `Some` constructor. Returns the contained expression. -fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { +fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ctxt: SyntaxContext) -> Option<&'tcx Expr<'tcx>> { // TODO: Allow more complex expressions. match expr.kind { ExprKind::Call( @@ -235,7 +299,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E .. }, [arg], - ) => { + ) if ctxt == expr.span.ctxt() => { if match_def_path(cx, path.res.opt_def_id()?, &paths::OPTION_SOME) { Some(arg) } else { @@ -249,7 +313,7 @@ fn get_some_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx E .. }, _, - ) => get_some_expr(cx, expr), + ) => get_some_expr(cx, expr, ctxt), _ => None, } } diff --git a/clippy_lints/src/map_clone.rs b/clippy_lints/src/map_clone.rs index bd0be880289..4b685c09a05 100644 --- a/clippy_lints/src/map_clone.rs +++ b/clippy_lints/src/map_clone.rs @@ -79,7 +79,9 @@ impl<'tcx> LateLintPass<'tcx> for MapClone { }, hir::ExprKind::MethodCall(ref method, _, [obj], _) => if_chain! { if ident_eq(name, obj) && method.ident.name == sym::clone; - if match_trait_method(cx, closure_expr, &paths::CLONE_TRAIT); + if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id); + if let Some(trait_id) = cx.tcx.trait_of_item(fn_id); + if cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id); // no autoderefs if !cx.typeck_results().expr_adjustments(obj).iter() .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 9c87759d51d..1d5a6e7fcc5 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -1173,9 +1173,9 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { cx, WILDCARD_IN_OR_PATTERNS, arm.pat.span, - "wildcard pattern covers any other pattern as it will match anyway.", + "wildcard pattern covers any other pattern as it will match anyway", None, - "Consider handling `_` separately.", + "consider handling `_` separately", ); } } diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index 540a1484a85..5decb81d9f2 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -158,7 +158,7 @@ pub(crate) trait BindInsteadOfMap { } /// Lint use of `_.and_then(|x| Some(y))` for `Option`s - fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool { + fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) -> bool { if !match_type(cx, cx.typeck_results().expr_ty(&args[0]), Self::TYPE_QPATH) { return false; } diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index defc50ede22..71a7e195e41 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -7,7 +7,7 @@ use rustc_span::sym; use super::BYTES_NTH; -pub(super) fn lints<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>]) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>]) { if_chain! { if let ExprKind::MethodCall(_, _, ref args, _) = expr.kind; let ty = cx.typeck_results().expr_ty(&iter_args[0]).peel_refs(); diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs new file mode 100644 index 00000000000..4a130ed47db --- /dev/null +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -0,0 +1,109 @@ +use crate::utils::{is_copy, span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use std::iter; + +use super::CLONE_DOUBLE_REF; +use super::CLONE_ON_COPY; + +/// Checks for the `CLONE_ON_COPY` lint. +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'_>) { + let ty = cx.typeck_results().expr_ty(expr); + if let ty::Ref(_, inner, _) = arg_ty.kind() { + if let ty::Ref(_, innermost, _) = inner.kind() { + span_lint_and_then( + cx, + CLONE_DOUBLE_REF, + expr.span, + &format!( + "using `clone` on a double-reference; \ + this will copy the reference of type `{}` instead of cloning the inner type", + ty + ), + |diag| { + if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) { + let mut ty = innermost; + let mut n = 0; + while let ty::Ref(_, inner, _) = ty.kind() { + ty = inner; + n += 1; + } + let refs: String = iter::repeat('&').take(n + 1).collect(); + let derefs: String = iter::repeat('*').take(n).collect(); + let explicit = format!("<{}{}>::clone({})", refs, ty, snip); + diag.span_suggestion( + expr.span, + "try dereferencing it", + format!("{}({}{}).clone()", refs, derefs, snip.deref()), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + expr.span, + "or try being explicit if you are sure, that you want to clone a reference", + explicit, + Applicability::MaybeIncorrect, + ); + } + }, + ); + return; // don't report clone_on_copy + } + } + + if is_copy(cx, ty) { + let snip; + if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { + let parent = cx.tcx.hir().get_parent_node(expr.hir_id); + match &cx.tcx.hir().get(parent) { + hir::Node::Expr(parent) => match parent.kind { + // &*x is a nop, &x.clone() is not + hir::ExprKind::AddrOf(..) => return, + // (*x).func() is useless, x.clone().func() can work in case func borrows mutably + hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => { + return; + }, + + _ => {}, + }, + hir::Node::Stmt(stmt) => { + if let hir::StmtKind::Local(ref loc) = stmt.kind { + if let hir::PatKind::Ref(..) = loc.pat.kind { + // let ref y = *x borrows x, let ref y = x.clone() does not + return; + } + } + }, + _ => {}, + } + + // x.clone() might have dereferenced x, possibly through Deref impls + if cx.typeck_results().expr_ty(arg) == ty { + snip = Some(("try removing the `clone` call", format!("{}", snippet))); + } else { + let deref_count = cx + .typeck_results() + .expr_adjustments(arg) + .iter() + .filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) + .count(); + let derefs: String = iter::repeat('*').take(deref_count).collect(); + snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); + } + } else { + snip = None; + } + span_lint_and_then( + cx, + CLONE_ON_COPY, + expr.span, + &format!("using `clone` on type `{}` which implements the `Copy` trait", ty), + |diag| { + if let Some((text, snip)) = snip { + diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable); + } + }, + ); + } +} diff --git a/clippy_lints/src/methods/clone_on_ref_ptr.rs b/clippy_lints/src/methods/clone_on_ref_ptr.rs new file mode 100644 index 00000000000..3d5a68d69d7 --- /dev/null +++ b/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -0,0 +1,36 @@ +use crate::utils::{is_type_diagnostic_item, match_type, paths, snippet_with_macro_callsite, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::CLONE_ON_REF_PTR; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let obj_ty = cx.typeck_results().expr_ty(arg).peel_refs(); + + if let ty::Adt(_, subst) = obj_ty.kind() { + let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) { + "Rc" + } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) { + "Arc" + } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) { + "Weak" + } else { + return; + }; + + let snippet = snippet_with_macro_callsite(cx, arg.span, ".."); + + span_lint_and_sugg( + cx, + CLONE_ON_REF_PTR, + expr.span, + "using `.clone()` on a ref-counted pointer", + "try this", + format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet), + Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak + ); + } +} diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs new file mode 100644 index 00000000000..6866e9c652a --- /dev/null +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -0,0 +1,199 @@ +use crate::utils::{is_expn_of, is_type_diagnostic_item, snippet, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use std::borrow::Cow; + +use super::EXPECT_FUN_CALL; + +/// Checks for the `EXPECT_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span, name: &str, args: &[hir::Expr<'_>]) { + // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or + // `&str` + fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { + let mut arg_root = arg; + loop { + arg_root = match &arg_root.kind { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, + hir::ExprKind::MethodCall(method_name, _, call_args, _) => { + if call_args.len() == 1 + && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref)) + && { + let arg_type = cx.typeck_results().expr_ty(&call_args[0]); + let base_type = arg_type.peel_refs(); + *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type) + } + { + &call_args[0] + } else { + break; + } + }, + _ => break, + }; + } + arg_root + } + + // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be + // converted to string. + fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { + let arg_ty = cx.typeck_results().expr_ty(arg); + if is_type_diagnostic_item(cx, arg_ty, sym::string_type) { + return false; + } + if let ty::Ref(_, ty, ..) = arg_ty.kind() { + if *ty.kind() == ty::Str && can_be_static_str(cx, arg) { + return false; + } + }; + true + } + + // Check if an expression could have type `&'static str`, knowing that it + // has type `&str` for some lifetime. + fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { + match arg.kind { + hir::ExprKind::Lit(_) => true, + hir::ExprKind::Call(fun, _) => { + if let hir::ExprKind::Path(ref p) = fun.kind { + match cx.qpath_res(p, fun.hir_id) { + hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( + cx.tcx.fn_sig(def_id).output().skip_binder().kind(), + ty::Ref(ty::ReStatic, ..) + ), + _ => false, + } + } else { + false + } + }, + hir::ExprKind::MethodCall(..) => { + cx.typeck_results() + .type_dependent_def_id(arg.hir_id) + .map_or(false, |method_id| { + matches!( + cx.tcx.fn_sig(method_id).output().skip_binder().kind(), + ty::Ref(ty::ReStatic, ..) + ) + }) + }, + hir::ExprKind::Path(ref p) => matches!( + cx.qpath_res(p, arg.hir_id), + hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) + ), + _ => false, + } + } + + fn generate_format_arg_snippet( + cx: &LateContext<'_>, + a: &hir::Expr<'_>, + applicability: &mut Applicability, + ) -> Vec<String> { + if_chain! { + if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; + if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; + if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; + + then { + format_arg_expr_tup + .iter() + .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned()) + .collect() + } else { + unreachable!() + } + } + } + + fn is_call(node: &hir::ExprKind<'_>) -> bool { + match node { + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { + is_call(&expr.kind) + }, + hir::ExprKind::Call(..) + | hir::ExprKind::MethodCall(..) + // These variants are debatable or require further examination + | hir::ExprKind::If(..) + | hir::ExprKind::Match(..) + | hir::ExprKind::Block{ .. } => true, + _ => false, + } + } + + if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) { + return; + } + + let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]); + let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) { + "||" + } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) { + "|_|" + } else { + return; + }; + + let arg_root = get_arg_root(cx, &args[1]); + + let span_replace_word = method_span.with_hi(expr.span.hi()); + + let mut applicability = Applicability::MachineApplicable; + + //Special handling for `format!` as arg_root + if_chain! { + if let hir::ExprKind::Block(block, None) = &arg_root.kind; + if block.stmts.len() == 1; + if let hir::StmtKind::Local(local) = &block.stmts[0].kind; + if let Some(arg_root) = &local.init; + if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; + if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; + if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; + then { + let fmt_spec = &format_args[0]; + let fmt_args = &format_args[1]; + + let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()]; + + args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability)); + + let sugg = args.join(", "); + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("unwrap_or_else({} panic!({}))", closure_args, sugg), + applicability, + ); + + return; + } + } + + let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); + if requires_to_string(cx, arg_root) { + arg_root_snippet.to_mut().push_str(".to_string()"); + } + + span_lint_and_sugg( + cx, + EXPECT_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!( + "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})", + closure_args, arg_root_snippet + ), + applicability, + ); +} diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs new file mode 100644 index 00000000000..90b781bd9d1 --- /dev/null +++ b/clippy_lints/src/methods/expect_used.rs @@ -0,0 +1,30 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::EXPECT_USED; + +/// lint use of `expect()` for `Option`s and `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&expect_args[0]).peel_refs(); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { + Some((EXPECT_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { + Some((EXPECT_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `expect()` on `{}` value", kind,), + None, + &format!("if this value is an `{}`, it will panic", none_value,), + ); + } +} diff --git a/clippy_lints/src/methods/filetype_is_file.rs b/clippy_lints/src/methods/filetype_is_file.rs new file mode 100644 index 00000000000..b03835f97e6 --- /dev/null +++ b/clippy_lints/src/methods/filetype_is_file.rs @@ -0,0 +1,39 @@ +use crate::utils::{get_parent_expr, match_type, paths, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::FILETYPE_IS_FILE; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.typeck_results().expr_ty(&args[0]); + + if !match_type(cx, ty, &paths::FILE_TYPE) { + return; + } + + let span: Span; + let verb: &str; + let lint_unary: &str; + let help_unary: &str; + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(op, _) = parent.kind; + if op == hir::UnOp::Not; + then { + lint_unary = "!"; + verb = "denies"; + help_unary = ""; + span = parent.span; + } else { + lint_unary = ""; + verb = "covers"; + help_unary = "!"; + span = expr.span; + } + } + let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb); + let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary); + span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg); +} diff --git a/clippy_lints/src/methods/filter_flat_map.rs b/clippy_lints/src/methods/filter_flat_map.rs new file mode 100644 index 00000000000..8da867fce51 --- /dev/null +++ b/clippy_lints/src/methods/filter_flat_map.rs @@ -0,0 +1,21 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_MAP; + +/// lint use of `filter().flat_map()` for `Iterators` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::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); + } +} diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs new file mode 100644 index 00000000000..f559160004c --- /dev/null +++ b/clippy_lints/src/methods/filter_map.rs @@ -0,0 +1,85 @@ +use crate::utils::{match_trait_method, path_to_local_id, paths, snippet, span_lint_and_sugg, SpanlessEq}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::{Expr, ExprKind, PatKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use rustc_span::symbol::sym; + +use super::MANUAL_FILTER_MAP; +use super::MANUAL_FIND_MAP; + +/// lint use of `filter().map()` or `find().map()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) { + if_chain! { + if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; + if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind; + if match_trait_method(cx, map_recv, &paths::ITERATOR); + + // filter(|x| ...is_some())... + if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; + let filter_body = cx.tcx.hir().body(filter_body_id); + if let [filter_param] = filter_body.params; + // optional ref pattern: `filter(|&x| ..)` + let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + (ref_pat, true) + } else { + (filter_param.pat, false) + }; + // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; + if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { + Some(false) + } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { + Some(true) + } else { + None + }; + if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + + // ...map(|x| ...unwrap()) + if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; + let map_body = cx.tcx.hir().body(map_body_id); + if let [map_param] = map_body.params; + if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; + // closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; + if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); + + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + if_chain! { + if path_to_local_id(a_path, filter_param_id); + if path_to_local_id(b, map_param_id); + if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + then { + return true; + } + } + false + }; + if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + then { + let span = filter_span.to(map_span); + let (filter_name, lint) = if is_find { + ("find", MANUAL_FIND_MAP) + } else { + ("filter", MANUAL_FILTER_MAP) + }; + let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); + let to_opt = if is_result { ".ok()" } else { "" }; + let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, + snippet(cx, map_arg.span, ".."), to_opt); + span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + } + } +} diff --git a/clippy_lints/src/methods/filter_map_flat_map.rs b/clippy_lints/src/methods/filter_map_flat_map.rs new file mode 100644 index 00000000000..a6db138623a --- /dev/null +++ b/clippy_lints/src/methods/filter_map_flat_map.rs @@ -0,0 +1,21 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +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<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter_map().flat_map()` is an Iterator + if match_trait_method(cx, expr, &paths::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); + } +} diff --git a/clippy_lints/src/methods/filter_map_map.rs b/clippy_lints/src/methods/filter_map_map.rs new file mode 100644 index 00000000000..d015b4c7b38 --- /dev/null +++ b/clippy_lints/src/methods/filter_map_map.rs @@ -0,0 +1,20 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_MAP; + +/// lint use of `filter_map().map()` for `Iterators` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + _filter_args: &'tcx [hir::Expr<'_>], + _map_args: &'tcx [hir::Expr<'_>], +) { + // lint if caller of `.filter_map().map()` is an Iterator + if match_trait_method(cx, expr, &paths::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); + } +} diff --git a/clippy_lints/src/methods/filter_map_next.rs b/clippy_lints/src/methods/filter_map_next.rs new file mode 100644 index 00000000000..a789df922ff --- /dev/null +++ b/clippy_lints/src/methods/filter_map_next.rs @@ -0,0 +1,40 @@ +use crate::utils::{match_trait_method, meets_msrv, paths, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; + +use super::FILTER_MAP_NEXT; + +const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0); + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + filter_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + if !meets_msrv(msrv, &FILTER_MAP_NEXT_MSRV) { + return; + } + + let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find_map(..)` instead"; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + let iter_snippet = snippet(cx, filter_args[0].span, ".."); + span_lint_and_sugg( + cx, + FILTER_MAP_NEXT, + expr.span, + msg, + "try this", + format!("{}.find_map({})", iter_snippet, filter_snippet), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, FILTER_MAP_NEXT, expr.span, msg); + } + } +} diff --git a/clippy_lints/src/methods/filter_next.rs b/clippy_lints/src/methods/filter_next.rs new file mode 100644 index 00000000000..81619e73017 --- /dev/null +++ b/clippy_lints/src/methods/filter_next.rs @@ -0,0 +1,31 @@ +use crate::utils::{match_trait_method, paths, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::FILTER_NEXT; + +/// lint use of `filter().next()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.filter().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ + `.find(..)` instead"; + let filter_snippet = snippet(cx, filter_args[1].span, ".."); + if filter_snippet.lines().count() <= 1 { + let iter_snippet = snippet(cx, filter_args[0].span, ".."); + // add note if not multi-line + span_lint_and_sugg( + cx, + FILTER_NEXT, + expr.span, + msg, + "try this", + format!("{}.find({})", iter_snippet, filter_snippet), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, FILTER_NEXT, expr.span, msg); + } + } +} diff --git a/clippy_lints/src/methods/flat_map_identity.rs b/clippy_lints/src/methods/flat_map_identity.rs new file mode 100644 index 00000000000..ce3194f8a23 --- /dev/null +++ b/clippy_lints/src/methods/flat_map_identity.rs @@ -0,0 +1,57 @@ +use crate::utils::{match_qpath, match_trait_method, paths, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::FLAT_MAP_IDENTITY; + +/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + flat_map_args: &'tcx [hir::Expr<'_>], + flat_map_span: Span, +) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let arg_node = &flat_map_args[1].kind; + + let apply_lint = |message: &str| { + span_lint_and_sugg( + cx, + FLAT_MAP_IDENTITY, + flat_map_span.with_hi(expr.span.hi()), + message, + "try", + "flatten()".to_string(), + Applicability::MachineApplicable, + ); + }; + + if_chain! { + if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; + 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(_, ref path)) = body.value.kind; + + if path.segments.len() == 1; + if path.segments[0].ident.name == binding_ident.name; + + then { + apply_lint("called `flat_map(|x| x)` on an `Iterator`"); + } + } + + 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`"); + } + } + } +} diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints/src/methods/from_iter_instead_of_collect.rs new file mode 100644 index 00000000000..e50d0a33400 --- /dev/null +++ b/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -0,0 +1,67 @@ +use crate::utils::{get_trait_def_id, implements_trait, paths, span_lint_and_sugg, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::Ty; + +use super::FROM_ITER_INSTEAD_OF_COLLECT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let ty = cx.typeck_results().expr_ty(expr); + let arg_ty = cx.typeck_results().expr_ty(&args[0]); + + if_chain! { + if let Some(from_iter_id) = get_trait_def_id(cx, &paths::FROM_ITERATOR); + if let Some(iter_id) = get_trait_def_id(cx, &paths::ITERATOR); + + if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]); + then { + // `expr` implements `FromIterator` trait + let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par(); + let turbofish = extract_turbofish(cx, expr, ty); + let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish); + span_lint_and_sugg( + cx, + FROM_ITER_INSTEAD_OF_COLLECT, + expr.span, + "usage of `FromIterator::from_iter`", + "use `.collect()` instead of `::from_iter()`", + sugg, + Applicability::MaybeIncorrect, + ); + } + } +} + +fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String { + if_chain! { + let call_site = expr.span.source_callsite(); + if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site); + let snippet_split = snippet.split("::").collect::<Vec<_>>(); + if let Some((_, elements)) = snippet_split.split_last(); + + then { + // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`) + if let Some(type_specifier) = snippet_split.iter().find(|e| e.starts_with('<') && e.ends_with('>')) { + // remove the type specifier from the path elements + let without_ts = elements.iter().filter_map(|e| { + if e == type_specifier { None } else { Some((*e).to_string()) } + }).collect::<Vec<_>>(); + // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`) + format!("{}{}", without_ts.join("::"), type_specifier) + } else { + // type is not explicitly specified so wildcards are needed + // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>` + let ty_str = ty.to_string(); + let start = ty_str.find('<').unwrap_or(0); + let end = ty_str.find('>').unwrap_or_else(|| ty_str.len()); + let nb_wildcard = ty_str[start..end].split(',').count(); + let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1)); + format!("{}<{}>", elements.join("::"), wildcards) + } + } else { + ty.to_string() + } + } +} diff --git a/clippy_lints/src/methods/get_unwrap.rs b/clippy_lints/src/methods/get_unwrap.rs new file mode 100644 index 00000000000..e157db2712a --- /dev/null +++ b/clippy_lints/src/methods/get_unwrap.rs @@ -0,0 +1,84 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{ + get_parent_expr, is_type_diagnostic_item, match_type, paths, snippet_with_applicability, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::GET_UNWRAP; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, get_args: &'tcx [hir::Expr<'_>], is_mut: bool) { + // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`, + // because they do not implement `IndexMut` + let mut applicability = Applicability::MachineApplicable; + let expr_ty = cx.typeck_results().expr_ty(&get_args[0]); + let get_args_str = if get_args.len() > 1 { + snippet_with_applicability(cx, get_args[1].span, "..", &mut applicability) + } else { + return; // not linting on a .get().unwrap() chain or variant + }; + let mut needs_ref; + let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() { + needs_ref = get_args_str.parse::<usize>().is_ok(); + "slice" + } else if is_type_diagnostic_item(cx, expr_ty, sym::vec_type) { + needs_ref = get_args_str.parse::<usize>().is_ok(); + "Vec" + } else if is_type_diagnostic_item(cx, expr_ty, sym::vecdeque_type) { + needs_ref = get_args_str.parse::<usize>().is_ok(); + "VecDeque" + } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::hashmap_type) { + needs_ref = true; + "HashMap" + } else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) { + needs_ref = true; + "BTreeMap" + } else { + return; // caller is not a type that we want to lint + }; + + let mut span = expr.span; + + // Handle the case where the result is immediately dereferenced + // by not requiring ref and pulling the dereference into the + // suggestion. + if_chain! { + if needs_ref; + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind; + then { + needs_ref = false; + span = parent.span; + } + } + + let mut_str = if is_mut { "_mut" } else { "" }; + let borrow_str = if !needs_ref { + "" + } else if is_mut { + "&mut " + } else { + "&" + }; + + span_lint_and_sugg( + cx, + GET_UNWRAP, + span, + &format!( + "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise", + mut_str, caller_type + ), + "try this", + format!( + "{}{}[{}]", + borrow_str, + snippet_with_applicability(cx, get_args[0].span, "..", &mut applicability), + get_args_str + ), + applicability, + ); +} diff --git a/clippy_lints/src/methods/implicit_clone.rs b/clippy_lints/src/methods/implicit_clone.rs new file mode 100644 index 00000000000..a769493d11d --- /dev/null +++ b/clippy_lints/src/methods/implicit_clone.rs @@ -0,0 +1,32 @@ +use crate::utils::span_lint_and_sugg; +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 super::IMPLICIT_CLONE; +use clippy_utils::is_diagnostic_assoc_item; + +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, trait_diagnostic: Symbol) { + if_chain! { + if let ExprKind::MethodCall(method_path, _, [arg], _) = &expr.kind; + 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); + 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), + "consider using", + "clone".to_string(), + Applicability::MachineApplicable + ); + } + } +} diff --git a/clippy_lints/src/methods/inefficient_to_string.rs b/clippy_lints/src/methods/inefficient_to_string.rs index c83b6f2c329..3045b09c238 100644 --- a/clippy_lints/src/methods/inefficient_to_string.rs +++ b/clippy_lints/src/methods/inefficient_to_string.rs @@ -10,7 +10,7 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::sym; /// Checks for the `INEFFICIENT_TO_STRING` lint -pub fn lint<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'tcx>) { +pub fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'tcx>) { if_chain! { if let Some(to_string_meth_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if match_def_path(cx, to_string_meth_did, &paths::TO_STRING_METHOD); diff --git a/clippy_lints/src/methods/inspect_for_each.rs b/clippy_lints/src/methods/inspect_for_each.rs index 6d41ee38a27..959457a5bfc 100644 --- a/clippy_lints/src/methods/inspect_for_each.rs +++ b/clippy_lints/src/methods/inspect_for_each.rs @@ -7,7 +7,7 @@ use crate::utils::{match_trait_method, paths, span_lint_and_help}; use super::INSPECT_FOR_EACH; /// lint use of `inspect().for_each()` for `Iterators` -pub(super) fn lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { if match_trait_method(cx, expr, &paths::ITERATOR) { let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; diff --git a/clippy_lints/src/methods/into_iter_on_ref.rs b/clippy_lints/src/methods/into_iter_on_ref.rs new file mode 100644 index 00000000000..1e8315dbee2 --- /dev/null +++ b/clippy_lints/src/methods/into_iter_on_ref.rs @@ -0,0 +1,43 @@ +use crate::utils::{has_iter_method, match_trait_method, paths, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::source_map::Span; +use rustc_span::symbol::Symbol; + +use super::INTO_ITER_ON_REF; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, self_ref_ty: Ty<'_>, method_span: Span) { + if !match_trait_method(cx, expr, &paths::INTO_ITERATOR) { + return; + } + if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ref_ty) { + span_lint_and_sugg( + cx, + INTO_ITER_ON_REF, + method_span, + &format!( + "this `.into_iter()` call is equivalent to `.{}()` and will not consume the `{}`", + method_name, kind, + ), + "call directly", + method_name.to_string(), + Applicability::MachineApplicable, + ); + } +} + +fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> { + has_iter_method(cx, self_ref_ty).map(|ty_name| { + let mutbl = match self_ref_ty.kind() { + ty::Ref(_, _, mutbl) => mutbl, + _ => unreachable!(), + }; + let method_name = match mutbl { + hir::Mutability::Not => "iter", + hir::Mutability::Mut => "iter_mut", + }; + (ty_name, method_name) + }) +} diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints/src/methods/iter_cloned_collect.rs new file mode 100644 index 00000000000..c3e48ffa5fa --- /dev/null +++ b/clippy_lints/src/methods/iter_cloned_collect.rs @@ -0,0 +1,30 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_CLONED_COLLECT; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + if_chain! { + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type); + if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])); + if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()); + + then { + span_lint_and_sugg( + cx, + ITER_CLONED_COLLECT, + to_replace, + "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \ + more readable", + "try", + ".to_vec()".to_string(), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/iter_count.rs b/clippy_lints/src/methods/iter_count.rs new file mode 100644 index 00000000000..869440e0165 --- /dev/null +++ b/clippy_lints/src/methods/iter_count.rs @@ -0,0 +1,47 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{is_type_diagnostic_item, match_type, paths, snippet_with_applicability, span_lint_and_sugg}; + +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::ITER_COUNT; + +pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, iter_args: &'tcx [Expr<'tcx>], iter_method: &str) { + let ty = cx.typeck_results().expr_ty(&iter_args[0]); + let caller_type = if derefs_to_slice(cx, &iter_args[0], ty).is_some() { + "slice" + } else if is_type_diagnostic_item(cx, ty, sym::vec_type) { + "Vec" + } else if is_type_diagnostic_item(cx, ty, sym::vecdeque_type) { + "VecDeque" + } else if is_type_diagnostic_item(cx, ty, sym::hashset_type) { + "HashSet" + } else if is_type_diagnostic_item(cx, ty, sym::hashmap_type) { + "HashMap" + } else if match_type(cx, ty, &paths::BTREEMAP) { + "BTreeMap" + } else if match_type(cx, ty, &paths::BTREESET) { + "BTreeSet" + } else if match_type(cx, ty, &paths::LINKED_LIST) { + "LinkedList" + } else if match_type(cx, ty, &paths::BINARY_HEAP) { + "BinaryHeap" + } else { + return; + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_COUNT, + expr.span, + &format!("called `.{}().count()` on a `{}`", iter_method, caller_type), + "try", + format!( + "{}.len()", + snippet_with_applicability(cx, iter_args[0].span, "..", &mut applicability), + ), + applicability, + ); +} diff --git a/clippy_lints/src/methods/iter_next_slice.rs b/clippy_lints/src/methods/iter_next_slice.rs new file mode 100644 index 00000000000..3c03a949cfe --- /dev/null +++ b/clippy_lints/src/methods/iter_next_slice.rs @@ -0,0 +1,68 @@ +use crate::methods::derefs_to_slice; +use crate::utils::{get_parent_expr, higher, is_type_diagnostic_item, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::ITER_NEXT_SLICE; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { + let caller_expr = &iter_args[0]; + + // Skip lint if the `iter().next()` expression is a for loop argument, + // since it is already covered by `&loops::ITER_NEXT_LOOP` + let mut parent_expr_opt = get_parent_expr(cx, expr); + while let Some(parent_expr) = parent_expr_opt { + if higher::for_loop(parent_expr).is_some() { + return; + } + parent_expr_opt = get_parent_expr(cx, parent_expr); + } + + if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() { + // caller is a Slice + if_chain! { + if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; + if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) + = higher::range(index_expr); + if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; + if let ast::LitKind::Int(start_idx, _) = start_lit.node; + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "using `.iter().next()` on a Slice without end index", + "try calling", + format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), + applicability, + ); + } + } + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(caller_expr), sym::vec_type) + || matches!( + &cx.typeck_results().expr_ty(caller_expr).peel_refs().kind(), + ty::Array(_, _) + ) + { + // caller is a Vec or an Array + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NEXT_SLICE, + expr.span, + "using `.iter().next()` on an array", + "try calling", + format!( + "{}.get(0)", + snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) + ), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/iter_nth.rs b/clippy_lints/src/methods/iter_nth.rs new file mode 100644 index 00000000000..cc3e56ea872 --- /dev/null +++ b/clippy_lints/src/methods/iter_nth.rs @@ -0,0 +1,38 @@ +use crate::methods::derefs_to_slice; +use crate::methods::iter_nth_zero; +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::ITER_NTH; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + nth_and_iter_args: &[&'tcx [hir::Expr<'tcx>]], + is_mut: bool, +) { + let iter_args = nth_and_iter_args[1]; + let mut_str = if is_mut { "_mut" } else { "" }; + let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() { + "slice" + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) { + "Vec" + } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vecdeque_type) { + "VecDeque" + } else { + let nth_args = nth_and_iter_args[0]; + iter_nth_zero::check(cx, expr, &nth_args); + return; // caller is not a type that we want to lint + }; + + span_lint_and_help( + cx, + ITER_NTH, + expr.span, + &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type), + None, + &format!("calling `.get{}()` is both faster and more readable", mut_str), + ); +} diff --git a/clippy_lints/src/methods/iter_nth_zero.rs b/clippy_lints/src/methods/iter_nth_zero.rs new file mode 100644 index 00000000000..247192d81f3 --- /dev/null +++ b/clippy_lints/src/methods/iter_nth_zero.rs @@ -0,0 +1,27 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{match_trait_method, paths, snippet_with_applicability, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITER_NTH_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, nth_args: &'tcx [hir::Expr<'_>]) { + if_chain! { + if match_trait_method(cx, expr, &paths::ITERATOR); + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &nth_args[1]); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + ITER_NTH_ZERO, + expr.span, + "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent", + "try calling `.next()` instead of `.nth(0)`", + format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/methods/iter_skip_next.rs b/clippy_lints/src/methods/iter_skip_next.rs new file mode 100644 index 00000000000..5f5969134e4 --- /dev/null +++ b/clippy_lints/src/methods/iter_skip_next.rs @@ -0,0 +1,24 @@ +use crate::utils::{match_trait_method, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITER_SKIP_NEXT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, skip_args: &[hir::Expr<'_>]) { + // lint if caller of skip is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + if let [caller, n] = skip_args { + let hint = format!(".nth({})", snippet(cx, n.span, "..")); + span_lint_and_sugg( + cx, + ITER_SKIP_NEXT, + expr.span.trim_start(caller.span).unwrap(), + "called `skip(..).next()` on an iterator", + "use `nth` instead", + hint, + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints/src/methods/iterator_step_by_zero.rs new file mode 100644 index 00000000000..3e05d7f76b7 --- /dev/null +++ b/clippy_lints/src/methods/iterator_step_by_zero.rs @@ -0,0 +1,19 @@ +use crate::consts::{constant, Constant}; +use crate::utils::{match_trait_method, paths, span_lint}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::ITERATOR_STEP_BY_ZERO; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, args: &'tcx [hir::Expr<'_>]) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &args[1]) { + span_lint( + cx, + ITERATOR_STEP_BY_ZERO, + expr.span, + "`Iterator::step_by(0)` will panic at runtime", + ); + } + } +} diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index eaa604c2ae6..0b414e0eb95 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -6,7 +6,7 @@ use rustc_hir as hir; use rustc_lint::LateContext; use rustc_target::abi::LayoutOf; -pub fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_>]], arith: &str) { +pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[&[hir::Expr<'_>]], arith: &str) { let unwrap_arg = &args[0][1]; let arith_lhs = &args[1][0]; let arith_rhs = &args[1][1]; diff --git a/clippy_lints/src/methods/map_collect_result_unit.rs b/clippy_lints/src/methods/map_collect_result_unit.rs new file mode 100644 index 00000000000..5b20e268d9f --- /dev/null +++ b/clippy_lints/src/methods/map_collect_result_unit.rs @@ -0,0 +1,45 @@ +use crate::utils::{is_type_diagnostic_item, match_trait_method, paths, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::MAP_COLLECT_RESULT_UNIT; + +pub(super) fn check( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + map_args: &[hir::Expr<'_>], + collect_args: &[hir::Expr<'_>], +) { + if_chain! { + // called on Iterator + if let [map_expr] = collect_args; + if match_trait_method(cx, map_expr, &paths::ITERATOR); + // return of collect `Result<(),_>` + let collect_ret_ty = cx.typeck_results().expr_ty(expr); + if is_type_diagnostic_item(cx, collect_ret_ty, sym::result_type); + if let ty::Adt(_, substs) = collect_ret_ty.kind(); + if let Some(result_t) = substs.types().next(); + if result_t.is_unit(); + // get parts for snippet + if let [iter, map_fn] = map_args; + then { + span_lint_and_sugg( + cx, + MAP_COLLECT_RESULT_UNIT, + expr.span, + "`.map().collect()` can be replaced with `.try_for_each()`", + "try this", + format!( + "{}.try_for_each({})", + snippet(cx, iter.span, ".."), + snippet(cx, map_fn.span, "..") + ), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs new file mode 100644 index 00000000000..14a14e4f9ec --- /dev/null +++ b/clippy_lints/src/methods/map_flatten.rs @@ -0,0 +1,61 @@ +use crate::utils::{is_type_diagnostic_item, match_trait_method, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::MAP_FLATTEN; + +/// lint use of `map().flatten()` for `Iterators` and 'Options' +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.map().flatten()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]); + let is_map_to_option = match map_closure_ty.kind() { + ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { + let map_closure_sig = match map_closure_ty.kind() { + ty::Closure(_, substs) => substs.as_closure().sig(), + _ => map_closure_ty.fn_sig(cx.tcx), + }; + let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); + is_type_diagnostic_item(cx, map_closure_return_ty, sym::option_type) + }, + _ => false, + }; + + let method_to_use = if is_map_to_option { + // `(...).map(...)` has type `impl Iterator<Item=Option<...>> + "filter_map" + } else { + // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>> + "flat_map" + }; + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!(".{0}({1})", method_to_use, func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span.with_lo(map_args[0].span.hi()), + "called `map(..).flatten()` on an `Iterator`", + &format!("try using `{}` instead", method_to_use), + hint, + Applicability::MachineApplicable, + ); + } + + // lint if caller of `.map().flatten()` is an Option + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { + let func_snippet = snippet(cx, map_args[1].span, ".."); + let hint = format!(".and_then({})", func_snippet); + span_lint_and_sugg( + cx, + MAP_FLATTEN, + expr.span.with_lo(map_args[0].span.hi()), + "called `map(..).flatten()` on an `Option`", + "try using `and_then` instead", + hint, + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/methods/map_unwrap_or.rs b/clippy_lints/src/methods/map_unwrap_or.rs new file mode 100644 index 00000000000..63b2cf87f32 --- /dev/null +++ b/clippy_lints/src/methods/map_unwrap_or.rs @@ -0,0 +1,76 @@ +use crate::utils::usage::mutated_variables; +use crate::utils::{is_type_diagnostic_item, meets_msrv, snippet, span_lint, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_semver::RustcVersion; +use rustc_span::symbol::sym; + +use super::MAP_UNWRAP_OR; + +const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); + +/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s +/// Return true if lint triggered +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + map_args: &'tcx [hir::Expr<'_>], + unwrap_args: &'tcx [hir::Expr<'_>], + msrv: Option<&RustcVersion>, +) -> bool { + if !meets_msrv(msrv, &MAP_UNWRAP_OR_MSRV) { + return false; + } + // lint if the caller of `map()` is an `Option` + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::result_type); + + if is_option || is_result { + // Don't make a suggestion that may fail to compile due to mutably borrowing + // the same variable twice. + let map_mutated_vars = mutated_variables(&map_args[0], cx); + let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); + if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { + if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { + return false; + } + } else { + return false; + } + + // lint message + let msg = if is_option { + "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling \ + `map_or_else(<g>, <f>)` instead" + } else { + "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \ + `.map_or_else(<g>, <f>)` instead" + }; + // get snippets for args to map() and unwrap_or_else() + let map_snippet = snippet(cx, map_args[1].span, ".."); + let unwrap_snippet = snippet(cx, unwrap_args[1].span, ".."); + // lint, with note if neither arg is > 1 line and both map() and + // unwrap_or_else() have the same span + let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; + let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt(); + if same_span && !multiline { + let var_snippet = snippet(cx, map_args[0].span, ".."); + span_lint_and_sugg( + cx, + MAP_UNWRAP_OR, + expr.span, + msg, + "try this", + format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet), + Applicability::MachineApplicable, + ); + return true; + } else if same_span && multiline { + span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); + return true; + } + } + + false +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5163074453b..7fd14c4f9b1 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1,42 +1,74 @@ mod bind_instead_of_map; mod bytes_nth; +mod clone_on_copy; +mod clone_on_ref_ptr; +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 from_iter_instead_of_collect; +mod get_unwrap; +mod implicit_clone; mod inefficient_to_string; mod inspect_for_each; +mod into_iter_on_ref; +mod iter_cloned_collect; +mod iter_count; +mod iter_next_slice; +mod iter_nth; +mod iter_nth_zero; +mod iter_skip_next; +mod iterator_step_by_zero; mod manual_saturating_arithmetic; +mod map_collect_result_unit; +mod map_flatten; +mod map_unwrap_or; +mod ok_expect; +mod option_as_ref_deref; +mod option_map_or_none; mod option_map_unwrap_or; +mod or_fun_call; +mod search_is_some; +mod single_char_insert_string; +mod single_char_pattern; +mod single_char_push_string; +mod skip_while_next; +mod string_extend_chars; +mod suspicious_map; +mod uninit_assumed_init; mod unnecessary_filter_map; +mod unnecessary_fold; mod unnecessary_lazy_eval; - -use std::borrow::Cow; -use std::fmt; -use std::iter; +mod unwrap_used; +mod useless_asref; +mod wrong_self_convention; +mod zst_offset; use bind_instead_of_map::BindInsteadOfMap; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{Expr, ExprKind, PatKind, TraitItem, TraitItemKind, UnOp}; +use rustc_hir::{TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, TraitRef, Ty, TyS}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::source_map::Span; -use rustc_span::symbol::{sym, Symbol, SymbolStr}; +use rustc_span::symbol::{sym, SymbolStr}; use rustc_typeck::hir_ty_to_ty; -use crate::consts::{constant, Constant}; -use crate::utils::eager_or_lazy::is_lazyness_candidate; -use crate::utils::usage::mutated_variables; use crate::utils::{ - contains_return, contains_ty, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, - in_macro, is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, - match_qpath, match_trait_method, match_type, meets_msrv, method_calls, method_chain_args, path_to_local_id, paths, - remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, strip_pat_refs, sugg, walk_ptrs_ty_depth, - SpanlessEq, + contains_return, contains_ty, get_trait_def_id, implements_trait, in_macro, is_copy, is_type_diagnostic_item, + iter_input_pats, match_def_path, match_qpath, method_calls, method_chain_args, paths, return_ty, + single_segment_path, snippet_with_applicability, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, }; declare_clippy_lint! { @@ -401,7 +433,7 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `_.map(_).flatten(_)`, + /// **What it does:** Checks for usage of `_.map(_).flatten(_)` on `Iterator` and `Option` /// /// **Why is this bad?** Readability, this can be written more concisely as /// `_.flat_map(_)` @@ -1513,6 +1545,58 @@ declare_clippy_lint! { "replace `.bytes().nth()` with `.as_bytes().get()`" } +declare_clippy_lint! { + /// **What it does:** Checks for the usage of `_.to_owned()`, `vec.to_vec()`, or similar when calling `_.clone()` would be clearer. + /// + /// **Why is this bad?** These methods do the same thing as `_.clone()` but may be confusing as + /// to why we are calling `to_vec` on something that is already a `Vec` or calling `to_owned` on something that is already owned. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let a = vec![1, 2, 3]; + /// let b = a.to_vec(); + /// let c = a.to_owned(); + /// ``` + /// Use instead: + /// ```rust + /// let a = vec![1, 2, 3]; + /// let b = a.clone(); + /// let c = a.clone(); + /// ``` + pub IMPLICIT_CLONE, + pedantic, + "implicitly cloning a value by invoking a function on its dereferenced type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for the use of `.iter().count()`. + /// + /// **Why is this bad?** `.len()` is more efficient and more + /// readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// let some_vec = vec![0, 1, 2, 3]; + /// let _ = some_vec.iter().count(); + /// let _ = &some_vec[..].iter().count(); + /// + /// // Good + /// let some_vec = vec![0, 1, 2, 3]; + /// let _ = some_vec.len(); + /// let _ = &some_vec[..].len(); + /// ``` + pub ITER_COUNT, + complexity, + "replace `.iter().count()` with `.len()`" +} + pub struct Methods { msrv: Option<RustcVersion>, } @@ -1558,6 +1642,7 @@ impl_lint_pass!(Methods => [ MAP_FLATTEN, ITERATOR_STEP_BY_ZERO, ITER_NEXT_SLICE, + ITER_COUNT, ITER_NTH, ITER_NTH_ZERO, BYTES_NTH, @@ -1579,6 +1664,7 @@ impl_lint_pass!(Methods => [ MAP_COLLECT_RESULT_UNIT, FROM_ITER_INSTEAD_OF_COLLECT, INSPECT_FOR_EACH, + IMPLICIT_CLONE ]); impl<'tcx> LateLintPass<'tcx> for Methods { @@ -1593,83 +1679,90 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect(); match method_names.as_slice() { - ["unwrap", "get"] => lint_get_unwrap(cx, expr, arg_lists[1], false), - ["unwrap", "get_mut"] => lint_get_unwrap(cx, expr, arg_lists[1], true), - ["unwrap", ..] => lint_unwrap(cx, expr, arg_lists[0]), - ["expect", "ok"] => lint_ok_expect(cx, expr, arg_lists[1]), - ["expect", ..] => lint_expect(cx, expr, arg_lists[0]), - ["unwrap_or", "map"] => option_map_unwrap_or::lint(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), + ["unwrap", "get"] => get_unwrap::check(cx, expr, arg_lists[1], false), + ["unwrap", "get_mut"] => get_unwrap::check(cx, expr, arg_lists[1], true), + ["unwrap", ..] => unwrap_used::check(cx, expr, arg_lists[0]), + ["expect", "ok"] => ok_expect::check(cx, expr, arg_lists[1]), + ["expect", ..] => expect_used::check(cx, expr, arg_lists[0]), + ["unwrap_or", "map"] => option_map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], method_spans[1]), ["unwrap_or_else", "map"] => { - if !lint_map_unwrap_or_else(cx, expr, arg_lists[1], arg_lists[0], self.msrv.as_ref()) { - unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"); + if !map_unwrap_or::check(cx, expr, arg_lists[1], arg_lists[0], self.msrv.as_ref()) { + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"); } }, - ["map_or", ..] => lint_map_or_none(cx, expr, arg_lists[0]), + ["map_or", ..] => option_map_or_none::check(cx, expr, arg_lists[0]), ["and_then", ..] => { - let biom_option_linted = bind_instead_of_map::OptionAndThenSome::lint(cx, expr, arg_lists[0]); - let biom_result_linted = bind_instead_of_map::ResultAndThenOk::lint(cx, expr, arg_lists[0]); + let biom_option_linted = bind_instead_of_map::OptionAndThenSome::check(cx, expr, arg_lists[0]); + let biom_result_linted = bind_instead_of_map::ResultAndThenOk::check(cx, expr, arg_lists[0]); if !biom_option_linted && !biom_result_linted { - unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "and"); + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "and"); } }, ["or_else", ..] => { - if !bind_instead_of_map::ResultOrElseErrInfo::lint(cx, expr, arg_lists[0]) { - unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "or"); + if !bind_instead_of_map::ResultOrElseErrInfo::check(cx, expr, arg_lists[0]) { + unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "or"); } }, - ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), - ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), - ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]), - ["map", "filter"] => lint_filter_map(cx, expr, false), - ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), - ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1], self.msrv.as_ref()), - ["map", "find"] => lint_filter_map(cx, expr, true), - ["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]), - ["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]), - ["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]), - ["flatten", "map"] => lint_map_flatten(cx, expr, arg_lists[1]), - ["is_some", "find"] => lint_search_is_some(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]), + ["next", "filter"] => filter_next::check(cx, expr, arg_lists[1]), + ["next", "skip_while"] => skip_while_next::check(cx, expr, arg_lists[1]), + ["next", "iter"] => iter_next_slice::check(cx, expr, arg_lists[1]), + ["map", "filter"] => filter_map::check(cx, expr, false), + ["map", "filter_map"] => filter_map_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["next", "filter_map"] => filter_map_next::check(cx, expr, arg_lists[1], self.msrv.as_ref()), + ["map", "find"] => filter_map::check(cx, expr, true), + ["flat_map", "filter"] => filter_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", "filter_map"] => filter_map_flat_map::check(cx, expr, arg_lists[1], arg_lists[0]), + ["flat_map", ..] => flat_map_identity::check(cx, expr, arg_lists[0], method_spans[0]), + ["flatten", "map"] => map_flatten::check(cx, expr, arg_lists[1]), + ["is_some", "find"] => search_is_some::check(cx, expr, "find", arg_lists[1], arg_lists[0], method_spans[1]), ["is_some", "position"] => { - lint_search_is_some(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1]) + search_is_some::check(cx, expr, "position", arg_lists[1], arg_lists[0], method_spans[1]) }, ["is_some", "rposition"] => { - lint_search_is_some(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1]) + search_is_some::check(cx, expr, "rposition", arg_lists[1], arg_lists[0], method_spans[1]) }, - ["extend", ..] => lint_extend(cx, expr, arg_lists[0]), - ["nth", "iter"] => lint_iter_nth(cx, expr, &arg_lists, false), - ["nth", "iter_mut"] => lint_iter_nth(cx, expr, &arg_lists, true), - ["nth", "bytes"] => bytes_nth::lints(cx, expr, &arg_lists[1]), - ["nth", ..] => lint_iter_nth_zero(cx, expr, arg_lists[0]), - ["step_by", ..] => lint_step_by(cx, expr, arg_lists[0]), - ["next", "skip"] => lint_iter_skip_next(cx, expr, arg_lists[1]), - ["collect", "cloned"] => lint_iter_cloned_collect(cx, expr, arg_lists[1]), - ["as_ref"] => lint_asref(cx, expr, "as_ref", arg_lists[0]), - ["as_mut"] => lint_asref(cx, expr, "as_mut", arg_lists[0]), - ["fold", ..] => lint_unnecessary_fold(cx, expr, arg_lists[0], method_spans[0]), + ["extend", ..] => string_extend_chars::check(cx, expr, arg_lists[0]), + ["count", "into_iter"] => iter_count::check(cx, expr, &arg_lists[1], "into_iter"), + ["count", "iter"] => iter_count::check(cx, expr, &arg_lists[1], "iter"), + ["count", "iter_mut"] => iter_count::check(cx, expr, &arg_lists[1], "iter_mut"), + ["nth", "iter"] => iter_nth::check(cx, expr, &arg_lists, false), + ["nth", "iter_mut"] => iter_nth::check(cx, expr, &arg_lists, true), + ["nth", "bytes"] => bytes_nth::check(cx, expr, &arg_lists[1]), + ["nth", ..] => iter_nth_zero::check(cx, expr, arg_lists[0]), + ["step_by", ..] => iterator_step_by_zero::check(cx, expr, arg_lists[0]), + ["next", "skip"] => iter_skip_next::check(cx, expr, arg_lists[1]), + ["collect", "cloned"] => iter_cloned_collect::check(cx, expr, arg_lists[1]), + ["as_ref"] => useless_asref::check(cx, expr, "as_ref", arg_lists[0]), + ["as_mut"] => useless_asref::check(cx, expr, "as_mut", arg_lists[0]), + ["fold", ..] => unnecessary_fold::check(cx, expr, arg_lists[0], method_spans[0]), ["filter_map", ..] => { - unnecessary_filter_map::lint(cx, expr, arg_lists[0]); + unnecessary_filter_map::check(cx, expr, arg_lists[0]); filter_map_identity::check(cx, expr, arg_lists[0], method_spans[0]); }, - ["count", "map"] => lint_suspicious_map(cx, expr), - ["assume_init"] => lint_maybe_uninit(cx, &arg_lists[0][0], expr), + ["count", "map"] => suspicious_map::check(cx, expr), + ["assume_init"] => uninit_assumed_init::check(cx, &arg_lists[0][0], expr), ["unwrap_or", arith @ ("checked_add" | "checked_sub" | "checked_mul")] => { - manual_saturating_arithmetic::lint(cx, expr, &arg_lists, &arith["checked_".len()..]) + manual_saturating_arithmetic::check(cx, expr, &arg_lists, &arith["checked_".len()..]) }, ["add" | "offset" | "sub" | "wrapping_offset" | "wrapping_add" | "wrapping_sub"] => { - check_pointer_offset(cx, expr, arg_lists[0]) + zst_offset::check(cx, expr, arg_lists[0]) }, - ["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]), + ["is_file", ..] => filetype_is_file::check(cx, expr, arg_lists[0]), ["map", "as_ref"] => { - lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref()) + option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref()) }, ["map", "as_mut"] => { - lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref()) + option_as_ref_deref::check(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref()) }, - ["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"), - ["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"), - ["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"), - ["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]), - ["for_each", "inspect"] => inspect_for_each::lint(cx, expr, method_spans[1]), + ["unwrap_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "unwrap_or"), + ["get_or_insert_with", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "get_or_insert"), + ["ok_or_else", ..] => unnecessary_lazy_eval::check(cx, expr, arg_lists[0], "ok_or"), + ["collect", "map"] => map_collect_result_unit::check(cx, expr, arg_lists[1], arg_lists[0]), + ["for_each", "inspect"] => inspect_for_each::check(cx, expr, method_spans[1]), + ["to_owned", ..] => implicit_clone::check(cx, expr, sym::ToOwned), + ["to_os_string", ..] => implicit_clone::check(cx, expr, sym::OsStr), + ["to_path_buf", ..] => implicit_clone::check(cx, expr, sym::Path), + ["to_vec", ..] => implicit_clone::check(cx, expr, sym::slice), _ => {}, } @@ -1677,28 +1770,28 @@ impl<'tcx> LateLintPass<'tcx> for Methods { hir::ExprKind::Call(ref func, ref args) => { if let hir::ExprKind::Path(path) = &func.kind { if match_qpath(path, &["from_iter"]) { - lint_from_iter(cx, expr, args); + from_iter_instead_of_collect::check(cx, expr, args); } } }, hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => { - lint_or_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args); - lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args); + or_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); + expect_fun_call::check(cx, expr, *method_span, &method_call.ident.as_str(), args); let self_ty = cx.typeck_results().expr_ty_adjusted(&args[0]); if args.len() == 1 && method_call.ident.name == sym::clone { - lint_clone_on_copy(cx, expr, &args[0], self_ty); - lint_clone_on_ref_ptr(cx, expr, &args[0]); + clone_on_copy::check(cx, expr, &args[0], self_ty); + clone_on_ref_ptr::check(cx, expr, &args[0]); } if args.len() == 1 && method_call.ident.name == sym!(to_string) { - inefficient_to_string::lint(cx, expr, &args[0], self_ty); + inefficient_to_string::check(cx, expr, &args[0], self_ty); } if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { if match_def_path(cx, fn_def_id, &paths::PUSH_STR) { - lint_single_char_push_string(cx, expr, args); + single_char_push_string::check(cx, expr, args); } else if match_def_path(cx, fn_def_id, &paths::INSERT_STR) { - lint_single_char_insert_string(cx, expr, args); + single_char_insert_string::check(cx, expr, args); } } @@ -1706,12 +1799,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ty::Ref(_, ty, _) if *ty.kind() == ty::Str => { for &(method, pos) in &PATTERN_METHODS { if method_call.ident.name.as_str() == method && args.len() > pos { - lint_single_char_pattern(cx, expr, &args[pos]); + single_char_pattern::check(cx, expr, &args[pos]); } } }, ty::Ref(..) if method_call.ident.name == sym::into_iter => { - lint_into_iter(cx, expr, self_ty, *method_span); + into_iter_on_ref::check(cx, expr, self_ty, *method_span); }, _ => (), } @@ -1789,7 +1882,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } } - lint_wrong_self_convention( + wrong_self_convention::check( cx, &name, item.vis.node.is_pub(), @@ -1845,7 +1938,14 @@ impl<'tcx> LateLintPass<'tcx> for Methods { let self_ty = TraitRef::identity(cx.tcx, item.def_id.to_def_id()).self_ty(); then { - lint_wrong_self_convention(cx, &item.ident.name.as_str(), false, self_ty, first_arg_ty, first_arg_span); + wrong_self_convention::check( + cx, + &item.ident.name.as_str(), + false, + self_ty, + first_arg_ty, + first_arg_span + ); } } @@ -1870,862 +1970,6 @@ impl<'tcx> LateLintPass<'tcx> for Methods { extract_msrv_attr!(LateContext); } -fn lint_wrong_self_convention<'tcx>( - cx: &LateContext<'tcx>, - item_name: &str, - is_pub: bool, - self_ty: &'tcx TyS<'tcx>, - first_arg_ty: &'tcx TyS<'tcx>, - first_arg_span: Span, -) { - let lint = if is_pub { - WRONG_PUB_SELF_CONVENTION - } else { - WRONG_SELF_CONVENTION - }; - if let Some((ref conv, self_kinds)) = &CONVENTIONS.iter().find(|(ref conv, _)| conv.check(item_name)) { - if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) { - span_lint( - cx, - lint, - first_arg_span, - &format!( - "methods called `{}` usually take {}; consider choosing a less ambiguous name", - conv, - &self_kinds - .iter() - .map(|k| k.description()) - .collect::<Vec<_>>() - .join(" or ") - ), - ); - } - } -} - -/// Checks for the `OR_FUN_CALL` lint. -#[allow(clippy::too_many_lines)] -fn lint_or_fun_call<'tcx>( - cx: &LateContext<'tcx>, - expr: &hir::Expr<'_>, - method_span: Span, - name: &str, - args: &'tcx [hir::Expr<'_>], -) { - /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`. - fn check_unwrap_or_default( - cx: &LateContext<'_>, - name: &str, - fun: &hir::Expr<'_>, - self_expr: &hir::Expr<'_>, - arg: &hir::Expr<'_>, - or_has_args: bool, - span: Span, - ) -> bool { - if_chain! { - if !or_has_args; - if name == "unwrap_or"; - if let hir::ExprKind::Path(ref qpath) = fun.kind; - let path = &*last_path_segment(qpath).ident.as_str(); - if ["default", "new"].contains(&path); - let arg_ty = cx.typeck_results().expr_ty(arg); - if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); - if implements_trait(cx, arg_ty, default_trait_id, &[]); - - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - OR_FUN_CALL, - span, - &format!("use of `{}` followed by a call to `{}`", name, path), - "try this", - format!( - "{}.unwrap_or_default()", - snippet_with_applicability(cx, self_expr.span, "..", &mut applicability) - ), - applicability, - ); - - true - } else { - false - } - } - } - - /// Checks for `*or(foo())`. - #[allow(clippy::too_many_arguments)] - fn check_general_case<'tcx>( - cx: &LateContext<'tcx>, - name: &str, - method_span: Span, - self_expr: &hir::Expr<'_>, - arg: &'tcx hir::Expr<'_>, - span: Span, - // None if lambda is required - fun_span: Option<Span>, - ) { - // (path, fn_has_argument, methods, suffix) - static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [ - (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), - (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), - (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), - (&paths::RESULT, true, &["or", "unwrap_or"], "else"), - ]; - - if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind { - if path.ident.as_str() == "len" { - let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); - - match ty.kind() { - ty::Slice(_) | ty::Array(_, _) => return, - _ => (), - } - - if is_type_diagnostic_item(cx, ty, sym::vec_type) { - return; - } - } - } - - if_chain! { - if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); - - if is_lazyness_candidate(cx, arg); - if !contains_return(&arg); - - let self_ty = cx.typeck_results().expr_ty(self_expr); - - if let Some(&(_, fn_has_arguments, poss, suffix)) = - KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0)); - - if poss.contains(&name); - - then { - let sugg: Cow<'_, str> = { - let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { - (false, Some(fun_span)) => (fun_span, false), - _ => (arg.span, true), - }; - let snippet = snippet_with_macro_callsite(cx, snippet_span, ".."); - if use_lambda { - let l_arg = if fn_has_arguments { "_" } else { "" }; - format!("|{}| {}", l_arg, snippet).into() - } else { - snippet - } - }; - let span_replace_word = method_span.with_hi(span.hi()); - span_lint_and_sugg( - cx, - OR_FUN_CALL, - span_replace_word, - &format!("use of `{}` followed by a function call", name), - "try this", - format!("{}_{}({})", name, suffix, sugg), - Applicability::HasPlaceholders, - ); - } - } - } - - if args.len() == 2 { - match args[1].kind { - hir::ExprKind::Call(ref fun, ref or_args) => { - let or_has_args = !or_args.is_empty(); - if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { - let fun_span = if or_has_args { None } else { Some(fun.span) }; - check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span); - } - }, - hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { - check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None); - }, - _ => {}, - } - } -} - -/// Checks for the `EXPECT_FUN_CALL` lint. -#[allow(clippy::too_many_lines)] -fn lint_expect_fun_call( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - method_span: Span, - name: &str, - args: &[hir::Expr<'_>], -) { - // Strip `&`, `as_ref()` and `as_str()` off `arg` until we're left with either a `String` or - // `&str` - fn get_arg_root<'a>(cx: &LateContext<'_>, arg: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - let mut arg_root = arg; - loop { - arg_root = match &arg_root.kind { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => expr, - hir::ExprKind::MethodCall(method_name, _, call_args, _) => { - if call_args.len() == 1 - && (method_name.ident.name == sym::as_str || method_name.ident.name == sym!(as_ref)) - && { - let arg_type = cx.typeck_results().expr_ty(&call_args[0]); - let base_type = arg_type.peel_refs(); - *base_type.kind() == ty::Str || is_type_diagnostic_item(cx, base_type, sym::string_type) - } - { - &call_args[0] - } else { - break; - } - }, - _ => break, - }; - } - arg_root - } - - // Only `&'static str` or `String` can be used directly in the `panic!`. Other types should be - // converted to string. - fn requires_to_string(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - let arg_ty = cx.typeck_results().expr_ty(arg); - if is_type_diagnostic_item(cx, arg_ty, sym::string_type) { - return false; - } - if let ty::Ref(_, ty, ..) = arg_ty.kind() { - if *ty.kind() == ty::Str && can_be_static_str(cx, arg) { - return false; - } - }; - true - } - - // Check if an expression could have type `&'static str`, knowing that it - // has type `&str` for some lifetime. - fn can_be_static_str(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { - match arg.kind { - hir::ExprKind::Lit(_) => true, - hir::ExprKind::Call(fun, _) => { - if let hir::ExprKind::Path(ref p) = fun.kind { - match cx.qpath_res(p, fun.hir_id) { - hir::def::Res::Def(hir::def::DefKind::Fn | hir::def::DefKind::AssocFn, def_id) => matches!( - cx.tcx.fn_sig(def_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) - ), - _ => false, - } - } else { - false - } - }, - hir::ExprKind::MethodCall(..) => { - cx.typeck_results() - .type_dependent_def_id(arg.hir_id) - .map_or(false, |method_id| { - matches!( - cx.tcx.fn_sig(method_id).output().skip_binder().kind(), - ty::Ref(ty::ReStatic, ..) - ) - }) - }, - hir::ExprKind::Path(ref p) => matches!( - cx.qpath_res(p, arg.hir_id), - hir::def::Res::Def(hir::def::DefKind::Const | hir::def::DefKind::Static, _) - ), - _ => false, - } - } - - fn generate_format_arg_snippet( - cx: &LateContext<'_>, - a: &hir::Expr<'_>, - applicability: &mut Applicability, - ) -> Vec<String> { - if_chain! { - if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, ref format_arg) = a.kind; - if let hir::ExprKind::Match(ref format_arg_expr, _, _) = format_arg.kind; - if let hir::ExprKind::Tup(ref format_arg_expr_tup) = format_arg_expr.kind; - - then { - format_arg_expr_tup - .iter() - .map(|a| snippet_with_applicability(cx, a.span, "..", applicability).into_owned()) - .collect() - } else { - unreachable!() - } - } - } - - fn is_call(node: &hir::ExprKind<'_>) -> bool { - match node { - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, expr) => { - is_call(&expr.kind) - }, - hir::ExprKind::Call(..) - | hir::ExprKind::MethodCall(..) - // These variants are debatable or require further examination - | hir::ExprKind::If(..) - | hir::ExprKind::Match(..) - | hir::ExprKind::Block{ .. } => true, - _ => false, - } - } - - if args.len() != 2 || name != "expect" || !is_call(&args[1].kind) { - return; - } - - let receiver_type = cx.typeck_results().expr_ty_adjusted(&args[0]); - let closure_args = if is_type_diagnostic_item(cx, receiver_type, sym::option_type) { - "||" - } else if is_type_diagnostic_item(cx, receiver_type, sym::result_type) { - "|_|" - } else { - return; - }; - - let arg_root = get_arg_root(cx, &args[1]); - - let span_replace_word = method_span.with_hi(expr.span.hi()); - - let mut applicability = Applicability::MachineApplicable; - - //Special handling for `format!` as arg_root - if_chain! { - if let hir::ExprKind::Block(block, None) = &arg_root.kind; - if block.stmts.len() == 1; - if let hir::StmtKind::Local(local) = &block.stmts[0].kind; - if let Some(arg_root) = &local.init; - if let hir::ExprKind::Call(ref inner_fun, ref inner_args) = arg_root.kind; - if is_expn_of(inner_fun.span, "format").is_some() && inner_args.len() == 1; - if let hir::ExprKind::Call(_, format_args) = &inner_args[0].kind; - then { - let fmt_spec = &format_args[0]; - let fmt_args = &format_args[1]; - - let mut args = vec![snippet(cx, fmt_spec.span, "..").into_owned()]; - - args.extend(generate_format_arg_snippet(cx, fmt_args, &mut applicability)); - - let sugg = args.join(", "); - - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - &format!("use of `{}` followed by a function call", name), - "try this", - format!("unwrap_or_else({} panic!({}))", closure_args, sugg), - applicability, - ); - - return; - } - } - - let mut arg_root_snippet: Cow<'_, _> = snippet_with_applicability(cx, arg_root.span, "..", &mut applicability); - if requires_to_string(cx, arg_root) { - arg_root_snippet.to_mut().push_str(".to_string()"); - } - - span_lint_and_sugg( - cx, - EXPECT_FUN_CALL, - span_replace_word, - &format!("use of `{}` followed by a function call", name), - "try this", - format!( - "unwrap_or_else({} {{ panic!(\"{{}}\", {}) }})", - closure_args, arg_root_snippet - ), - applicability, - ); -} - -/// Checks for the `CLONE_ON_COPY` lint. -fn lint_clone_on_copy(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, arg_ty: Ty<'_>) { - let ty = cx.typeck_results().expr_ty(expr); - if let ty::Ref(_, inner, _) = arg_ty.kind() { - if let ty::Ref(_, innermost, _) = inner.kind() { - span_lint_and_then( - cx, - CLONE_DOUBLE_REF, - expr.span, - &format!( - "using `clone` on a double-reference; \ - this will copy the reference of type `{}` instead of cloning the inner type", - ty - ), - |diag| { - if let Some(snip) = sugg::Sugg::hir_opt(cx, arg) { - let mut ty = innermost; - let mut n = 0; - while let ty::Ref(_, inner, _) = ty.kind() { - ty = inner; - n += 1; - } - let refs: String = iter::repeat('&').take(n + 1).collect(); - let derefs: String = iter::repeat('*').take(n).collect(); - let explicit = format!("<{}{}>::clone({})", refs, ty, snip); - diag.span_suggestion( - expr.span, - "try dereferencing it", - format!("{}({}{}).clone()", refs, derefs, snip.deref()), - Applicability::MaybeIncorrect, - ); - diag.span_suggestion( - expr.span, - "or try being explicit if you are sure, that you want to clone a reference", - explicit, - Applicability::MaybeIncorrect, - ); - } - }, - ); - return; // don't report clone_on_copy - } - } - - if is_copy(cx, ty) { - let snip; - if let Some(snippet) = sugg::Sugg::hir_opt(cx, arg) { - let parent = cx.tcx.hir().get_parent_node(expr.hir_id); - match &cx.tcx.hir().get(parent) { - hir::Node::Expr(parent) => match parent.kind { - // &*x is a nop, &x.clone() is not - hir::ExprKind::AddrOf(..) => return, - // (*x).func() is useless, x.clone().func() can work in case func borrows mutably - hir::ExprKind::MethodCall(_, _, parent_args, _) if expr.hir_id == parent_args[0].hir_id => { - return; - }, - - _ => {}, - }, - hir::Node::Stmt(stmt) => { - if let hir::StmtKind::Local(ref loc) = stmt.kind { - if let hir::PatKind::Ref(..) = loc.pat.kind { - // let ref y = *x borrows x, let ref y = x.clone() does not - return; - } - } - }, - _ => {}, - } - - // x.clone() might have dereferenced x, possibly through Deref impls - if cx.typeck_results().expr_ty(arg) == ty { - snip = Some(("try removing the `clone` call", format!("{}", snippet))); - } else { - let deref_count = cx - .typeck_results() - .expr_adjustments(arg) - .iter() - .filter(|adj| matches!(adj.kind, ty::adjustment::Adjust::Deref(_))) - .count(); - let derefs: String = iter::repeat('*').take(deref_count).collect(); - snip = Some(("try dereferencing it", format!("{}{}", derefs, snippet))); - } - } else { - snip = None; - } - span_lint_and_then( - cx, - CLONE_ON_COPY, - expr.span, - &format!("using `clone` on type `{}` which implements the `Copy` trait", ty), - |diag| { - if let Some((text, snip)) = snip { - diag.span_suggestion(expr.span, text, snip, Applicability::MachineApplicable); - } - }, - ); - } -} - -fn lint_clone_on_ref_ptr(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { - let obj_ty = cx.typeck_results().expr_ty(arg).peel_refs(); - - if let ty::Adt(_, subst) = obj_ty.kind() { - let caller_type = if is_type_diagnostic_item(cx, obj_ty, sym::Rc) { - "Rc" - } else if is_type_diagnostic_item(cx, obj_ty, sym::Arc) { - "Arc" - } else if match_type(cx, obj_ty, &paths::WEAK_RC) || match_type(cx, obj_ty, &paths::WEAK_ARC) { - "Weak" - } else { - return; - }; - - let snippet = snippet_with_macro_callsite(cx, arg.span, ".."); - - span_lint_and_sugg( - cx, - CLONE_ON_REF_PTR, - expr.span, - "using `.clone()` on a ref-counted pointer", - "try this", - format!("{}::<{}>::clone(&{})", caller_type, subst.type_at(0), snippet), - Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak - ); - } -} - -fn lint_string_extend(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let arg = &args[1]; - if let Some(arglists) = method_chain_args(arg, &["chars"]) { - let target = &arglists[0][0]; - let self_ty = cx.typeck_results().expr_ty(target).peel_refs(); - let ref_str = if *self_ty.kind() == ty::Str { - "" - } else if is_type_diagnostic_item(cx, self_ty, sym::string_type) { - "&" - } else { - return; - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - STRING_EXTEND_CHARS, - expr.span, - "calling `.extend(_.chars())`", - "try this", - format!( - "{}.push_str({}{})", - snippet_with_applicability(cx, args[0].span, "..", &mut applicability), - ref_str, - snippet_with_applicability(cx, target.span, "..", &mut applicability) - ), - applicability, - ); - } -} - -fn lint_extend(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); - if is_type_diagnostic_item(cx, obj_ty, sym::string_type) { - lint_string_extend(cx, expr, args); - } -} - -fn lint_iter_cloned_collect<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { - if_chain! { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::vec_type); - if let Some(slice) = derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])); - if let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()); - - then { - span_lint_and_sugg( - cx, - ITER_CLONED_COLLECT, - to_replace, - "called `iter().cloned().collect()` on a slice to create a `Vec`. Calling `to_vec()` is both faster and \ - more readable", - "try", - ".to_vec()".to_string(), - Applicability::MachineApplicable, - ); - } - } -} - -fn lint_unnecessary_fold(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) { - fn check_fold_with_op( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - fold_args: &[hir::Expr<'_>], - fold_span: Span, - op: hir::BinOpKind, - replacement_method_name: &str, - replacement_has_args: bool, - ) { - if_chain! { - // Extract the body of the closure passed to fold - if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind; - let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); - - // Check if the closure body is of the form `acc <op> some_expr(x)` - if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind; - if bin_op.node == op; - - // Extract the names of the two arguments to the closure - if let [param_a, param_b] = closure_body.params; - if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(¶m_a.pat).kind; - if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(¶m_b.pat).kind; - - if path_to_local_id(left_expr, first_arg_id); - if replacement_has_args || path_to_local_id(right_expr, second_arg_id); - - then { - let mut applicability = Applicability::MachineApplicable; - let sugg = if replacement_has_args { - format!( - "{replacement}(|{s}| {r})", - replacement = replacement_method_name, - s = second_arg_ident, - r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), - ) - } else { - format!( - "{replacement}()", - replacement = replacement_method_name, - ) - }; - - span_lint_and_sugg( - cx, - UNNECESSARY_FOLD, - fold_span.with_hi(expr.span.hi()), - // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f) - "this `.fold` can be written more succinctly using another method", - "try", - sugg, - applicability, - ); - } - } - } - - // Check that this is a call to Iterator::fold rather than just some function called fold - if !match_trait_method(cx, expr, &paths::ITERATOR) { - return; - } - - assert!( - fold_args.len() == 3, - "Expected fold_args to have three entries - the receiver, the initial value and the closure" - ); - - // Check if the first argument to .fold is a suitable literal - if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind { - match lit.node { - ast::LitKind::Bool(false) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true) - }, - ast::LitKind::Bool(true) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true) - }, - ast::LitKind::Int(0, _) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false) - }, - ast::LitKind::Int(1, _) => { - check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false) - }, - _ => (), - } - } -} - -fn lint_step_by<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, args: &'tcx [hir::Expr<'_>]) { - if match_trait_method(cx, expr, &paths::ITERATOR) { - if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &args[1]) { - span_lint( - cx, - ITERATOR_STEP_BY_ZERO, - expr.span, - "Iterator::step_by(0) will panic at runtime", - ); - } - } -} - -fn lint_iter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, iter_args: &'tcx [hir::Expr<'_>]) { - let caller_expr = &iter_args[0]; - - // Skip lint if the `iter().next()` expression is a for loop argument, - // since it is already covered by `&loops::ITER_NEXT_LOOP` - let mut parent_expr_opt = get_parent_expr(cx, expr); - while let Some(parent_expr) = parent_expr_opt { - if higher::for_loop(parent_expr).is_some() { - return; - } - parent_expr_opt = get_parent_expr(cx, parent_expr); - } - - if derefs_to_slice(cx, caller_expr, cx.typeck_results().expr_ty(caller_expr)).is_some() { - // caller is a Slice - if_chain! { - if let hir::ExprKind::Index(ref caller_var, ref index_expr) = &caller_expr.kind; - if let Some(higher::Range { start: Some(start_expr), end: None, limits: ast::RangeLimits::HalfOpen }) - = higher::range(index_expr); - if let hir::ExprKind::Lit(ref start_lit) = &start_expr.kind; - if let ast::LitKind::Int(start_idx, _) = start_lit.node; - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - ITER_NEXT_SLICE, - expr.span, - "using `.iter().next()` on a Slice without end index", - "try calling", - format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx), - applicability, - ); - } - } - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(caller_expr), sym::vec_type) - || matches!( - &cx.typeck_results().expr_ty(caller_expr).peel_refs().kind(), - ty::Array(_, _) - ) - { - // caller is a Vec or an Array - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - ITER_NEXT_SLICE, - expr.span, - "using `.iter().next()` on an array", - "try calling", - format!( - "{}.get(0)", - snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability) - ), - applicability, - ); - } -} - -fn lint_iter_nth<'tcx>( - cx: &LateContext<'tcx>, - expr: &hir::Expr<'_>, - nth_and_iter_args: &[&'tcx [hir::Expr<'tcx>]], - is_mut: bool, -) { - let iter_args = nth_and_iter_args[1]; - let mut_str = if is_mut { "_mut" } else { "" }; - let caller_type = if derefs_to_slice(cx, &iter_args[0], cx.typeck_results().expr_ty(&iter_args[0])).is_some() { - "slice" - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vec_type) { - "Vec" - } else if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&iter_args[0]), sym::vecdeque_type) { - "VecDeque" - } else { - let nth_args = nth_and_iter_args[0]; - lint_iter_nth_zero(cx, expr, &nth_args); - return; // caller is not a type that we want to lint - }; - - span_lint_and_help( - cx, - ITER_NTH, - expr.span, - &format!("called `.iter{0}().nth()` on a {1}", mut_str, caller_type), - None, - &format!("calling `.get{}()` is both faster and more readable", mut_str), - ); -} - -fn lint_iter_nth_zero<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, nth_args: &'tcx [hir::Expr<'_>]) { - if_chain! { - if match_trait_method(cx, expr, &paths::ITERATOR); - if let Some((Constant::Int(0), _)) = constant(cx, cx.typeck_results(), &nth_args[1]); - then { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - ITER_NTH_ZERO, - expr.span, - "called `.nth(0)` on a `std::iter::Iterator`, when `.next()` is equivalent", - "try calling `.next()` instead of `.nth(0)`", - format!("{}.next()", snippet_with_applicability(cx, nth_args[0].span, "..", &mut applicability)), - applicability, - ); - } - } -} - -fn lint_get_unwrap<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, get_args: &'tcx [hir::Expr<'_>], is_mut: bool) { - // Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`, - // because they do not implement `IndexMut` - let mut applicability = Applicability::MachineApplicable; - let expr_ty = cx.typeck_results().expr_ty(&get_args[0]); - let get_args_str = if get_args.len() > 1 { - snippet_with_applicability(cx, get_args[1].span, "..", &mut applicability) - } else { - return; // not linting on a .get().unwrap() chain or variant - }; - let mut needs_ref; - let caller_type = if derefs_to_slice(cx, &get_args[0], expr_ty).is_some() { - needs_ref = get_args_str.parse::<usize>().is_ok(); - "slice" - } else if is_type_diagnostic_item(cx, expr_ty, sym::vec_type) { - needs_ref = get_args_str.parse::<usize>().is_ok(); - "Vec" - } else if is_type_diagnostic_item(cx, expr_ty, sym::vecdeque_type) { - needs_ref = get_args_str.parse::<usize>().is_ok(); - "VecDeque" - } else if !is_mut && is_type_diagnostic_item(cx, expr_ty, sym::hashmap_type) { - needs_ref = true; - "HashMap" - } else if !is_mut && match_type(cx, expr_ty, &paths::BTREEMAP) { - needs_ref = true; - "BTreeMap" - } else { - return; // caller is not a type that we want to lint - }; - - let mut span = expr.span; - - // Handle the case where the result is immediately dereferenced - // by not requiring ref and pulling the dereference into the - // suggestion. - if_chain! { - if needs_ref; - if let Some(parent) = get_parent_expr(cx, expr); - if let hir::ExprKind::Unary(hir::UnOp::Deref, _) = parent.kind; - then { - needs_ref = false; - span = parent.span; - } - } - - let mut_str = if is_mut { "_mut" } else { "" }; - let borrow_str = if !needs_ref { - "" - } else if is_mut { - "&mut " - } else { - "&" - }; - - span_lint_and_sugg( - cx, - GET_UNWRAP, - span, - &format!( - "called `.get{0}().unwrap()` on a {1}. Using `[]` is more clear and more concise", - mut_str, caller_type - ), - "try this", - format!( - "{}{}[{}]", - borrow_str, - snippet_with_applicability(cx, get_args[0].span, "..", &mut applicability), - get_args_str - ), - applicability, - ); -} - -fn lint_iter_skip_next(cx: &LateContext<'_>, expr: &hir::Expr<'_>, skip_args: &[hir::Expr<'_>]) { - // lint if caller of skip is an Iterator - if match_trait_method(cx, expr, &paths::ITERATOR) { - if let [caller, n] = skip_args { - let hint = format!(".nth({})", snippet(cx, n.span, "..")); - span_lint_and_sugg( - cx, - ITER_SKIP_NEXT, - expr.span.trim_start(caller.span).unwrap(), - "called `skip(..).next()` on an iterator", - "use `nth` instead", - hint, - Applicability::MachineApplicable, - ); - } - } -} - fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, @@ -2766,605 +2010,6 @@ fn derefs_to_slice<'tcx>( } } -/// lint use of `unwrap()` for `Option`s and `Result`s -fn lint_unwrap(cx: &LateContext<'_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&unwrap_args[0]).peel_refs(); - - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { - Some((UNWRAP_USED, "an Option", "None")) - } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { - Some((UNWRAP_USED, "a Result", "Err")) - } else { - None - }; - - if let Some((lint, kind, none_value)) = mess { - span_lint_and_help( - cx, - lint, - expr.span, - &format!("used `unwrap()` on `{}` value", kind,), - None, - &format!( - "if you don't want to handle the `{}` case gracefully, consider \ - using `expect()` to provide a better panic message", - none_value, - ), - ); - } -} - -/// lint use of `expect()` for `Option`s and `Result`s -fn lint_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expect_args: &[hir::Expr<'_>]) { - let obj_ty = cx.typeck_results().expr_ty(&expect_args[0]).peel_refs(); - - let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { - Some((EXPECT_USED, "an Option", "None")) - } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { - Some((EXPECT_USED, "a Result", "Err")) - } else { - None - }; - - if let Some((lint, kind, none_value)) = mess { - span_lint_and_help( - cx, - lint, - expr.span, - &format!("used `expect()` on `{}` value", kind,), - None, - &format!("if this value is an `{}`, it will panic", none_value,), - ); - } -} - -/// lint use of `ok().expect()` for `Result`s -fn lint_ok_expect(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) { - if_chain! { - // lint if the caller of `ok()` is a `Result` - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&ok_args[0]), sym::result_type); - let result_type = cx.typeck_results().expr_ty(&ok_args[0]); - if let Some(error_type) = get_error_type(cx, result_type); - if has_debug_impl(error_type, cx); - - then { - span_lint_and_help( - cx, - OK_EXPECT, - expr.span, - "called `ok().expect()` on a `Result` value", - None, - "you can call `expect()` directly on the `Result`", - ); - } - } -} - -/// lint use of `map().flatten()` for `Iterators` and 'Options' -fn lint_map_flatten<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_args: &'tcx [hir::Expr<'_>]) { - // lint if caller of `.map().flatten()` is an Iterator - if match_trait_method(cx, expr, &paths::ITERATOR) { - let map_closure_ty = cx.typeck_results().expr_ty(&map_args[1]); - let is_map_to_option = match map_closure_ty.kind() { - ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { - let map_closure_sig = match map_closure_ty.kind() { - ty::Closure(_, substs) => substs.as_closure().sig(), - _ => map_closure_ty.fn_sig(cx.tcx), - }; - let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); - is_type_diagnostic_item(cx, map_closure_return_ty, sym::option_type) - }, - _ => false, - }; - - let method_to_use = if is_map_to_option { - // `(...).map(...)` has type `impl Iterator<Item=Option<...>> - "filter_map" - } else { - // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>> - "flat_map" - }; - let func_snippet = snippet(cx, map_args[1].span, ".."); - let hint = format!(".{0}({1})", method_to_use, func_snippet); - span_lint_and_sugg( - cx, - MAP_FLATTEN, - expr.span.with_lo(map_args[0].span.hi()), - "called `map(..).flatten()` on an `Iterator`", - &format!("try using `{}` instead", method_to_use), - hint, - Applicability::MachineApplicable, - ); - } - - // lint if caller of `.map().flatten()` is an Option - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type) { - let func_snippet = snippet(cx, map_args[1].span, ".."); - let hint = format!(".and_then({})", func_snippet); - span_lint_and_sugg( - cx, - MAP_FLATTEN, - expr.span.with_lo(map_args[0].span.hi()), - "called `map(..).flatten()` on an `Option`", - "try using `and_then` instead", - hint, - Applicability::MachineApplicable, - ); - } -} - -const MAP_UNWRAP_OR_MSRV: RustcVersion = RustcVersion::new(1, 41, 0); - -/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s -/// Return true if lint triggered -fn lint_map_unwrap_or_else<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - map_args: &'tcx [hir::Expr<'_>], - unwrap_args: &'tcx [hir::Expr<'_>], - msrv: Option<&RustcVersion>, -) -> bool { - if !meets_msrv(msrv, &MAP_UNWRAP_OR_MSRV) { - return false; - } - // lint if the caller of `map()` is an `Option` - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::option_type); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_args[0]), sym::result_type); - - if is_option || is_result { - // Don't make a suggestion that may fail to compile due to mutably borrowing - // the same variable twice. - let map_mutated_vars = mutated_variables(&map_args[0], cx); - let unwrap_mutated_vars = mutated_variables(&unwrap_args[1], cx); - if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) { - if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() { - return false; - } - } else { - return false; - } - - // lint message - let msg = if is_option { - "called `map(<f>).unwrap_or_else(<g>)` on an `Option` value. This can be done more directly by calling \ - `map_or_else(<g>, <f>)` instead" - } else { - "called `map(<f>).unwrap_or_else(<g>)` on a `Result` value. This can be done more directly by calling \ - `.map_or_else(<g>, <f>)` instead" - }; - // get snippets for args to map() and unwrap_or_else() - let map_snippet = snippet(cx, map_args[1].span, ".."); - let unwrap_snippet = snippet(cx, unwrap_args[1].span, ".."); - // lint, with note if neither arg is > 1 line and both map() and - // unwrap_or_else() have the same span - let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1; - let same_span = map_args[1].span.ctxt() == unwrap_args[1].span.ctxt(); - if same_span && !multiline { - let var_snippet = snippet(cx, map_args[0].span, ".."); - span_lint_and_sugg( - cx, - MAP_UNWRAP_OR, - expr.span, - msg, - "try this", - format!("{}.map_or_else({}, {})", var_snippet, unwrap_snippet, map_snippet), - Applicability::MachineApplicable, - ); - return true; - } else if same_span && multiline { - span_lint(cx, MAP_UNWRAP_OR, expr.span, msg); - return true; - } - } - - false -} - -/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s -fn lint_map_or_none<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_or_args: &'tcx [hir::Expr<'_>]) { - let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::option_type); - let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::result_type); - - // There are two variants of this `map_or` lint: - // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>` - // (2) using `map_or` as a combinator instead of `and_then` - // - // (For this lint) we don't care if any other type calls `map_or` - if !is_option && !is_result { - return; - } - - let (lint_name, msg, instead, hint) = { - let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind { - match_qpath(qpath, &paths::OPTION_NONE) - } else { - return; - }; - - if !default_arg_is_none { - // nothing to lint! - return; - } - - let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind { - match_qpath(qpath, &paths::OPTION_SOME) - } else { - false - }; - - if is_option { - let self_snippet = snippet(cx, map_or_args[0].span, ".."); - let func_snippet = snippet(cx, map_or_args[2].span, ".."); - let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ - `and_then(..)` instead"; - ( - OPTION_MAP_OR_NONE, - msg, - "try using `and_then` instead", - format!("{0}.and_then({1})", self_snippet, func_snippet), - ) - } else if f_arg_is_some { - let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ - `ok()` instead"; - let self_snippet = snippet(cx, map_or_args[0].span, ".."); - ( - RESULT_MAP_OR_INTO_OPTION, - msg, - "try using `ok` instead", - format!("{0}.ok()", self_snippet), - ) - } else { - // nothing to lint! - return; - } - }; - - span_lint_and_sugg( - cx, - lint_name, - expr.span, - msg, - instead, - hint, - Applicability::MachineApplicable, - ); -} - -/// lint use of `filter().next()` for `Iterators` -fn lint_filter_next<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, filter_args: &'tcx [hir::Expr<'_>]) { - // lint if caller of `.filter().next()` is an Iterator - if match_trait_method(cx, expr, &paths::ITERATOR) { - let msg = "called `filter(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ - `.find(..)` instead."; - let filter_snippet = snippet(cx, filter_args[1].span, ".."); - if filter_snippet.lines().count() <= 1 { - let iter_snippet = snippet(cx, filter_args[0].span, ".."); - // add note if not multi-line - span_lint_and_sugg( - cx, - FILTER_NEXT, - expr.span, - msg, - "try this", - format!("{}.find({})", iter_snippet, filter_snippet), - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, FILTER_NEXT, expr.span, msg); - } - } -} - -/// lint use of `skip_while().next()` for `Iterators` -fn lint_skip_while_next<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _skip_while_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.skip_while().next()` is an Iterator - if match_trait_method(cx, expr, &paths::ITERATOR) { - span_lint_and_help( - cx, - SKIP_WHILE_NEXT, - expr.span, - "called `skip_while(<p>).next()` on an `Iterator`", - None, - "this is more succinctly expressed by calling `.find(!<p>)` instead", - ); - } -} - -/// lint use of `filter().map()` or `find().map()` for `Iterators` -fn lint_filter_map<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) { - if_chain! { - if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; - if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind; - if match_trait_method(cx, map_recv, &paths::ITERATOR); - - // filter(|x| ...is_some())... - if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; - let filter_body = cx.tcx.hir().body(filter_body_id); - if let [filter_param] = filter_body.params; - // optional ref pattern: `filter(|&x| ..)` - let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { - (ref_pat, true) - } else { - (filter_param.pat, false) - }; - // closure ends with is_some() or is_ok() - if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; - if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; - if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); - if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { - Some(false) - } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { - Some(true) - } else { - None - }; - if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; - - // ...map(|x| ...unwrap()) - if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; - let map_body = cx.tcx.hir().body(map_body_id); - if let [map_param] = map_body.params; - if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; - // closure ends with expect() or unwrap() - if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; - if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); - - let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { - // in `filter(|x| ..)`, replace `*x` with `x` - let a_path = if_chain! { - if !is_filter_param_ref; - if let ExprKind::Unary(UnOp::Deref, expr_path) = a.kind; - then { expr_path } else { a } - }; - // let the filter closure arg and the map closure arg be equal - if_chain! { - if path_to_local_id(a_path, filter_param_id); - if path_to_local_id(b, map_param_id); - if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); - then { - return true; - } - } - false - }; - if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); - then { - let span = filter_span.to(map_span); - let (filter_name, lint) = if is_find { - ("find", MANUAL_FIND_MAP) - } else { - ("filter", MANUAL_FILTER_MAP) - }; - let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); - let to_opt = if is_result { ".ok()" } else { "" }; - let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, - snippet(cx, map_arg.span, ".."), to_opt); - span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); - } - } -} - -const FILTER_MAP_NEXT_MSRV: RustcVersion = RustcVersion::new(1, 30, 0); - -/// lint use of `filter_map().next()` for `Iterators` -fn lint_filter_map_next<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - filter_args: &'tcx [hir::Expr<'_>], - msrv: Option<&RustcVersion>, -) { - if match_trait_method(cx, expr, &paths::ITERATOR) { - if !meets_msrv(msrv, &FILTER_MAP_NEXT_MSRV) { - return; - } - - let msg = "called `filter_map(..).next()` on an `Iterator`. This is more succinctly expressed by calling \ - `.find_map(..)` instead."; - let filter_snippet = snippet(cx, filter_args[1].span, ".."); - if filter_snippet.lines().count() <= 1 { - let iter_snippet = snippet(cx, filter_args[0].span, ".."); - span_lint_and_sugg( - cx, - FILTER_MAP_NEXT, - expr.span, - msg, - "try this", - format!("{}.find_map({})", iter_snippet, filter_snippet), - Applicability::MachineApplicable, - ); - } else { - span_lint(cx, FILTER_MAP_NEXT, expr.span, msg); - } - } -} - -/// lint use of `filter_map().map()` for `Iterators` -fn lint_filter_map_map<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _filter_args: &'tcx [hir::Expr<'_>], - _map_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.filter_map().map()` is an Iterator - if match_trait_method(cx, expr, &paths::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); - } -} - -/// lint use of `filter().flat_map()` for `Iterators` -fn lint_filter_flat_map<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _filter_args: &'tcx [hir::Expr<'_>], - _map_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.filter().flat_map()` is an Iterator - if match_trait_method(cx, expr, &paths::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); - } -} - -/// lint use of `filter_map().flat_map()` for `Iterators` -fn lint_filter_map_flat_map<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _filter_args: &'tcx [hir::Expr<'_>], - _map_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.filter_map().flat_map()` is an Iterator - if match_trait_method(cx, expr, &paths::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); - } -} - -/// lint use of `flat_map` for `Iterators` where `flatten` would be sufficient -fn lint_flat_map_identity<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - flat_map_args: &'tcx [hir::Expr<'_>], - flat_map_span: Span, -) { - if match_trait_method(cx, expr, &paths::ITERATOR) { - let arg_node = &flat_map_args[1].kind; - - let apply_lint = |message: &str| { - span_lint_and_sugg( - cx, - FLAT_MAP_IDENTITY, - flat_map_span.with_hi(expr.span.hi()), - message, - "try", - "flatten()".to_string(), - Applicability::MachineApplicable, - ); - }; - - if_chain! { - if let hir::ExprKind::Closure(_, _, body_id, _, _) = arg_node; - 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(_, ref path)) = body.value.kind; - - if path.segments.len() == 1; - if path.segments[0].ident.name == binding_ident.name; - - then { - apply_lint("called `flat_map(|x| x)` on an `Iterator`"); - } - } - - 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`"); - } - } - } -} - -/// lint searching an Iterator followed by `is_some()` -/// or calling `find()` on a string followed by `is_some()` -fn lint_search_is_some<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - search_method: &str, - search_args: &'tcx [hir::Expr<'_>], - is_some_args: &'tcx [hir::Expr<'_>], - method_span: Span, -) { - // lint if caller of search is an Iterator - if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) { - let msg = format!( - "called `is_some()` after searching an `Iterator` with `{}`", - search_method - ); - let hint = "this is more succinctly expressed by calling `any()`"; - let search_snippet = snippet(cx, search_args[1].span, ".."); - if search_snippet.lines().count() <= 1 { - // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` - // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` - let any_search_snippet = if_chain! { - if search_method == "find"; - if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind; - let closure_body = cx.tcx.hir().body(body_id); - if let Some(closure_arg) = closure_body.params.get(0); - then { - if let hir::PatKind::Ref(..) = closure_arg.pat.kind { - Some(search_snippet.replacen('&', "", 1)) - } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind { - let name = &*ident.name.as_str(); - Some(search_snippet.replace(&format!("*{}", name), name)) - } else { - None - } - } else { - None - } - }; - // add note if not multi-line - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - method_span.with_hi(expr.span.hi()), - &msg, - "use `any()` instead", - format!( - "any({})", - any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) - ), - Applicability::MachineApplicable, - ); - } else { - span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint); - } - } - // lint if `find()` is called by `String` or `&str` - else if search_method == "find" { - let is_string_or_str_slice = |e| { - let self_ty = cx.typeck_results().expr_ty(e).peel_refs(); - if is_type_diagnostic_item(cx, self_ty, sym::string_type) { - true - } else { - *self_ty.kind() == ty::Str - } - }; - if_chain! { - if is_string_or_str_slice(&search_args[0]); - if is_string_or_str_slice(&search_args[1]); - then { - let msg = "called `is_some()` after calling `find()` on a string"; - let mut applicability = Applicability::MachineApplicable; - let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability); - span_lint_and_sugg( - cx, - SEARCH_IS_SOME, - method_span.with_hi(expr.span.hi()), - msg, - "use `contains()` instead", - format!("contains({})", find_arg), - applicability, - ); - } - } - } -} - /// Used for `lint_binary_expr_with_method_call`. #[derive(Copy, Clone)] struct BinaryExprInfo<'a> { @@ -3528,346 +2173,6 @@ fn get_hint_if_single_char_arg( } } -/// lint for length-1 `str`s for methods in `PATTERN_METHODS` -fn lint_single_char_pattern(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { - let mut applicability = Applicability::MachineApplicable; - if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability) { - span_lint_and_sugg( - cx, - SINGLE_CHAR_PATTERN, - arg.span, - "single-character string constant used as pattern", - "try using a `char` instead", - hint, - applicability, - ); - } -} - -/// lint for length-1 `str`s as argument for `push_str` -fn lint_single_char_push_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) { - let base_string_snippet = - snippet_with_applicability(cx, args[0].span.source_callsite(), "..", &mut applicability); - let sugg = format!("{}.push({})", base_string_snippet, extension_string); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `push_str()` using a single-character string literal", - "consider using `push` with a character literal", - sugg, - applicability, - ); - } -} - -/// lint for length-1 `str`s as argument for `insert_str` -fn lint_single_char_insert_string(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let mut applicability = Applicability::MachineApplicable; - if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) { - let base_string_snippet = - snippet_with_applicability(cx, args[0].span.source_callsite(), "_", &mut applicability); - let pos_arg = snippet_with_applicability(cx, args[1].span, "..", &mut applicability); - let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string); - span_lint_and_sugg( - cx, - SINGLE_CHAR_ADD_STR, - expr.span, - "calling `insert_str()` using a single-character string literal", - "consider using `insert` with a character literal", - sugg, - applicability, - ); - } -} - -/// Checks for the `USELESS_ASREF` lint. -fn lint_asref(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { - // when we get here, we've already checked that the call name is "as_ref" or "as_mut" - // check if the call is to the actual `AsRef` or `AsMut` trait - if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) { - // check if the type after `as_ref` or `as_mut` is the same as before - let recvr = &as_ref_args[0]; - let rcv_ty = cx.typeck_results().expr_ty(recvr); - let res_ty = cx.typeck_results().expr_ty(expr); - let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); - let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); - if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { - // allow the `as_ref` or `as_mut` if it is followed by another method call - if_chain! { - if let Some(parent) = get_parent_expr(cx, expr); - if let hir::ExprKind::MethodCall(_, ref span, _, _) = parent.kind; - if span != &expr.span; - then { - return; - } - } - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - USELESS_ASREF, - expr.span, - &format!("this call to `{}` does nothing", call_name), - "try this", - snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(), - applicability, - ); - } - } -} - -fn ty_has_iter_method(cx: &LateContext<'_>, self_ref_ty: Ty<'_>) -> Option<(Symbol, &'static str)> { - has_iter_method(cx, self_ref_ty).map(|ty_name| { - let mutbl = match self_ref_ty.kind() { - ty::Ref(_, _, mutbl) => mutbl, - _ => unreachable!(), - }; - let method_name = match mutbl { - hir::Mutability::Not => "iter", - hir::Mutability::Mut => "iter_mut", - }; - (ty_name, method_name) - }) -} - -fn lint_into_iter(cx: &LateContext<'_>, expr: &hir::Expr<'_>, self_ref_ty: Ty<'_>, method_span: Span) { - if !match_trait_method(cx, expr, &paths::INTO_ITERATOR) { - return; - } - if let Some((kind, method_name)) = ty_has_iter_method(cx, self_ref_ty) { - span_lint_and_sugg( - cx, - INTO_ITER_ON_REF, - method_span, - &format!( - "this `.into_iter()` call is equivalent to `.{}()` and will not consume the `{}`", - method_name, kind, - ), - "call directly", - method_name.to_string(), - Applicability::MachineApplicable, - ); - } -} - -/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) -fn lint_maybe_uninit(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) { - if_chain! { - if let hir::ExprKind::Call(ref callee, ref args) = expr.kind; - if args.is_empty(); - if let hir::ExprKind::Path(ref path) = callee.kind; - if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT); - if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(outer)); - then { - span_lint( - cx, - UNINIT_ASSUMED_INIT, - outer.span, - "this call for this type may be undefined behavior" - ); - } - } -} - -fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - match ty.kind() { - ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component), - ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), - ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), - _ => false, - } -} - -fn lint_suspicious_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) { - span_lint_and_help( - cx, - SUSPICIOUS_MAP, - expr.span, - "this call to `map()` won't have an effect on the call to `count()`", - None, - "make sure you did not confuse `map` with `filter` or `for_each`", - ); -} - -const OPTION_AS_REF_DEREF_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); - -/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s -fn lint_option_as_ref_deref<'tcx>( - cx: &LateContext<'tcx>, - expr: &hir::Expr<'_>, - as_ref_args: &[hir::Expr<'_>], - map_args: &[hir::Expr<'_>], - is_mut: bool, - msrv: Option<&RustcVersion>, -) { - if !meets_msrv(msrv, &OPTION_AS_REF_DEREF_MSRV) { - return; - } - - let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); - - let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]); - if !is_type_diagnostic_item(cx, option_ty, sym::option_type) { - return; - } - - let deref_aliases: [&[&str]; 9] = [ - &paths::DEREF_TRAIT_METHOD, - &paths::DEREF_MUT_TRAIT_METHOD, - &paths::CSTRING_AS_C_STR, - &paths::OS_STRING_AS_OS_STR, - &paths::PATH_BUF_AS_PATH, - &paths::STRING_AS_STR, - &paths::STRING_AS_MUT_STR, - &paths::VEC_AS_SLICE, - &paths::VEC_AS_MUT_SLICE, - ]; - - let is_deref = match map_args[1].kind { - hir::ExprKind::Path(ref expr_qpath) => cx - .qpath_res(expr_qpath, map_args[1].hir_id) - .opt_def_id() - .map_or(false, |fun_def_id| { - deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path)) - }), - hir::ExprKind::Closure(_, _, body_id, _, _) => { - let closure_body = cx.tcx.hir().body(body_id); - let closure_expr = remove_blocks(&closure_body.value); - - match &closure_expr.kind { - hir::ExprKind::MethodCall(_, _, args, _) => { - if_chain! { - if args.len() == 1; - if path_to_local_id(&args[0], closure_body.params[0].pat.hir_id); - let adj = cx - .typeck_results() - .expr_adjustments(&args[0]) - .iter() - .map(|x| &x.kind) - .collect::<Box<[_]>>(); - if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj; - then { - let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); - deref_aliases.iter().any(|path| match_def_path(cx, method_did, path)) - } else { - false - } - } - }, - hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => { - if_chain! { - if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner1) = inner.kind; - if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner2) = inner1.kind; - then { - path_to_local_id(inner2, closure_body.params[0].pat.hir_id) - } else { - false - } - } - }, - _ => false, - } - }, - _ => false, - }; - - if is_deref { - let current_method = if is_mut { - format!(".as_mut().map({})", snippet(cx, map_args[1].span, "..")) - } else { - format!(".as_ref().map({})", snippet(cx, map_args[1].span, "..")) - }; - let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" }; - let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint); - let suggestion = format!("try using {} instead", method_hint); - - let msg = format!( - "called `{0}` on an Option value. This can be done more directly \ - by calling `{1}` instead", - current_method, hint - ); - span_lint_and_sugg( - cx, - OPTION_AS_REF_DEREF, - expr.span, - &msg, - &suggestion, - hint, - Applicability::MachineApplicable, - ); - } -} - -fn lint_map_collect( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - map_args: &[hir::Expr<'_>], - collect_args: &[hir::Expr<'_>], -) { - if_chain! { - // called on Iterator - if let [map_expr] = collect_args; - if match_trait_method(cx, map_expr, &paths::ITERATOR); - // return of collect `Result<(),_>` - let collect_ret_ty = cx.typeck_results().expr_ty(expr); - if is_type_diagnostic_item(cx, collect_ret_ty, sym::result_type); - if let ty::Adt(_, substs) = collect_ret_ty.kind(); - if let Some(result_t) = substs.types().next(); - if result_t.is_unit(); - // get parts for snippet - if let [iter, map_fn] = map_args; - then { - span_lint_and_sugg( - cx, - MAP_COLLECT_RESULT_UNIT, - expr.span, - "`.map().collect()` can be replaced with `.try_for_each()`", - "try this", - format!( - "{}.try_for_each({})", - snippet(cx, iter.span, ".."), - snippet(cx, map_fn.span, "..") - ), - Applicability::MachineApplicable, - ); - } - } -} - -/// Given a `Result<T, E>` type, return its error type (`E`). -fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> { - match ty.kind() { - ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::result_type) => substs.types().nth(1), - _ => None, - } -} - -/// This checks whether a given type is known to implement Debug. -fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { - cx.tcx - .get_diagnostic_item(sym::debug_trait) - .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) -} - -enum Convention { - Eq(&'static str), - StartsWith(&'static str), -} - -#[rustfmt::skip] -const CONVENTIONS: [(Convention, &[SelfKind]); 7] = [ - (Convention::Eq("new"), &[SelfKind::No]), - (Convention::StartsWith("as_"), &[SelfKind::Ref, SelfKind::RefMut]), - (Convention::StartsWith("from_"), &[SelfKind::No]), - (Convention::StartsWith("into_"), &[SelfKind::Value]), - (Convention::StartsWith("is_"), &[SelfKind::Ref, SelfKind::No]), - (Convention::Eq("to_mut"), &[SelfKind::RefMut]), - (Convention::StartsWith("to_"), &[SelfKind::Ref]), -]; - const FN_HEADER: hir::FnHeader = hir::FnHeader { unsafety: hir::Unsafety::Normal, constness: hir::Constness::NotConst, @@ -4039,25 +2344,6 @@ impl SelfKind { } } -impl Convention { - #[must_use] - fn check(&self, other: &str) -> bool { - match *self { - Self::Eq(this) => this == other, - Self::StartsWith(this) => other.starts_with(this) && this != other, - } - } -} - -impl fmt::Display for Convention { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match *self { - Self::Eq(this) => this.fmt(f), - Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)), - } - } -} - #[derive(Clone, Copy)] enum OutType { Unit, @@ -4088,109 +2374,6 @@ fn is_bool(ty: &hir::Ty<'_>) -> bool { } } -fn check_pointer_offset(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - if_chain! { - if args.len() == 2; - if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.typeck_results().expr_ty(&args[0]).kind(); - if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); - if layout.is_zst(); - then { - span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); - } - } -} - -fn lint_filetype_is_file(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let ty = cx.typeck_results().expr_ty(&args[0]); - - if !match_type(cx, ty, &paths::FILE_TYPE) { - return; - } - - let span: Span; - let verb: &str; - let lint_unary: &str; - let help_unary: &str; - if_chain! { - if let Some(parent) = get_parent_expr(cx, expr); - if let hir::ExprKind::Unary(op, _) = parent.kind; - if op == hir::UnOp::Not; - then { - lint_unary = "!"; - verb = "denies"; - help_unary = ""; - span = parent.span; - } else { - lint_unary = ""; - verb = "covers"; - help_unary = "!"; - span = expr.span; - } - } - let lint_msg = format!("`{}FileType::is_file()` only {} regular files", lint_unary, verb); - let help_msg = format!("use `{}FileType::is_dir()` instead", help_unary); - span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg); -} - -fn lint_from_iter(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { - let ty = cx.typeck_results().expr_ty(expr); - let arg_ty = cx.typeck_results().expr_ty(&args[0]); - - if_chain! { - if let Some(from_iter_id) = get_trait_def_id(cx, &paths::FROM_ITERATOR); - if let Some(iter_id) = get_trait_def_id(cx, &paths::ITERATOR); - - if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]); - then { - // `expr` implements `FromIterator` trait - let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par(); - let turbofish = extract_turbofish(cx, expr, ty); - let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish); - span_lint_and_sugg( - cx, - FROM_ITER_INSTEAD_OF_COLLECT, - expr.span, - "usage of `FromIterator::from_iter`", - "use `.collect()` instead of `::from_iter()`", - sugg, - Applicability::MaybeIncorrect, - ); - } - } -} - -fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String { - if_chain! { - let call_site = expr.span.source_callsite(); - if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site); - let snippet_split = snippet.split("::").collect::<Vec<_>>(); - if let Some((_, elements)) = snippet_split.split_last(); - - then { - // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`) - if let Some(type_specifier) = snippet_split.iter().find(|e| e.starts_with('<') && e.ends_with('>')) { - // remove the type specifier from the path elements - let without_ts = elements.iter().filter_map(|e| { - if e == type_specifier { None } else { Some((*e).to_string()) } - }).collect::<Vec<_>>(); - // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`) - format!("{}{}", without_ts.join("::"), type_specifier) - } else { - // type is not explicitly specified so wildcards are needed - // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>` - let ty_str = ty.to_string(); - let start = ty_str.find('<').unwrap_or(0); - let end = ty_str.find('>').unwrap_or_else(|| ty_str.len()); - let nb_wildcard = ty_str[start..end].split(',').count(); - let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1)); - format!("{}<{}>", elements.join("::"), wildcards) - } - } else { - ty.to_string() - } - } -} - fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { expected.constness == actual.constness && expected.unsafety == actual.unsafety diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs new file mode 100644 index 00000000000..c1706cc7cc7 --- /dev/null +++ b/clippy_lints/src/methods/ok_expect.rs @@ -0,0 +1,45 @@ +use crate::utils::{implements_trait, is_type_diagnostic_item, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::sym; + +use super::OK_EXPECT; + +/// lint use of `ok().expect()` for `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ok_args: &[hir::Expr<'_>]) { + if_chain! { + // lint if the caller of `ok()` is a `Result` + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&ok_args[0]), sym::result_type); + let result_type = cx.typeck_results().expr_ty(&ok_args[0]); + if let Some(error_type) = get_error_type(cx, result_type); + if has_debug_impl(error_type, cx); + + then { + span_lint_and_help( + cx, + OK_EXPECT, + expr.span, + "called `ok().expect()` on a `Result` value", + None, + "you can call `expect()` directly on the `Result`", + ); + } + } +} + +/// Given a `Result<T, E>` type, return its error type (`E`). +fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option<Ty<'a>> { + match ty.kind() { + ty::Adt(_, substs) if is_type_diagnostic_item(cx, ty, sym::result_type) => substs.types().nth(1), + _ => None, + } +} + +/// This checks whether a given type is known to implement Debug. +fn has_debug_impl<'tcx>(ty: Ty<'tcx>, cx: &LateContext<'tcx>) -> bool { + cx.tcx + .get_diagnostic_item(sym::debug_trait) + .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) +} diff --git a/clippy_lints/src/methods/option_as_ref_deref.rs b/clippy_lints/src/methods/option_as_ref_deref.rs new file mode 100644 index 00000000000..89067dbfe0e --- /dev/null +++ b/clippy_lints/src/methods/option_as_ref_deref.rs @@ -0,0 +1,122 @@ +use crate::utils::{ + is_type_diagnostic_item, match_def_path, meets_msrv, path_to_local_id, paths, remove_blocks, snippet, + span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_semver::RustcVersion; +use rustc_span::sym; + +use super::OPTION_AS_REF_DEREF; + +const OPTION_AS_REF_DEREF_MSRV: RustcVersion = RustcVersion::new(1, 40, 0); + +/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + as_ref_args: &[hir::Expr<'_>], + map_args: &[hir::Expr<'_>], + is_mut: bool, + msrv: Option<&RustcVersion>, +) { + if !meets_msrv(msrv, &OPTION_AS_REF_DEREF_MSRV) { + return; + } + + let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not); + + let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]); + if !is_type_diagnostic_item(cx, option_ty, sym::option_type) { + return; + } + + let deref_aliases: [&[&str]; 9] = [ + &paths::DEREF_TRAIT_METHOD, + &paths::DEREF_MUT_TRAIT_METHOD, + &paths::CSTRING_AS_C_STR, + &paths::OS_STRING_AS_OS_STR, + &paths::PATH_BUF_AS_PATH, + &paths::STRING_AS_STR, + &paths::STRING_AS_MUT_STR, + &paths::VEC_AS_SLICE, + &paths::VEC_AS_MUT_SLICE, + ]; + + let is_deref = match map_args[1].kind { + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_args[1].hir_id) + .opt_def_id() + .map_or(false, |fun_def_id| { + deref_aliases.iter().any(|path| match_def_path(cx, fun_def_id, path)) + }), + hir::ExprKind::Closure(_, _, body_id, _, _) => { + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + match &closure_expr.kind { + hir::ExprKind::MethodCall(_, _, args, _) => { + if_chain! { + if args.len() == 1; + if path_to_local_id(&args[0], closure_body.params[0].pat.hir_id); + let adj = cx + .typeck_results() + .expr_adjustments(&args[0]) + .iter() + .map(|x| &x.kind) + .collect::<Box<[_]>>(); + if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj; + then { + let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); + deref_aliases.iter().any(|path| match_def_path(cx, method_did, path)) + } else { + false + } + } + }, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, m, ref inner) if same_mutability(m) => { + if_chain! { + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner1) = inner.kind; + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner2) = inner1.kind; + then { + path_to_local_id(inner2, closure_body.params[0].pat.hir_id) + } else { + false + } + } + }, + _ => false, + } + }, + _ => false, + }; + + if is_deref { + let current_method = if is_mut { + format!(".as_mut().map({})", snippet(cx, map_args[1].span, "..")) + } else { + format!(".as_ref().map({})", snippet(cx, map_args[1].span, "..")) + }; + let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" }; + let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint); + let suggestion = format!("try using {} instead", method_hint); + + let msg = format!( + "called `{0}` on an Option value. This can be done more directly \ + by calling `{1}` instead", + current_method, hint + ); + span_lint_and_sugg( + cx, + OPTION_AS_REF_DEREF, + expr.span, + &msg, + &suggestion, + hint, + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs new file mode 100644 index 00000000000..64f6ebc5062 --- /dev/null +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -0,0 +1,78 @@ +use crate::utils::{is_type_diagnostic_item, match_qpath, paths, snippet, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use super::OPTION_MAP_OR_NONE; +use super::RESULT_MAP_OR_INTO_OPTION; + +/// lint use of `_.map_or(None, _)` for `Option`s and `Result`s +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, map_or_args: &'tcx [hir::Expr<'_>]) { + let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::option_type); + let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&map_or_args[0]), sym::result_type); + + // There are two variants of this `map_or` lint: + // (1) using `map_or` as an adapter from `Result<T,E>` to `Option<T>` + // (2) using `map_or` as a combinator instead of `and_then` + // + // (For this lint) we don't care if any other type calls `map_or` + if !is_option && !is_result { + return; + } + + let (lint_name, msg, instead, hint) = { + let default_arg_is_none = if let hir::ExprKind::Path(ref qpath) = map_or_args[1].kind { + match_qpath(qpath, &paths::OPTION_NONE) + } else { + return; + }; + + if !default_arg_is_none { + // nothing to lint! + return; + } + + let f_arg_is_some = if let hir::ExprKind::Path(ref qpath) = map_or_args[2].kind { + match_qpath(qpath, &paths::OPTION_SOME) + } else { + false + }; + + if is_option { + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + let func_snippet = snippet(cx, map_or_args[2].span, ".."); + let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \ + `and_then(..)` instead"; + ( + OPTION_MAP_OR_NONE, + msg, + "try using `and_then` instead", + format!("{0}.and_then({1})", self_snippet, func_snippet), + ) + } else if f_arg_is_some { + let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \ + `ok()` instead"; + let self_snippet = snippet(cx, map_or_args[0].span, ".."); + ( + RESULT_MAP_OR_INTO_OPTION, + msg, + "try using `ok` instead", + format!("{0}.ok()", self_snippet), + ) + } else { + // nothing to lint! + return; + } + }; + + span_lint_and_sugg( + cx, + lint_name, + expr.span, + msg, + instead, + hint, + Applicability::MachineApplicable, + ); +} diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index 7763fd5f113..7cdd49bbf03 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -12,7 +12,7 @@ use rustc_span::{sym, Symbol}; use super::MAP_UNWRAP_OR; /// lint use of `map().unwrap_or()` for `Option`s -pub(super) fn lint<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &rustc_hir::Expr<'_>, map_args: &'tcx [rustc_hir::Expr<'_>], diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs new file mode 100644 index 00000000000..5f7fc431d22 --- /dev/null +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -0,0 +1,173 @@ +use crate::utils::eager_or_lazy::is_lazyness_candidate; +use crate::utils::{ + contains_return, get_trait_def_id, implements_trait, is_type_diagnostic_item, last_path_segment, match_type, paths, + snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint_and_sugg, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; +use std::borrow::Cow; + +use super::OR_FUN_CALL; + +/// Checks for the `OR_FUN_CALL` lint. +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + method_span: Span, + name: &str, + args: &'tcx [hir::Expr<'_>], +) { + /// Checks for `unwrap_or(T::new())` or `unwrap_or(T::default())`. + fn check_unwrap_or_default( + cx: &LateContext<'_>, + name: &str, + fun: &hir::Expr<'_>, + self_expr: &hir::Expr<'_>, + arg: &hir::Expr<'_>, + or_has_args: bool, + span: Span, + ) -> bool { + if_chain! { + if !or_has_args; + if name == "unwrap_or"; + if let hir::ExprKind::Path(ref qpath) = fun.kind; + let path = &*last_path_segment(qpath).ident.as_str(); + if ["default", "new"].contains(&path); + let arg_ty = cx.typeck_results().expr_ty(arg); + if let Some(default_trait_id) = get_trait_def_id(cx, &paths::DEFAULT_TRAIT); + if implements_trait(cx, arg_ty, default_trait_id, &[]); + + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span, + &format!("use of `{}` followed by a call to `{}`", name, path), + "try this", + format!( + "{}.unwrap_or_default()", + snippet_with_applicability(cx, self_expr.span, "..", &mut applicability) + ), + applicability, + ); + + true + } else { + false + } + } + } + + /// Checks for `*or(foo())`. + #[allow(clippy::too_many_arguments)] + fn check_general_case<'tcx>( + cx: &LateContext<'tcx>, + name: &str, + method_span: Span, + self_expr: &hir::Expr<'_>, + arg: &'tcx hir::Expr<'_>, + span: Span, + // None if lambda is required + fun_span: Option<Span>, + ) { + // (path, fn_has_argument, methods, suffix) + static KNOW_TYPES: [(&[&str], bool, &[&str], &str); 4] = [ + (&paths::BTREEMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::HASHMAP_ENTRY, false, &["or_insert"], "with"), + (&paths::OPTION, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), + (&paths::RESULT, true, &["or", "unwrap_or"], "else"), + ]; + + if let hir::ExprKind::MethodCall(ref path, _, ref args, _) = &arg.kind { + if path.ident.as_str() == "len" { + let ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); + + match ty.kind() { + ty::Slice(_) | ty::Array(_, _) => return, + _ => (), + } + + if is_type_diagnostic_item(cx, ty, sym::vec_type) { + return; + } + } + } + + if_chain! { + if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); + + if is_lazyness_candidate(cx, arg); + if !contains_return(&arg); + + let self_ty = cx.typeck_results().expr_ty(self_expr); + + if let Some(&(_, fn_has_arguments, poss, suffix)) = + KNOW_TYPES.iter().find(|&&i| match_type(cx, self_ty, i.0)); + + if poss.contains(&name); + + then { + let macro_expanded_snipped; + let sugg: Cow<'_, str> = { + let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { + (false, Some(fun_span)) => (fun_span, false), + _ => (arg.span, true), + }; + let snippet = { + let not_macro_argument_snippet = snippet_with_macro_callsite(cx, snippet_span, ".."); + if not_macro_argument_snippet == "vec![]" { + macro_expanded_snipped = snippet(cx, snippet_span, ".."); + match macro_expanded_snipped.strip_prefix("$crate::vec::") { + Some(stripped) => Cow::from(stripped), + None => macro_expanded_snipped + } + } + else { + not_macro_argument_snippet + } + }; + + if use_lambda { + let l_arg = if fn_has_arguments { "_" } else { "" }; + format!("|{}| {}", l_arg, snippet).into() + } else { + snippet + } + }; + let span_replace_word = method_span.with_hi(span.hi()); + span_lint_and_sugg( + cx, + OR_FUN_CALL, + span_replace_word, + &format!("use of `{}` followed by a function call", name), + "try this", + format!("{}_{}({})", name, suffix, sugg), + Applicability::HasPlaceholders, + ); + } + } + } + + if args.len() == 2 { + match args[1].kind { + hir::ExprKind::Call(ref fun, ref or_args) => { + let or_has_args = !or_args.is_empty(); + if !check_unwrap_or_default(cx, name, fun, &args[0], &args[1], or_has_args, expr.span) { + let fun_span = if or_has_args { None } else { Some(fun.span) }; + check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, fun_span); + } + }, + hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { + check_general_case(cx, name, method_span, &args[0], &args[1], expr.span, None); + }, + _ => {}, + } + } +} diff --git a/clippy_lints/src/methods/search_is_some.rs b/clippy_lints/src/methods/search_is_some.rs new file mode 100644 index 00000000000..e9e65443220 --- /dev/null +++ b/clippy_lints/src/methods/search_is_some.rs @@ -0,0 +1,101 @@ +use crate::utils::{ + is_type_diagnostic_item, match_trait_method, paths, snippet, snippet_with_applicability, span_lint_and_help, + span_lint_and_sugg, strip_pat_refs, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::PatKind; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::source_map::Span; +use rustc_span::symbol::sym; + +use super::SEARCH_IS_SOME; + +/// lint searching an Iterator followed by `is_some()` +/// or calling `find()` on a string followed by `is_some()` +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + search_method: &str, + search_args: &'tcx [hir::Expr<'_>], + is_some_args: &'tcx [hir::Expr<'_>], + method_span: Span, +) { + // lint if caller of search is an Iterator + if match_trait_method(cx, &is_some_args[0], &paths::ITERATOR) { + let msg = format!( + "called `is_some()` after searching an `Iterator` with `{}`", + search_method + ); + let hint = "this is more succinctly expressed by calling `any()`"; + let search_snippet = snippet(cx, search_args[1].span, ".."); + if search_snippet.lines().count() <= 1 { + // suggest `any(|x| ..)` instead of `any(|&x| ..)` for `find(|&x| ..).is_some()` + // suggest `any(|..| *..)` instead of `any(|..| **..)` for `find(|..| **..).is_some()` + let any_search_snippet = if_chain! { + if search_method == "find"; + if let hir::ExprKind::Closure(_, _, body_id, ..) = search_args[1].kind; + let closure_body = cx.tcx.hir().body(body_id); + if let Some(closure_arg) = closure_body.params.get(0); + then { + if let hir::PatKind::Ref(..) = closure_arg.pat.kind { + Some(search_snippet.replacen('&', "", 1)) + } else if let PatKind::Binding(_, _, ident, _) = strip_pat_refs(&closure_arg.pat).kind { + let name = &*ident.name.as_str(); + Some(search_snippet.replace(&format!("*{}", name), name)) + } else { + None + } + } else { + None + } + }; + // add note if not multi-line + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + &msg, + "use `any()` instead", + format!( + "any({})", + any_search_snippet.as_ref().map_or(&*search_snippet, String::as_str) + ), + Applicability::MachineApplicable, + ); + } else { + span_lint_and_help(cx, SEARCH_IS_SOME, expr.span, &msg, None, hint); + } + } + // lint if `find()` is called by `String` or `&str` + else if search_method == "find" { + let is_string_or_str_slice = |e| { + let self_ty = cx.typeck_results().expr_ty(e).peel_refs(); + if is_type_diagnostic_item(cx, self_ty, sym::string_type) { + true + } else { + *self_ty.kind() == ty::Str + } + }; + if_chain! { + if is_string_or_str_slice(&search_args[0]); + if is_string_or_str_slice(&search_args[1]); + then { + let msg = "called `is_some()` after calling `find()` on a string"; + let mut applicability = Applicability::MachineApplicable; + let find_arg = snippet_with_applicability(cx, search_args[1].span, "..", &mut applicability); + span_lint_and_sugg( + cx, + SEARCH_IS_SOME, + method_span.with_hi(expr.span.hi()), + msg, + "use `contains()` instead", + format!("contains({})", find_arg), + applicability, + ); + } + } + } +} diff --git a/clippy_lints/src/methods/single_char_insert_string.rs b/clippy_lints/src/methods/single_char_insert_string.rs new file mode 100644 index 00000000000..0ce8b66978d --- /dev/null +++ b/clippy_lints/src/methods/single_char_insert_string.rs @@ -0,0 +1,27 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_ADD_STR; + +/// lint for length-1 `str`s as argument for `insert_str` +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[2], &mut applicability) { + let base_string_snippet = + snippet_with_applicability(cx, args[0].span.source_callsite(), "_", &mut applicability); + let pos_arg = snippet_with_applicability(cx, args[1].span, "..", &mut applicability); + let sugg = format!("{}.insert({}, {})", base_string_snippet, pos_arg, extension_string); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `insert_str()` using a single-character string literal", + "consider using `insert` with a character literal", + sugg, + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/single_char_pattern.rs b/clippy_lints/src/methods/single_char_pattern.rs new file mode 100644 index 00000000000..61cbc9d2f0a --- /dev/null +++ b/clippy_lints/src/methods/single_char_pattern.rs @@ -0,0 +1,23 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::span_lint_and_sugg; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_PATTERN; + +/// lint for length-1 `str`s for methods in `PATTERN_METHODS` +pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) { + let mut applicability = Applicability::MachineApplicable; + if let Some(hint) = get_hint_if_single_char_arg(cx, arg, &mut applicability) { + span_lint_and_sugg( + cx, + SINGLE_CHAR_PATTERN, + arg.span, + "single-character string constant used as pattern", + "try using a `char` instead", + hint, + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/single_char_push_string.rs b/clippy_lints/src/methods/single_char_push_string.rs new file mode 100644 index 00000000000..deacc70b713 --- /dev/null +++ b/clippy_lints/src/methods/single_char_push_string.rs @@ -0,0 +1,26 @@ +use crate::methods::get_hint_if_single_char_arg; +use crate::utils::{snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SINGLE_CHAR_ADD_STR; + +/// lint for length-1 `str`s as argument for `push_str` +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let mut applicability = Applicability::MachineApplicable; + if let Some(extension_string) = get_hint_if_single_char_arg(cx, &args[1], &mut applicability) { + let base_string_snippet = + snippet_with_applicability(cx, args[0].span.source_callsite(), "..", &mut applicability); + let sugg = format!("{}.push({})", base_string_snippet, extension_string); + span_lint_and_sugg( + cx, + SINGLE_CHAR_ADD_STR, + expr.span, + "calling `push_str()` using a single-character string literal", + "consider using `push` with a character literal", + sugg, + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/skip_while_next.rs b/clippy_lints/src/methods/skip_while_next.rs new file mode 100644 index 00000000000..8ba6ae95200 --- /dev/null +++ b/clippy_lints/src/methods/skip_while_next.rs @@ -0,0 +1,20 @@ +use crate::utils::{match_trait_method, paths, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SKIP_WHILE_NEXT; + +/// lint use of `skip_while().next()` for `Iterators` +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, _skip_while_args: &'tcx [hir::Expr<'_>]) { + // lint if caller of `.skip_while().next()` is an Iterator + if match_trait_method(cx, expr, &paths::ITERATOR) { + span_lint_and_help( + cx, + SKIP_WHILE_NEXT, + expr.span, + "called `skip_while(<p>).next()` on an `Iterator`", + None, + "this is more succinctly expressed by calling `.find(!<p>)` instead", + ); + } +} diff --git a/clippy_lints/src/methods/string_extend_chars.rs b/clippy_lints/src/methods/string_extend_chars.rs new file mode 100644 index 00000000000..0a08ea26175 --- /dev/null +++ b/clippy_lints/src/methods/string_extend_chars.rs @@ -0,0 +1,42 @@ +use crate::utils::{is_type_diagnostic_item, method_chain_args, snippet_with_applicability, span_lint_and_sugg}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::symbol::sym; + +use super::STRING_EXTEND_CHARS; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&args[0]).peel_refs(); + if is_type_diagnostic_item(cx, obj_ty, sym::string_type) { + let arg = &args[1]; + if let Some(arglists) = method_chain_args(arg, &["chars"]) { + let target = &arglists[0][0]; + let self_ty = cx.typeck_results().expr_ty(target).peel_refs(); + let ref_str = if *self_ty.kind() == ty::Str { + "" + } else if is_type_diagnostic_item(cx, self_ty, sym::string_type) { + "&" + } else { + return; + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + STRING_EXTEND_CHARS, + expr.span, + "calling `.extend(_.chars())`", + "try this", + format!( + "{}.push_str({}{})", + snippet_with_applicability(cx, args[0].span, "..", &mut applicability), + ref_str, + snippet_with_applicability(cx, target.span, "..", &mut applicability) + ), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints/src/methods/suspicious_map.rs new file mode 100644 index 00000000000..e135a826dc4 --- /dev/null +++ b/clippy_lints/src/methods/suspicious_map.rs @@ -0,0 +1,16 @@ +use crate::utils::span_lint_and_help; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::SUSPICIOUS_MAP; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + span_lint_and_help( + cx, + SUSPICIOUS_MAP, + expr.span, + "this call to `map()` won't have an effect on the call to `count()`", + None, + "make sure you did not confuse `map` with `filter` or `for_each`", + ); +} diff --git a/clippy_lints/src/methods/uninit_assumed_init.rs b/clippy_lints/src/methods/uninit_assumed_init.rs new file mode 100644 index 00000000000..798b66192c8 --- /dev/null +++ b/clippy_lints/src/methods/uninit_assumed_init.rs @@ -0,0 +1,35 @@ +use crate::utils::{match_def_path, match_qpath, paths, span_lint}; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::UNINIT_ASSUMED_INIT; + +/// lint for `MaybeUninit::uninit().assume_init()` (we already have the latter) +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, outer: &hir::Expr<'_>) { + if_chain! { + if let hir::ExprKind::Call(ref callee, ref args) = expr.kind; + if args.is_empty(); + if let hir::ExprKind::Path(ref path) = callee.kind; + if match_qpath(path, &paths::MEM_MAYBEUNINIT_UNINIT); + if !is_maybe_uninit_ty_valid(cx, cx.typeck_results().expr_ty_adjusted(outer)); + then { + span_lint( + cx, + UNINIT_ASSUMED_INIT, + outer.span, + "this call for this type may be undefined behavior" + ); + } + } +} + +fn is_maybe_uninit_ty_valid(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + match ty.kind() { + ty::Array(ref component, _) => is_maybe_uninit_ty_valid(cx, component), + ty::Tuple(ref types) => types.types().all(|ty| is_maybe_uninit_ty_valid(cx, ty)), + ty::Adt(ref adt, _) => match_def_path(cx, adt.did, &paths::MEM_MAYBEUNINIT), + _ => false, + } +} diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 5691fcb88e9..12b2cf0a165 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -9,7 +9,7 @@ use if_chain::if_chain; use super::UNNECESSARY_FILTER_MAP; -pub(super) fn lint(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { if !match_trait_method(cx, expr, &paths::ITERATOR) { return; } diff --git a/clippy_lints/src/methods/unnecessary_fold.rs b/clippy_lints/src/methods/unnecessary_fold.rs new file mode 100644 index 00000000000..a26443f4ee9 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_fold.rs @@ -0,0 +1,101 @@ +use crate::utils::{ + match_trait_method, path_to_local_id, paths, remove_blocks, snippet_with_applicability, span_lint_and_sugg, + strip_pat_refs, +}; +use if_chain::if_chain; +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::PatKind; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use super::UNNECESSARY_FOLD; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, fold_args: &[hir::Expr<'_>], fold_span: Span) { + fn check_fold_with_op( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + fold_args: &[hir::Expr<'_>], + fold_span: Span, + op: hir::BinOpKind, + replacement_method_name: &str, + replacement_has_args: bool, + ) { + if_chain! { + // Extract the body of the closure passed to fold + if let hir::ExprKind::Closure(_, _, body_id, _, _) = fold_args[2].kind; + let closure_body = cx.tcx.hir().body(body_id); + let closure_expr = remove_blocks(&closure_body.value); + + // Check if the closure body is of the form `acc <op> some_expr(x)` + if let hir::ExprKind::Binary(ref bin_op, ref left_expr, ref right_expr) = closure_expr.kind; + if bin_op.node == op; + + // Extract the names of the two arguments to the closure + if let [param_a, param_b] = closure_body.params; + if let PatKind::Binding(_, first_arg_id, ..) = strip_pat_refs(¶m_a.pat).kind; + if let PatKind::Binding(_, second_arg_id, second_arg_ident, _) = strip_pat_refs(¶m_b.pat).kind; + + if path_to_local_id(left_expr, first_arg_id); + if replacement_has_args || path_to_local_id(right_expr, second_arg_id); + + then { + let mut applicability = Applicability::MachineApplicable; + let sugg = if replacement_has_args { + format!( + "{replacement}(|{s}| {r})", + replacement = replacement_method_name, + s = second_arg_ident, + r = snippet_with_applicability(cx, right_expr.span, "EXPR", &mut applicability), + ) + } else { + format!( + "{replacement}()", + replacement = replacement_method_name, + ) + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_FOLD, + fold_span.with_hi(expr.span.hi()), + // TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f) + "this `.fold` can be written more succinctly using another method", + "try", + sugg, + applicability, + ); + } + } + } + + // Check that this is a call to Iterator::fold rather than just some function called fold + if !match_trait_method(cx, expr, &paths::ITERATOR) { + return; + } + + assert!( + fold_args.len() == 3, + "Expected fold_args to have three entries - the receiver, the initial value and the closure" + ); + + // Check if the first argument to .fold is a suitable literal + if let hir::ExprKind::Lit(ref lit) = fold_args[1].kind { + match lit.node { + ast::LitKind::Bool(false) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Or, "any", true) + }, + ast::LitKind::Bool(true) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::And, "all", true) + }, + ast::LitKind::Int(0, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Add, "sum", false) + }, + ast::LitKind::Int(1, _) => { + check_fold_with_op(cx, expr, fold_args, fold_span, hir::BinOpKind::Mul, "product", false) + }, + _ => (), + } + } +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index a867bdb326d..a17259d697f 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -9,7 +9,7 @@ use super::UNNECESSARY_LAZY_EVALUATIONS; /// lint use of `<fn>_else(simple closure)` for `Option`s and `Result`s that can be /// replaced with `<fn>(return value of simple closure)` -pub(super) fn lint<'tcx>( +pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, args: &'tcx [hir::Expr<'_>], @@ -50,7 +50,7 @@ pub(super) fn lint<'tcx>( UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, - &format!("Use `{}` instead", simplify_using), + &format!("use `{}` instead", simplify_using), format!( "{0}.{1}({2})", snippet(cx, args[0].span, ".."), diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs new file mode 100644 index 00000000000..094c3fc45c4 --- /dev/null +++ b/clippy_lints/src/methods/unwrap_used.rs @@ -0,0 +1,34 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_help}; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::UNWRAP_USED; + +/// lint use of `unwrap()` for `Option`s and `Result`s +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, unwrap_args: &[hir::Expr<'_>]) { + let obj_ty = cx.typeck_results().expr_ty(&unwrap_args[0]).peel_refs(); + + let mess = if is_type_diagnostic_item(cx, obj_ty, sym::option_type) { + Some((UNWRAP_USED, "an Option", "None")) + } else if is_type_diagnostic_item(cx, obj_ty, sym::result_type) { + Some((UNWRAP_USED, "a Result", "Err")) + } else { + None + }; + + if let Some((lint, kind, none_value)) = mess { + span_lint_and_help( + cx, + lint, + expr.span, + &format!("used `unwrap()` on `{}` value", kind,), + None, + &format!( + "if you don't want to handle the `{}` case gracefully, consider \ + using `expect()` to provide a better panic message", + none_value, + ), + ); + } +} diff --git a/clippy_lints/src/methods/useless_asref.rs b/clippy_lints/src/methods/useless_asref.rs new file mode 100644 index 00000000000..e4554f8d489 --- /dev/null +++ b/clippy_lints/src/methods/useless_asref.rs @@ -0,0 +1,45 @@ +use crate::utils::{ + get_parent_expr, match_trait_method, paths, snippet_with_applicability, span_lint_and_sugg, walk_ptrs_ty_depth, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +use super::USELESS_ASREF; + +/// Checks for the `USELESS_ASREF` lint. +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, as_ref_args: &[hir::Expr<'_>]) { + // when we get here, we've already checked that the call name is "as_ref" or "as_mut" + // check if the call is to the actual `AsRef` or `AsMut` trait + if match_trait_method(cx, expr, &paths::ASREF_TRAIT) || match_trait_method(cx, expr, &paths::ASMUT_TRAIT) { + // check if the type after `as_ref` or `as_mut` is the same as before + let recvr = &as_ref_args[0]; + let rcv_ty = cx.typeck_results().expr_ty(recvr); + let res_ty = cx.typeck_results().expr_ty(expr); + let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); + let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); + if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { + // allow the `as_ref` or `as_mut` if it is followed by another method call + if_chain! { + if let Some(parent) = get_parent_expr(cx, expr); + if let hir::ExprKind::MethodCall(_, ref span, _, _) = parent.kind; + if span != &expr.span; + then { + return; + } + } + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + USELESS_ASREF, + expr.span, + &format!("this call to `{}` does nothing", call_name), + "try this", + snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs new file mode 100644 index 00000000000..90fab577436 --- /dev/null +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -0,0 +1,76 @@ +use crate::methods::SelfKind; +use crate::utils::span_lint; +use rustc_lint::LateContext; +use rustc_middle::ty::TyS; +use rustc_span::source_map::Span; +use std::fmt; + +use super::WRONG_PUB_SELF_CONVENTION; +use super::WRONG_SELF_CONVENTION; + +#[rustfmt::skip] +const CONVENTIONS: [(Convention, &[SelfKind]); 7] = [ + (Convention::Eq("new"), &[SelfKind::No]), + (Convention::StartsWith("as_"), &[SelfKind::Ref, SelfKind::RefMut]), + (Convention::StartsWith("from_"), &[SelfKind::No]), + (Convention::StartsWith("into_"), &[SelfKind::Value]), + (Convention::StartsWith("is_"), &[SelfKind::Ref, SelfKind::No]), + (Convention::Eq("to_mut"), &[SelfKind::RefMut]), + (Convention::StartsWith("to_"), &[SelfKind::Ref]), +]; +enum Convention { + Eq(&'static str), + StartsWith(&'static str), +} + +impl Convention { + #[must_use] + fn check(&self, other: &str) -> bool { + match *self { + Self::Eq(this) => this == other, + Self::StartsWith(this) => other.starts_with(this) && this != other, + } + } +} + +impl fmt::Display for Convention { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match *self { + Self::Eq(this) => this.fmt(f), + Self::StartsWith(this) => this.fmt(f).and_then(|_| '*'.fmt(f)), + } + } +} + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + item_name: &str, + is_pub: bool, + self_ty: &'tcx TyS<'tcx>, + first_arg_ty: &'tcx TyS<'tcx>, + first_arg_span: Span, +) { + let lint = if is_pub { + WRONG_PUB_SELF_CONVENTION + } else { + WRONG_SELF_CONVENTION + }; + if let Some((ref conv, self_kinds)) = &CONVENTIONS.iter().find(|(ref conv, _)| conv.check(item_name)) { + if !self_kinds.iter().any(|k| k.matches(cx, self_ty, first_arg_ty)) { + span_lint( + cx, + lint, + first_arg_span, + &format!( + "methods called `{}` usually take {}; consider choosing a less ambiguous name", + conv, + &self_kinds + .iter() + .map(|k| k.description()) + .collect::<Vec<_>>() + .join(" or ") + ), + ); + } + } +} diff --git a/clippy_lints/src/methods/zst_offset.rs b/clippy_lints/src/methods/zst_offset.rs new file mode 100644 index 00000000000..f1335726736 --- /dev/null +++ b/clippy_lints/src/methods/zst_offset.rs @@ -0,0 +1,19 @@ +use crate::utils::span_lint; +use if_chain::if_chain; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty; + +use super::ZST_OFFSET; + +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { + if_chain! { + if args.len() == 2; + if let ty::RawPtr(ty::TypeAndMut { ref ty, .. }) = cx.typeck_results().expr_ty(&args[0]).kind(); + if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)); + if layout.is_zst(); + then { + span_lint(cx, ZST_OFFSET, expr.span, "offset calculation on zero-sized value"); + } + } +} diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 35b4c3d5b03..acdc245456b 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -12,12 +12,13 @@ use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::{ExpnKind, Span}; +use rustc_span::symbol::sym; use crate::consts::{constant, Constant}; use crate::utils::sugg::Sugg; use crate::utils::{ - get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_integer_const, iter_input_pats, - last_path_segment, match_qpath, match_trait_method, paths, snippet, snippet_opt, span_lint, span_lint_and_sugg, + get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_diagnostic_assoc_item, is_integer_const, + iter_input_pats, last_path_segment, match_qpath, snippet, snippet_opt, span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then, unsext, SpanlessEq, }; @@ -292,7 +293,7 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints { TOPLEVEL_REF_ARG, arg.pat.span, "`ref` directly on a function argument is ignored. \ - Consider using a reference type instead.", + Consider using a reference type instead", ); } } @@ -422,7 +423,7 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints { expr.span, &format!( "used binding `{}` which is prefixed with an underscore. A leading \ - underscore signals that a binding will not be used.", + underscore signals that a binding will not be used", binding ), ); @@ -554,11 +555,16 @@ fn check_to_owned(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: let (arg_ty, snip) = match expr.kind { ExprKind::MethodCall(.., ref args, _) if args.len() == 1 => { - if match_trait_method(cx, expr, &paths::TO_STRING) || match_trait_method(cx, expr, &paths::TO_OWNED) { - (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, "..")) - } else { - return; - } + 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); + then { + (cx.typeck_results().expr_ty(&args[0]), snippet(cx, args[0].span, "..")) + } else { + return; + } + ) }, ExprKind::Call(ref path, ref v) if v.len() == 1 => { if let ExprKind::Path(ref path) = path.kind { diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 9b604471573..da59c820999 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -69,21 +69,21 @@ fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[ast::Attribute], sp } } -fn is_executable(cx: &LateContext<'_>) -> bool { +fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool { use rustc_session::config::CrateType; cx.tcx .sess .crate_types() .iter() - .any(|t: &CrateType| matches!(t, CrateType::Executable)) + .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) } declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); impl<'tcx> LateLintPass<'tcx> for MissingInline { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { - if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable(cx) { + if rustc_middle::lint::in_external_macro(cx.sess(), it.span) || is_executable_or_proc_macro(cx) { return; } @@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { use rustc_middle::ty::{ImplContainer, TraitContainer}; - if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable(cx) { + if rustc_middle::lint::in_external_macro(cx.sess(), impl_item.span) || is_executable_or_proc_macro(cx) { return; } diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 71f91eb4bfb..9caacb5db7c 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -73,11 +73,7 @@ impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> { } fn expr_span(&self) -> Option<Span> { - if self.found { - self.expr_span - } else { - None - } + if self.found { self.expr_span } else { None } } } diff --git a/clippy_lints/src/needless_borrowed_ref.rs b/clippy_lints/src/needless_borrowed_ref.rs index 85184fdea47..f449f397e7d 100644 --- a/clippy_lints/src/needless_borrowed_ref.rs +++ b/clippy_lints/src/needless_borrowed_ref.rs @@ -1,7 +1,3 @@ -//! Checks for useless borrowed references. -//! -//! This lint is **warn** by default - use crate::utils::{snippet_with_applicability, span_lint_and_then}; use if_chain::if_chain; use rustc_errors::Applicability; @@ -10,44 +6,37 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { - /// **What it does:** Checks for useless borrowed references. - /// - /// **Why is this bad?** It is mostly useless and make the code look more - /// complex than it - /// actually is. + /// **What it does:** Checks for bindings that destructure a reference and borrow the inner + /// value with `&ref`. /// - /// **Known problems:** It seems that the `&ref` pattern is sometimes useful. - /// For instance in the following snippet: - /// ```rust,ignore - /// enum Animal { - /// Cat(u64), - /// Dog(u64), - /// } + /// **Why is this bad?** This pattern has no effect in almost all cases. /// - /// fn foo(a: &Animal, b: &Animal) { + /// **Known problems:** In some cases, `&ref` is needed to avoid a lifetime mismatch error. + /// Example: + /// ```rust + /// fn foo(a: &Option<String>, b: &Option<String>) { /// match (a, b) { - /// (&Animal::Cat(v), k) | (k, &Animal::Cat(v)) => (), // lifetime mismatch error - /// (&Animal::Dog(ref c), &Animal::Dog(_)) => () - /// } + /// (None, &ref c) | (&ref c, None) => (), + /// (&Some(ref c), _) => (), + /// }; /// } /// ``` - /// There is a lifetime mismatch error for `k` (indeed a and b have distinct - /// lifetime). - /// This can be fixed by using the `&ref` pattern. - /// However, the code can also be fixed by much cleaner ways /// /// **Example:** + /// Bad: /// ```rust /// let mut v = Vec::<String>::new(); /// let _ = v.iter_mut().filter(|&ref a| a.is_empty()); /// ``` - /// This closure takes a reference on something that has been matched as a - /// reference and - /// de-referenced. - /// As such, it could just be |a| a.is_empty() + /// + /// Good: + /// ```rust + /// let mut v = Vec::<String>::new(); + /// let _ = v.iter_mut().filter(|a| a.is_empty()); + /// ``` pub NEEDLESS_BORROWED_REFERENCE, complexity, - "taking a needless borrowed reference" + "destructuring a reference and borrowing the inner value" } declare_lint_pass!(NeedlessBorrowedRef => [NEEDLESS_BORROWED_REFERENCE]); diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 603071a5f4a..30fe2d6225c 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -416,11 +416,7 @@ fn erode_from_back(s: &str) -> String { break; } } - if ret.is_empty() { - s.to_string() - } else { - ret - } + if ret.is_empty() { s.to_string() } else { ret } } fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> { diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index fe8d4d07abc..a3293f1b361 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -142,7 +142,7 @@ fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) { cx, NEEDLESS_QUESTION_MARK, entire_expr.span, - "Question mark operator is useless here", + "question mark operator is useless here", "try", format!("{}", utils::snippet(cx, inner_expr.span, r#""...""#)), Applicability::MachineApplicable, diff --git a/clippy_lints/src/open_options.rs b/clippy_lints/src/open_options.rs index 73a99a3a2f8..07ca196990d 100644 --- a/clippy_lints/src/open_options.rs +++ b/clippy_lints/src/open_options.rs @@ -69,15 +69,11 @@ fn get_open_options(cx: &LateContext<'_>, argument: &Expr<'_>, options: &mut Vec .. } = *span { - if lit { - Argument::True - } else { - Argument::False - } + if lit { Argument::True } else { Argument::False } } else { - return; // The function is called with a literal - // which is not a boolean literal. This is theoretically - // possible, but not very likely. + // The function is called with a literal which is not a boolean literal. + // This is theoretically possible, but not very likely. + return; } }, _ => Argument::Unknown, diff --git a/clippy_lints/src/path_buf_push_overwrite.rs b/clippy_lints/src/path_buf_push_overwrite.rs index 6eeb031d383..4a7b0ad07aa 100644 --- a/clippy_lints/src/path_buf_push_overwrite.rs +++ b/clippy_lints/src/path_buf_push_overwrite.rs @@ -1,10 +1,11 @@ -use crate::utils::{match_type, paths, span_lint_and_sugg}; +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::sym; use std::path::{Component, Path}; declare_clippy_lint! { @@ -46,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for PathBufPushOverwrite { if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind; if path.ident.name == sym!(push); if args.len() == 2; - if match_type(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), &paths::PATH_BUF); + if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(&args[0]).peel_refs(), sym::PathBuf); if let Some(get_index_arg) = args.get(1); if let ExprKind::Lit(ref lit) = get_index_arg.kind; if let LitKind::Str(ref path_lit, _) = lit.node; diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index de2fb8decb7..6ea2d8b06d8 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -188,7 +188,7 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: PTR_ARG, arg.span, "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \ - with non-Vec-based slices.", + with non-Vec-based slices", |diag| { if let Some(ref snippet) = get_only_generic_arg_snippet(cx, arg) { diag.span_suggestion( @@ -217,7 +217,7 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: cx, PTR_ARG, arg.span, - "writing `&String` instead of `&str` involves a new object where a slice will do.", + "writing `&String` instead of `&str` involves a new object where a slice will do", |diag| { diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified); for (clonespan, suggestion) in spans { @@ -233,13 +233,13 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: }, ); } - } else if match_type(cx, ty, &paths::PATH_BUF) { + } else if is_type_diagnostic_item(cx, ty, sym::PathBuf) { if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_path_buf()"), ("as_path", "")]) { span_lint_and_then( cx, PTR_ARG, arg.span, - "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do.", + "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do", |diag| { diag.span_suggestion( arg.span, @@ -278,7 +278,7 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: cx, PTR_ARG, arg.span, - "using a reference to `Cow` is not recommended.", + "using a reference to `Cow` is not recommended", "change this to", "&".to_owned() + &r, Applicability::Unspecified, diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index 6a4537e6735..f90d4820563 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -1,6 +1,6 @@ use crate::utils::{ - fn_has_unsatisfiable_preds, has_drop, is_copy, is_type_diagnostic_item, match_def_path, match_type, paths, - snippet_opt, span_lint_hir, span_lint_hir_and_then, walk_ptrs_ty_depth, + fn_has_unsatisfiable_preds, has_drop, is_copy, is_type_diagnostic_item, match_def_path, paths, snippet_opt, + span_lint_hir, span_lint_hir_and_then, walk_ptrs_ty_depth, }; use if_chain::if_chain; use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation}; @@ -165,9 +165,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) = is_call_with_ref_arg(cx, mir, &pred_terminator.kind); if res == cloned; - if match_def_path(cx, pred_fn_def_id, &paths::DEREF_TRAIT_METHOD); - if match_type(cx, pred_arg_ty, &paths::PATH_BUF) - || match_type(cx, pred_arg_ty, &paths::OS_STRING); + if cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id); + if is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) + || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString); then { (pred_arg, res) } else { diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index cccd24ccf94..44521885d20 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -261,8 +261,8 @@ fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicabilit cx, SUSPICIOUS_OPERATION_GROUPINGS, span, - "This sequence of operators looks suspiciously like a bug.", - "I think you meant", + "this sequence of operators looks suspiciously like a bug", + "did you mean", sugg, applicability, ) diff --git a/clippy_lints/src/to_string_in_display.rs b/clippy_lints/src/to_string_in_display.rs index fdd105e6246..84ec2aa18ab 100644 --- a/clippy_lints/src/to_string_in_display.rs +++ b/clippy_lints/src/to_string_in_display.rs @@ -1,8 +1,9 @@ -use crate::utils::{match_def_path, match_trait_method, path_to_local_id, paths, span_lint}; +use crate::utils::{is_diagnostic_assoc_item, match_def_path, path_to_local_id, paths, span_lint}; use if_chain::if_chain; use rustc_hir::{Expr, ExprKind, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::symbol::sym; declare_clippy_lint! { /// **What it does:** Checks for uses of `to_string()` in `Display` traits. @@ -92,7 +93,8 @@ impl LateLintPass<'_> for ToStringInDisplay { if let Some(self_hir_id) = self.self_hir_id; if let ExprKind::MethodCall(ref path, _, args, _) = expr.kind; if path.ident.name == sym!(to_string); - if match_trait_method(cx, expr, &paths::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 path_to_local_id(&args[0], self_hir_id); then { span_lint( diff --git a/clippy_lints/src/transmute.rs b/clippy_lints/src/transmute.rs deleted file mode 100644 index dc938ed0238..00000000000 --- a/clippy_lints/src/transmute.rs +++ /dev/null @@ -1,763 +0,0 @@ -use crate::utils::{ - in_constant, is_normalizable, last_path_segment, match_def_path, paths, snippet, span_lint, span_lint_and_sugg, - span_lint_and_then, sugg, -}; -use if_chain::if_chain; -use rustc_ast as ast; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, TyKind, UnOp}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, cast::CastKind, Ty}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::DUMMY_SP; -use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited}; -use std::borrow::Cow; - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes that can't ever be correct on any - /// architecture. - /// - /// **Why is this bad?** It's basically guaranteed to be undefined behaviour. - /// - /// **Known problems:** When accessing C, users might want to store pointer - /// sized objects in `extradata` arguments to save an allocation. - /// - /// **Example:** - /// ```ignore - /// let ptr: *const T = core::intrinsics::transmute('x') - /// ``` - pub WRONG_TRANSMUTE, - correctness, - "transmutes that are confusing at best, undefined behaviour at worst and always useless" -} - -// FIXME: Move this to `complexity` again, after #5343 is fixed -declare_clippy_lint! { - /// **What it does:** Checks for transmutes to the original type of the object - /// and transmutes that could be a cast. - /// - /// **Why is this bad?** Readability. The code tricks people into thinking that - /// something complex is going on. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust,ignore - /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s - /// ``` - pub USELESS_TRANSMUTE, - nursery, - "transmutes that have the same to and from types or could be a cast/coercion" -} - -// FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery. -declare_clippy_lint! { - /// **What it does:**Checks for transmutes that could be a pointer cast. - /// - /// **Why is this bad?** Readability. The code tricks people into thinking that - /// something complex is going on. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// ```rust - /// # let p: *const [i32] = &[]; - /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) }; - /// ``` - /// Use instead: - /// ```rust - /// # let p: *const [i32] = &[]; - /// p as *const [u16]; - /// ``` - pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, - complexity, - "transmutes that could be a pointer cast" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes between a type `T` and `*T`. - /// - /// **Why is this bad?** It's easy to mistakenly transmute between a type and a - /// pointer to that type. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust,ignore - /// core::intrinsics::transmute(t) // where the result type is the same as - /// // `*t` or `&t`'s - /// ``` - pub CROSSPOINTER_TRANSMUTE, - complexity, - "transmutes that have to or from types that are a pointer to the other" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from a pointer to a reference. - /// - /// **Why is this bad?** This can always be rewritten with `&` and `*`. - /// - /// **Known problems:** - /// - `mem::transmute` in statics and constants is stable from Rust 1.46.0, - /// while dereferencing raw pointer is not stable yet. - /// If you need to do this in those places, - /// you would have to use `transmute` instead. - /// - /// **Example:** - /// ```rust,ignore - /// unsafe { - /// let _: &T = std::mem::transmute(p); // where p: *const T - /// } - /// - /// // can be written: - /// let _: &T = &*p; - /// ``` - pub TRANSMUTE_PTR_TO_REF, - complexity, - "transmutes from a pointer to a reference type" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from an integer to a `char`. - /// - /// **Why is this bad?** Not every integer is a Unicode scalar value. - /// - /// **Known problems:** - /// - [`from_u32`] which this lint suggests using is slower than `transmute` - /// as it needs to validate the input. - /// If you are certain that the input is always a valid Unicode scalar value, - /// use [`from_u32_unchecked`] which is as fast as `transmute` - /// but has a semantically meaningful name. - /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`. - /// - /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html - /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html - /// - /// **Example:** - /// ```rust - /// let x = 1_u32; - /// unsafe { - /// let _: char = std::mem::transmute(x); // where x: u32 - /// } - /// - /// // should be: - /// let _ = std::char::from_u32(x).unwrap(); - /// ``` - pub TRANSMUTE_INT_TO_CHAR, - complexity, - "transmutes from an integer to a `char`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`. - /// - /// **Why is this bad?** Not every byte slice is a valid UTF-8 string. - /// - /// **Known problems:** - /// - [`from_utf8`] which this lint suggests using is slower than `transmute` - /// as it needs to validate the input. - /// If you are certain that the input is always a valid UTF-8, - /// use [`from_utf8_unchecked`] which is as fast as `transmute` - /// but has a semantically meaningful name. - /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`. - /// - /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html - /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html - /// - /// **Example:** - /// ```rust - /// let b: &[u8] = &[1_u8, 2_u8]; - /// unsafe { - /// let _: &str = std::mem::transmute(b); // where b: &[u8] - /// } - /// - /// // should be: - /// let _ = std::str::from_utf8(b).unwrap(); - /// ``` - pub TRANSMUTE_BYTES_TO_STR, - complexity, - "transmutes from a `&[u8]` to a `&str`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from an integer to a `bool`. - /// - /// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let x = 1_u8; - /// unsafe { - /// let _: bool = std::mem::transmute(x); // where x: u8 - /// } - /// - /// // should be: - /// let _: bool = x != 0; - /// ``` - pub TRANSMUTE_INT_TO_BOOL, - complexity, - "transmutes from an integer to a `bool`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from an integer to a float. - /// - /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive - /// and safe. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// unsafe { - /// let _: f32 = std::mem::transmute(1_u32); // where x: u32 - /// } - /// - /// // should be: - /// let _: f32 = f32::from_bits(1_u32); - /// ``` - pub TRANSMUTE_INT_TO_FLOAT, - complexity, - "transmutes from an integer to a float" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from a float to an integer. - /// - /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive - /// and safe. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// unsafe { - /// let _: u32 = std::mem::transmute(1f32); - /// } - /// - /// // should be: - /// let _: u32 = 1f32.to_bits(); - /// ``` - pub TRANSMUTE_FLOAT_TO_INT, - complexity, - "transmutes from a float to an integer" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes from a pointer to a pointer, or - /// from a reference to a reference. - /// - /// **Why is this bad?** Transmutes are dangerous, and these can instead be - /// written as casts. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let ptr = &1u32 as *const u32; - /// unsafe { - /// // pointer-to-pointer transmute - /// let _: *const f32 = std::mem::transmute(ptr); - /// // ref-ref transmute - /// let _: &f32 = std::mem::transmute(&1u32); - /// } - /// // These can be respectively written: - /// let _ = ptr as *const f32; - /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) }; - /// ``` - pub TRANSMUTE_PTR_TO_PTR, - complexity, - "transmutes from a pointer to a pointer / a reference to a reference" -} - -declare_clippy_lint! { - /// **What it does:** Checks for transmutes between collections whose - /// types have different ABI, size or alignment. - /// - /// **Why is this bad?** This is undefined behavior. - /// - /// **Known problems:** Currently, we cannot know whether a type is a - /// collection, so we just lint the ones that come with `std`. - /// - /// **Example:** - /// ```rust - /// // different size, therefore likely out-of-bounds memory access - /// // You absolutely do not want this in your code! - /// unsafe { - /// std::mem::transmute::<_, Vec<u32>>(vec![2_u16]) - /// }; - /// ``` - /// - /// You must always iterate, map and collect the values: - /// - /// ```rust - /// vec![2_u16].into_iter().map(u32::from).collect::<Vec<_>>(); - /// ``` - pub UNSOUND_COLLECTION_TRANSMUTE, - correctness, - "transmute between collections of layout-incompatible types" -} - -declare_lint_pass!(Transmute => [ - CROSSPOINTER_TRANSMUTE, - TRANSMUTE_PTR_TO_REF, - TRANSMUTE_PTR_TO_PTR, - USELESS_TRANSMUTE, - WRONG_TRANSMUTE, - TRANSMUTE_INT_TO_CHAR, - TRANSMUTE_BYTES_TO_STR, - TRANSMUTE_INT_TO_BOOL, - TRANSMUTE_INT_TO_FLOAT, - TRANSMUTE_FLOAT_TO_INT, - UNSOUND_COLLECTION_TRANSMUTE, - TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, -]); - -// used to check for UNSOUND_COLLECTION_TRANSMUTE -static COLLECTIONS: &[&[&str]] = &[ - &paths::VEC, - &paths::VEC_DEQUE, - &paths::BINARY_HEAP, - &paths::BTREESET, - &paths::BTREEMAP, - &paths::HASHSET, - &paths::HASHMAP, -]; -impl<'tcx> LateLintPass<'tcx> for Transmute { - #[allow(clippy::similar_names, clippy::too_many_lines)] - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::Call(ref path_expr, ref args) = e.kind; - if let ExprKind::Path(ref qpath) = path_expr.kind; - if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id(); - if match_def_path(cx, def_id, &paths::TRANSMUTE); - then { - // Avoid suggesting from/to bits and dereferencing raw pointers in const contexts. - // See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`. - // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. - let const_context = in_constant(cx, e.hir_id); - - let from_ty = cx.typeck_results().expr_ty(&args[0]); - let to_ty = cx.typeck_results().expr_ty(e); - - match (&from_ty.kind(), &to_ty.kind()) { - _ if from_ty == to_ty => span_lint( - cx, - USELESS_TRANSMUTE, - e.span, - &format!("transmute from a type (`{}`) to itself", from_ty), - ), - (ty::Ref(_, rty, rty_mutbl), ty::RawPtr(ptr_ty)) => span_lint_and_then( - cx, - USELESS_TRANSMUTE, - e.span, - "transmute from a reference to a pointer", - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { - let rty_and_mut = ty::TypeAndMut { - ty: rty, - mutbl: *rty_mutbl, - }; - - let sugg = if *ptr_ty == rty_and_mut { - arg.as_ty(to_ty) - } else { - arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty) - }; - - diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); - } - }, - ), - (ty::Int(_) | ty::Uint(_), ty::RawPtr(_)) => span_lint_and_then( - cx, - USELESS_TRANSMUTE, - e.span, - "transmute from an integer to a pointer", - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { - diag.span_suggestion( - e.span, - "try", - arg.as_ty(&to_ty.to_string()).to_string(), - Applicability::Unspecified, - ); - } - }, - ), - (ty::Float(_) | ty::Char, ty::Ref(..) | ty::RawPtr(_)) => span_lint( - cx, - WRONG_TRANSMUTE, - e.span, - &format!("transmute from a `{}` to a pointer", from_ty), - ), - (ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => span_lint( - cx, - CROSSPOINTER_TRANSMUTE, - e.span, - &format!( - "transmute from a type (`{}`) to the type that it points to (`{}`)", - from_ty, to_ty - ), - ), - (_, ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => span_lint( - cx, - CROSSPOINTER_TRANSMUTE, - e.span, - &format!( - "transmute from a type (`{}`) to a pointer to that type (`{}`)", - from_ty, to_ty - ), - ), - (ty::RawPtr(from_pty), ty::Ref(_, to_ref_ty, mutbl)) => span_lint_and_then( - cx, - TRANSMUTE_PTR_TO_REF, - e.span, - &format!( - "transmute from a pointer type (`{}`) to a reference type \ - (`{}`)", - from_ty, to_ty - ), - |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); - let (deref, cast) = if *mutbl == Mutability::Mut { - ("&mut *", "*mut") - } else { - ("&*", "*const") - }; - - let arg = if from_pty.ty == *to_ref_ty { - arg - } else { - arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty))) - }; - - diag.span_suggestion( - e.span, - "try", - sugg::make_unop(deref, arg).to_string(), - Applicability::Unspecified, - ); - }, - ), - (ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => { - span_lint_and_then( - cx, - TRANSMUTE_INT_TO_CHAR, - e.span, - &format!("transmute from a `{}` to a `char`", from_ty), - |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); - let arg = if let ty::Int(_) = from_ty.kind() { - arg.as_ty(ast::UintTy::U32.name_str()) - } else { - arg - }; - diag.span_suggestion( - e.span, - "consider using", - format!("std::char::from_u32({}).unwrap()", arg.to_string()), - Applicability::Unspecified, - ); - }, - ) - }, - (ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) => { - if_chain! { - if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind(), &ty_to.kind()); - if let ty::Uint(ty::UintTy::U8) = slice_ty.kind(); - if from_mutbl == to_mutbl; - then { - let postfix = if *from_mutbl == Mutability::Mut { - "_mut" - } else { - "" - }; - - span_lint_and_sugg( - cx, - TRANSMUTE_BYTES_TO_STR, - e.span, - &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), - "consider using", - format!( - "std::str::from_utf8{}({}).unwrap()", - postfix, - snippet(cx, args[0].span, ".."), - ), - Applicability::Unspecified, - ); - } else { - if (cx.tcx.erase_regions(from_ty) != cx.tcx.erase_regions(to_ty)) - && !const_context { - span_lint_and_then( - cx, - TRANSMUTE_PTR_TO_PTR, - e.span, - "transmute from a reference to a reference", - |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { - let ty_from_and_mut = ty::TypeAndMut { - ty: ty_from, - mutbl: *from_mutbl - }; - let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: *to_mutbl }; - let sugg_paren = arg - .as_ty(cx.tcx.mk_ptr(ty_from_and_mut)) - .as_ty(cx.tcx.mk_ptr(ty_to_and_mut)); - let sugg = if *to_mutbl == Mutability::Mut { - sugg_paren.mut_addr_deref() - } else { - sugg_paren.addr_deref() - }; - diag.span_suggestion( - e.span, - "try", - sugg.to_string(), - Applicability::Unspecified, - ); - }, - ) - } - } - } - }, - (ty::RawPtr(_), ty::RawPtr(to_ty)) => span_lint_and_then( - cx, - TRANSMUTE_PTR_TO_PTR, - e.span, - "transmute from a pointer to a pointer", - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { - let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty)); - diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); - } - }, - ), - (ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => { - span_lint_and_then( - cx, - TRANSMUTE_INT_TO_BOOL, - e.span, - &format!("transmute from a `{}` to a `bool`", from_ty), - |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); - let zero = sugg::Sugg::NonParen(Cow::from("0")); - diag.span_suggestion( - e.span, - "consider using", - sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string(), - Applicability::Unspecified, - ); - }, - ) - }, - (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => span_lint_and_then( - cx, - TRANSMUTE_INT_TO_FLOAT, - e.span, - &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), - |diag| { - let arg = sugg::Sugg::hir(cx, &args[0], ".."); - let arg = if let ty::Int(int_ty) = from_ty.kind() { - arg.as_ty(format!( - "u{}", - int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string()) - )) - } else { - arg - }; - diag.span_suggestion( - e.span, - "consider using", - format!("{}::from_bits({})", to_ty, arg.to_string()), - Applicability::Unspecified, - ); - }, - ), - (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => span_lint_and_then( - cx, - TRANSMUTE_FLOAT_TO_INT, - e.span, - &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), - |diag| { - let mut expr = &args[0]; - let mut arg = sugg::Sugg::hir(cx, expr, ".."); - - if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { - expr = &inner_expr; - } - - if_chain! { - // if the expression is a float literal and it is unsuffixed then - // add a suffix so the suggestion is valid and unambiguous - let op = format!("{}{}", arg, float_ty.name_str()).into(); - if let ExprKind::Lit(lit) = &expr.kind; - if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; - then { - match arg { - sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), - _ => arg = sugg::Sugg::NonParen(op) - } - } - } - - arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); - - // cast the result of `to_bits` if `to_ty` is signed - arg = if let ty::Int(int_ty) = to_ty.kind() { - arg.as_ty(int_ty.name_str().to_string()) - } else { - arg - }; - - diag.span_suggestion( - e.span, - "consider using", - arg.to_string(), - Applicability::Unspecified, - ); - }, - ), - (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => { - if from_adt.did != to_adt.did || - !COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) { - return; - } - if from_substs.types().zip(to_substs.types()) - .any(|(from_ty, to_ty)| is_layout_incompatible(cx, from_ty, to_ty)) { - span_lint( - cx, - UNSOUND_COLLECTION_TRANSMUTE, - e.span, - &format!( - "transmute from `{}` to `{}` with mismatched layout is unsound", - from_ty, - to_ty - ) - ); - } - }, - (_, _) if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) => span_lint_and_then( - cx, - TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, - e.span, - &format!( - "transmute from `{}` to `{}` which could be expressed as a pointer cast instead", - from_ty, - to_ty - ), - |diag| { - if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { - let sugg = arg.as_ty(&to_ty.to_string()).to_string(); - diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); - } - } - ), - _ => { - return; - }, - } - } - } - } -} - -/// Gets the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is -/// not available , use -/// the type's `ToString` implementation. In weird cases it could lead to types -/// with invalid `'_` -/// lifetime, but it should be rare. -fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String { - let seg = last_path_segment(path); - if_chain! { - if let Some(ref params) = seg.args; - if !params.parenthesized; - if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }).nth(1); - if let TyKind::Rptr(_, ref to_ty) = to_ty.kind; - then { - return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string(); - } - } - - to_ref_ty.to_string() -} - -// check if the component types of the transmuted collection and the result have different ABI, -// size or alignment -fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { - let empty_param_env = ty::ParamEnv::empty(); - // check if `from` and `to` are normalizable to avoid ICE (#4968) - if !(is_normalizable(cx, empty_param_env, from) && is_normalizable(cx, empty_param_env, to)) { - return false; - } - let from_ty_layout = cx.tcx.layout_of(empty_param_env.and(from)); - let to_ty_layout = cx.tcx.layout_of(empty_param_env.and(to)); - if let (Ok(from_layout), Ok(to_layout)) = (from_ty_layout, to_ty_layout) { - from_layout.size != to_layout.size || from_layout.align != to_layout.align || from_layout.abi != to_layout.abi - } else { - // no idea about layout, so don't lint - false - } -} - -/// Check if the type conversion can be expressed as a pointer cast, instead of -/// a transmute. In certain cases, including some invalid casts from array -/// references to pointers, this may cause additional errors to be emitted and/or -/// ICE error messages. This function will panic if that occurs. -fn can_be_expressed_as_pointer_cast<'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - from_ty: Ty<'tcx>, - to_ty: Ty<'tcx>, -) -> bool { - use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; - matches!( - check_cast(cx, e, from_ty, to_ty), - Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) - ) -} - -/// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of -/// the cast. In certain cases, including some invalid casts from array references -/// to pointers, this may cause additional errors to be emitted and/or ICE error -/// messages. This function will panic if that occurs. -fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> { - let hir_id = e.hir_id; - let local_def_id = hir_id.owner; - - Inherited::build(cx.tcx, local_def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, hir_id); - - // If we already have errors, we can't be sure we can pointer cast. - assert!( - !fn_ctxt.errors_reported_since_creation(), - "Newly created FnCtxt contained errors" - ); - - if let Ok(check) = CastCheck::new( - &fn_ctxt, e, from_ty, to_ty, - // We won't show any error to the user, so we don't care what the span is here. - DUMMY_SP, DUMMY_SP, - ) { - let res = check.do_check(&fn_ctxt); - - // do_check's documentation says that it might return Ok and create - // errors in the fcx instead of returing Err in some cases. Those cases - // should be filtered out before getting here. - assert!( - !fn_ctxt.errors_reported_since_creation(), - "`fn_ctxt` contained errors after cast check!" - ); - - res.ok() - } else { - None - } - }) -} diff --git a/clippy_lints/src/transmute/crosspointer_transmute.rs b/clippy_lints/src/transmute/crosspointer_transmute.rs new file mode 100644 index 00000000000..ce87defaa94 --- /dev/null +++ b/clippy_lints/src/transmute/crosspointer_transmute.rs @@ -0,0 +1,37 @@ +use super::CROSSPOINTER_TRANSMUTE; +use crate::utils::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `crosspointer_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(from_ptr), _) if from_ptr.ty == to_ty => { + span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to the type that it points to (`{}`)", + from_ty, to_ty + ), + ); + true + }, + (_, ty::RawPtr(to_ptr)) if to_ptr.ty == from_ty => { + span_lint( + cx, + CROSSPOINTER_TRANSMUTE, + e.span, + &format!( + "transmute from a type (`{}`) to a pointer to that type (`{}`)", + from_ty, to_ty + ), + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs new file mode 100644 index 00000000000..c1870f5208b --- /dev/null +++ b/clippy_lints/src/transmute/mod.rs @@ -0,0 +1,363 @@ +mod crosspointer_transmute; +mod transmute_float_to_int; +mod transmute_int_to_bool; +mod transmute_int_to_char; +mod transmute_int_to_float; +mod transmute_ptr_to_ptr; +mod transmute_ptr_to_ref; +mod transmute_ref_to_ref; +mod transmutes_expressible_as_ptr_casts; +mod unsound_collection_transmute; +mod useless_transmute; +mod utils; +mod wrong_transmute; + +use crate::utils::{in_constant, match_def_path, paths}; +use if_chain::if_chain; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes that can't ever be correct on any + /// architecture. + /// + /// **Why is this bad?** It's basically guaranteed to be undefined behaviour. + /// + /// **Known problems:** When accessing C, users might want to store pointer + /// sized objects in `extradata` arguments to save an allocation. + /// + /// **Example:** + /// ```ignore + /// let ptr: *const T = core::intrinsics::transmute('x') + /// ``` + pub WRONG_TRANSMUTE, + correctness, + "transmutes that are confusing at best, undefined behaviour at worst and always useless" +} + +// FIXME: Move this to `complexity` again, after #5343 is fixed +declare_clippy_lint! { + /// **What it does:** Checks for transmutes to the original type of the object + /// and transmutes that could be a cast. + /// + /// **Why is this bad?** Readability. The code tricks people into thinking that + /// something complex is going on. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t); // where the result type is the same as `t`'s + /// ``` + pub USELESS_TRANSMUTE, + nursery, + "transmutes that have the same to and from types or could be a cast/coercion" +} + +// FIXME: Merge this lint with USELESS_TRANSMUTE once that is out of the nursery. +declare_clippy_lint! { + /// **What it does:**Checks for transmutes that could be a pointer cast. + /// + /// **Why is this bad?** Readability. The code tricks people into thinking that + /// something complex is going on. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// # let p: *const [i32] = &[]; + /// unsafe { std::mem::transmute::<*const [i32], *const [u16]>(p) }; + /// ``` + /// Use instead: + /// ```rust + /// # let p: *const [i32] = &[]; + /// p as *const [u16]; + /// ``` + pub TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + complexity, + "transmutes that could be a pointer cast" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between a type `T` and `*T`. + /// + /// **Why is this bad?** It's easy to mistakenly transmute between a type and a + /// pointer to that type. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust,ignore + /// core::intrinsics::transmute(t) // where the result type is the same as + /// // `*t` or `&t`'s + /// ``` + pub CROSSPOINTER_TRANSMUTE, + complexity, + "transmutes that have to or from types that are a pointer to the other" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a reference. + /// + /// **Why is this bad?** This can always be rewritten with `&` and `*`. + /// + /// **Known problems:** + /// - `mem::transmute` in statics and constants is stable from Rust 1.46.0, + /// while dereferencing raw pointer is not stable yet. + /// If you need to do this in those places, + /// you would have to use `transmute` instead. + /// + /// **Example:** + /// ```rust,ignore + /// unsafe { + /// let _: &T = std::mem::transmute(p); // where p: *const T + /// } + /// + /// // can be written: + /// let _: &T = &*p; + /// ``` + pub TRANSMUTE_PTR_TO_REF, + complexity, + "transmutes from a pointer to a reference type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `char`. + /// + /// **Why is this bad?** Not every integer is a Unicode scalar value. + /// + /// **Known problems:** + /// - [`from_u32`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid Unicode scalar value, + /// use [`from_u32_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle `None` returned from [`from_u32`] instead of calling `unwrap`. + /// + /// [`from_u32`]: https://doc.rust-lang.org/std/char/fn.from_u32.html + /// [`from_u32_unchecked`]: https://doc.rust-lang.org/std/char/fn.from_u32_unchecked.html + /// + /// **Example:** + /// ```rust + /// let x = 1_u32; + /// unsafe { + /// let _: char = std::mem::transmute(x); // where x: u32 + /// } + /// + /// // should be: + /// let _ = std::char::from_u32(x).unwrap(); + /// ``` + pub TRANSMUTE_INT_TO_CHAR, + complexity, + "transmutes from an integer to a `char`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a `&[u8]` to a `&str`. + /// + /// **Why is this bad?** Not every byte slice is a valid UTF-8 string. + /// + /// **Known problems:** + /// - [`from_utf8`] which this lint suggests using is slower than `transmute` + /// as it needs to validate the input. + /// If you are certain that the input is always a valid UTF-8, + /// use [`from_utf8_unchecked`] which is as fast as `transmute` + /// but has a semantically meaningful name. + /// - You might want to handle errors returned from [`from_utf8`] instead of calling `unwrap`. + /// + /// [`from_utf8`]: https://doc.rust-lang.org/std/str/fn.from_utf8.html + /// [`from_utf8_unchecked`]: https://doc.rust-lang.org/std/str/fn.from_utf8_unchecked.html + /// + /// **Example:** + /// ```rust + /// let b: &[u8] = &[1_u8, 2_u8]; + /// unsafe { + /// let _: &str = std::mem::transmute(b); // where b: &[u8] + /// } + /// + /// // should be: + /// let _ = std::str::from_utf8(b).unwrap(); + /// ``` + pub TRANSMUTE_BYTES_TO_STR, + complexity, + "transmutes from a `&[u8]` to a `&str`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a `bool`. + /// + /// **Why is this bad?** This might result in an invalid in-memory representation of a `bool`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let x = 1_u8; + /// unsafe { + /// let _: bool = std::mem::transmute(x); // where x: u8 + /// } + /// + /// // should be: + /// let _: bool = x != 0; + /// ``` + pub TRANSMUTE_INT_TO_BOOL, + complexity, + "transmutes from an integer to a `bool`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from an integer to a float. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `from_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: f32 = std::mem::transmute(1_u32); // where x: u32 + /// } + /// + /// // should be: + /// let _: f32 = f32::from_bits(1_u32); + /// ``` + pub TRANSMUTE_INT_TO_FLOAT, + complexity, + "transmutes from an integer to a float" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a float to an integer. + /// + /// **Why is this bad?** Transmutes are dangerous and error-prone, whereas `to_bits` is intuitive + /// and safe. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// unsafe { + /// let _: u32 = std::mem::transmute(1f32); + /// } + /// + /// // should be: + /// let _: u32 = 1f32.to_bits(); + /// ``` + pub TRANSMUTE_FLOAT_TO_INT, + complexity, + "transmutes from a float to an integer" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes from a pointer to a pointer, or + /// from a reference to a reference. + /// + /// **Why is this bad?** Transmutes are dangerous, and these can instead be + /// written as casts. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let ptr = &1u32 as *const u32; + /// unsafe { + /// // pointer-to-pointer transmute + /// let _: *const f32 = std::mem::transmute(ptr); + /// // ref-ref transmute + /// let _: &f32 = std::mem::transmute(&1u32); + /// } + /// // These can be respectively written: + /// let _ = ptr as *const f32; + /// let _ = unsafe{ &*(&1u32 as *const u32 as *const f32) }; + /// ``` + pub TRANSMUTE_PTR_TO_PTR, + complexity, + "transmutes from a pointer to a pointer / a reference to a reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for transmutes between collections whose + /// types have different ABI, size or alignment. + /// + /// **Why is this bad?** This is undefined behavior. + /// + /// **Known problems:** Currently, we cannot know whether a type is a + /// collection, so we just lint the ones that come with `std`. + /// + /// **Example:** + /// ```rust + /// // different size, therefore likely out-of-bounds memory access + /// // You absolutely do not want this in your code! + /// unsafe { + /// std::mem::transmute::<_, Vec<u32>>(vec![2_u16]) + /// }; + /// ``` + /// + /// You must always iterate, map and collect the values: + /// + /// ```rust + /// vec![2_u16].into_iter().map(u32::from).collect::<Vec<_>>(); + /// ``` + pub UNSOUND_COLLECTION_TRANSMUTE, + correctness, + "transmute between collections of layout-incompatible types" +} + +declare_lint_pass!(Transmute => [ + CROSSPOINTER_TRANSMUTE, + TRANSMUTE_PTR_TO_REF, + TRANSMUTE_PTR_TO_PTR, + USELESS_TRANSMUTE, + WRONG_TRANSMUTE, + TRANSMUTE_INT_TO_CHAR, + TRANSMUTE_BYTES_TO_STR, + TRANSMUTE_INT_TO_BOOL, + TRANSMUTE_INT_TO_FLOAT, + TRANSMUTE_FLOAT_TO_INT, + UNSOUND_COLLECTION_TRANSMUTE, + TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, +]); + +impl<'tcx> LateLintPass<'tcx> for Transmute { + #[allow(clippy::similar_names, clippy::too_many_lines)] + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if_chain! { + if let ExprKind::Call(ref path_expr, ref args) = e.kind; + if let ExprKind::Path(ref qpath) = path_expr.kind; + if let Some(def_id) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id(); + if match_def_path(cx, def_id, &paths::TRANSMUTE); + then { + // Avoid suggesting from/to bits and dereferencing raw pointers in const contexts. + // See https://github.com/rust-lang/rust/issues/73736 for progress on making them `const fn`. + // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. + let const_context = in_constant(cx, e.hir_id); + + let from_ty = cx.typeck_results().expr_ty(&args[0]); + let to_ty = cx.typeck_results().expr_ty(e); + + // If useless_transmute is triggered, the other lints can be skipped. + if useless_transmute::check(cx, e, from_ty, to_ty, args) { + return; + } + + let mut linted = wrong_transmute::check(cx, e, from_ty, to_ty); + linted |= crosspointer_transmute::check(cx, e, from_ty, to_ty); + linted |= transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, args, qpath); + linted |= transmute_int_to_char::check(cx, e, from_ty, to_ty, args); + linted |= transmute_ref_to_ref::check(cx, e, from_ty, to_ty, args, const_context); + linted |= transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, args); + linted |= transmute_int_to_bool::check(cx, e, from_ty, to_ty, args); + linted |= transmute_int_to_float::check(cx, e, from_ty, to_ty, args, const_context); + linted |= transmute_float_to_int::check(cx, e, from_ty, to_ty, args, const_context); + linted |= unsound_collection_transmute::check(cx, e, from_ty, to_ty); + + if !linted { + transmutes_expressible_as_ptr_casts::check(cx, e, from_ty, to_ty, args); + } + } + } + } +} diff --git a/clippy_lints/src/transmute/transmute_float_to_int.rs b/clippy_lints/src/transmute/transmute_float_to_int.rs new file mode 100644 index 00000000000..562d880e39a --- /dev/null +++ b/clippy_lints/src/transmute/transmute_float_to_int.rs @@ -0,0 +1,65 @@ +use super::TRANSMUTE_FLOAT_TO_INT; +use crate::utils::{span_lint_and_then, sugg}; +use if_chain::if_chain; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_float_to_int` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Float(float_ty), ty::Int(_) | ty::Uint(_)) if !const_context => { + span_lint_and_then( + cx, + TRANSMUTE_FLOAT_TO_INT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let mut expr = &args[0]; + let mut arg = sugg::Sugg::hir(cx, expr, ".."); + + if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind { + expr = &inner_expr; + } + + if_chain! { + // if the expression is a float literal and it is unsuffixed then + // add a suffix so the suggestion is valid and unambiguous + let op = format!("{}{}", arg, float_ty.name_str()).into(); + if let ExprKind::Lit(lit) = &expr.kind; + if let ast::LitKind::Float(_, ast::LitFloatType::Unsuffixed) = lit.node; + then { + match arg { + sugg::Sugg::MaybeParen(_) => arg = sugg::Sugg::MaybeParen(op), + _ => arg = sugg::Sugg::NonParen(op) + } + } + } + + arg = sugg::Sugg::NonParen(format!("{}.to_bits()", arg.maybe_par()).into()); + + // cast the result of `to_bits` if `to_ty` is signed + arg = if let ty::Int(int_ty) = to_ty.kind() { + arg.as_ty(int_ty.name_str().to_string()) + } else { + arg + }; + + diag.span_suggestion(e.span, "consider using", arg.to_string(), Applicability::Unspecified); + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_int_to_bool.rs b/clippy_lints/src/transmute/transmute_int_to_bool.rs new file mode 100644 index 00000000000..5b609f906a3 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_int_to_bool.rs @@ -0,0 +1,41 @@ +use super::TRANSMUTE_INT_TO_BOOL; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use std::borrow::Cow; + +/// Checks for `transmute_int_to_bool` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(ty::IntTy::I8) | ty::Uint(ty::UintTy::U8), ty::Bool) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_BOOL, + e.span, + &format!("transmute from a `{}` to a `bool`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let zero = sugg::Sugg::NonParen(Cow::from("0")); + diag.span_suggestion( + e.span, + "consider using", + sugg::make_binop(ast::BinOpKind::Ne, &arg, &zero).to_string(), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_int_to_char.rs b/clippy_lints/src/transmute/transmute_int_to_char.rs new file mode 100644 index 00000000000..29d2450618a --- /dev/null +++ b/clippy_lints/src/transmute/transmute_int_to_char.rs @@ -0,0 +1,44 @@ +use super::TRANSMUTE_INT_TO_CHAR; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_ast as ast; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_int_to_char` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(ty::IntTy::I32) | ty::Uint(ty::UintTy::U32), &ty::Char) => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_CHAR, + e.span, + &format!("transmute from a `{}` to a `char`", from_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(_) = from_ty.kind() { + arg.as_ty(ast::UintTy::U32.name_str()) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("std::char::from_u32({}).unwrap()", arg.to_string()), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_int_to_float.rs b/clippy_lints/src/transmute/transmute_int_to_float.rs new file mode 100644 index 00000000000..f83fba8966a --- /dev/null +++ b/clippy_lints/src/transmute/transmute_int_to_float.rs @@ -0,0 +1,47 @@ +use super::TRANSMUTE_INT_TO_FLOAT; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_int_to_float` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Int(_) | ty::Uint(_), ty::Float(_)) if !const_context => { + span_lint_and_then( + cx, + TRANSMUTE_INT_TO_FLOAT, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let arg = if let ty::Int(int_ty) = from_ty.kind() { + arg.as_ty(format!( + "u{}", + int_ty.bit_width().map_or_else(|| "size".to_string(), |v| v.to_string()) + )) + } else { + arg + }; + diag.span_suggestion( + e.span, + "consider using", + format!("{}::from_bits({})", to_ty, arg.to_string()), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs new file mode 100644 index 00000000000..f4e60a3020c --- /dev/null +++ b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs @@ -0,0 +1,35 @@ +use super::TRANSMUTE_PTR_TO_PTR; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_ptr_to_ptr` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(_), ty::RawPtr(to_ty)) => { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a pointer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let sugg = arg.as_ty(cx.tcx.mk_ptr(*to_ty)); + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs new file mode 100644 index 00000000000..f5dbbbe33bc --- /dev/null +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -0,0 +1,55 @@ +use super::utils::get_type_snippet; +use super::TRANSMUTE_PTR_TO_REF; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability, QPath}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_ptr_to_ref` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + qpath: &'tcx QPath<'_>, +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::RawPtr(from_ptr_ty), ty::Ref(_, to_ref_ty, mutbl)) => { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_REF, + e.span, + &format!( + "transmute from a pointer type (`{}`) to a reference type (`{}`)", + from_ty, to_ty + ), + |diag| { + let arg = sugg::Sugg::hir(cx, &args[0], ".."); + let (deref, cast) = if *mutbl == Mutability::Mut { + ("&mut *", "*mut") + } else { + ("&*", "*const") + }; + + let arg = if from_ptr_ty.ty == *to_ref_ty { + arg + } else { + arg.as_ty(&format!("{} {}", cast, get_type_snippet(cx, qpath, to_ref_ty))) + }; + + diag.span_suggestion( + e.span, + "try", + sugg::make_unop(deref, arg).to_string(), + Applicability::Unspecified, + ); + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/transmute_ref_to_ref.rs b/clippy_lints/src/transmute/transmute_ref_to_ref.rs new file mode 100644 index 00000000000..01b00bb0a22 --- /dev/null +++ b/clippy_lints/src/transmute/transmute_ref_to_ref.rs @@ -0,0 +1,85 @@ +use super::{TRANSMUTE_BYTES_TO_STR, TRANSMUTE_PTR_TO_PTR}; +use crate::utils::{snippet, span_lint_and_sugg, span_lint_and_then, sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, Mutability}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `transmute_bytes_to_str` and `transmute_ptr_to_ptr` lints. +/// Returns `true` if either one triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], + const_context: bool, +) -> bool { + let mut triggered = false; + + if let (ty::Ref(_, ty_from, from_mutbl), ty::Ref(_, ty_to, to_mutbl)) = (&from_ty.kind(), &to_ty.kind()) { + if_chain! { + if let (&ty::Slice(slice_ty), &ty::Str) = (&ty_from.kind(), &ty_to.kind()); + if let ty::Uint(ty::UintTy::U8) = slice_ty.kind(); + if from_mutbl == to_mutbl; + then { + let postfix = if *from_mutbl == Mutability::Mut { + "_mut" + } else { + "" + }; + + span_lint_and_sugg( + cx, + TRANSMUTE_BYTES_TO_STR, + e.span, + &format!("transmute from a `{}` to a `{}`", from_ty, to_ty), + "consider using", + format!( + "std::str::from_utf8{}({}).unwrap()", + postfix, + snippet(cx, args[0].span, ".."), + ), + Applicability::Unspecified, + ); + triggered = true; + } else { + if (cx.tcx.erase_regions(from_ty) != cx.tcx.erase_regions(to_ty)) + && !const_context { + span_lint_and_then( + cx, + TRANSMUTE_PTR_TO_PTR, + e.span, + "transmute from a reference to a reference", + |diag| if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let ty_from_and_mut = ty::TypeAndMut { + ty: ty_from, + mutbl: *from_mutbl + }; + let ty_to_and_mut = ty::TypeAndMut { ty: ty_to, mutbl: *to_mutbl }; + let sugg_paren = arg + .as_ty(cx.tcx.mk_ptr(ty_from_and_mut)) + .as_ty(cx.tcx.mk_ptr(ty_to_and_mut)); + let sugg = if *to_mutbl == Mutability::Mut { + sugg_paren.mut_addr_deref() + } else { + sugg_paren.addr_deref() + }; + diag.span_suggestion( + e.span, + "try", + sugg.to_string(), + Applicability::Unspecified, + ); + }, + ); + + triggered = true; + } + } + } + } + + triggered +} diff --git a/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs new file mode 100644 index 00000000000..dea896622f1 --- /dev/null +++ b/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs @@ -0,0 +1,38 @@ +use super::utils::can_be_expressed_as_pointer_cast; +use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS; +use crate::utils::{span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; + +/// Checks for `transmutes_expressible_as_ptr_casts` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + if can_be_expressed_as_pointer_cast(cx, e, from_ty, to_ty) { + span_lint_and_then( + cx, + TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS, + e.span, + &format!( + "transmute from `{}` to `{}` which could be expressed as a pointer cast instead", + from_ty, to_ty + ), + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let sugg = arg.as_ty(&to_ty.to_string()).to_string(); + diag.span_suggestion(e.span, "try", sugg, Applicability::MachineApplicable); + } + }, + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/transmute/unsound_collection_transmute.rs b/clippy_lints/src/transmute/unsound_collection_transmute.rs new file mode 100644 index 00000000000..503c5e0ff38 --- /dev/null +++ b/clippy_lints/src/transmute/unsound_collection_transmute.rs @@ -0,0 +1,48 @@ +use super::utils::is_layout_incompatible; +use super::UNSOUND_COLLECTION_TRANSMUTE; +use crate::utils::{match_def_path, paths, span_lint}; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +// used to check for UNSOUND_COLLECTION_TRANSMUTE +static COLLECTIONS: &[&[&str]] = &[ + &paths::VEC, + &paths::VEC_DEQUE, + &paths::BINARY_HEAP, + &paths::BTREESET, + &paths::BTREEMAP, + &paths::HASHSET, + &paths::HASHMAP, +]; + +/// Checks for `unsound_collection_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => { + if from_adt.did != to_adt.did || !COLLECTIONS.iter().any(|path| match_def_path(cx, to_adt.did, path)) { + return false; + } + if from_substs + .types() + .zip(to_substs.types()) + .any(|(from_ty, to_ty)| is_layout_incompatible(cx, from_ty, to_ty)) + { + span_lint( + cx, + UNSOUND_COLLECTION_TRANSMUTE, + e.span, + &format!( + "transmute from `{}` to `{}` with mismatched layout is unsound", + from_ty, to_ty + ), + ); + true + } else { + false + } + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs new file mode 100644 index 00000000000..83441514af0 --- /dev/null +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -0,0 +1,73 @@ +use super::USELESS_TRANSMUTE; +use crate::utils::{span_lint, span_lint_and_then, sugg}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `useless_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, + args: &'tcx [Expr<'_>], +) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + _ if from_ty == to_ty => { + span_lint( + cx, + USELESS_TRANSMUTE, + e.span, + &format!("transmute from a type (`{}`) to itself", from_ty), + ); + true + }, + (ty::Ref(_, rty, rty_mutbl), ty::RawPtr(ptr_ty)) => { + span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from a reference to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + let rty_and_mut = ty::TypeAndMut { + ty: rty, + mutbl: *rty_mutbl, + }; + + let sugg = if *ptr_ty == rty_and_mut { + arg.as_ty(to_ty) + } else { + arg.as_ty(cx.tcx.mk_ptr(rty_and_mut)).as_ty(to_ty) + }; + + diag.span_suggestion(e.span, "try", sugg.to_string(), Applicability::Unspecified); + } + }, + ); + true + }, + (ty::Int(_) | ty::Uint(_), ty::RawPtr(_)) => { + span_lint_and_then( + cx, + USELESS_TRANSMUTE, + e.span, + "transmute from an integer to a pointer", + |diag| { + if let Some(arg) = sugg::Sugg::hir_opt(cx, &args[0]) { + diag.span_suggestion( + e.span, + "try", + arg.as_ty(&to_ty.to_string()).to_string(), + Applicability::Unspecified, + ); + } + }, + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmute/utils.rs b/clippy_lints/src/transmute/utils.rs new file mode 100644 index 00000000000..55008d8ec3f --- /dev/null +++ b/clippy_lints/src/transmute/utils.rs @@ -0,0 +1,104 @@ +use crate::utils::{is_normalizable, last_path_segment, snippet}; +use if_chain::if_chain; +use rustc_hir::{Expr, GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, cast::CastKind, Ty}; +use rustc_span::DUMMY_SP; +use rustc_typeck::check::{cast::CastCheck, FnCtxt, Inherited}; + +/// Gets the snippet of `Bar` in `…::transmute<Foo, &Bar>`. If that snippet is +/// not available , use +/// the type's `ToString` implementation. In weird cases it could lead to types +/// with invalid `'_` +/// lifetime, but it should be rare. +pub(super) fn get_type_snippet(cx: &LateContext<'_>, path: &QPath<'_>, to_ref_ty: Ty<'_>) -> String { + let seg = last_path_segment(path); + if_chain! { + if let Some(ref params) = seg.args; + if !params.parenthesized; + if let Some(to_ty) = params.args.iter().filter_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }).nth(1); + if let TyKind::Rptr(_, ref to_ty) = to_ty.kind; + then { + return snippet(cx, to_ty.ty.span, &to_ref_ty.to_string()).to_string(); + } + } + + to_ref_ty.to_string() +} + +// check if the component types of the transmuted collection and the result have different ABI, +// size or alignment +pub(super) fn is_layout_incompatible<'tcx>(cx: &LateContext<'tcx>, from: Ty<'tcx>, to: Ty<'tcx>) -> bool { + let empty_param_env = ty::ParamEnv::empty(); + // check if `from` and `to` are normalizable to avoid ICE (#4968) + if !(is_normalizable(cx, empty_param_env, from) && is_normalizable(cx, empty_param_env, to)) { + return false; + } + let from_ty_layout = cx.tcx.layout_of(empty_param_env.and(from)); + let to_ty_layout = cx.tcx.layout_of(empty_param_env.and(to)); + if let (Ok(from_layout), Ok(to_layout)) = (from_ty_layout, to_ty_layout) { + from_layout.size != to_layout.size || from_layout.align != to_layout.align || from_layout.abi != to_layout.abi + } else { + // no idea about layout, so don't lint + false + } +} + +/// Check if the type conversion can be expressed as a pointer cast, instead of +/// a transmute. In certain cases, including some invalid casts from array +/// references to pointers, this may cause additional errors to be emitted and/or +/// ICE error messages. This function will panic if that occurs. +pub(super) fn can_be_expressed_as_pointer_cast<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + from_ty: Ty<'tcx>, + to_ty: Ty<'tcx>, +) -> bool { + use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast}; + matches!( + check_cast(cx, e, from_ty, to_ty), + Some(PtrPtrCast | PtrAddrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) + ) +} + +/// If a cast from `from_ty` to `to_ty` is valid, returns an Ok containing the kind of +/// the cast. In certain cases, including some invalid casts from array references +/// to pointers, this may cause additional errors to be emitted and/or ICE error +/// messages. This function will panic if that occurs. +fn check_cast<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> Option<CastKind> { + let hir_id = e.hir_id; + let local_def_id = hir_id.owner; + + Inherited::build(cx.tcx, local_def_id).enter(|inherited| { + let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, hir_id); + + // If we already have errors, we can't be sure we can pointer cast. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "Newly created FnCtxt contained errors" + ); + + if let Ok(check) = CastCheck::new( + &fn_ctxt, e, from_ty, to_ty, + // We won't show any error to the user, so we don't care what the span is here. + DUMMY_SP, DUMMY_SP, + ) { + let res = check.do_check(&fn_ctxt); + + // do_check's documentation says that it might return Ok and create + // errors in the fcx instead of returing Err in some cases. Those cases + // should be filtered out before getting here. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "`fn_ctxt` contained errors after cast check!" + ); + + res.ok() + } else { + None + } + }) +} diff --git a/clippy_lints/src/transmute/wrong_transmute.rs b/clippy_lints/src/transmute/wrong_transmute.rs new file mode 100644 index 00000000000..d6d77f2c834 --- /dev/null +++ b/clippy_lints/src/transmute/wrong_transmute.rs @@ -0,0 +1,22 @@ +use super::WRONG_TRANSMUTE; +use crate::utils::span_lint; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +/// Checks for `wrong_transmute` lint. +/// Returns `true` if it's triggered, otherwise returns `false`. +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool { + match (&from_ty.kind(), &to_ty.kind()) { + (ty::Float(_) | ty::Char, ty::Ref(..) | ty::RawPtr(_)) => { + span_lint( + cx, + WRONG_TRANSMUTE, + e.span, + &format!("transmute from a `{}` to a pointer", from_ty), + ); + true + }, + _ => false, + } +} diff --git a/clippy_lints/src/transmuting_null.rs b/clippy_lints/src/transmuting_null.rs index 6b171a0fa1a..2ba2b646f00 100644 --- a/clippy_lints/src/transmuting_null.rs +++ b/clippy_lints/src/transmuting_null.rs @@ -27,7 +27,7 @@ declare_clippy_lint! { declare_lint_pass!(TransmutingNull => [TRANSMUTING_NULL]); -const LINT_MSG: &str = "transmuting a known null pointer into a reference."; +const LINT_MSG: &str = "transmuting a known null pointer into a reference"; impl<'tcx> LateLintPass<'tcx> for TransmutingNull { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs new file mode 100644 index 00000000000..a7a511b21cf --- /dev/null +++ b/clippy_lints/src/types/borrowed_box.rs @@ -0,0 +1,114 @@ +use rustc_errors::Applicability; +use rustc_hir::{ + self as hir, GenericArg, GenericBounds, GenericParamKind, HirId, Lifetime, MutTy, Mutability, Node, QPath, + SyntheticTyParamKind, TyKind, +}; +use rustc_lint::LateContext; + +use if_chain::if_chain; + +use crate::utils::{match_path, paths, snippet, span_lint_and_sugg}; + +use super::BORROWED_BOX; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, mut_ty: &MutTy<'_>) -> bool { + match mut_ty.ty.kind { + TyKind::Path(ref qpath) => { + let hir_id = mut_ty.ty.hir_id; + let def = cx.qpath_res(qpath, hir_id); + if_chain! { + if let Some(def_id) = def.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + if let QPath::Resolved(None, ref path) = *qpath; + if let [ref bx] = *path.segments; + if let Some(ref params) = bx.args; + if !params.parenthesized; + if let Some(inner) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + then { + if is_any_trait(inner) { + // Ignore `Box<Any>` types; see issue #1884 for details. + return false; + } + + let ltopt = if lt.is_elided() { + String::new() + } else { + format!("{} ", lt.name.ident().as_str()) + }; + + if mut_ty.mutbl == Mutability::Mut { + // Ignore `&mut Box<T>` types; see issue #2907 for + // details. + return false; + } + + // When trait objects or opaque types have lifetime or auto-trait bounds, + // we need to add parentheses to avoid a syntax error due to its ambiguity. + // Originally reported as the issue #3128. + let inner_snippet = snippet(cx, inner.span, ".."); + let suggestion = match &inner.kind { + TyKind::TraitObject(bounds, lt_bound) if bounds.len() > 1 || !lt_bound.is_elided() => { + format!("&{}({})", ltopt, &inner_snippet) + }, + TyKind::Path(qpath) + if get_bounds_if_impl_trait(cx, qpath, inner.hir_id) + .map_or(false, |bounds| bounds.len() > 1) => + { + format!("&{}({})", ltopt, &inner_snippet) + }, + _ => format!("&{}{}", ltopt, &inner_snippet), + }; + span_lint_and_sugg( + cx, + BORROWED_BOX, + hir_ty.span, + "you seem to be trying to use `&Box<T>`. Consider using just `&T`", + "try", + suggestion, + // To make this `MachineApplicable`, at least one needs to check if it isn't a trait item + // because the trait impls of it will break otherwise; + // and there may be other cases that result in invalid code. + // For example, type coercion doesn't work nicely. + Applicability::Unspecified, + ); + return true; + } + }; + false + }, + _ => false, + } +} + +// Returns true if given type is `Any` trait. +fn is_any_trait(t: &hir::Ty<'_>) -> bool { + if_chain! { + if let TyKind::TraitObject(ref traits, _) = t.kind; + if !traits.is_empty(); + // 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); + then { + return true; + } + } + + false +} + +fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> { + if_chain! { + if let Some(did) = cx.qpath_res(qpath, id).opt_def_id(); + if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did); + if let GenericParamKind::Type { synthetic, .. } = generic_param.kind; + if synthetic == Some(SyntheticTyParamKind::ImplTrait); + then { + Some(generic_param.bounds) + } else { + None + } + } +} diff --git a/clippy_lints/src/types/box_vec.rs b/clippy_lints/src/types/box_vec.rs new file mode 100644 index 00000000000..6aa98e435e1 --- /dev/null +++ b/clippy_lints/src/types/box_vec.rs @@ -0,0 +1,25 @@ +use rustc_hir::{self as hir, def_id::DefId, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{is_ty_param_diagnostic_item, span_lint_and_help}; + +use super::BOX_VEC; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if Some(def_id) == cx.tcx.lang_items().owned_box() + && is_ty_param_diagnostic_item(cx, qpath, sym::vec_type).is_some() + { + span_lint_and_help( + cx, + BOX_VEC, + hir_ty.span, + "you seem to be trying to use `Box<Vec<T>>`. Consider using just `Vec<T>`", + None, + "`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation", + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/types/linked_list.rs b/clippy_lints/src/types/linked_list.rs new file mode 100644 index 00000000000..47eb4ede4e4 --- /dev/null +++ b/clippy_lints/src/types/linked_list.rs @@ -0,0 +1,22 @@ +use rustc_hir::{self as hir, def_id::DefId}; +use rustc_lint::LateContext; + +use crate::utils::{match_def_path, paths, span_lint_and_help}; + +use super::LINKEDLIST; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, def_id: DefId) -> bool { + if match_def_path(cx, def_id, &paths::LINKED_LIST) { + span_lint_and_help( + cx, + LINKEDLIST, + hir_ty.span, + "you seem to be using a `LinkedList`! Perhaps you meant some other data structure?", + None, + "a `VecDeque` might work", + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/types.rs b/clippy_lints/src/types/mod.rs index eb2016db3dc..13da768b0ca 100644 --- a/clippy_lints/src/types.rs +++ b/clippy_lints/src/types/mod.rs @@ -1,25 +1,31 @@ #![allow(rustc::default_hash_types)] +mod borrowed_box; +mod box_vec; +mod linked_list; +mod option_option; +mod rc_buffer; +mod redundant_allocation; +mod utils; +mod vec_box; + use std::borrow::Cow; use std::cmp::Ordering; use std::collections::BTreeMap; use if_chain::if_chain; -use rustc_ast::{LitFloatType, LitIntType, LitKind}; use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::{ - BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericBounds, GenericParamKind, HirId, - ImplItem, ImplItemKind, Item, ItemKind, Lifetime, Lit, Local, MatchSource, MutTy, Mutability, Node, QPath, Stmt, - StmtKind, SyntheticTyParamKind, TraitFn, TraitItem, TraitItemKind, TyKind, UnOp, + BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, + ImplItemKind, Item, ItemKind, Local, MatchSource, MutTy, Node, QPath, Stmt, StmtKind, TraitFn, TraitItem, + TraitItemKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::TypeFoldable; -use rustc_middle::ty::{self, FloatTy, InferTy, IntTy, Ty, TyCtxt, TyS, TypeAndMut, TypeckResults, UintTy}; -use rustc_semver::RustcVersion; +use rustc_middle::ty::{self, IntTy, Ty, TyS, TypeckResults, UintTy}; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::Span; @@ -30,13 +36,10 @@ use rustc_typeck::hir_ty_to_ty; use crate::consts::{constant, Constant}; use crate::utils::paths; -use crate::utils::sugg::Sugg; use crate::utils::{ - clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_hir_ty_cfg_dependant, - is_type_diagnostic_item, last_path_segment, match_def_path, match_path, meets_msrv, method_chain_args, - multispan_sugg, numeric_literal::NumericLiteral, reindent_multiline, sext, snippet, snippet_opt, - snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, - span_lint_and_then, unsext, + clip, comparisons, differing_macro_contexts, higher, indent_of, int_bits, is_isize_or_usize, + is_type_diagnostic_item, match_path, multispan_sugg, reindent_multiline, sext, snippet, snippet_opt, + snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_then, unsext, }; declare_clippy_lint! { @@ -287,56 +290,6 @@ impl<'tcx> LateLintPass<'tcx> for Types { } } -/// Checks if `qpath` has last segment with type parameter matching `path` -fn match_type_parameter(cx: &LateContext<'_>, qpath: &QPath<'_>, path: &[&str]) -> Option<Span> { - let last = last_path_segment(qpath); - if_chain! { - if let Some(ref params) = last.args; - if !params.parenthesized; - if let Some(ty) = params.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }); - if let TyKind::Path(ref qpath) = ty.kind; - if let Some(did) = cx.qpath_res(qpath, ty.hir_id).opt_def_id(); - if match_def_path(cx, did, path); - then { - return Some(ty.span); - } - } - None -} - -fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { - if match_type_parameter(cx, qpath, &paths::STRING).is_some() { - return Some("str"); - } - if match_type_parameter(cx, qpath, &paths::OS_STRING).is_some() { - return Some("std::ffi::OsStr"); - } - if match_type_parameter(cx, qpath, &paths::PATH_BUF).is_some() { - return Some("std::path::Path"); - } - None -} - -fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Span> { - let last = last_path_segment(qpath); - if_chain! { - if let Some(ref params) = last.args; - if !params.parenthesized; - if let Some(ty) = params.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }); - if let TyKind::Rptr(..) = ty.kind; - then { - return Some(ty.span); - } - } - None -} - impl Types { pub fn new(vec_box_size_threshold: u64) -> Self { Self { vec_box_size_threshold } @@ -355,9 +308,7 @@ impl Types { /// Recursively check for `TypePass` lints in the given type. Stop at the first /// lint found. /// - /// The parameter `is_local` distinguishes the context of the type; types from - /// local bindings should only be checked for the `BORROWED_BOX` lint. - #[allow(clippy::too_many_lines)] + /// The parameter `is_local` distinguishes the context of the type. fn check_ty(&mut self, cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, is_local: bool) { if hir_ty.span.from_expansion() { return; @@ -367,222 +318,16 @@ impl Types { let hir_id = hir_ty.hir_id; let res = cx.qpath_res(qpath, hir_id); if let Some(def_id) = res.opt_def_id() { - if Some(def_id) == cx.tcx.lang_items().owned_box() { - if let Some(span) = match_borrows_parameter(cx, qpath) { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - REDUNDANT_ALLOCATION, - hir_ty.span, - "usage of `Box<&T>`", - "try", - snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), - applicability, - ); - return; // don't recurse into the type - } - if match_type_parameter(cx, qpath, &paths::VEC).is_some() { - span_lint_and_help( - cx, - BOX_VEC, - hir_ty.span, - "you seem to be trying to use `Box<Vec<T>>`. Consider using just `Vec<T>`", - None, - "`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation.", - ); - return; // don't recurse into the type - } - } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { - if let Some(span) = match_type_parameter(cx, qpath, &paths::RC) { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - REDUNDANT_ALLOCATION, - hir_ty.span, - "usage of `Rc<Rc<T>>`", - "try", - snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), - applicability, - ); - return; // don't recurse into the type - } - if match_type_parameter(cx, qpath, &paths::BOX).is_some() { - let box_ty = match &last_path_segment(qpath).args.unwrap().args[0] { - GenericArg::Type(ty) => match &ty.kind { - TyKind::Path(qpath) => qpath, - _ => return, - }, - _ => return, - }; - let inner_span = match &last_path_segment(&box_ty).args.unwrap().args[0] { - GenericArg::Type(ty) => ty.span, - _ => return, - }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - REDUNDANT_ALLOCATION, - hir_ty.span, - "usage of `Rc<Box<T>>`", - "try", - format!( - "Rc<{}>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - applicability, - ); - return; // don't recurse into the type - } - if let Some(alternate) = match_buffer_type(cx, qpath) { - span_lint_and_sugg( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc<T>` when T is a buffer type", - "try", - format!("Rc<{}>", alternate), - Applicability::MachineApplicable, - ); - return; // don't recurse into the type - } - if match_type_parameter(cx, qpath, &paths::VEC).is_some() { - let vec_ty = match &last_path_segment(qpath).args.unwrap().args[0] { - GenericArg::Type(ty) => match &ty.kind { - TyKind::Path(qpath) => qpath, - _ => return, - }, - _ => return, - }; - let inner_span = match &last_path_segment(&vec_ty).args.unwrap().args[0] { - GenericArg::Type(ty) => ty.span, - _ => return, - }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc<T>` when T is a buffer type", - "try", - format!( - "Rc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - Applicability::MachineApplicable, - ); - return; // don't recurse into the type - } - if let Some(span) = match_borrows_parameter(cx, qpath) { - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - REDUNDANT_ALLOCATION, - hir_ty.span, - "usage of `Rc<&T>`", - "try", - snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), - applicability, - ); - return; // don't recurse into the type - } - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { - if let Some(alternate) = match_buffer_type(cx, qpath) { - span_lint_and_sugg( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc<T>` when T is a buffer type", - "try", - format!("Arc<{}>", alternate), - Applicability::MachineApplicable, - ); - return; // don't recurse into the type - } - if match_type_parameter(cx, qpath, &paths::VEC).is_some() { - let vec_ty = match &last_path_segment(qpath).args.unwrap().args[0] { - GenericArg::Type(ty) => match &ty.kind { - TyKind::Path(qpath) => qpath, - _ => return, - }, - _ => return, - }; - let inner_span = match &last_path_segment(&vec_ty).args.unwrap().args[0] { - GenericArg::Type(ty) => ty.span, - _ => return, - }; - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc<T>` when T is a buffer type", - "try", - format!( - "Arc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - Applicability::MachineApplicable, - ); - return; // don't recurse into the type - } - } else if cx.tcx.is_diagnostic_item(sym::vec_type, def_id) { - if_chain! { - // Get the _ part of Vec<_> - if let Some(ref last) = last_path_segment(qpath).args; - if let Some(ty) = last.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }); - // ty is now _ at this point - if let TyKind::Path(ref ty_qpath) = ty.kind; - let res = cx.qpath_res(ty_qpath, ty.hir_id); - if let Some(def_id) = res.opt_def_id(); - if Some(def_id) == cx.tcx.lang_items().owned_box(); - // At this point, we know ty is Box<T>, now get T - if let Some(ref last) = last_path_segment(ty_qpath).args; - if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }); - let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty); - if !ty_ty.has_escaping_bound_vars(); - if ty_ty.is_sized(cx.tcx.at(ty.span), cx.param_env); - if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes()); - if ty_ty_size <= self.vec_box_size_threshold; - then { - span_lint_and_sugg( - cx, - VEC_BOX, - hir_ty.span, - "`Vec<T>` is already on the heap, the boxing is unnecessary.", - "try", - format!("Vec<{}>", snippet(cx, boxed_ty.span, "..")), - Applicability::MachineApplicable, - ); - return; // don't recurse into the type - } - } - } else if cx.tcx.is_diagnostic_item(sym::option_type, def_id) { - if match_type_parameter(cx, qpath, &paths::OPTION).is_some() { - span_lint( - cx, - OPTION_OPTION, - hir_ty.span, - "consider using `Option<T>` instead of `Option<Option<T>>` or a custom \ - enum if you need to distinguish all 3 cases", - ); - return; // don't recurse into the type - } - } else if match_def_path(cx, def_id, &paths::LINKED_LIST) { - span_lint_and_help( - cx, - LINKEDLIST, - hir_ty.span, - "I see you're using a LinkedList! Perhaps you meant some other data structure?", - None, - "a `VecDeque` might work", - ); - return; // don't recurse into the type + let mut triggered = false; + triggered |= box_vec::check(cx, hir_ty, qpath, def_id); + triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id); + triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id); + triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold); + triggered |= option_option::check(cx, hir_ty, qpath, def_id); + triggered |= linked_list::check(cx, hir_ty, def_id); + + if triggered { + return; } } match *qpath { @@ -627,8 +372,11 @@ impl Types { QPath::LangItem(..) => {}, } }, - TyKind::Rptr(ref lt, ref mut_ty) => self.check_ty_rptr(cx, hir_ty, is_local, lt, mut_ty), - // recurse + TyKind::Rptr(ref lt, ref mut_ty) => { + if !borrowed_box::check(cx, hir_ty, lt, mut_ty) { + self.check_ty(cx, &mut_ty.ty, is_local); + } + }, TyKind::Slice(ref ty) | TyKind::Array(ref ty, _) | TyKind::Ptr(MutTy { ref ty, .. }) => { self.check_ty(cx, ty, is_local) }, @@ -640,115 +388,6 @@ impl Types { _ => {}, } } - - fn check_ty_rptr( - &mut self, - cx: &LateContext<'_>, - hir_ty: &hir::Ty<'_>, - is_local: bool, - lt: &Lifetime, - mut_ty: &MutTy<'_>, - ) { - match mut_ty.ty.kind { - TyKind::Path(ref qpath) => { - let hir_id = mut_ty.ty.hir_id; - let def = cx.qpath_res(qpath, hir_id); - if_chain! { - if let Some(def_id) = def.opt_def_id(); - if Some(def_id) == cx.tcx.lang_items().owned_box(); - if let QPath::Resolved(None, ref path) = *qpath; - if let [ref bx] = *path.segments; - if let Some(ref params) = bx.args; - if !params.parenthesized; - if let Some(inner) = params.args.iter().find_map(|arg| match arg { - GenericArg::Type(ty) => Some(ty), - _ => None, - }); - then { - if is_any_trait(inner) { - // Ignore `Box<Any>` types; see issue #1884 for details. - return; - } - - let ltopt = if lt.is_elided() { - String::new() - } else { - format!("{} ", lt.name.ident().as_str()) - }; - - if mut_ty.mutbl == Mutability::Mut { - // Ignore `&mut Box<T>` types; see issue #2907 for - // details. - return; - } - - // When trait objects or opaque types have lifetime or auto-trait bounds, - // we need to add parentheses to avoid a syntax error due to its ambiguity. - // Originally reported as the issue #3128. - let inner_snippet = snippet(cx, inner.span, ".."); - let suggestion = match &inner.kind { - TyKind::TraitObject(bounds, lt_bound) if bounds.len() > 1 || !lt_bound.is_elided() => { - format!("&{}({})", ltopt, &inner_snippet) - }, - TyKind::Path(qpath) - if get_bounds_if_impl_trait(cx, qpath, inner.hir_id) - .map_or(false, |bounds| bounds.len() > 1) => - { - format!("&{}({})", ltopt, &inner_snippet) - }, - _ => format!("&{}{}", ltopt, &inner_snippet), - }; - span_lint_and_sugg( - cx, - BORROWED_BOX, - hir_ty.span, - "you seem to be trying to use `&Box<T>`. Consider using just `&T`", - "try", - suggestion, - // To make this `MachineApplicable`, at least one needs to check if it isn't a trait item - // because the trait impls of it will break otherwise; - // and there may be other cases that result in invalid code. - // For example, type coercion doesn't work nicely. - Applicability::Unspecified, - ); - return; // don't recurse into the type - } - }; - self.check_ty(cx, &mut_ty.ty, is_local); - }, - _ => self.check_ty(cx, &mut_ty.ty, is_local), - } - } -} - -// Returns true if given type is `Any` trait. -fn is_any_trait(t: &hir::Ty<'_>) -> bool { - if_chain! { - if let TyKind::TraitObject(ref traits, _) = t.kind; - if !traits.is_empty(); - // 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); - then { - return true; - } - } - - false -} - -fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> { - if_chain! { - if let Some(did) = cx.qpath_res(qpath, id).opt_def_id(); - if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did); - if let GenericParamKind::Type { synthetic, .. } = generic_param.kind; - if synthetic == Some(SyntheticTyParamKind::ImplTrait); - then { - Some(generic_param.bounds) - } else { - None - } - } } declare_clippy_lint! { @@ -955,7 +594,10 @@ impl<'tcx> LateLintPass<'tcx> for UnitArg { .iter() .filter(|arg| { if is_unit(cx.typeck_results().expr_ty(arg)) && !is_unit_literal(arg) { - !matches!(&arg.kind, ExprKind::Match(.., MatchSource::TryDesugar)) + !matches!( + &arg.kind, + ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..) + ) } else { false } @@ -1133,749 +775,6 @@ fn is_unit_literal(expr: &Expr<'_>) -> bool { } declare_clippy_lint! { - /// **What it does:** Checks for casts from any numerical to a float type where - /// the receiving type cannot store all values from the original type without - /// rounding errors. This possible rounding is to be expected, so this lint is - /// `Allow` by default. - /// - /// Basically, this warns on casting any integer with 32 or more bits to `f32` - /// or any 64-bit integer to `f64`. - /// - /// **Why is this bad?** It's not bad at all. But in some applications it can be - /// helpful to know where precision loss can take place. This lint can help find - /// those places in the code. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let x = u64::MAX; - /// x as f64; - /// ``` - pub CAST_PRECISION_LOSS, - pedantic, - "casts that cause loss of precision, e.g., `x as f32` where `x: u64`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts from a signed to an unsigned numerical - /// type. In this case, negative values wrap around to large positive values, - /// which can be quite surprising in practice. However, as the cast works as - /// defined, this lint is `Allow` by default. - /// - /// **Why is this bad?** Possibly surprising results. You can activate this lint - /// as a one-time check to see where numerical wrapping can arise. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let y: i8 = -1; - /// y as u128; // will return 18446744073709551615 - /// ``` - pub CAST_SIGN_LOSS, - pedantic, - "casts from signed types to unsigned types, e.g., `x as u32` where `x: i32`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts between numerical types that may - /// truncate large values. This is expected behavior, so the cast is `Allow` by - /// default. - /// - /// **Why is this bad?** In some problem domains, it is good practice to avoid - /// truncation. This lint can be activated to help assess where additional - /// checks could be beneficial. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn as_u8(x: u64) -> u8 { - /// x as u8 - /// } - /// ``` - pub CAST_POSSIBLE_TRUNCATION, - pedantic, - "casts that may cause truncation of the value, e.g., `x as u8` where `x: u32`, or `x as i32` where `x: f32`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts from an unsigned type to a signed type of - /// the same size. Performing such a cast is a 'no-op' for the compiler, - /// i.e., nothing is changed at the bit level, and the binary representation of - /// the value is reinterpreted. This can cause wrapping if the value is too big - /// for the target signed type. However, the cast works as defined, so this lint - /// is `Allow` by default. - /// - /// **Why is this bad?** While such a cast is not bad in itself, the results can - /// be surprising when this is not the intended behavior, as demonstrated by the - /// example below. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// u32::MAX as i32; // will yield a value of `-1` - /// ``` - pub CAST_POSSIBLE_WRAP, - pedantic, - "casts that may cause wrapping around the value, e.g., `x as i32` where `x: u32` and `x > i32::MAX`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts between numerical types that may - /// be replaced by safe conversion functions. - /// - /// **Why is this bad?** Rust's `as` keyword will perform many kinds of - /// conversions, including silently lossy conversions. Conversion functions such - /// as `i32::from` will only perform lossless conversions. Using the conversion - /// functions prevents conversions from turning into silent lossy conversions if - /// the types of the input expressions ever change, and make it easier for - /// people reading the code to know that the conversion is lossless. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// fn as_u64(x: u8) -> u64 { - /// x as u64 - /// } - /// ``` - /// - /// Using `::from` would look like this: - /// - /// ```rust - /// fn as_u64(x: u8) -> u64 { - /// u64::from(x) - /// } - /// ``` - pub CAST_LOSSLESS, - pedantic, - "casts using `as` that are known to be lossless, e.g., `x as u64` where `x: u8`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts to the same type, casts of int literals to integer types - /// and casts of float literals to float types. - /// - /// **Why is this bad?** It's just unnecessary. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let _ = 2i32 as i32; - /// let _ = 0.5 as f32; - /// ``` - /// - /// Better: - /// - /// ```rust - /// let _ = 2_i32; - /// let _ = 0.5_f32; - /// ``` - pub UNNECESSARY_CAST, - complexity, - "cast to the same type, e.g., `x as i32` where `x: i32`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts, using `as` or `pointer::cast`, - /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer - /// - /// **Why is this bad?** Dereferencing the resulting pointer may be undefined - /// behavior. - /// - /// **Known problems:** Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar - /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like - /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. - /// - /// **Example:** - /// ```rust - /// let _ = (&1u8 as *const u8) as *const u16; - /// let _ = (&mut 1u8 as *mut u8) as *mut u16; - /// - /// (&1u8 as *const u8).cast::<u16>(); - /// (&mut 1u8 as *mut u8).cast::<u16>(); - /// ``` - pub CAST_PTR_ALIGNMENT, - pedantic, - "cast from a pointer to a more-strictly-aligned pointer" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts of function pointers to something other than usize - /// - /// **Why is this bad?** - /// Casting a function pointer to anything other than usize/isize is not portable across - /// architectures, because you end up losing bits if the target type is too small or end up with a - /// bunch of extra bits that waste space and add more instructions to the final binary than - /// strictly necessary for the problem - /// - /// Casting to isize also doesn't make sense since there are no signed addresses. - /// - /// **Example** - /// - /// ```rust - /// // Bad - /// fn fun() -> i32 { 1 } - /// let a = fun as i64; - /// - /// // Good - /// fn fun2() -> i32 { 1 } - /// let a = fun2 as usize; - /// ``` - pub FN_TO_NUMERIC_CAST, - style, - "casting a function pointer to a numeric type other than usize" -} - -declare_clippy_lint! { - /// **What it does:** Checks for casts of a function pointer to a numeric type not wide enough to - /// store address. - /// - /// **Why is this bad?** - /// Such a cast discards some bits of the function's address. If this is intended, it would be more - /// clearly expressed by casting to usize first, then casting the usize to the intended type (with - /// a comment) to perform the truncation. - /// - /// **Example** - /// - /// ```rust - /// // Bad - /// fn fn1() -> i16 { - /// 1 - /// }; - /// let _ = fn1 as i32; - /// - /// // Better: Cast to usize first, then comment with the reason for the truncation - /// fn fn2() -> i16 { - /// 1 - /// }; - /// let fn_ptr = fn2 as usize; - /// let fn_ptr_truncated = fn_ptr as i32; - /// ``` - pub FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - style, - "casting a function pointer to a numeric type not wide enough to store the address" -} - -/// Returns the size in bits of an integral type. -/// Will return 0 if the type is not an int or uint variant -fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { - match typ.kind() { - ty::Int(i) => match i { - IntTy::Isize => tcx.data_layout.pointer_size.bits(), - IntTy::I8 => 8, - IntTy::I16 => 16, - IntTy::I32 => 32, - IntTy::I64 => 64, - IntTy::I128 => 128, - }, - ty::Uint(i) => match i { - UintTy::Usize => tcx.data_layout.pointer_size.bits(), - UintTy::U8 => 8, - UintTy::U16 => 16, - UintTy::U32 => 32, - UintTy::U64 => 64, - UintTy::U128 => 128, - }, - _ => 0, - } -} - -fn is_isize_or_usize(typ: Ty<'_>) -> bool { - matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) -} - -fn span_precision_loss_lint(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to_f64: bool) { - let mantissa_nbits = if cast_to_f64 { 52 } else { 23 }; - let arch_dependent = is_isize_or_usize(cast_from) && cast_to_f64; - let arch_dependent_str = "on targets with 64-bit wide pointers "; - let from_nbits_str = if arch_dependent { - "64".to_owned() - } else if is_isize_or_usize(cast_from) { - "32 or 64".to_owned() - } else { - int_ty_to_nbits(cast_from, cx.tcx).to_string() - }; - span_lint( - cx, - CAST_PRECISION_LOSS, - expr.span, - &format!( - "casting `{0}` to `{1}` causes a loss of precision {2}(`{0}` is {3} bits wide, \ - but `{1}`'s mantissa is only {4} bits wide)", - cast_from, - if cast_to_f64 { "f64" } else { "f32" }, - if arch_dependent { arch_dependent_str } else { "" }, - from_nbits_str, - mantissa_nbits - ), - ); -} - -fn should_strip_parens(op: &Expr<'_>, snip: &str) -> bool { - if let ExprKind::Binary(_, _, _) = op.kind { - if snip.starts_with('(') && snip.ends_with(')') { - return true; - } - } - false -} - -fn span_lossless_lint(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - // Do not suggest using From in consts/statics until it is valid to do so (see #2267). - if in_constant(cx, expr.hir_id) { - return; - } - // The suggestion is to use a function call, so if the original expression - // has parens on the outside, they are no longer needed. - let mut applicability = Applicability::MachineApplicable; - let opt = snippet_opt(cx, op.span); - let sugg = opt.as_ref().map_or_else( - || { - applicability = Applicability::HasPlaceholders; - ".." - }, - |snip| { - if should_strip_parens(op, snip) { - &snip[1..snip.len() - 1] - } else { - snip.as_str() - } - }, - ); - - span_lint_and_sugg( - cx, - CAST_LOSSLESS, - expr.span, - &format!( - "casting `{}` to `{}` may become silently lossy if you later change the type", - cast_from, cast_to - ), - "try", - format!("{}::from({})", cast_to, sugg), - applicability, - ); -} - -enum ArchSuffix { - _32, - _64, - None, -} - -fn check_loss_of_sign(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !cast_from.is_signed() || cast_to.is_signed() { - return; - } - - // don't lint for positive constants - let const_val = constant(cx, &cx.typeck_results(), op); - if_chain! { - if let Some((Constant::Int(n), _)) = const_val; - if let ty::Int(ity) = *cast_from.kind(); - if sext(cx.tcx, n, ity) >= 0; - then { - return - } - } - - // don't lint for the result of methods that always return non-negative values - if let ExprKind::MethodCall(ref path, _, _, _) = op.kind { - let mut method_name = path.ident.name.as_str(); - let allowed_methods = ["abs", "checked_abs", "rem_euclid", "checked_rem_euclid"]; - - if_chain! { - if method_name == "unwrap"; - if let Some(arglist) = method_chain_args(op, &["unwrap"]); - if let ExprKind::MethodCall(ref inner_path, _, _, _) = &arglist[0][0].kind; - then { - method_name = inner_path.ident.name.as_str(); - } - } - - if allowed_methods.iter().any(|&name| method_name == name) { - return; - } - } - - span_lint( - cx, - CAST_SIGN_LOSS, - expr.span, - &format!( - "casting `{}` to `{}` may lose the sign of the value", - cast_from, cast_to - ), - ); -} - -fn check_truncation_and_wrapping(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - let arch_64_suffix = " on targets with 64-bit wide pointers"; - let arch_32_suffix = " on targets with 32-bit wide pointers"; - let cast_unsigned_to_signed = !cast_from.is_signed() && cast_to.is_signed(); - let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); - let (span_truncation, suffix_truncation, span_wrap, suffix_wrap) = - match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { - (true, true) | (false, false) => ( - to_nbits < from_nbits, - ArchSuffix::None, - to_nbits == from_nbits && cast_unsigned_to_signed, - ArchSuffix::None, - ), - (true, false) => ( - to_nbits <= 32, - if to_nbits == 32 { - ArchSuffix::_64 - } else { - ArchSuffix::None - }, - to_nbits <= 32 && cast_unsigned_to_signed, - ArchSuffix::_32, - ), - (false, true) => ( - from_nbits == 64, - ArchSuffix::_32, - cast_unsigned_to_signed, - if from_nbits == 64 { - ArchSuffix::_64 - } else { - ArchSuffix::_32 - }, - ), - }; - if span_truncation { - span_lint( - cx, - CAST_POSSIBLE_TRUNCATION, - expr.span, - &format!( - "casting `{}` to `{}` may truncate the value{}", - cast_from, - cast_to, - match suffix_truncation { - ArchSuffix::_32 => arch_32_suffix, - ArchSuffix::_64 => arch_64_suffix, - ArchSuffix::None => "", - } - ), - ); - } - if span_wrap { - span_lint( - cx, - CAST_POSSIBLE_WRAP, - expr.span, - &format!( - "casting `{}` to `{}` may wrap around the value{}", - cast_from, - cast_to, - match suffix_wrap { - ArchSuffix::_32 => arch_32_suffix, - ArchSuffix::_64 => arch_64_suffix, - ArchSuffix::None => "", - } - ), - ); - } -} - -fn check_lossless(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); - let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); - if !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned - { - span_lossless_lint(cx, expr, op, cast_from, cast_to); - } -} - -declare_lint_pass!(Casts => [ - CAST_PRECISION_LOSS, - CAST_SIGN_LOSS, - CAST_POSSIBLE_TRUNCATION, - CAST_POSSIBLE_WRAP, - CAST_LOSSLESS, - UNNECESSARY_CAST, - CAST_PTR_ALIGNMENT, - FN_TO_NUMERIC_CAST, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, -]); - -// Check if the given type is either `core::ffi::c_void` or -// one of the platform specific `libc::<platform>::c_void` of libc. -fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - if let ty::Adt(adt, _) = ty.kind() { - let names = cx.get_def_path(adt.did); - - if names.is_empty() { - return false; - } - if names[0] == sym::libc || names[0] == sym::core && *names.last().unwrap() == sym!(c_void) { - return true; - } - } - false -} - -/// Returns the mantissa bits wide of a fp type. -/// Will return 0 if the type is not a fp -fn fp_ty_mantissa_nbits(typ: Ty<'_>) -> u32 { - match typ.kind() { - ty::Float(FloatTy::F32) => 23, - ty::Float(FloatTy::F64) | ty::Infer(InferTy::FloatVar(_)) => 52, - _ => 0, - } -} - -impl<'tcx> LateLintPass<'tcx> for Casts { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - if let ExprKind::Cast(ref ex, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { - return; - } - let (cast_from, cast_to) = (cx.typeck_results().expr_ty(ex), cx.typeck_results().expr_ty(expr)); - lint_fn_to_numeric_cast(cx, expr, ex, cast_from, cast_to); - if let Some(lit) = get_numeric_literal(ex) { - let literal_str = snippet_opt(cx, ex.span).unwrap_or_default(); - - if_chain! { - if let LitKind::Int(n, _) = lit.node; - if let Some(src) = snippet_opt(cx, lit.span); - if cast_to.is_floating_point(); - if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node); - let from_nbits = 128 - n.leading_zeros(); - let to_nbits = fp_ty_mantissa_nbits(cast_to); - if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); - then { - let literal_str = if is_unary_neg(ex) { format!("-{}", num_lit.integer) } else { num_lit.integer.into() }; - show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); - return; - } - } - - match lit.node { - LitKind::Int(_, LitIntType::Unsuffixed) if cast_to.is_integral() => { - show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); - }, - LitKind::Float(_, LitFloatType::Unsuffixed) if cast_to.is_floating_point() => { - show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); - }, - LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {}, - _ => { - if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) { - span_lint( - cx, - UNNECESSARY_CAST, - expr.span, - &format!( - "casting to the same type is unnecessary (`{}` -> `{}`)", - cast_from, cast_to - ), - ); - } - }, - } - } - if cast_from.is_numeric() && cast_to.is_numeric() && !in_external_macro(cx.sess(), expr.span) { - lint_numeric_casts(cx, expr, ex, cast_from, cast_to); - } - - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } else if let ExprKind::MethodCall(method_path, _, args, _) = expr.kind { - if_chain! { - if method_path.ident.name == sym!(cast); - if let Some(generic_args) = method_path.args; - if let [GenericArg::Type(cast_to)] = generic_args.args; - // There probably is no obvious reason to do this, just to be consistent with `as` cases. - if !is_hir_ty_cfg_dependant(cx, cast_to); - then { - let (cast_from, cast_to) = - (cx.typeck_results().expr_ty(&args[0]), cx.typeck_results().expr_ty(expr)); - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } - } - } - } -} - -fn is_unary_neg(expr: &Expr<'_>) -> bool { - matches!(expr.kind, ExprKind::Unary(UnOp::Neg, _)) -} - -fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> { - match expr.kind { - ExprKind::Lit(ref lit) => Some(lit), - ExprKind::Unary(UnOp::Neg, e) => { - if let ExprKind::Lit(ref lit) = e.kind { - Some(lit) - } else { - None - } - }, - _ => None, - } -} - -fn show_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { - let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; - span_lint_and_sugg( - cx, - UNNECESSARY_CAST, - expr.span, - &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), - "try", - format!("{}_{}", literal_str.trim_end_matches('.'), cast_to), - Applicability::MachineApplicable, - ); -} - -fn lint_numeric_casts<'tcx>( - cx: &LateContext<'tcx>, - expr: &Expr<'tcx>, - cast_expr: &Expr<'_>, - cast_from: Ty<'tcx>, - cast_to: Ty<'tcx>, -) { - match (cast_from.is_integral(), cast_to.is_integral()) { - (true, false) => { - let from_nbits = int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { - 32 - } else { - 64 - }; - if is_isize_or_usize(cast_from) || from_nbits >= to_nbits { - span_precision_loss_lint(cx, expr, cast_from, to_nbits == 64); - } - if from_nbits < to_nbits { - span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to); - } - }, - (false, true) => { - span_lint( - cx, - CAST_POSSIBLE_TRUNCATION, - expr.span, - &format!("casting `{}` to `{}` may truncate the value", cast_from, cast_to), - ); - if !cast_to.is_signed() { - span_lint( - cx, - CAST_SIGN_LOSS, - expr.span, - &format!( - "casting `{}` to `{}` may lose the sign of the value", - cast_from, cast_to - ), - ); - } - }, - (true, true) => { - check_loss_of_sign(cx, expr, cast_expr, cast_from, cast_to); - check_truncation_and_wrapping(cx, expr, cast_from, cast_to); - check_lossless(cx, expr, cast_expr, cast_from, cast_to); - }, - (false, false) => { - if let (&ty::Float(FloatTy::F64), &ty::Float(FloatTy::F32)) = (&cast_from.kind(), &cast_to.kind()) { - span_lint( - cx, - CAST_POSSIBLE_TRUNCATION, - expr.span, - "casting `f64` to `f32` may truncate the value", - ); - } - if let (&ty::Float(FloatTy::F32), &ty::Float(FloatTy::F64)) = (&cast_from.kind(), &cast_to.kind()) { - span_lossless_lint(cx, expr, cast_expr, cast_from, cast_to); - } - }, - } -} - -fn lint_cast_ptr_alignment<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { - if_chain! { - if let ty::RawPtr(from_ptr_ty) = &cast_from.kind(); - if let ty::RawPtr(to_ptr_ty) = &cast_to.kind(); - if let Ok(from_layout) = cx.layout_of(from_ptr_ty.ty); - if let Ok(to_layout) = cx.layout_of(to_ptr_ty.ty); - if from_layout.align.abi < to_layout.align.abi; - // with c_void, we inherently need to trust the user - if !is_c_void(cx, from_ptr_ty.ty); - // when casting from a ZST, we don't know enough to properly lint - if !from_layout.is_zst(); - then { - span_lint( - cx, - CAST_PTR_ALIGNMENT, - expr.span, - &format!( - "casting from `{}` to a more-strictly-aligned pointer (`{}`) ({} < {} bytes)", - cast_from, - cast_to, - from_layout.align.abi.bytes(), - to_layout.align.abi.bytes(), - ), - ); - } - } -} - -fn lint_fn_to_numeric_cast( - cx: &LateContext<'_>, - expr: &Expr<'_>, - cast_expr: &Expr<'_>, - cast_from: Ty<'_>, - cast_to: Ty<'_>, -) { - // We only want to check casts to `ty::Uint` or `ty::Int` - match cast_to.kind() { - ty::Uint(_) | ty::Int(..) => { /* continue on */ }, - _ => return, - } - match cast_from.kind() { - ty::FnDef(..) | ty::FnPtr(_) => { - let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - - let to_nbits = int_ty_to_nbits(cast_to, cx.tcx); - if to_nbits < cx.tcx.data_layout.pointer_size.bits() { - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST_WITH_TRUNCATION, - expr.span, - &format!( - "casting function pointer `{}` to `{}`, which truncates the value", - from_snippet, cast_to - ), - "try", - format!("{} as usize", from_snippet), - applicability, - ); - } else if *cast_to.kind() != ty::Uint(UintTy::Usize) { - span_lint_and_sugg( - cx, - FN_TO_NUMERIC_CAST, - expr.span, - &format!("casting function pointer `{}` to `{}`", from_snippet, cast_to), - "try", - format!("{} as usize", from_snippet), - applicability, - ); - } - }, - _ => {}, - } -} - -declare_clippy_lint! { /// **What it does:** Checks for types used in structs, parameters and `let` /// declarations above a certain complexity threshold. /// @@ -2041,69 +940,6 @@ impl<'tcx> Visitor<'tcx> for TypeComplexityVisitor { } declare_clippy_lint! { - /// **What it does:** Checks for expressions where a character literal is cast - /// to `u8` and suggests using a byte literal instead. - /// - /// **Why is this bad?** In general, casting values to smaller types is - /// error-prone and should be avoided where possible. In the particular case of - /// converting a character literal to u8, it is easy to avoid by just using a - /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter - /// than `'a' as u8`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust,ignore - /// 'x' as u8 - /// ``` - /// - /// A better version, using the byte literal: - /// - /// ```rust,ignore - /// b'x' - /// ``` - pub CHAR_LIT_AS_U8, - complexity, - "casting a character literal to `u8` truncates" -} - -declare_lint_pass!(CharLitAsU8 => [CHAR_LIT_AS_U8]); - -impl<'tcx> LateLintPass<'tcx> for CharLitAsU8 { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - if !expr.span.from_expansion(); - if let ExprKind::Cast(e, _) = &expr.kind; - if let ExprKind::Lit(l) = &e.kind; - if let LitKind::Char(c) = l.node; - if ty::Uint(UintTy::U8) == *cx.typeck_results().expr_ty(expr).kind(); - then { - let mut applicability = Applicability::MachineApplicable; - let snippet = snippet_with_applicability(cx, e.span, "'x'", &mut applicability); - - span_lint_and_then( - cx, - CHAR_LIT_AS_U8, - expr.span, - "casting a character literal to `u8` truncates", - |diag| { - diag.note("`char` is four bytes wide, but `u8` is a single byte"); - - if c.is_ascii() { - diag.span_suggestion( - expr.span, - "use a byte literal instead", - format!("b{}", snippet), - applicability, - ); - } - }); - } - } - } -} - -declare_clippy_lint! { /// **What it does:** Checks for comparisons where one side of the relation is /// either the minimum or maximum value for its type and warns if it involves a /// case that is always true or always false. Only integer and boolean types are @@ -2830,150 +1666,3 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 't NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) } } - -declare_clippy_lint! { - /// **What it does:** Checks for casts of `&T` to `&mut T` anywhere in the code. - /// - /// **Why is this bad?** It’s basically guaranteed to be undefined behaviour. - /// `UnsafeCell` is the only way to obtain aliasable data that is considered - /// mutable. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust,ignore - /// fn x(r: &i32) { - /// unsafe { - /// *(r as *const _ as *mut _) += 1; - /// } - /// } - /// ``` - /// - /// Instead consider using interior mutability types. - /// - /// ```rust - /// use std::cell::UnsafeCell; - /// - /// fn x(r: &UnsafeCell<i32>) { - /// unsafe { - /// *r.get() += 1; - /// } - /// } - /// ``` - pub CAST_REF_TO_MUT, - correctness, - "a cast of reference to a mutable pointer" -} - -declare_lint_pass!(RefToMut => [CAST_REF_TO_MUT]); - -impl<'tcx> LateLintPass<'tcx> for RefToMut { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if_chain! { - if let ExprKind::Unary(UnOp::Deref, e) = &expr.kind; - if let ExprKind::Cast(e, t) = &e.kind; - if let TyKind::Ptr(MutTy { mutbl: Mutability::Mut, .. }) = t.kind; - if let ExprKind::Cast(e, t) = &e.kind; - if let TyKind::Ptr(MutTy { mutbl: Mutability::Not, .. }) = t.kind; - if let ty::Ref(..) = cx.typeck_results().node_type(e.hir_id).kind(); - then { - span_lint( - cx, - CAST_REF_TO_MUT, - expr.span, - "casting `&T` to `&mut T` may cause undefined behavior, consider instead using an `UnsafeCell`", - ); - } - } - } -} - -const PTR_AS_PTR_MSRV: RustcVersion = RustcVersion::new(1, 38, 0); - -declare_clippy_lint! { - /// **What it does:** - /// Checks for `as` casts between raw pointers without changing its mutability, - /// namely `*const T` to `*const U` and `*mut T` to `*mut U`. - /// - /// **Why is this bad?** - /// Though `as` casts between raw pointers is not terrible, `pointer::cast` is safer because - /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// ```rust - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr: *mut u32 = &mut 42_u32; - /// let _ = ptr as *const i32; - /// let _ = mut_ptr as *mut i32; - /// ``` - /// Use instead: - /// ```rust - /// let ptr: *const u32 = &42_u32; - /// let mut_ptr: *mut u32 = &mut 42_u32; - /// let _ = ptr.cast::<i32>(); - /// let _ = mut_ptr.cast::<i32>(); - /// ``` - pub PTR_AS_PTR, - pedantic, - "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" -} - -pub struct PtrAsPtr { - msrv: Option<RustcVersion>, -} - -impl PtrAsPtr { - #[must_use] - pub fn new(msrv: Option<RustcVersion>) -> Self { - Self { msrv } - } -} - -impl_lint_pass!(PtrAsPtr => [PTR_AS_PTR]); - -impl<'tcx> LateLintPass<'tcx> for PtrAsPtr { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !meets_msrv(self.msrv.as_ref(), &PTR_AS_PTR_MSRV) { - return; - } - - if expr.span.from_expansion() { - return; - } - - if_chain! { - if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind; - let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)); - if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind(); - if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind(); - if matches!((from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)); - // The `U` in `pointer::cast` have to be `Sized` - // as explained here: https://github.com/rust-lang/rust/issues/60602. - if to_pointee_ty.is_sized(cx.tcx.at(expr.span), cx.param_env); - then { - let mut applicability = Applicability::MachineApplicable; - let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability); - let turbofish = match &cast_to_hir_ty.kind { - TyKind::Infer => Cow::Borrowed(""), - TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""), - _ => Cow::Owned(format!("::<{}>", to_pointee_ty)), - }; - span_lint_and_sugg( - cx, - PTR_AS_PTR, - expr.span, - "`as` casting between raw pointers without changing its mutability", - "try `pointer::cast`, a safer alternative", - format!("{}.cast{}()", cast_expr_sugg.maybe_par(), turbofish), - applicability, - ); - } - } - } - - extract_msrv_attr!(LateContext); -} diff --git a/clippy_lints/src/types/option_option.rs b/clippy_lints/src/types/option_option.rs new file mode 100644 index 00000000000..dc5db963b4e --- /dev/null +++ b/clippy_lints/src/types/option_option.rs @@ -0,0 +1,24 @@ +use rustc_hir::{self as hir, def_id::DefId, QPath}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{is_ty_param_diagnostic_item, span_lint}; + +use super::OPTION_OPTION; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if cx.tcx.is_diagnostic_item(sym::option_type, def_id) + && is_ty_param_diagnostic_item(cx, qpath, sym::option_type).is_some() + { + span_lint( + cx, + OPTION_OPTION, + hir_ty.span, + "consider using `Option<T>` instead of `Option<Option<T>>` or a custom \ + enum if you need to distinguish all 3 cases", + ); + true + } else { + false + } +} diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs new file mode 100644 index 00000000000..e34b95147e1 --- /dev/null +++ b/clippy_lints/src/types/rc_buffer.rs @@ -0,0 +1,98 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{ + get_qpath_generic_tys, is_ty_param_diagnostic_item, snippet_with_applicability, span_lint_and_sugg, +}; + +use super::RC_BUFFER; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + if let Some(alternate) = match_buffer_type(cx, qpath) { + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Rc<T>` when T is a buffer type", + "try", + format!("Rc<{}>", alternate), + Applicability::MachineApplicable, + ); + } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::vec_type) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Rc<T>` when T is a buffer type", + "try", + format!( + "Rc<[{}]>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + Applicability::MachineApplicable, + ); + return true; + } + } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + if let Some(alternate) = match_buffer_type(cx, qpath) { + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Arc<T>` when T is a buffer type", + "try", + format!("Arc<{}>", alternate), + Applicability::MachineApplicable, + ); + } else if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::vec_type) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + RC_BUFFER, + hir_ty.span, + "usage of `Arc<T>` when T is a buffer type", + "try", + format!( + "Arc<[{}]>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + Applicability::MachineApplicable, + ); + return true; + } + } + + false +} + +fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { + if is_ty_param_diagnostic_item(cx, qpath, sym::string_type).is_some() { + Some("str") + } else if is_ty_param_diagnostic_item(cx, qpath, sym::OsString).is_some() { + Some("std::ffi::OsStr") + } else if is_ty_param_diagnostic_item(cx, qpath, sym::PathBuf).is_some() { + Some("std::path::Path") + } else { + None + } +} diff --git a/clippy_lints/src/types/redundant_allocation.rs b/clippy_lints/src/types/redundant_allocation.rs new file mode 100644 index 00000000000..5da6db179c4 --- /dev/null +++ b/clippy_lints/src/types/redundant_allocation.rs @@ -0,0 +1,84 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, LangItem, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; + +use crate::utils::{ + get_qpath_generic_tys, is_ty_param_diagnostic_item, is_ty_param_lang_item, snippet_with_applicability, + span_lint_and_sugg, +}; + +use super::{utils, REDUNDANT_ALLOCATION}; + +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + if Some(def_id) == cx.tcx.lang_items().owned_box() { + if let Some(span) = utils::match_borrows_parameter(cx, qpath) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Box<&T>`", + "try", + snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), + applicability, + ); + return true; + } + } + + if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + if let Some(ty) = is_ty_param_diagnostic_item(cx, qpath, sym::Rc) { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc<Rc<T>>`", + "try", + snippet_with_applicability(cx, ty.span, "..", &mut applicability).to_string(), + applicability, + ); + true + } else if let Some(ty) = is_ty_param_lang_item(cx, qpath, LangItem::OwnedBox) { + let qpath = match &ty.kind { + TyKind::Path(qpath) => qpath, + _ => return false, + }; + let inner_span = match get_qpath_generic_tys(qpath).next() { + Some(ty) => ty.span, + None => return false, + }; + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc<Box<T>>`", + "try", + format!( + "Rc<{}>", + snippet_with_applicability(cx, inner_span, "..", &mut applicability) + ), + applicability, + ); + true + } else { + utils::match_borrows_parameter(cx, qpath).map_or(false, |span| { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + REDUNDANT_ALLOCATION, + hir_ty.span, + "usage of `Rc<&T>`", + "try", + snippet_with_applicability(cx, span, "..", &mut applicability).to_string(), + applicability, + ); + true + }) + } + } else { + false + } +} diff --git a/clippy_lints/src/types/utils.rs b/clippy_lints/src/types/utils.rs new file mode 100644 index 00000000000..4d64748f998 --- /dev/null +++ b/clippy_lints/src/types/utils.rs @@ -0,0 +1,24 @@ +use rustc_hir::{GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use crate::utils::last_path_segment; + +use if_chain::if_chain; + +pub(super) fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<Span> { + let last = last_path_segment(qpath); + if_chain! { + if let Some(ref params) = last.args; + if !params.parenthesized; + if let Some(ty) = params.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + if let TyKind::Rptr(..) = ty.kind; + then { + return Some(ty.span); + } + } + None +} diff --git a/clippy_lints/src/types/vec_box.rs b/clippy_lints/src/types/vec_box.rs new file mode 100644 index 00000000000..2530cc133c6 --- /dev/null +++ b/clippy_lints/src/types/vec_box.rs @@ -0,0 +1,64 @@ +use rustc_errors::Applicability; +use rustc_hir::{self as hir, def_id::DefId, GenericArg, QPath, TyKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::TypeFoldable; +use rustc_span::symbol::sym; +use rustc_target::abi::LayoutOf; +use rustc_typeck::hir_ty_to_ty; + +use if_chain::if_chain; + +use crate::utils::{last_path_segment, snippet, span_lint_and_sugg}; + +use super::VEC_BOX; + +pub(super) fn check( + cx: &LateContext<'_>, + hir_ty: &hir::Ty<'_>, + qpath: &QPath<'_>, + def_id: DefId, + box_size_threshold: u64, +) -> bool { + if cx.tcx.is_diagnostic_item(sym::vec_type, def_id) { + if_chain! { + // Get the _ part of Vec<_> + if let Some(ref last) = last_path_segment(qpath).args; + if let Some(ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + // ty is now _ at this point + if let TyKind::Path(ref ty_qpath) = ty.kind; + let res = cx.qpath_res(ty_qpath, ty.hir_id); + if let Some(def_id) = res.opt_def_id(); + if Some(def_id) == cx.tcx.lang_items().owned_box(); + // At this point, we know ty is Box<T>, now get T + if let Some(ref last) = last_path_segment(ty_qpath).args; + if let Some(boxed_ty) = last.args.iter().find_map(|arg| match arg { + GenericArg::Type(ty) => Some(ty), + _ => None, + }); + let ty_ty = hir_ty_to_ty(cx.tcx, boxed_ty); + if !ty_ty.has_escaping_bound_vars(); + if ty_ty.is_sized(cx.tcx.at(ty.span), cx.param_env); + if let Ok(ty_ty_size) = cx.layout_of(ty_ty).map(|l| l.size.bytes()); + if ty_ty_size <= box_size_threshold; + then { + span_lint_and_sugg( + cx, + VEC_BOX, + hir_ty.span, + "`Vec<T>` is already on the heap, the boxing is unnecessary", + "try", + format!("Vec<{}>", snippet(cx, boxed_ty.span, "..")), + Applicability::MachineApplicable, + ); + true + } else { + false + } + } + } else { + false + } +} diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index be7b9e9ff2d..f0523cec621 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -262,12 +262,17 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { // FIXME: this span manipulation should not be necessary // @flip1995 found an ast lowering issue in // https://github.com/rust-lang/rust/blob/master/src/librustc_ast_lowering/path.rs#l142-l162 - match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_ty.hir_id)) { - Some(Node::Expr(Expr { - kind: ExprKind::Path(QPath::TypeRelative(_, segment)), - .. - })) => span_lint_until_last_segment(cx, hir_ty.span, segment), - _ => span_lint(cx, hir_ty.span), + let hir = cx.tcx.hir(); + let id = hir.get_parent_node(hir_ty.hir_id); + + if !hir.opt_span(id).map_or(false, in_macro) { + match hir.find(id) { + Some(Node::Expr(Expr { + kind: ExprKind::Path(QPath::TypeRelative(_, segment)), + .. + })) => span_lint_until_last_segment(cx, hir_ty.span, segment), + _ => span_lint(cx, hir_ty.span), + } } } } diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index 4b81a27632d..11d96e15ff1 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -55,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for ZeroDiv { "constant division of `0.0` with `0.0` will always result in NaN", None, &format!( - "Consider using `{}::NAN` if you would like a constant representing NaN", + "consider using `{}::NAN` if you would like a constant representing NaN", float_type, ), ); diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index 316b8d820a7..adf7077e650 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -3,9 +3,9 @@ use rustc_hir::{self as hir, HirId, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{Adt, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; use rustc_target::abi::LayoutOf as _; use rustc_typeck::hir_ty_to_ty; -use rustc_span::sym; use crate::utils::{is_normalizable, is_type_diagnostic_item, match_type, paths, span_lint_and_help}; |
