diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:11:38 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:13:23 +0000 |
commit | 20431706a863f92cb37dc512fef6e48d192aaf2c (patch) | |
tree | 2867f13f5fd5437ba628c67d7f87309ccadcd286 /src/tools/clippy/clippy_lints/src/dereference.rs | |
parent | Releasing progress-linux version 1.65.0+dfsg1-2~progress7.99u1. (diff) | |
download | rustc-20431706a863f92cb37dc512fef6e48d192aaf2c.tar.xz rustc-20431706a863f92cb37dc512fef6e48d192aaf2c.zip |
Merging upstream version 1.66.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/dereference.rs')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/dereference.rs | 203 |
1 files changed, 144 insertions, 59 deletions
diff --git a/src/tools/clippy/clippy_lints/src/dereference.rs b/src/tools/clippy/clippy_lints/src/dereference.rs index 88e28018e..a37ee82d4 100644 --- a/src/tools/clippy/clippy_lints/src/dereference.rs +++ b/src/tools/clippy/clippy_lints/src/dereference.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; +use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res}; @@ -11,21 +12,24 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, - GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, - Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp, + self as hir, + def_id::{DefId, LocalDefId}, + BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem, + ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, + TraitItemKind, TyKind, UnOp, }; use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{ - self, subst::Subst, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, + self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind, ProjectionPredicate, Ty, TyCtxt, TypeVisitable, TypeckResults, }; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{symbol::sym, Span, Symbol, DUMMY_SP}; +use rustc_span::{symbol::sym, Span, Symbol}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause}; use std::collections::VecDeque; @@ -135,13 +139,13 @@ declare_clippy_lint! { /// let x = String::new(); /// let y: &str = &x; /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.64.0"] pub EXPLICIT_AUTO_DEREF, complexity, "dereferencing when the compiler would automatically dereference" } -impl_lint_pass!(Dereferencing => [ +impl_lint_pass!(Dereferencing<'_> => [ EXPLICIT_DEREF_METHODS, NEEDLESS_BORROW, REF_BINDING_TO_REFERENCE, @@ -149,7 +153,7 @@ impl_lint_pass!(Dereferencing => [ ]); #[derive(Default)] -pub struct Dereferencing { +pub struct Dereferencing<'tcx> { state: Option<(State, StateData)>, // While parsing a `deref` method call in ufcs form, the path to the function is itself an @@ -170,11 +174,16 @@ pub struct Dereferencing { /// e.g. `m!(x) | Foo::Bar(ref x)` ref_locals: FxIndexMap<HirId, Option<RefPat>>, + /// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by + /// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead + /// be moved. + possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, + // `IntoIterator` for arrays requires Rust 1.53. msrv: Option<RustcVersion>, } -impl Dereferencing { +impl<'tcx> Dereferencing<'tcx> { #[must_use] pub fn new(msrv: Option<RustcVersion>) -> Self { Self { @@ -184,6 +193,7 @@ impl Dereferencing { } } +#[derive(Debug)] struct StateData { /// Span of the top level expression span: Span, @@ -191,12 +201,14 @@ struct StateData { position: Position, } +#[derive(Debug)] struct DerefedBorrow { count: usize, msg: &'static str, snip_expr: Option<HirId>, } +#[derive(Debug)] enum State { // Any number of deref method calls. DerefMethod { @@ -241,7 +253,7 @@ struct RefPat { hir_id: HirId, } -impl<'tcx> LateLintPass<'tcx> for Dereferencing { +impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Skip path expressions from deref calls. e.g. `Deref::deref(e)` @@ -275,11 +287,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { match (self.state.take(), kind) { (None, kind) => { let expr_ty = typeck.expr_ty(expr); - let (position, adjustments) = walk_parents(cx, expr, self.msrv); - + let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv); match kind { RefOp::Deref => { - if let Position::FieldAccess(name) = position + if let Position::FieldAccess { + name, + of_union: false, + } = position && !ty_contains_field(typeck.expr_ty(sub_expr), name) { self.state = Some(( @@ -297,13 +311,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) && position.lint_explicit_deref() => { + let ty_changed_count = usize::from(!deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr))); self.state = Some(( State::DerefMethod { - ty_changed_count: if deref_method_same_type(expr_ty, typeck.expr_ty(sub_expr)) { - 0 - } else { - 1 - }, + ty_changed_count, is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), target_mut, }, @@ -454,7 +465,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { (Some((State::DerefedBorrow(state), data)), RefOp::Deref) => { let position = data.position; report(cx, expr, State::DerefedBorrow(state), data); - if let Position::FieldAccess(name) = position + if let Position::FieldAccess{name, ..} = position && !ty_contains_field(typeck.expr_ty(sub_expr), name) { self.state = Some(( @@ -548,6 +559,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { } fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { + if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| { + local_def_id == cx.tcx.hir().body_owner_def_id(body.id()) + }) { + self.possible_borrowers.pop(); + } + if Some(body.id()) == self.current_body { for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { let replacements = pat.replacements; @@ -619,14 +636,17 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool { } /// The position of an expression relative to it's parent. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum Position { MethodReceiver, /// The method is defined on a reference type. e.g. `impl Foo for &T` MethodReceiverRefImpl, Callee, ImplArg(HirId), - FieldAccess(Symbol), + FieldAccess { + name: Symbol, + of_union: bool, + }, // union fields cannot be auto borrowed Postfix, Deref, /// Any other location which will trigger auto-deref to a specific time. @@ -648,7 +668,10 @@ impl Position { } fn can_auto_borrow(self) -> bool { - matches!(self, Self::MethodReceiver | Self::FieldAccess(_) | Self::Callee) + matches!( + self, + Self::MethodReceiver | Self::FieldAccess { of_union: false, .. } | Self::Callee + ) } fn lint_explicit_deref(self) -> bool { @@ -660,7 +683,7 @@ impl Position { Self::MethodReceiver | Self::MethodReceiverRefImpl | Self::Callee - | Self::FieldAccess(_) + | Self::FieldAccess { .. } | Self::Postfix => PREC_POSTFIX, Self::ImplArg(_) | Self::Deref => PREC_PREFIX, Self::DerefStable(p, _) | Self::ReborrowStable(p) | Self::Other(p) => p, @@ -674,6 +697,7 @@ impl Position { #[expect(clippy::too_many_lines)] fn walk_parents<'tcx>( cx: &LateContext<'tcx>, + possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, e: &'tcx Expr<'_>, msrv: Option<RustcVersion>, ) -> (Position, &'tcx [Adjustment<'tcx>]) { @@ -691,47 +715,47 @@ fn walk_parents<'tcx>( }, Node::Item(&Item { kind: ItemKind::Static(..) | ItemKind::Const(..), - def_id, + owner_id, span, .. }) | Node::TraitItem(&TraitItem { kind: TraitItemKind::Const(..), - def_id, + owner_id, span, .. }) | Node::ImplItem(&ImplItem { kind: ImplItemKind::Const(..), - def_id, + owner_id, span, .. }) if span.ctxt() == ctxt => { - let ty = cx.tcx.type_of(def_id); + let ty = cx.tcx.type_of(owner_id.def_id); Some(ty_auto_deref_stability(cx, ty, precedence).position_for_result(cx)) }, Node::Item(&Item { kind: ItemKind::Fn(..), - def_id, + owner_id, span, .. }) | Node::TraitItem(&TraitItem { kind: TraitItemKind::Fn(..), - def_id, + owner_id, span, .. }) | Node::ImplItem(&ImplItem { kind: ImplItemKind::Fn(..), - def_id, + owner_id, span, .. }) if span.ctxt() == ctxt => { let output = cx .tcx - .erase_late_bound_regions(cx.tcx.fn_sig(def_id.to_def_id()).output()); + .erase_late_bound_regions(cx.tcx.fn_sig(owner_id.to_def_id()).output()); Some(ty_auto_deref_stability(cx, output, precedence).position_for_result(cx)) }, @@ -788,7 +812,16 @@ fn walk_parents<'tcx>( Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()), None => { if let ty::Param(param_ty) = ty.skip_binder().kind() { - needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv) + needless_borrow_impl_arg_position( + cx, + possible_borrowers, + parent, + i, + *param_ty, + e, + precedence, + msrv, + ) } else { ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence) .position_for_arg() @@ -823,11 +856,10 @@ fn walk_parents<'tcx>( // Trait methods taking `self` arg_ty } && impl_ty.is_ref() - && cx.tcx.infer_ctxt().enter(|infcx| - infcx - .type_implements_trait(trait_id, impl_ty, subs, cx.param_env) - .must_apply_modulo_regions() - ) + && let infcx = cx.tcx.infer_ctxt().build() + && infcx + .type_implements_trait(trait_id, impl_ty, subs, cx.param_env) + .must_apply_modulo_regions() { return Some(Position::MethodReceiverRefImpl) } @@ -836,7 +868,16 @@ fn walk_parents<'tcx>( args.iter().position(|arg| arg.hir_id == child_id).map(|i| { let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1]; if let ty::Param(param_ty) = ty.kind() { - needless_borrow_impl_arg_position(cx, parent, i + 1, *param_ty, e, precedence, msrv) + needless_borrow_impl_arg_position( + cx, + possible_borrowers, + parent, + i + 1, + *param_ty, + e, + precedence, + msrv, + ) } else { ty_auto_deref_stability( cx, @@ -847,7 +888,10 @@ fn walk_parents<'tcx>( } }) }, - ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess(name.name)), + ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(Position::FieldAccess { + name: name.name, + of_union: is_union(cx.typeck_results(), child), + }), ExprKind::Unary(UnOp::Deref, child) if child.hir_id == e.hir_id => Some(Position::Deref), ExprKind::Match(child, _, MatchSource::TryDesugar | MatchSource::AwaitDesugar) | ExprKind::Index(child, _) @@ -868,6 +912,13 @@ fn walk_parents<'tcx>( (position, adjustments) } +fn is_union<'tcx>(typeck: &'tcx TypeckResults<'_>, path_expr: &'tcx Expr<'_>) -> bool { + typeck + .expr_ty_adjusted(path_expr) + .ty_adt_def() + .map_or(false, rustc_middle::ty::AdtDef::is_union) +} + fn closure_result_position<'tcx>( cx: &LateContext<'tcx>, closure: &'tcx Closure<'_>, @@ -939,7 +990,7 @@ fn binding_ty_auto_deref_stability<'tcx>( cx.typeck_results().node_type(ty.ty.hir_id), binder_args, )) - .is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()), + .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), ) } }, @@ -954,7 +1005,7 @@ fn binding_ty_auto_deref_stability<'tcx>( cx.typeck_results().node_type(ty.ty.hir_id), binder_args, )) - .is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()), + .is_sized(cx.tcx, cx.param_env.without_caller_bounds()), ), TyKind::OpaqueDef(..) | TyKind::Infer | TyKind::Typeof(..) | TyKind::TraitObject(..) | TyKind::Err => { Position::ReborrowStable(precedence) @@ -1000,8 +1051,10 @@ fn ty_contains_infer(ty: &hir::Ty<'_>) -> bool { // If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`. // The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to // be moved, but it cannot be. +#[expect(clippy::too_many_arguments)] fn needless_borrow_impl_arg_position<'tcx>( cx: &LateContext<'tcx>, + possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, parent: &Expr<'tcx>, arg_index: usize, param_ty: ParamTy, @@ -1064,10 +1117,13 @@ fn needless_borrow_impl_arg_position<'tcx>( // elements are modified each time `check_referent` is called. let mut substs_with_referent_ty = substs_with_expr_ty.to_vec(); - let mut check_referent = |referent| { + let mut check_reference_and_referent = |reference, referent| { let referent_ty = cx.typeck_results().expr_ty(referent); - if !is_copy(cx, referent_ty) { + if !is_copy(cx, referent_ty) + && (referent_ty.has_significant_drop(cx.tcx, cx.param_env) + || !referent_used_exactly_once(cx, possible_borrowers, reference)) + { return false; } @@ -1101,15 +1157,14 @@ fn needless_borrow_impl_arg_position<'tcx>( let predicate = EarlyBinder(predicate).subst(cx.tcx, &substs_with_referent_ty); let obligation = Obligation::new(ObligationCause::dummy(), cx.param_env, predicate); - cx.tcx - .infer_ctxt() - .enter(|infcx| infcx.predicate_must_hold_modulo_regions(&obligation)) + let infcx = cx.tcx.infer_ctxt().build(); + infcx.predicate_must_hold_modulo_regions(&obligation) }) }; let mut needless_borrow = false; while let ExprKind::AddrOf(_, _, referent) = expr.kind { - if !check_referent(referent) { + if !check_reference_and_referent(expr, referent) { break; } expr = referent; @@ -1137,6 +1192,36 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool { }) } +fn referent_used_exactly_once<'tcx>( + cx: &LateContext<'tcx>, + possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, + reference: &Expr<'tcx>, +) -> bool { + let mir = enclosing_mir(cx.tcx, reference.hir_id); + if let Some(local) = expr_local(cx.tcx, reference) + && let [location] = *local_assignments(mir, local).as_slice() + && let Some(statement) = mir.basic_blocks[location.block].statements.get(location.statement_index) + && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind + && !place.has_deref() + { + let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id); + if possible_borrowers + .last() + .map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id) + { + possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir))); + } + let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1; + // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is + // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of + // itself. See the comment in that method for an explanation as to why. + possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location) + && used_exactly_once(mir, place.local).unwrap_or(false) + } else { + false + } +} + // Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting // projected type that is a type parameter. Returns `false` if replacing the types would have an // effect on the function signature beyond substituting `new_ty` for `param_ty`. @@ -1212,7 +1297,7 @@ impl<'tcx> TyPosition<'tcx> { fn position_for_result(self, cx: &LateContext<'tcx>) -> Position { match (self.position, self.ty) { (Position::ReborrowStable(precedence), Some(ty)) => { - Position::DerefStable(precedence, ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env)) + Position::DerefStable(precedence, ty.is_sized(cx.tcx, cx.param_env)) }, (position, _) => position, } @@ -1241,7 +1326,7 @@ fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedenc ty::Adt(..) if ty.has_placeholders() || ty.has_opaque_types() => { Position::ReborrowStable(precedence).into() }, - ty::Adt(_, substs) if substs.has_param_types_or_consts() => { + ty::Adt(_, substs) if substs.has_non_region_param() => { TyPosition::new_deref_stable_for_result(precedence, ty) }, ty::Bool @@ -1263,7 +1348,7 @@ fn ty_auto_deref_stability<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, precedenc | ty::Tuple(_) | ty::Projection(_) => Position::DerefStable( precedence, - ty.is_sized(cx.tcx.at(DUMMY_SP), cx.param_env.without_caller_bounds()), + ty.is_sized(cx.tcx, cx.param_env.without_caller_bounds()), ) .into(), }; @@ -1311,7 +1396,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data }; let expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX { - format!("({})", expr_str) + format!("({expr_str})") } else { expr_str.into_owned() }; @@ -1325,7 +1410,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data Mutability::Mut => "explicit `deref_mut` method call", }, "try this", - format!("{}{}{}", addr_of_str, deref_str, expr_str), + format!("{addr_of_str}{deref_str}{expr_str}"), app, ); }, @@ -1339,7 +1424,7 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data && !has_enclosing_paren(&snip) && (expr.precedence().order() < data.position.precedence() || calls_field) { - format!("({})", snip) + format!("({snip})") } else { snip.into() }; @@ -1382,9 +1467,9 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); let sugg = if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) { - format!("{}({})", prefix, snip) + format!("{prefix}({snip})") } else { - format!("{}{}", prefix, snip) + format!("{prefix}{snip}") }; diag.span_suggestion(data.span, "try this", sugg, app); }, @@ -1421,8 +1506,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data } } -impl Dereferencing { - fn check_local_usage<'tcx>(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) { +impl<'tcx> Dereferencing<'tcx> { + fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) { if let Some(outer_pat) = self.ref_locals.get_mut(&local) { if let Some(pat) = outer_pat { // Check for auto-deref @@ -1463,14 +1548,14 @@ impl Dereferencing { } else { pat.always_deref = false; let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0; - pat.replacements.push((e.span, format!("&{}", snip))); + pat.replacements.push((e.span, format!("&{snip}"))); } }, _ if !e.span.from_expansion() => { // Double reference might be needed at this point. pat.always_deref = false; let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app); - pat.replacements.push((e.span, format!("&{}", snip))); + pat.replacements.push((e.span, format!("&{snip}"))); }, // Edge case for macros. The span of the identifier will usually match the context of the // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc |