From b3d53774e7ffcde40b18e18be700f63204ec96bd Mon Sep 17 00:00:00 2001 From: modelflat Date: Mon, 21 Aug 2023 14:39:42 +0200 Subject: [PATCH] Make `redundant_closure_for_method_calls` suggest relative paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #10854. Co-authored-by: Alejandra González --- clippy_lints/src/eta_reduction.rs | 36 ++------ clippy_utils/src/lib.rs | 134 +++++++++++++++++++++++++++++- tests/ui/eta.fixed | 54 ++++++++++++ tests/ui/eta.rs | 54 ++++++++++++ tests/ui/eta.stderr | 26 +++++- 5 files changed, 271 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 1ea5b7898..40be71a0e 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; use clippy_utils::ty::type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; -use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id}; +use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id}; use rustc_errors::Applicability; -use rustc_hir::def_id::DefId; use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef, - ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, + self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind, + Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -203,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { "redundant closure", |diag| { let args = typeck.node_args(body.value.hir_id); - let name = get_ufcs_type_name(cx, method_def_id, args); + let caller = self_.hir_id.owner.def_id; + let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args); diag.span_suggestion( expr.span, "replace the closure with the method itself", - format!("{}::{}", name, path.ident.name), + format!("{}::{}", type_name, path.ident.name), Applicability::MachineApplicable, ); }, @@ -309,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<' .zip(to_sig.inputs_and_output) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) } - -fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String { - let assoc_item = cx.tcx.associated_item(method_def_id); - let def_id = assoc_item.container_id(cx.tcx); - match assoc_item.container { - ty::TraitContainer => cx.tcx.def_path_str(def_id), - ty::ImplContainer => { - let ty = cx.tcx.type_of(def_id).instantiate_identity(); - match ty.kind() { - ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()), - ty::Array(..) - | ty::Dynamic(..) - | ty::Never - | ty::RawPtr(_) - | ty::Ref(..) - | ty::Slice(_) - | ty::Tuple(_) => { - format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args)) - }, - _ => ty.to_string(), - } - }, - } -} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 4e499ff4c..74284c959 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -75,6 +75,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; @@ -84,6 +85,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}; @@ -102,8 +104,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; @@ -3264,3 +3266,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? +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/tests/ui/eta.fixed b/tests/ui/eta.fixed index 3aeb4dae3..da28ec2e6 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -417,3 +417,57 @@ fn _closure_with_types() { let _ = f2(|x: u32| f(x)); let _ = f2(|x| -> u32 { f(x) }); } + +/// https://github.com/rust-lang/rust-clippy/issues/10854 +/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative. +mod issue_10854 { + pub mod test_mod { + pub struct Test; + + impl Test { + pub fn method(self) -> i32 { + 0 + } + } + + pub fn calls_test(test: Option) -> Option { + test.map(Test::method) + } + + pub fn calls_outer(test: Option) -> Option { + test.map(super::Outer::method) + } + } + + pub struct Outer; + + impl Outer { + pub fn method(self) -> i32 { + 0 + } + } + + pub fn calls_into_mod(test: Option) -> Option { + test.map(test_mod::Test::method) + } + + mod a { + pub mod b { + pub mod c { + pub fn extreme_nesting(test: Option) -> Option { + test.map(crate::issue_10854::d::Test::method) + } + } + } + } + + mod d { + pub struct Test; + + impl Test { + pub fn method(self) -> i32 { + 0 + } + } + } +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index b9b3303b3..f924100f8 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -417,3 +417,57 @@ fn _closure_with_types() { let _ = f2(|x: u32| f(x)); let _ = f2(|x| -> u32 { f(x) }); } + +/// https://github.com/rust-lang/rust-clippy/issues/10854 +/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative. +mod issue_10854 { + pub mod test_mod { + pub struct Test; + + impl Test { + pub fn method(self) -> i32 { + 0 + } + } + + pub fn calls_test(test: Option) -> Option { + test.map(|t| t.method()) + } + + pub fn calls_outer(test: Option) -> Option { + test.map(|t| t.method()) + } + } + + pub struct Outer; + + impl Outer { + pub fn method(self) -> i32 { + 0 + } + } + + pub fn calls_into_mod(test: Option) -> Option { + test.map(|t| t.method()) + } + + mod a { + pub mod b { + pub mod c { + pub fn extreme_nesting(test: Option) -> Option { + test.map(|t| t.method()) + } + } + } + } + + mod d { + pub struct Test; + + impl Test { + pub fn method(self) -> i32 { + 0 + } + } + } +} diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index 7c4d2d709..945de466d 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -166,5 +166,29 @@ error: redundant closure LL | let _ = f(&0, |x, y| f2(x, y)); | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` -error: aborting due to 27 previous errors +error: redundant closure + --> $DIR/eta.rs:434:22 + | +LL | test.map(|t| t.method()) + | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method` + +error: redundant closure + --> $DIR/eta.rs:438:22 + | +LL | test.map(|t| t.method()) + | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method` + +error: redundant closure + --> $DIR/eta.rs:451:18 + | +LL | test.map(|t| t.method()) + | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method` + +error: redundant closure + --> $DIR/eta.rs:458:30 + | +LL | test.map(|t| t.method()) + | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method` + +error: aborting due to 31 previous errors