diff options
| author | Matthias Krüger <matthias.krueger@famsik.de> | 2024-02-09 19:21:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-09 19:21:17 +0100 |
| commit | 5250abaeb84df7407bf9a2d887cbd30e1b2f96d0 (patch) | |
| tree | 369412322fc24f7eb2ab3098a5fe63290d10c64a /src/tools/clippy/clippy_utils | |
| parent | c874b1a42435e2a7ec249861271aad18350d7af6 (diff) | |
| parent | 4ec9eec9e83fb6a19b899247357e3d5dabfebc78 (diff) | |
| download | rust-5250abaeb84df7407bf9a2d887cbd30e1b2f96d0.tar.gz rust-5250abaeb84df7407bf9a2d887cbd30e1b2f96d0.zip | |
Rollup merge of #120806 - flip1995:clippy-subtree-update, r=Manishearth
Clippy subtree update r? `@Manishearth`
Diffstat (limited to 'src/tools/clippy/clippy_utils')
| -rw-r--r-- | src/tools/clippy/clippy_utils/Cargo.toml | 2 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/lib.rs | 359 | ||||
| -rw-r--r-- | src/tools/clippy/clippy_utils/src/ty.rs | 29 |
3 files changed, 211 insertions, 179 deletions
diff --git a/src/tools/clippy/clippy_utils/Cargo.toml b/src/tools/clippy/clippy_utils/Cargo.toml index b8869eedf52..c7454fa3328 100644 --- a/src/tools/clippy/clippy_utils/Cargo.toml +++ b/src/tools/clippy/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.77" +version = "0.1.78" edition = "2021" publish = false diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs index d2bc49d1e29..6a33e11be46 100644 --- a/src/tools/clippy/clippy_utils/src/lib.rs +++ b/src/tools/clippy/clippy_utils/src/lib.rs @@ -1,5 +1,6 @@ #![feature(array_chunks)] #![feature(box_patterns)] +#![feature(control_flow_enum)] #![feature(if_let_guard)] #![feature(let_chains)] #![feature(lint_reasons)] @@ -81,6 +82,7 @@ use core::mem; use core::ops::ControlFlow; use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; +use std::iter::{once, repeat}; use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; @@ -90,6 +92,7 @@ use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnhashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE}; +use rustc_hir::definitions::{DefPath, DefPathData}; use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; @@ -108,8 +111,8 @@ use rustc_middle::ty::binding::BindingMode; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ - self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut, - TypeVisitableExt, UintTy, UpvarCapture, + self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv, + ParamEnvAnd, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; @@ -120,10 +123,7 @@ use visitors::Visitable; use crate::consts::{constant, mir_to_const, Constant}; use crate::higher::Range; -use crate::ty::{ - adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, - ty_is_fn_once_param, -}; +use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr; use rustc_middle::hir::nested_filter; @@ -1359,46 +1359,12 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>( for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) { match node { Node::Expr(e) => match e.kind { - ExprKind::Closure { .. } => { + ExprKind::Closure { .. } if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind() - && subs.as_closure().kind() == ClosureKind::FnOnce - { - continue; - } - let is_once = walk_to_expr_usage(cx, e, |node, id| { - let Node::Expr(e) = node else { - return None; - }; - match e.kind { - ExprKind::Call(f, _) if f.hir_id == id => Some(()), - ExprKind::Call(f, args) => { - let i = args.iter().position(|arg| arg.hir_id == id)?; - let sig = expr_sig(cx, f)?; - let predicates = sig - .predicates_id() - .map_or(cx.param_env, |id| cx.tcx.param_env(id)) - .caller_bounds(); - sig.input(i).and_then(|ty| { - ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(()) - }) - }, - ExprKind::MethodCall(_, receiver, args, _) => { - let i = std::iter::once(receiver) - .chain(args.iter()) - .position(|arg| arg.hir_id == id)?; - let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?; - let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_binder().inputs()[i]; - ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(()) - }, - _ => None, - } - }) - .is_some(); - if !is_once { - return Some(e); - } - }, - ExprKind::Loop(..) => return Some(e), + && subs.as_closure().kind() == ClosureKind::FnOnce => {}, + + // Note: A closure's kind is determined by how it's used, not it's captures. + ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e), _ => (), }, Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), @@ -2596,26 +2562,30 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") } -/// Walks the HIR tree from the given expression, up to the node where the value produced by the -/// expression is consumed. Calls the function for every node encountered this way until it returns -/// `Some`. +/// Walks up the HIR tree from the given expression in an attempt to find where the value is +/// consumed. +/// +/// Termination has three conditions: +/// - The given function returns `Break`. This function will return the value. +/// - The consuming node is found. This function will return `Continue(use_node, child_id)`. +/// - No further parent nodes are found. This will trigger a debug assert or return `None`. /// -/// This allows walking through `if`, `match`, `break`, block expressions to find where the value -/// produced by the expression is consumed. +/// This allows walking through `if`, `match`, `break`, and block expressions to find where the +/// value produced by the expression is consumed. pub fn walk_to_expr_usage<'tcx, T>( cx: &LateContext<'tcx>, e: &Expr<'tcx>, - mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>, -) -> Option<T> { + mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>, +) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> { let map = cx.tcx.hir(); let mut iter = map.parent_iter(e.hir_id); let mut child_id = e.hir_id; while let Some((parent_id, parent)) = iter.next() { - if let Some(x) = f(parent, child_id) { - return Some(x); + if let ControlFlow::Break(x) = f(parent_id, parent, child_id) { + return Some(ControlFlow::Break(x)); } - let parent = match parent { + let parent_expr = match parent { Node::Expr(e) => e, Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { child_id = parent_id; @@ -2625,18 +2595,19 @@ pub fn walk_to_expr_usage<'tcx, T>( child_id = parent_id; continue; }, - _ => return None, + _ => return Some(ControlFlow::Continue((parent, child_id))), }; - match parent.kind { + match parent_expr.kind { ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { child_id = id; iter = map.parent_iter(id); }, - ExprKind::Block(..) => child_id = parent_id, - _ => return None, + ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id, + _ => return Some(ControlFlow::Continue((parent, child_id))), } } + debug_assert!(false, "no parent node found for `{child_id:?}`"); None } @@ -2680,6 +2651,8 @@ pub enum ExprUseNode<'tcx> { Callee, /// Access of a field. FieldAccess(Ident), + Expr, + Other, } impl<'tcx> ExprUseNode<'tcx> { /// Checks if the value is returned from the function. @@ -2756,144 +2729,104 @@ impl<'tcx> ExprUseNode<'tcx> { let sig = cx.tcx.fn_sig(id).skip_binder(); Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) }, - Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None, + Self::Local(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None, } } } /// Gets the context an expression's value is used in. -#[expect(clippy::too_many_lines)] pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> { let mut adjustments = [].as_slice(); let mut is_ty_unified = false; let mut moved_before_use = false; let ctxt = e.span.ctxt(); - walk_to_expr_usage(cx, e, &mut |parent, child_id| { - // LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead. + walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| { if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir_node(child_id) { adjustments = cx.typeck_results().expr_adjustments(e); } - match parent { - Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt { - node: ExprUseNode::Local(l), - adjustments, - is_ty_unified, - moved_before_use, - }), + if cx.tcx.hir().span(parent_id).ctxt() != ctxt { + return ControlFlow::Break(()); + } + if let Node::Expr(e) = parent { + match e.kind { + ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { + is_ty_unified = true; + moved_before_use = true; + }, + ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { + is_ty_unified = true; + moved_before_use = true; + }, + ExprKind::Block(..) => moved_before_use = true, + _ => {}, + } + } + ControlFlow::Continue(()) + })? + .continue_value() + .map(|(use_node, child_id)| { + let node = match use_node { + Node::Local(l) => ExprUseNode::Local(l), + Node::ExprField(field) => ExprUseNode::Field(field), + Node::Item(&Item { kind: ItemKind::Static(..) | ItemKind::Const(..), owner_id, - span, .. }) | Node::TraitItem(&TraitItem { kind: TraitItemKind::Const(..), owner_id, - span, .. }) | Node::ImplItem(&ImplItem { kind: ImplItemKind::Const(..), owner_id, - span, .. - }) if span.ctxt() == ctxt => Some(ExprUseCtxt { - node: ExprUseNode::ConstStatic(owner_id), - adjustments, - is_ty_unified, - moved_before_use, - }), + }) => ExprUseNode::ConstStatic(owner_id), Node::Item(&Item { kind: ItemKind::Fn(..), owner_id, - span, .. }) | Node::TraitItem(&TraitItem { kind: TraitItemKind::Fn(..), owner_id, - span, .. }) | Node::ImplItem(&ImplItem { kind: ImplItemKind::Fn(..), owner_id, - span, .. - }) if span.ctxt() == ctxt => Some(ExprUseCtxt { - node: ExprUseNode::Return(owner_id), - adjustments, - is_ty_unified, - moved_before_use, - }), - - Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt { - node: ExprUseNode::Field(field), - adjustments, - is_ty_unified, - moved_before_use, - }), + }) => ExprUseNode::Return(owner_id), - Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { - ExprKind::Ret(_) => Some(ExprUseCtxt { - node: ExprUseNode::Return(OwnerId { - def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), - }), - adjustments, - is_ty_unified, - moved_before_use, - }), - ExprKind::Closure(closure) => Some(ExprUseCtxt { - node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }), - adjustments, - is_ty_unified, - moved_before_use, - }), - ExprKind::Call(func, args) => Some(ExprUseCtxt { - node: match args.iter().position(|arg| arg.hir_id == child_id) { - Some(i) => ExprUseNode::FnArg(func, i), - None => ExprUseNode::Callee, - }, - adjustments, - is_ty_unified, - moved_before_use, - }), - ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt { - node: ExprUseNode::MethodArg( - parent.hir_id, - name.args, - args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), - ), - adjustments, - is_ty_unified, - moved_before_use, - }), - ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt { - node: ExprUseNode::FieldAccess(name), - adjustments, - is_ty_unified, - moved_before_use, + Node::Expr(use_expr) => match use_expr.kind { + ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { + def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), }), - ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { - is_ty_unified = true; - moved_before_use = true; - None - }, - ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { - is_ty_unified = true; - moved_before_use = true; - None + ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), + ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) { + Some(i) => ExprUseNode::FnArg(func, i), + None => ExprUseNode::Callee, }, - ExprKind::Block(..) => { - moved_before_use = true; - None - }, - _ => None, + ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( + use_expr.hir_id, + name.args, + args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), + ), + ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name), + _ => ExprUseNode::Expr, }, - _ => None, + _ => ExprUseNode::Other, + }; + ExprUseCtxt { + node, + adjustments, + is_ty_unified, + moved_before_use, } }) } @@ -3270,3 +3203,131 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option< }) } } + +/// Produces a path from a local caller to the type of the called method. Suitable for user +/// output/suggestions. +/// +/// Returned path can be either absolute (for methods defined non-locally), or relative (for local +/// methods). +pub fn get_path_from_caller_to_method_type<'tcx>( + tcx: TyCtxt<'tcx>, + from: LocalDefId, + method: DefId, + args: GenericArgsRef<'tcx>, +) -> String { + let assoc_item = tcx.associated_item(method); + let def_id = assoc_item.container_id(tcx); + match assoc_item.container { + rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id), + rustc_ty::ImplContainer => { + let ty = tcx.type_of(def_id).instantiate_identity(); + get_path_to_ty(tcx, from, ty, args) + }, + } +} + +fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String { + match ty.kind() { + rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()), + // TODO these types need to be recursively resolved as well + rustc_ty::Array(..) + | rustc_ty::Dynamic(..) + | rustc_ty::Never + | rustc_ty::RawPtr(_) + | rustc_ty::Ref(..) + | rustc_ty::Slice(_) + | rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)), + _ => ty.to_string(), + } +} + +/// Produce a path from some local caller to the callee. Suitable for user output/suggestions. +fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String { + // only search for a relative path if the call is fully local + if callee.is_local() { + let callee_path = tcx.def_path(callee); + let caller_path = tcx.def_path(from.to_def_id()); + maybe_get_relative_path(&caller_path, &callee_path, 2) + } else { + tcx.def_path_str(callee) + } +} + +/// Tries to produce a relative path from `from` to `to`; if such a path would contain more than +/// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in +/// the local crate. +/// +/// Suitable for user output/suggestions. +/// +/// This ignores use items, and assumes that the target path is visible from the source +/// path (which _should_ be a reasonable assumption since we in order to be able to use an object of +/// certain type T, T is required to be visible). +/// +/// TODO make use of `use` items. Maybe we should have something more sophisticated like +/// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27> +fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String { + use itertools::EitherOrBoth::{Both, Left, Right}; + + // 1. skip the segments common for both paths (regardless of their type) + let unique_parts = to + .data + .iter() + .zip_longest(from.data.iter()) + .skip_while(|el| matches!(el, Both(l, r) if l == r)) + .map(|el| match el { + Both(l, r) => Both(l.data, r.data), + Left(l) => Left(l.data), + Right(r) => Right(r.data), + }); + + // 2. for the remaning segments, construct relative path using only mod names and `super` + let mut go_up_by = 0; + let mut path = Vec::new(); + for el in unique_parts { + match el { + Both(l, r) => { + // consider: + // a::b::sym:: :: refers to + // c::d::e ::f::sym + // result should be super::super::c::d::e::f + // + // alternatively: + // a::b::c ::d::sym refers to + // e::f::sym:: :: + // result should be super::super::super::super::e::f + if let DefPathData::TypeNs(s) = l { + path.push(s.to_string()); + } + if let DefPathData::TypeNs(_) = r { + go_up_by += 1; + } + }, + // consider: + // a::b::sym:: :: refers to + // c::d::e ::f::sym + // when looking at `f` + Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()), + // consider: + // a::b::c ::d::sym refers to + // e::f::sym:: :: + // when looking at `d` + Right(DefPathData::TypeNs(_)) => go_up_by += 1, + _ => {}, + } + } + + if go_up_by > max_super { + // `super` chain would be too long, just use the absolute path instead + once(String::from("crate")) + .chain(to.data.iter().filter_map(|el| { + if let DefPathData::TypeNs(sym) = el.data { + Some(sym.to_string()) + } else { + None + } + })) + .join("::") + } else { + repeat(String::from("super")).take(go_up_by).chain(path).join("::") + } +} diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index 11bb16ff6c5..6762c883005 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -1000,35 +1000,6 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option< } } -/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`. -pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [ty::Clause<'_>]) -> bool { - let ty::Param(ty) = *ty.kind() else { - return false; - }; - let lang = tcx.lang_items(); - let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait()) - else { - return false; - }; - predicates - .iter() - .try_fold(false, |found, p| { - if let ty::ClauseKind::Trait(p) = p.kind().skip_binder() - && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() - && ty.index == self_ty.index - { - // This should use `super_traits_of`, but that's a private function. - if p.trait_ref.def_id == fn_once_id { - return Some(true); - } else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id { - return None; - } - } - Some(found) - }) - .unwrap_or(false) -} - /// Comes up with an "at least" guesstimate for the type's size, not taking into /// account the layout of type parameters. pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { |
