diff --git a/CHANGELOG.md b/CHANGELOG.md index 763571657..24204f86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3715,6 +3715,7 @@ Released 2018-09-13 [`short_circuit_statement`]: https://rust-lang.github.io/rust-clippy/master/index.html#short_circuit_statement [`should_assert_eq`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_assert_eq [`should_implement_trait`]: https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait +[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee [`similar_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#similar_names [`single_char_add_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_add_str [`single_char_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_lifetime_names diff --git a/Cargo.toml b/Cargo.toml index 03e6bf03a..373e720b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.62" +version = "0.1.63" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index aebf9a87c..0a3f04da3 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_lints" -version = "0.1.62" +version = "0.1.63" description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_lints/src/collapsible_match.rs b/clippy_lints/src/collapsible_match.rs index cc354b50a..ec55009f3 100644 --- a/clippy_lints/src/collapsible_match.rs +++ b/clippy_lints/src/collapsible_match.rs @@ -5,7 +5,7 @@ use clippy_utils::{is_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_s use if_chain::if_chain; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, Guard, HirId, Pat, PatKind}; +use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; @@ -109,7 +109,10 @@ fn check_arm<'tcx>( (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b), }; // the binding must not be used in the if guard - if outer_guard.map_or(true, |(Guard::If(e) | Guard::IfLet(_, e))| !is_local_used(cx, *e, binding_id)); + if outer_guard.map_or( + true, + |(Guard::If(e) | Guard::IfLet(Let { init: e, .. }))| !is_local_used(cx, *e, binding_id) + ); // ...or anywhere in the inner expression if match inner { IfLetOrMatch::IfLet(_, _, body, els) => { diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 140983407..34a5f8444 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::{is_automatically_derived, is_default_equivalent, peel_blocks}; +use clippy_utils::{is_default_equivalent, peel_blocks}; use rustc_hir::{ def::{DefKind, Res}, Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, TyKind, @@ -71,8 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { self_ty, .. }) = item.kind; - if let attrs = cx.tcx.hir().attrs(item.hir_id()); - if !is_automatically_derived(attrs); + if !cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived); if !item.span.from_expansion(); if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::Default, def_id); @@ -81,6 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { if let ImplItemKind::Fn(_, b) = &impl_item.kind; if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b); if let Some(adt_def) = cx.tcx.type_of(item.def_id).ty_adt_def(); + if let attrs = cx.tcx.hir().attrs(item.hir_id()); if !attrs.iter().any(|attr| attr.doc_str().is_some()); if let child_attrs = cx.tcx.hir().attrs(impl_item_hir); if !child_attrs.iter().any(|attr| attr.doc_str().is_some()); diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index fbbc5bf78..fe99f4a8d 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::paths; use clippy_utils::ty::{implements_trait, is_copy}; -use clippy_utils::{is_automatically_derived, is_lint_allowed, match_def_path}; +use clippy_utils::{is_lint_allowed, match_def_path}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; @@ -205,8 +205,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive { }) = item.kind { let ty = cx.tcx.type_of(item.def_id); - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let is_automatically_derived = is_automatically_derived(attrs); + let is_automatically_derived = cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived); check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); @@ -236,7 +235,7 @@ fn check_hash_peq<'tcx>( then { // Look for the PartialEq implementations for `ty` cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { - let peq_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); + let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived); if peq_is_automatically_derived == hash_is_automatically_derived { return; @@ -290,7 +289,7 @@ fn check_ord_partial_ord<'tcx>( then { // Look for the PartialOrd implementations for `ty` cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { - let partial_ord_is_automatically_derived = is_automatically_derived(cx.tcx.get_attrs(impl_id)); + let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived); if partial_ord_is_automatically_derived == ord_is_automatically_derived { return; diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 6c5ed5dca..c5a987842 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -11,7 +11,7 @@ use rustc_errors::Applicability; use rustc_hir::{ hir_id::HirIdSet, intravisit::{walk_expr, Visitor}, - Block, Expr, ExprKind, Guard, HirId, Pat, Stmt, StmtKind, UnOp, + Block, Expr, ExprKind, Guard, HirId, Let, Pat, Stmt, StmtKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -478,7 +478,7 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> { let mut is_map_used = self.is_map_used; for arm in arms { self.visit_pat(arm.pat); - if let Some(Guard::If(guard) | Guard::IfLet(_, guard)) = arm.guard { + if let Some(Guard::If(guard) | Guard::IfLet(&Let { init: guard, .. })) = arm.guard { self.visit_non_tail_expr(guard); } is_map_used |= self.visit_cond_arm(arm.body); diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 1b19868e4..530d6d4de 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -150,7 +150,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { if check_inputs(cx, body.params, args); let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap(); let substs = cx.typeck_results().node_substs(body.value.hir_id); - let call_ty = cx.tcx.type_of(method_def_id).subst(cx.tcx, substs); + let call_ty = cx.tcx.bound_type_of(method_def_id).subst(cx.tcx, substs); if check_sig(cx, closure_ty, call_ty); then { span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| { diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 38e943d2e..6672a6cb0 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -13,13 +13,13 @@ use clippy_utils::attrs::is_proc_macro; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::is_must_use_ty; -use clippy_utils::{match_def_path, must_use_attr, return_ty, trait_ref_of_method}; +use clippy_utils::{match_def_path, return_ty, trait_ref_of_method}; use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT}; pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); + let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use); if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind { let is_public = cx.access_levels.is_exported(item.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); @@ -44,7 +44,7 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp let is_public = cx.access_levels.is_exported(item.def_id); let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); + let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use); 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.def_id).is_none() { @@ -67,7 +67,7 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); let attrs = cx.tcx.hir().attrs(item.hir_id()); - let attr = must_use_attr(attrs); + let attr = cx.tcx.get_attr(item.def_id.to_def_id(), sym::must_use); if let Some(attr) = attr { check_needless_must_use(cx, sig.decl, item.hir_id(), item.span, fn_header_span, attr); } else if let hir::TraitFn::Provided(eid) = *eid { diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 43911a313..5c46d6c7d 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -5,7 +5,7 @@ use rustc_hir::{Body, FnDecl, HirId}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::subst::Subst; -use rustc_middle::ty::{Opaque, PredicateKind::Trait}; +use rustc_middle::ty::{EarlyBinder, Opaque, PredicateKind::Trait}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::{sym, Span}; use rustc_trait_selection::traits::error_reporting::suggestions::InferCtxtExt; @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { let preds = cx.tcx.explicit_item_bounds(id); let mut is_future = false; for &(p, _span) in preds { - let p = p.subst(cx.tcx, subst); + let p = EarlyBinder(p).subst(cx.tcx, subst); if let Some(trait_pred) = p.to_opt_poly_trait_pred() { if Some(trait_pred.skip_binder().trait_ref.def_id) == cx.tcx.lang_items().future_trait() { is_future = true; diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 48ceeb43e..be5c47890 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -278,6 +278,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS), LintId::of(serde_api::SERDE_API_MISUSE), + LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE), LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT), LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 05008bc58..5552ea8aa 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -473,6 +473,7 @@ store.register_lints(&[ shadow::SHADOW_REUSE, shadow::SHADOW_SAME, shadow::SHADOW_UNRELATED, + significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE, single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES, single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS, size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 338a6a28b..2de49f162 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -27,6 +27,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(mut_key::MUTABLE_KEY_TYPE), LintId::of(octal_escapes::OCTAL_ESCAPES), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), + LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE), LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL), LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL), ]) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ba49fcd27..4ac834f72 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -367,6 +367,7 @@ mod self_named_constructors; mod semicolon_if_nothing_returned; mod serde_api; mod shadow; +mod significant_drop_in_scrutinee; mod single_char_lifetime_names; mod single_component_path_imports; mod size_of_in_element_count; @@ -886,6 +887,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv))); store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion)); + store.register_late_pass(|| Box::new(significant_drop_in_scrutinee::SignificantDropInScrutinee)); store.register_late_pass(|| Box::new(dbg_macro::DbgMacro)); let cargo_ignore_publish = conf.cargo_ignore_publish; store.register_late_pass(move || { diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index ab5d3fa7b..51d5b510a 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -9,8 +9,8 @@ use rustc_hir::intravisit::{ use rustc_hir::FnRetTy::Return; use rustc_hir::{ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem, - ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, TraitBoundModifier, - TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, + TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter as middle_nested_filter; @@ -145,7 +145,7 @@ fn check_fn_inner<'tcx>( .filter(|param| matches!(param.kind, GenericParamKind::Type { .. })); for typ in types { for pred in generics.bounds_for_param(cx.tcx.hir().local_def_id(typ.hir_id)) { - if pred.in_where_clause { + if pred.origin == PredicateOrigin::WhereClause { // has_where_lifetimes checked that this predicate contains no lifetime. continue; } diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 99564a5c0..80845ace3 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -1,7 +1,6 @@ -use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_opt; -use clippy_utils::{is_lint_allowed, meets_msrv, msrvs}; +use clippy_utils::{is_doc_hidden, is_lint_allowed, meets_msrv, msrvs}; use rustc_ast::ast::{self, VisibilityKind}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -161,7 +160,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum { let id = cx.tcx.hir().local_def_id(v.id); (matches!(v.data, hir::VariantData::Unit(_)) && v.ident.as_str().starts_with('_') - && is_doc_hidden(cx.tcx.get_attrs(id.to_def_id()))) + && is_doc_hidden(cx.tcx.hir().attrs(v.id))) .then(|| (id, v.span)) }); if let Some((id, span)) = iter.next() diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index a3a26d9c3..6f8d766ae 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -192,6 +192,5 @@ impl<'a> CommonPrefixSearcher<'a> { } fn is_hidden(cx: &LateContext<'_>, variant_def: &VariantDef) -> bool { - let attrs = cx.tcx.get_attrs(variant_def.def_id); - clippy_utils::attrs::is_doc_hidden(attrs) || clippy_utils::attrs::is_unstable(attrs) + cx.tcx.is_doc_hidden(variant_def.def_id) || cx.tcx.has_attr(variant_def.def_id, sym::unstable) } diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 5c3e505c0..9d8f8999c 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed { ExprKind::MethodCall(path, arguments, _) => { let def_id = cx.typeck_results().type_dependent_def_id(e.hir_id).unwrap(); let substs = cx.typeck_results().node_substs(e.hir_id); - let method_type = cx.tcx.type_of(def_id).subst(cx.tcx, substs); + let method_type = cx.tcx.bound_type_of(def_id).subst(cx.tcx, substs); check_arguments(cx, arguments, method_type, path.ident.as_str(), "method"); }, _ => (), diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 6e7627639..093ec3893 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // can't be implemented for unsafe new return; } - if clippy_utils::is_doc_hidden(cx.tcx.hir().attrs(id)) { + if cx.tcx.is_doc_hidden(impl_item.def_id) { // shouldn't be implemented when it is hidden in docs return; } diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index beb812793..d66698f8a 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -596,7 +596,7 @@ impl<'tcx> SideEffectVisit<'tcx> { let mut vars = std::mem::take(&mut self.ret_vars); let _ = arm.guard.as_ref().map(|guard| { self.visit_expr(match guard { - Guard::If(expr) | Guard::IfLet(_, expr) => expr, + Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => expr, }); vars.append(&mut self.ret_vars); }); diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 1469cb434..09ac514d0 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_hir; -use clippy_utils::is_automatically_derived; use if_chain::if_chain; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -37,8 +36,7 @@ impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if_chain! { if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), items: impl_items, .. }) = item.kind; - let attrs = cx.tcx.hir().attrs(item.hir_id()); - if !is_automatically_derived(attrs); + if !cx.tcx.has_attr(item.def_id.to_def_id(), sym::automatically_derived); if let Some(eq_trait) = cx.tcx.lang_items().eq_trait(); if trait_ref.path.res.def_id() == eq_trait; then { diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index b19f9aff6..0004b8afd 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { } // Give up on loops - if terminator.successors().any(|s| *s == bb) { + if terminator.successors().any(|s| s == bb) { continue; } @@ -439,7 +439,7 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, // Short-circuit if (usage.cloned_used && usage.clone_consumed_or_mutated) || // Give up on loops - tdata.terminator().successors().any(|s| *s == bb) + tdata.terminator().successors().any(|s| s == bb) { return CloneUsage { cloned_used: true, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index f63925a2f..9158cbcc0 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { let mut map = FxHashMap::::default(); for id in cx.tcx.hir().items() { - if matches!(cx.tcx.hir().def_kind(id.def_id), DefKind::Impl) + if matches!(cx.tcx.def_kind(id.def_id), DefKind::Impl) && let item = cx.tcx.hir().item(id) && let ItemKind::Impl(Impl { items, diff --git a/clippy_lints/src/significant_drop_in_scrutinee.rs b/clippy_lints/src/significant_drop_in_scrutinee.rs new file mode 100644 index 000000000..424b361a9 --- /dev/null +++ b/clippy_lints/src/significant_drop_in_scrutinee.rs @@ -0,0 +1,406 @@ +use crate::FxHashSet; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_attr; +use clippy_utils::source::{indent_of, snippet}; +use rustc_errors::{Applicability, Diagnostic}; +use rustc_hir::intravisit::{walk_expr, Visitor}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::ty::subst::GenericArgKind; +use rustc_middle::ty::{Ty, TypeAndMut}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Check for temporaries returned from function calls in a match scrutinee that have the + /// `clippy::has_significant_drop` attribute. + /// + /// ### Why is this bad? + /// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have + /// an important side-effect, such as unlocking a mutex, making it important for users to be + /// able to accurately understand their lifetimes. When a temporary is returned in a function + /// call in a match scrutinee, its lifetime lasts until the end of the match block, which may + /// be surprising. + /// + /// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a + /// function call that returns a `MutexGuard` and then tries to lock again in one of the match + /// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of + /// the match block and thus will not unlock. + /// + /// ### Example + /// ```rust.ignore + /// # use std::sync::Mutex; + /// + /// # struct State {} + /// + /// # impl State { + /// # fn foo(&self) -> bool { + /// # true + /// # } + /// + /// # fn bar(&self) {} + /// # } + /// + /// + /// let mutex = Mutex::new(State {}); + /// + /// match mutex.lock().unwrap().foo() { + /// true => { + /// mutex.lock().unwrap().bar(); // Deadlock! + /// } + /// false => {} + /// }; + /// + /// println!("All done!"); + /// + /// ``` + /// Use instead: + /// ```rust + /// # use std::sync::Mutex; + /// + /// # struct State {} + /// + /// # impl State { + /// # fn foo(&self) -> bool { + /// # true + /// # } + /// + /// # fn bar(&self) {} + /// # } + /// + /// let mutex = Mutex::new(State {}); + /// + /// let is_foo = mutex.lock().unwrap().foo(); + /// match is_foo { + /// true => { + /// mutex.lock().unwrap().bar(); + /// } + /// false => {} + /// }; + /// + /// println!("All done!"); + /// ``` + #[clippy::version = "1.60.0"] + pub SIGNIFICANT_DROP_IN_SCRUTINEE, + suspicious, + "warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime" +} + +declare_lint_pass!(SignificantDropInScrutinee => [SIGNIFICANT_DROP_IN_SCRUTINEE]); + +impl<'tcx> LateLintPass<'tcx> for SignificantDropInScrutinee { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let Some(suggestions) = has_significant_drop_in_scrutinee(cx, expr) { + for found in suggestions { + span_lint_and_then( + cx, + SIGNIFICANT_DROP_IN_SCRUTINEE, + found.found_span, + "temporary with significant drop in match scrutinee", + |diag| set_diagnostic(diag, cx, expr, found), + ); + } + } + } +} + +fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { + if found.lint_suggestion == LintSuggestion::MoveAndClone { + // If our suggestion is to move and clone, then we want to leave it to the user to + // decide how to address this lint, since it may be that cloning is inappropriate. + // Therefore, we won't to emit a suggestion. + return; + } + + let original = snippet(cx, found.found_span, ".."); + let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); + + let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy { + format!("let value = *{};\n{}", original, trailing_indent) + } else if found.is_unit_return_val { + // If the return value of the expression to be moved is unit, then we don't need to + // capture the result in a temporary -- we can just replace it completely with `()`. + format!("{};\n{}", original, trailing_indent) + } else { + format!("let value = {};\n{}", original, trailing_indent) + }; + + let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly { + "try moving the temporary above the match" + } else { + "try moving the temporary above the match and create a copy" + }; + + let scrutinee_replacement = if found.is_unit_return_val { + "()".to_owned() + } else { + "value".to_owned() + }; + + diag.multipart_suggestion( + suggestion_message, + vec![ + (expr.span.shrink_to_lo(), replacement), + (found.found_span, scrutinee_replacement), + ], + Applicability::MaybeIncorrect, + ); +} + +/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that +/// may have a surprising lifetime. +fn has_significant_drop_in_scrutinee<'tcx, 'a>( + cx: &'a LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option> { + let mut helper = SigDropHelper::new(cx); + match expr.kind { + ExprKind::Match(match_expr, _, _) => helper.find_sig_drop(match_expr), + _ => None, + } +} + +struct SigDropHelper<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + is_chain_end: bool, + seen_types: FxHashSet>, + has_significant_drop: bool, + current_sig_drop: Option, + sig_drop_spans: Option>, + special_handling_for_binary_op: bool, +} + +#[expect(clippy::enum_variant_names)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum LintSuggestion { + MoveOnly, + MoveAndDerefToCopy, + MoveAndClone, +} + +#[derive(Clone, Copy)] +struct FoundSigDrop { + found_span: Span, + is_unit_return_val: bool, + lint_suggestion: LintSuggestion, +} + +impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { + fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { + SigDropHelper { + cx, + is_chain_end: true, + seen_types: FxHashSet::default(), + has_significant_drop: false, + current_sig_drop: None, + sig_drop_spans: None, + special_handling_for_binary_op: false, + } + } + + fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option> { + self.visit_expr(match_expr); + + // If sig drop spans is empty but we found a significant drop, it means that we didn't find + // a type that was trivially copyable as we moved up the chain after finding a significant + // drop, so move the entire scrutinee. + if self.has_significant_drop && self.sig_drop_spans.is_none() { + self.try_setting_current_suggestion(match_expr, true); + self.move_current_suggestion(); + } + + self.sig_drop_spans.take() + } + + /// This will try to set the current suggestion (so it can be moved into the suggestions vec + /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us + /// an opportunity to look for another type in the chain that will be trivially copyable. + /// However, if we are at the the end of the chain, we want to accept whatever is there. (The + /// suggestion won't actually be output, but the diagnostic message will be output, so the user + /// can determine the best way to handle the lint.) + fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) { + if self.current_sig_drop.is_some() { + return; + } + let ty = self.get_type(expr); + if ty.is_ref() { + // We checked that the type was ref, so builtin_deref will return Some TypeAndMut, + // but let's avoid any chance of an ICE + if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) { + if ty.is_trivially_pure_clone_copy() { + self.current_sig_drop.replace(FoundSigDrop { + found_span: expr.span, + is_unit_return_val: false, + lint_suggestion: LintSuggestion::MoveAndDerefToCopy, + }); + } else if allow_move_and_clone { + self.current_sig_drop.replace(FoundSigDrop { + found_span: expr.span, + is_unit_return_val: false, + lint_suggestion: LintSuggestion::MoveAndClone, + }); + } + } + } else if ty.is_trivially_pure_clone_copy() { + self.current_sig_drop.replace(FoundSigDrop { + found_span: expr.span, + is_unit_return_val: false, + lint_suggestion: LintSuggestion::MoveOnly, + }); + } + } + + fn move_current_suggestion(&mut self) { + if let Some(current) = self.current_sig_drop.take() { + self.sig_drop_spans.get_or_insert_with(Vec::new).push(current); + } + } + + fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> { + self.cx.typeck_results().expr_ty(ex) + } + + fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool { + !self.seen_types.insert(ty) + } + + fn visit_exprs_for_binary_ops( + &mut self, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + is_unit_return_val: bool, + span: Span, + ) { + self.special_handling_for_binary_op = true; + self.visit_expr(left); + self.visit_expr(right); + + // If either side had a significant drop, suggest moving the entire scrutinee to avoid + // unnecessary copies and to simplify cases where both sides have significant drops. + if self.has_significant_drop { + self.current_sig_drop.replace(FoundSigDrop { + found_span: span, + is_unit_return_val, + lint_suggestion: LintSuggestion::MoveOnly, + }); + } + + self.special_handling_for_binary_op = false; + } + + fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + if let Some(adt) = ty.ty_adt_def() { + if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 { + return true; + } + } + + match ty.kind() { + rustc_middle::ty::Adt(a, b) => { + for f in a.all_fields() { + let ty = f.ty(cx.tcx, b); + if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) { + return true; + } + } + + for generic_arg in b.iter() { + if let GenericArgKind::Type(ty) = generic_arg.unpack() { + if self.has_sig_drop_attr(cx, ty) { + return true; + } + } + } + false + }, + rustc_middle::ty::Array(ty, _) + | rustc_middle::ty::RawPtr(TypeAndMut { ty, .. }) + | rustc_middle::ty::Ref(_, ty, _) + | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty), + _ => false, + } + } +} + +impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) { + self.has_significant_drop = true; + return; + } + self.is_chain_end = false; + + match ex.kind { + ExprKind::MethodCall(_, [ref expr, ..], _) => { + self.visit_expr(expr); + } + ExprKind::Binary(_, left, right) => { + self.visit_exprs_for_binary_ops(left, right, false, ex.span); + } + ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => { + self.visit_exprs_for_binary_ops(left, right, true, ex.span); + } + ExprKind::Tup(exprs) => { + for expr in exprs { + self.visit_expr(expr); + if self.has_significant_drop { + // We may have not have set current_sig_drop if all the suggestions were + // MoveAndClone, so add this tuple item's full expression in that case. + if self.current_sig_drop.is_none() { + self.try_setting_current_suggestion(expr, true); + } + + // Now we are guaranteed to have something, so add it to the final vec. + self.move_current_suggestion(); + } + // Reset `has_significant_drop` after each tuple expression so we can look for + // additional cases. + self.has_significant_drop = false; + } + if self.sig_drop_spans.is_some() { + self.has_significant_drop = true; + } + } + ExprKind::Box(..) | + ExprKind::Array(..) | + ExprKind::Call(..) | + ExprKind::Unary(..) | + ExprKind::If(..) | + ExprKind::Match(..) | + ExprKind::Field(..) | + ExprKind::Index(..) | + ExprKind::Ret(..) | + ExprKind::Repeat(..) | + ExprKind::Yield(..) | + ExprKind::MethodCall(..) => walk_expr(self, ex), + ExprKind::AddrOf(_, _, _) | + ExprKind::Block(_, _) | + ExprKind::Break(_, _) | + ExprKind::Cast(_, _) | + // Don't want to check the closure itself, only invocation, which is covered by MethodCall + ExprKind::Closure(_, _, _, _, _) | + ExprKind::ConstBlock(_) | + ExprKind::Continue(_) | + ExprKind::DropTemps(_) | + ExprKind::Err | + ExprKind::InlineAsm(_) | + ExprKind::Let(_) | + ExprKind::Lit(_) | + ExprKind::Loop(_, _, _, _) | + ExprKind::Path(_) | + ExprKind::Struct(_, _, _) | + ExprKind::Type(_, _) => { + return; + } + } + + // Once a significant temporary has been found, we need to go back up at least 1 level to + // find the span to extract for replacement, so the temporary gets dropped. However, for + // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to + // simplify cases where both sides have significant drops. + if self.has_significant_drop && !self.special_handling_for_binary_op { + self.try_setting_current_suggestion(ex, false); + } + } +} diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 78e388a49..6c60fb4b8 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -8,7 +8,8 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, QPath, TraitItem, Ty, TyKind, WherePredicate, + GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitItem, Ty, TyKind, + WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; @@ -95,6 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { for predicate in item.generics.predicates { if_chain! { if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; + if bound_predicate.origin != PredicateOrigin::ImplTrait; if !bound_predicate.span.from_expansion(); if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind; if let Some(PathSegment { @@ -168,6 +170,7 @@ impl TraitBounds { for bound in gen.predicates { if_chain! { if let WherePredicate::BoundPredicate(ref p) = bound; + if p.origin != PredicateOrigin::ImplTrait; if p.bounds.len() as u64 <= self.max_trait_bounds; if !p.span.from_expansion(); if let Some(ref v) = map.insert( @@ -223,6 +226,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { for predicate in gen.predicates { if_chain! { if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate; + if bound_predicate.origin != PredicateOrigin::ImplTrait; if !bound_predicate.span.from_expansion(); if let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind; if let Some(segment) = segments.first(); diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index f5e21267a..be6277332 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -307,7 +307,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> .non_enum_variant() .fields .iter() - .map(|f| cx.tcx.type_of(f.did).subst(cx.tcx, substs)); + .map(|f| cx.tcx.bound_type_of(f.did).subst(cx.tcx, substs)); let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::TypeErasure; }; diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index ff5be825b..3f4d0fd19 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -315,11 +315,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { out!("if let Some(Guard::If({expr})) = {arm}.guard;"); self.expr(expr); }, - Some(hir::Guard::IfLet(pat, expr)) => { - bind!(self, pat, expr); - out!("if let Some(Guard::IfLet({pat}, {expr}) = {arm}.guard;"); - self.pat(pat); - self.expr(expr); + Some(hir::Guard::IfLet(let_expr)) => { + bind!(self, let_expr); + out!("if let Some(Guard::IfLet({let_expr}) = {arm}.guard;"); + self.pat(field!(let_expr.pat)); + self.expr(field!(let_expr.init)); }, } self.expr(field!(arm.body)); diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 0b1fd95c3..c4e0b8448 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.62" +version = "0.1.63" edition = "2021" publish = false diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 5bbc2b5b0..49318849d 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -1,4 +1,5 @@ -use rustc_ast::{ast, attr}; +use rustc_ast::ast; +use rustc_ast::attr; use rustc_errors::Applicability; use rustc_session::Session; use rustc_span::sym; @@ -21,6 +22,7 @@ pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[ ("cyclomatic_complexity", DeprecationStatus::Replaced("cognitive_complexity")), ("dump", DeprecationStatus::None), ("msrv", DeprecationStatus::None), + ("has_significant_drop", DeprecationStatus::None), ]; pub struct LimitStack { @@ -155,8 +157,3 @@ pub fn is_doc_hidden(attrs: &[ast::Attribute]) -> bool { .filter_map(ast::Attribute::meta_item_list) .any(|l| attr::list_contains_name(&l, sym::hidden)) } - -/// Return true if the attributes contain `#[unstable]` -pub fn is_unstable(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| attr.has_name(sym::unstable)) -} diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 75fab624f..9f162a117 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -9,7 +9,7 @@ use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, use rustc_lint::LateContext; use rustc_middle::mir::interpret::Scalar; use rustc_middle::ty::subst::{Subst, SubstsRef}; -use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty, TyCtxt}; +use rustc_middle::ty::{self, EarlyBinder, FloatTy, ScalarInt, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::symbol::Symbol; use std::cmp::Ordering::{self, Equal}; @@ -417,7 +417,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { let substs = if self.substs.is_empty() { substs } else { - substs.subst(self.lcx.tcx, self.substs) + EarlyBinder(substs).subst(self.lcx.tcx, self.substs) }; let result = self diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 951e63008..c440793b9 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -300,7 +300,9 @@ impl HirEqInterExpr<'_, '_, '_> { fn eq_guard(&mut self, left: &Guard<'_>, right: &Guard<'_>) -> bool { match (left, right) { (Guard::If(l), Guard::If(r)) => self.eq_expr(l, r), - (Guard::IfLet(lp, le), Guard::IfLet(rp, re)) => self.eq_pat(lp, rp) && self.eq_expr(le, re), + (Guard::IfLet(l), Guard::IfLet(r)) => { + self.eq_pat(l.pat, r.pat) && both(&l.ty, &r.ty, |l, r| self.eq_ty(l, r)) && self.eq_expr(l.init, r.init) + }, _ => false, } } @@ -892,7 +894,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { pub fn hash_guard(&mut self, g: &Guard<'_>) { match g { - Guard::If(expr) | Guard::IfLet(_, expr) => { + Guard::If(expr) | Guard::IfLet(Let { init: expr, .. }) => { self.hash_expr(expr); }, } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8f9e687fb..adb37cc9d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -66,7 +66,8 @@ use std::lazy::SyncOnceCell; use std::sync::{Mutex, MutexGuard}; use if_chain::if_chain; -use rustc_ast::ast::{self, Attribute, LitKind}; +use rustc_ast::ast::{self, LitKind}; +use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; use rustc_hir as hir; @@ -74,13 +75,12 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, CRATE_DEF_ID}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; -use rustc_hir::itemlikevisit::ItemLikeVisitor; use rustc_hir::LangItem::{OptionNone, ResultErr, ResultOk}; use rustc_hir::{ def, Arm, ArrayLen, BindingAnnotation, Block, BlockCheckMode, Body, Constness, Destination, Expr, ExprKind, FnDecl, - ForeignItem, HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, - Mutability, Node, Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, - TraitRef, TyKind, UnOp, + HirId, Impl, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Mutability, Node, + Param, Pat, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, + UnOp, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::place::PlaceBase; @@ -1472,12 +1472,6 @@ pub fn recurse_or_patterns<'tcx, F: FnMut(&'tcx Pat<'tcx>)>(pat: &'tcx Pat<'tcx> } } -/// Checks for the `#[automatically_derived]` attribute all `#[derive]`d -/// implementations have. -pub fn is_automatically_derived(attrs: &[ast::Attribute]) -> bool { - has_attr(attrs, sym::automatically_derived) -} - pub fn is_self(slf: &Param<'_>) -> bool { if let PatKind::Binding(.., name, _) = slf.pat.kind { name.name == kw::SelfLower @@ -1724,11 +1718,6 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t None } -// Finds the `#[must_use]` attribute, if any -pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { - attrs.iter().find(|a| a.has_name(sym::must_use)) -} - // check if expr is calling method or function with #[must_use] attribute pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let did = match expr.kind { @@ -1745,7 +1734,7 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => None, }; - did.map_or(false, |did| must_use_attr(cx.tcx.get_attrs(did)).is_some()) + did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use)) } /// Checks if an expression represents the identity function @@ -2079,35 +2068,6 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { false } -struct TestItemNamesVisitor<'tcx> { - tcx: TyCtxt<'tcx>, - names: Vec, -} - -impl<'hir> ItemLikeVisitor<'hir> for TestItemNamesVisitor<'hir> { - fn visit_item(&mut self, item: &Item<'_>) { - if let ItemKind::Const(ty, _body) = item.kind { - if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { - // We could also check for the type name `test::TestDescAndFn` - if let Res::Def(DefKind::Struct, _) = path.res { - let has_test_marker = self - .tcx - .hir() - .attrs(item.hir_id()) - .iter() - .any(|a| a.has_name(sym::rustc_test_marker)); - if has_test_marker { - self.names.push(item.ident.name); - } - } - } - } - } - fn visit_trait_item(&mut self, _: &TraitItem<'_>) {} - fn visit_impl_item(&mut self, _: &ImplItem<'_>) {} - fn visit_foreign_item(&mut self, _: &ForeignItem<'_>) {} -} - static TEST_ITEM_NAMES_CACHE: SyncOnceCell>>> = SyncOnceCell::new(); fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn(&[Symbol]) -> bool) -> bool { @@ -2116,10 +2076,28 @@ fn with_test_item_names<'tcx>(tcx: TyCtxt<'tcx>, module: LocalDefId, f: impl Fn( match map.entry(module) { Entry::Occupied(entry) => f(entry.get()), Entry::Vacant(entry) => { - let mut visitor = TestItemNamesVisitor { tcx, names: Vec::new() }; - tcx.hir().visit_item_likes_in_module(module, &mut visitor); - visitor.names.sort_unstable(); - f(&*entry.insert(visitor.names)) + let mut names = Vec::new(); + for id in tcx.hir().module_items(module) { + if matches!(tcx.def_kind(id.def_id), DefKind::Const) + && let item = tcx.hir().item(id) + && let ItemKind::Const(ty, _body) = item.kind { + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind { + // We could also check for the type name `test::TestDescAndFn` + if let Res::Def(DefKind::Struct, _) = path.res { + let has_test_marker = tcx + .hir() + .attrs(item.hir_id()) + .iter() + .any(|a| a.has_name(sym::rustc_test_marker)); + if has_test_marker { + names.push(item.ident.name); + } + } + } + } + } + names.sort_unstable(); + f(&*entry.insert(names)) }, } } diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 6a8ed4e7b..a6d7042fa 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -14,7 +14,6 @@ use rustc_middle::ty::{self, adjustment::PointerCast, Ty, TyCtxt}; use rustc_semver::RustcVersion; use rustc_span::symbol::sym; use rustc_span::Span; -use rustc_target::spec::abi::Abi::RustIntrinsic; use std::borrow::Cow; type McfResult = Result<(), (Span, Cow<'static, str>)>; @@ -323,7 +322,7 @@ fn check_terminator<'a, 'tcx>( // within const fns. `transmute` is allowed in all other const contexts. // This won't really scale to more intrinsics or functions. Let's allow const // transmutes in const fn before we add more hacks to this. - if tcx.fn_sig(fn_def_id).abi() == RustIntrinsic && tcx.item_name(fn_def_id) == sym::transmute { + if tcx.is_intrinsic(fn_def_id) && tcx.item_name(fn_def_id) == sym::transmute { return Err(( span, "can only call `transmute` from const items, not `const fn`".into(), diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 5767a573a..07d3d2807 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -22,7 +22,7 @@ use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::normalize::AtExt; use std::iter; -use crate::{match_def_path, must_use_attr, path_res, paths}; +use crate::{match_def_path, path_res, paths}; // Checks if the given type implements copy. pub fn is_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { @@ -178,18 +178,18 @@ pub fn has_drop<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { // Returns whether the type has #[must_use] attribute pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind() { - ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did())).is_some(), - ty::Foreign(ref did) => must_use_attr(cx.tcx.get_attrs(*did)).is_some(), + ty::Adt(adt, _) => cx.tcx.has_attr(adt.did(), sym::must_use), + ty::Foreign(did) => cx.tcx.has_attr(*did, sym::must_use), ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => { // for the Array case we don't need to care for the len == 0 case // because we don't want to lint functions returning empty arrays is_must_use_ty(cx, *ty) }, ty::Tuple(substs) => substs.iter().any(|ty| is_must_use_ty(cx, ty)), - ty::Opaque(ref def_id, _) => { + ty::Opaque(def_id, _) => { for (predicate, _) in cx.tcx.explicit_item_bounds(*def_id) { if let ty::PredicateKind::Trait(trait_predicate) = predicate.kind().skip_binder() { - if must_use_attr(cx.tcx.get_attrs(trait_predicate.trait_ref.def_id)).is_some() { + if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) { return true; } } @@ -199,7 +199,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { ty::Dynamic(binder, _) => { for predicate in binder.iter() { if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { - if must_use_attr(cx.tcx.get_attrs(trait_ref.def_id)).is_some() { + if cx.tcx.has_attr(trait_ref.def_id, sym::must_use) { return true; } } @@ -520,7 +520,7 @@ pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option Some(ExprFnSig::Closure(subs.as_closure().sig())), - ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.fn_sig(id).subst(cx.tcx, subs))), + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))), ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); diff --git a/rust-toolchain b/rust-toolchain index 03acb5130..997e7ba93 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-05-05" +channel = "nightly-2022-05-19" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui/crashes/ice-6252.stderr b/tests/ui/crashes/ice-6252.stderr index c8239897f..d930d486f 100644 --- a/tests/ui/crashes/ice-6252.stderr +++ b/tests/ui/crashes/ice-6252.stderr @@ -30,7 +30,15 @@ LL | const VAL: T; LL | impl TypeVal for Multiply where N: TypeVal {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation -error: aborting due to 3 previous errors +error: constant expression depends on a generic parameter + --> $DIR/ice-6252.rs:13:9 + | +LL | [1; >::VAL]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: aborting due to 4 previous errors Some errors have detailed explanations: E0046, E0412. For more information about an error, try `rustc --explain E0046`. diff --git a/tests/ui/expect_tool_lint_rfc_2383.rs b/tests/ui/expect_tool_lint_rfc_2383.rs new file mode 100644 index 000000000..985835ffa --- /dev/null +++ b/tests/ui/expect_tool_lint_rfc_2383.rs @@ -0,0 +1,142 @@ +// check-pass +#![feature(lint_reasons)] +//! This file tests the `#[expect]` attribute implementation for tool lints. The same +//! file is used to test clippy and rustdoc. Any changes to this file should be synced +//! to the other test files as well. +//! +//! Expectations: +//! * rustc: only rustc lint expectations are emitted +//! * clippy: rustc and Clippy's expectations are emitted +//! * rustdoc: only rustdoc lint expectations are emitted +//! +//! This test can't cover every lint from Clippy, rustdoc and potentially other +//! tools that will be developed. This therefore only tests a small subset of lints +#![expect(rustdoc::missing_crate_level_docs)] + +mod rustc_ok { + //! See + + #[expect(dead_code)] + pub fn rustc_lints() { + let x = 42.0; + + #[expect(illegal_floating_point_literal_pattern)] + match x { + 5.0 => {}, + 6.0 => {}, + _ => {}, + } + } +} + +mod rustc_warn { + //! See + + #[expect(dead_code)] + pub fn rustc_lints() { + let x = 42; + + #[expect(illegal_floating_point_literal_pattern)] + match x { + 5 => {}, + 6 => {}, + _ => {}, + } + } +} + +pub mod rustdoc_ok { + //! See + + #[expect(rustdoc::broken_intra_doc_links)] + /// I want to link to [`Nonexistent`] but it doesn't exist! + pub fn foo() {} + + #[expect(rustdoc::invalid_html_tags)] + ///

+ pub fn bar() {} + + #[expect(rustdoc::bare_urls)] + /// http://example.org + pub fn baz() {} +} + +pub mod rustdoc_warn { + //! See + + #[expect(rustdoc::broken_intra_doc_links)] + /// I want to link to [`bar`] but it doesn't exist! + pub fn foo() {} + + #[expect(rustdoc::invalid_html_tags)] + ///

+ pub fn bar() {} + + #[expect(rustdoc::bare_urls)] + /// + pub fn baz() {} +} + +mod clippy_ok { + //! See + + #[expect(clippy::almost_swapped)] + fn foo() { + let mut a = 0; + let mut b = 9; + a = b; + b = a; + } + + #[expect(clippy::bytes_nth)] + fn bar() { + let _ = "Hello".bytes().nth(3); + } + + #[expect(clippy::if_same_then_else)] + fn baz() { + let _ = if true { 42 } else { 42 }; + } + + #[expect(clippy::logic_bug)] + fn burger() { + let a = false; + let b = true; + + if a && b || a {} + } +} + +mod clippy_warn { + //! See + + #[expect(clippy::almost_swapped)] + fn foo() { + let mut a = 0; + let mut b = 9; + a = b; + } + + #[expect(clippy::bytes_nth)] + fn bar() { + let _ = "Hello".as_bytes().get(3); + } + + #[expect(clippy::if_same_then_else)] + fn baz() { + let _ = if true { 33 } else { 42 }; + } + + #[expect(clippy::logic_bug)] + fn burger() { + let a = false; + let b = true; + let c = false; + + if a && b || c {} + } +} + +fn main() { + rustc_warn::rustc_lints(); +} diff --git a/tests/ui/expect_tool_lint_rfc_2383.stderr b/tests/ui/expect_tool_lint_rfc_2383.stderr new file mode 100644 index 000000000..db29e85a8 --- /dev/null +++ b/tests/ui/expect_tool_lint_rfc_2383.stderr @@ -0,0 +1,40 @@ +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:35:14 + | +LL | #[expect(dead_code)] + | ^^^^^^^^^ + | + = note: `-D unfulfilled-lint-expectations` implied by `-D warnings` + +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:39:18 + | +LL | #[expect(illegal_floating_point_literal_pattern)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:113:14 + | +LL | #[expect(clippy::almost_swapped)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:120:14 + | +LL | #[expect(clippy::bytes_nth)] + | ^^^^^^^^^^^^^^^^^ + +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:125:14 + | +LL | #[expect(clippy::if_same_then_else)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this lint expectation is unfulfilled + --> $DIR/expect_tool_lint_rfc_2383.rs:130:14 + | +LL | #[expect(clippy::logic_bug)] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/tests/ui/indexing_slicing_index.stderr b/tests/ui/indexing_slicing_index.stderr index 83a36f407..6ae700753 100644 --- a/tests/ui/indexing_slicing_index.stderr +++ b/tests/ui/indexing_slicing_index.stderr @@ -1,4 +1,4 @@ -error[E0080]: evaluation of `main::{constant#3}::<&i32>` failed +error[E0080]: evaluation of `main::{constant#3}` failed --> $DIR/indexing_slicing_index.rs:31:14 | LL | const { &ARR[idx4()] }; // Ok, let rustc handle const contexts. diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs new file mode 100644 index 000000000..f83a6dd0e --- /dev/null +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -0,0 +1,555 @@ +// FIXME: Ideally these suggestions would be fixed via rustfix. Blocked by rust-lang/rust#53934 +// // run-rustfix + +#![warn(clippy::significant_drop_in_scrutinee)] +#![allow(clippy::single_match)] +#![allow(clippy::match_single_binding)] +#![allow(unused_assignments)] +#![allow(dead_code)] + +use std::ops::Deref; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Mutex, MutexGuard}; + +struct State {} + +impl State { + fn foo(&self) -> bool { + true + } + + fn bar(&self) {} +} + +fn should_not_trigger_lint_with_mutex_guard_outside_match() { + let mutex = Mutex::new(State {}); + + // Should not trigger lint because the temporary should drop at the `;` on line before the match + let is_foo = mutex.lock().unwrap().foo(); + match is_foo { + true => { + mutex.lock().unwrap().bar(); + }, + false => {}, + }; +} + +fn should_not_trigger_lint_with_mutex_guard_when_taking_ownership_in_match() { + let mutex = Mutex::new(State {}); + + // Should not trigger lint because the scrutinee is explicitly returning the MutexGuard, + // so its lifetime should not be surprising. + match mutex.lock() { + Ok(guard) => { + guard.foo(); + mutex.lock().unwrap().bar(); + }, + _ => {}, + }; +} + +fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() { + let mutex = Mutex::new(State {}); + + // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it + // is preserved until the end of the match, but there is no clear indication that this is the + // case. + match mutex.lock().unwrap().foo() { + true => { + mutex.lock().unwrap().bar(); + }, + false => {}, + }; +} + +fn should_not_trigger_lint_for_insignificant_drop() { + // Should not trigger lint because there are no temporaries whose drops have a significant + // side effect. + match 1u64.to_string().is_empty() { + true => { + println!("It was empty") + }, + false => { + println!("It was not empty") + }, + } +} + +struct StateWithMutex { + m: Mutex, +} + +struct MutexGuardWrapper<'a> { + mg: MutexGuard<'a, u64>, +} + +impl<'a> MutexGuardWrapper<'a> { + fn get_the_value(&self) -> u64 { + *self.mg.deref() + } +} + +struct MutexGuardWrapperWrapper<'a> { + mg: MutexGuardWrapper<'a>, +} + +impl<'a> MutexGuardWrapperWrapper<'a> { + fn get_the_value(&self) -> u64 { + *self.mg.mg.deref() + } +} + +impl StateWithMutex { + fn lock_m(&self) -> MutexGuardWrapper<'_> { + MutexGuardWrapper { + mg: self.m.lock().unwrap(), + } + } + + fn lock_m_m(&self) -> MutexGuardWrapperWrapper<'_> { + MutexGuardWrapperWrapper { + mg: MutexGuardWrapper { + mg: self.m.lock().unwrap(), + }, + } + } + + fn foo(&self) -> bool { + true + } + + fn bar(&self) {} +} + +fn should_trigger_lint_with_wrapped_mutex() { + let s = StateWithMutex { m: Mutex::new(1) }; + + // Should trigger lint because a temporary contains a type with a significant drop and its + // lifetime is not obvious. Additionally, it is not obvious from looking at the scrutinee that + // the temporary contains such a type, making it potentially even more surprising. + match s.lock_m().get_the_value() { + 1 => { + println!("Got 1. Is it still 1?"); + println!("{}", s.lock_m().get_the_value()); + }, + 2 => { + println!("Got 2. Is it still 2?"); + println!("{}", s.lock_m().get_the_value()); + }, + _ => {}, + } + println!("All done!"); +} + +fn should_trigger_lint_with_double_wrapped_mutex() { + let s = StateWithMutex { m: Mutex::new(1) }; + + // Should trigger lint because a temporary contains a type which further contains a type with a + // significant drop and its lifetime is not obvious. Additionally, it is not obvious from + // looking at the scrutinee that the temporary contains such a type, making it potentially even + // more surprising. + match s.lock_m_m().get_the_value() { + 1 => { + println!("Got 1. Is it still 1?"); + println!("{}", s.lock_m().get_the_value()); + }, + 2 => { + println!("Got 2. Is it still 2?"); + println!("{}", s.lock_m().get_the_value()); + }, + _ => {}, + } + println!("All done!"); +} + +struct Counter { + i: AtomicU64, +} + +#[clippy::has_significant_drop] +struct CounterWrapper<'a> { + counter: &'a Counter, +} + +impl<'a> CounterWrapper<'a> { + fn new(counter: &Counter) -> CounterWrapper { + counter.i.fetch_add(1, Ordering::Relaxed); + CounterWrapper { counter } + } +} + +impl<'a> Drop for CounterWrapper<'a> { + fn drop(&mut self) { + self.counter.i.fetch_sub(1, Ordering::Relaxed); + } +} + +impl Counter { + fn temp_increment(&self) -> Vec { + vec![CounterWrapper::new(self), CounterWrapper::new(self)] + } +} + +fn should_trigger_lint_for_vec() { + let counter = Counter { i: AtomicU64::new(0) }; + + // Should trigger lint because the temporary in the scrutinee returns a collection of types + // which have significant drops. The types with significant drops are also non-obvious when + // reading the expression in the scrutinee. + match counter.temp_increment().len() { + 2 => { + let current_count = counter.i.load(Ordering::Relaxed); + println!("Current count {}", current_count); + assert_eq!(current_count, 0); + }, + 1 => {}, + 3 => {}, + _ => {}, + }; +} + +struct StateWithField { + s: String, +} + +// Should trigger lint only on the type in the tuple which is created using a temporary +// with a significant drop. Additionally, this test ensures that the format of the tuple +// is preserved correctly in the suggestion. +fn should_trigger_lint_for_tuple_in_scrutinee() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + + { + match (mutex1.lock().unwrap().s.len(), true) { + (3, _) => { + println!("started"); + mutex1.lock().unwrap().s.len(); + println!("done"); + }, + (_, _) => {}, + }; + + match (true, mutex1.lock().unwrap().s.len(), true) { + (_, 3, _) => { + println!("started"); + mutex1.lock().unwrap().s.len(); + println!("done"); + }, + (_, _, _) => {}, + }; + + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); + match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { + (3, _, 3) => { + println!("started"); + mutex1.lock().unwrap().s.len(); + mutex2.lock().unwrap().s.len(); + println!("done"); + }, + (_, _, _) => {}, + }; + + let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() }); + match mutex3.lock().unwrap().s.as_str() { + "three" => { + println!("started"); + mutex1.lock().unwrap().s.len(); + mutex2.lock().unwrap().s.len(); + println!("done"); + }, + _ => {}, + }; + + match (true, mutex3.lock().unwrap().s.as_str()) { + (_, "three") => { + println!("started"); + mutex1.lock().unwrap().s.len(); + mutex2.lock().unwrap().s.len(); + println!("done"); + }, + (_, _) => {}, + }; + } +} + +// Should trigger lint when either side of a binary operation creates a temporary with a +// significant drop. +// To avoid potential unnecessary copies or creating references that would trigger the significant +// drop problem, the lint recommends moving the entire binary operation. +fn should_trigger_lint_for_accessing_field_in_mutex_in_one_side_of_binary_op() { + let mutex = Mutex::new(StateWithField { s: "state".to_owned() }); + + match mutex.lock().unwrap().s.len() > 1 { + true => { + mutex.lock().unwrap().s.len(); + }, + false => {}, + }; + + match 1 < mutex.lock().unwrap().s.len() { + true => { + mutex.lock().unwrap().s.len(); + }, + false => {}, + }; +} + +// Should trigger lint when both sides of a binary operation creates a temporary with a +// significant drop. +// To avoid potential unnecessary copies or creating references that would trigger the significant +// drop problem, the lint recommends moving the entire binary operation. +fn should_trigger_lint_for_accessing_fields_in_mutex_in_both_sides_of_binary_op() { + let mutex1 = Mutex::new(StateWithField { s: "state".to_owned() }); + let mutex2 = Mutex::new(StateWithField { + s: "statewithfield".to_owned(), + }); + + match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + true => { + println!( + "{} < {}", + mutex1.lock().unwrap().s.len(), + mutex2.lock().unwrap().s.len() + ); + }, + false => {}, + }; + + match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { + true => { + println!( + "{} >= {}", + mutex1.lock().unwrap().s.len(), + mutex2.lock().unwrap().s.len() + ); + }, + false => {}, + }; +} + +fn should_not_trigger_lint_for_closure_in_scrutinee() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + + let get_mutex_guard = || mutex1.lock().unwrap().s.len(); + + // Should not trigger lint because the temporary with a significant drop will be dropped + // at the end of the closure, so the MutexGuard will be unlocked and not have a potentially + // surprising lifetime. + match get_mutex_guard() > 1 { + true => { + mutex1.lock().unwrap().s.len(); + }, + false => {}, + }; +} + +fn should_trigger_lint_for_return_from_closure_in_scrutinee() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + + let get_mutex_guard = || mutex1.lock().unwrap(); + + // Should trigger lint because the temporary with a significant drop is returned from the + // closure but not used directly in any match arms, so it has a potentially surprising lifetime. + match get_mutex_guard().s.len() > 1 { + true => { + mutex1.lock().unwrap().s.len(); + }, + false => {}, + }; +} + +fn should_trigger_lint_for_return_from_match_in_scrutinee() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); + + let i = 100; + + // Should trigger lint because the nested match within the scrutinee returns a temporary with a + // significant drop is but not used directly in any match arms, so it has a potentially + // surprising lifetime. + match match i { + 100 => mutex1.lock().unwrap(), + _ => mutex2.lock().unwrap(), + } + .s + .len() + > 1 + { + true => { + mutex1.lock().unwrap().s.len(); + }, + false => { + println!("nothing to do here"); + }, + }; +} + +fn should_trigger_lint_for_return_from_if_in_scrutinee() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); + + let i = 100; + + // Should trigger lint because the nested if-expression within the scrutinee returns a temporary + // with a significant drop is but not used directly in any match arms, so it has a potentially + // surprising lifetime. + match if i > 1 { + mutex1.lock().unwrap() + } else { + mutex2.lock().unwrap() + } + .s + .len() + > 1 + { + true => { + mutex1.lock().unwrap().s.len(); + }, + false => {}, + }; +} + +fn should_not_trigger_lint_for_if_in_scrutinee() { + let mutex = Mutex::new(StateWithField { s: "state".to_owned() }); + + let i = 100; + + // Should not trigger the lint because the temporary with a significant drop *is* dropped within + // the body of the if-expression nested within the match scrutinee, and therefore does not have + // a potentially surprising lifetime. + match if i > 1 { + mutex.lock().unwrap().s.len() > 1 + } else { + false + } { + true => { + mutex.lock().unwrap().s.len(); + }, + false => {}, + }; +} + +struct StateWithBoxedMutexGuard { + u: Mutex, +} + +impl StateWithBoxedMutexGuard { + fn new() -> StateWithBoxedMutexGuard { + StateWithBoxedMutexGuard { u: Mutex::new(42) } + } + fn lock(&self) -> Box> { + Box::new(self.u.lock().unwrap()) + } +} + +fn should_trigger_lint_for_boxed_mutex_guard() { + let s = StateWithBoxedMutexGuard::new(); + + // Should trigger lint because a temporary Box holding a type with a significant drop in a match + // scrutinee may have a potentially surprising lifetime. + match s.lock().deref().deref() { + 0 | 1 => println!("Value was less than 2"), + _ => println!("Value is {}", s.lock().deref()), + }; +} + +struct StateStringWithBoxedMutexGuard { + s: Mutex, +} + +impl StateStringWithBoxedMutexGuard { + fn new() -> StateStringWithBoxedMutexGuard { + StateStringWithBoxedMutexGuard { + s: Mutex::new("A String".to_owned()), + } + } + fn lock(&self) -> Box> { + Box::new(self.s.lock().unwrap()) + } +} + +fn should_trigger_lint_for_boxed_mutex_guard_holding_string() { + let s = StateStringWithBoxedMutexGuard::new(); + + let matcher = String::from("A String"); + + // Should trigger lint because a temporary Box holding a type with a significant drop in a match + // scrutinee may have a potentially surprising lifetime. + match s.lock().deref().deref() { + matcher => println!("Value is {}", s.lock().deref()), + _ => println!("Value was not a match"), + }; +} + +struct StateWithIntField { + i: u64, +} + +// Should trigger lint when either side of an assign expression contains a temporary with a +// significant drop, because the temporary's lifetime will be extended to the end of the match. +// To avoid potential unnecessary copies or creating references that would trigger the significant +// drop problem, the lint recommends moving the entire binary operation. +fn should_trigger_lint_in_assign_expr() { + let mutex = Mutex::new(StateWithIntField { i: 10 }); + + let mut i = 100; + + match mutex.lock().unwrap().i = i { + _ => { + println!("{}", mutex.lock().unwrap().i); + }, + }; + + match i = mutex.lock().unwrap().i { + _ => { + println!("{}", mutex.lock().unwrap().i); + }, + }; + + match mutex.lock().unwrap().i += 1 { + _ => { + println!("{}", mutex.lock().unwrap().i); + }, + }; + + match i += mutex.lock().unwrap().i { + _ => { + println!("{}", mutex.lock().unwrap().i); + }, + }; +} + +#[derive(Debug)] +enum RecursiveEnum { + Foo(Option>), +} + +#[derive(Debug)] +enum GenericRecursiveEnum { + Foo(T, Option>>), +} + +fn should_not_cause_stack_overflow() { + // Test that when a type recursively contains itself, a stack overflow does not occur when + // checking sub-types for significant drops. + let f = RecursiveEnum::Foo(Some(Box::new(RecursiveEnum::Foo(None)))); + match f { + RecursiveEnum::Foo(Some(f)) => { + println!("{:?}", f) + }, + RecursiveEnum::Foo(f) => { + println!("{:?}", f) + }, + } + + let f = GenericRecursiveEnum::Foo(1u64, Some(Box::new(GenericRecursiveEnum::Foo(2u64, None)))); + match f { + GenericRecursiveEnum::Foo(i, Some(f)) => { + println!("{} {:?}", i, f) + }, + GenericRecursiveEnum::Foo(i, f) => { + println!("{} {:?}", i, f) + }, + } +} + +fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr new file mode 100644 index 000000000..af1605649 --- /dev/null +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -0,0 +1,283 @@ +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:57:11 + | +LL | match mutex.lock().unwrap().foo() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::significant-drop-in-scrutinee` implied by `-D warnings` +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().foo(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:130:11 + | +LL | match s.lock_m().get_the_value() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = s.lock_m().get_the_value(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:151:11 + | +LL | match s.lock_m_m().get_the_value() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = s.lock_m_m().get_the_value(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:199:11 + | +LL | match counter.temp_increment().len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = counter.temp_increment().len(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:222:16 + | +LL | match (mutex1.lock().unwrap().s.len(), true) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match (value, true) { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:231:22 + | +LL | match (true, mutex1.lock().unwrap().s.len(), true) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match (true, value, true) { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:241:16 + | +LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match (value, true, mutex2.lock().unwrap().s.len()) { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:241:54 + | +LL | match (mutex1.lock().unwrap().s.len(), true, mutex2.lock().unwrap().s.len()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:252:15 + | +LL | match mutex3.lock().unwrap().s.as_str() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:262:22 + | +LL | match (true, mutex3.lock().unwrap().s.as_str()) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:281:11 + | +LL | match mutex.lock().unwrap().s.len() > 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().s.len() > 1; +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:288:11 + | +LL | match 1 < mutex.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = 1 < mutex.lock().unwrap().s.len(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:306:11 + | +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:317:11 + | +LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:352:11 + | +LL | match get_mutex_guard().s.len() > 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ let value = get_mutex_guard().s.len() > 1; +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:369:11 + | +LL | match match i { + | ___________^ +LL | | 100 => mutex1.lock().unwrap(), +LL | | _ => mutex2.lock().unwrap(), +LL | | } +LL | | .s +LL | | .len() +LL | | > 1 + | |___________^ + | +help: try moving the temporary above the match + | +LL ~ let value = match i { +LL + 100 => mutex1.lock().unwrap(), +LL + _ => mutex2.lock().unwrap(), +LL + } +LL + .s +LL + .len() + ... + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:395:11 + | +LL | match if i > 1 { + | ___________^ +LL | | mutex1.lock().unwrap() +LL | | } else { +LL | | mutex2.lock().unwrap() +... | +LL | | .len() +LL | | > 1 + | |___________^ + | +help: try moving the temporary above the match + | +LL ~ let value = if i > 1 { +LL + mutex1.lock().unwrap() +LL + } else { +LL + mutex2.lock().unwrap() +LL + } +LL + .s + ... + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:449:11 + | +LL | match s.lock().deref().deref() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match and create a copy + | +LL ~ let value = *s.lock().deref().deref(); +LL ~ match value { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:477:11 + | +LL | match s.lock().deref().deref() { + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:496:11 + | +LL | match mutex.lock().unwrap().i = i { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ mutex.lock().unwrap().i = i; +LL ~ match () { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:502:11 + | +LL | match i = mutex.lock().unwrap().i { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ i = mutex.lock().unwrap().i; +LL ~ match () { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:508:11 + | +LL | match mutex.lock().unwrap().i += 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ mutex.lock().unwrap().i += 1; +LL ~ match () { + | + +error: temporary with significant drop in match scrutinee + --> $DIR/significant_drop_in_scrutinee.rs:514:11 + | +LL | match i += mutex.lock().unwrap().i { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try moving the temporary above the match + | +LL ~ i += mutex.lock().unwrap().i; +LL ~ match () { + | + +error: aborting due to 23 previous errors + diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index d0a4cfb88..6f8c8e47d 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -67,13 +67,5 @@ LL | Self: Iterator, | = help: consider removing this trait bound -error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:99:23 - | -LL | fn impl_trait(_: impl AsRef, _: impl AsRef) {} - | ^^^^^^^^^^ - | - = help: consider removing this trait bound - -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/type_repetition_in_bounds.stderr b/tests/ui/type_repetition_in_bounds.stderr index abc25e594..148c19c7d 100644 --- a/tests/ui/type_repetition_in_bounds.stderr +++ b/tests/ui/type_repetition_in_bounds.stderr @@ -19,13 +19,5 @@ LL | Self: Copy + Default + Ord, | = help: consider combining the bounds: `Self: Clone + Copy + Default + Ord` -error: this type has already been used as a bound predicate - --> $DIR/type_repetition_in_bounds.rs:83:43 - | -LL | fn impl_trait(_: impl AsRef, _: impl AsRef) {} - | ^^^^^^^^^^ - | - = help: consider combining the bounds: `impl AsRef: AsRef + AsRef` - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors