Skip to content

Commit b3d5377

Browse files
modelflatblyxyas
andcommittedJan 29, 2024
Make redundant_closure_for_method_calls suggest relative paths
Fixes #10854. Co-authored-by: Alejandra González <blyxyas@gmail.com>
1 parent 8ccf6a6 commit b3d5377

File tree

5 files changed

+271
-33
lines changed

5 files changed

+271
-33
lines changed
 

Diff for: ‎clippy_lints/src/eta_reduction.rs

+6-30
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs;
33
use clippy_utils::source::snippet_opt;
44
use clippy_utils::ty::type_diagnostic_name;
55
use clippy_utils::usage::{local_used_after_expr, local_used_in};
6-
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id};
6+
use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id};
77
use rustc_errors::Applicability;
8-
use rustc_hir::def_id::DefId;
98
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety};
109
use rustc_infer::infer::TyCtxtInferExt;
1110
use rustc_lint::{LateContext, LateLintPass};
1211
use rustc_middle::ty::{
13-
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
14-
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
12+
self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind,
13+
Ty, TypeVisitableExt, TypeckResults,
1514
};
1615
use rustc_session::declare_lint_pass;
1716
use rustc_span::symbol::sym;
@@ -203,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
203202
"redundant closure",
204203
|diag| {
205204
let args = typeck.node_args(body.value.hir_id);
206-
let name = get_ufcs_type_name(cx, method_def_id, args);
205+
let caller = self_.hir_id.owner.def_id;
206+
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args);
207207
diag.span_suggestion(
208208
expr.span,
209209
"replace the closure with the method itself",
210-
format!("{}::{}", name, path.ident.name),
210+
format!("{}::{}", type_name, path.ident.name),
211211
Applicability::MachineApplicable,
212212
);
213213
},
@@ -309,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
309309
.zip(to_sig.inputs_and_output)
310310
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
311311
}
312-
313-
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String {
314-
let assoc_item = cx.tcx.associated_item(method_def_id);
315-
let def_id = assoc_item.container_id(cx.tcx);
316-
match assoc_item.container {
317-
ty::TraitContainer => cx.tcx.def_path_str(def_id),
318-
ty::ImplContainer => {
319-
let ty = cx.tcx.type_of(def_id).instantiate_identity();
320-
match ty.kind() {
321-
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
322-
ty::Array(..)
323-
| ty::Dynamic(..)
324-
| ty::Never
325-
| ty::RawPtr(_)
326-
| ty::Ref(..)
327-
| ty::Slice(_)
328-
| ty::Tuple(_) => {
329-
format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args))
330-
},
331-
_ => ty.to_string(),
332-
}
333-
},
334-
}
335-
}

Diff for: ‎clippy_utils/src/lib.rs

+132-2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ use core::mem;
7575
use core::ops::ControlFlow;
7676
use std::collections::hash_map::Entry;
7777
use std::hash::BuildHasherDefault;
78+
use std::iter::{once, repeat};
7879
use std::sync::{Mutex, MutexGuard, OnceLock};
7980

8081
use itertools::Itertools;
@@ -84,6 +85,7 @@ use rustc_data_structures::packed::Pu128;
8485
use rustc_data_structures::unhash::UnhashMap;
8586
use rustc_hir::def::{DefKind, Res};
8687
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
88+
use rustc_hir::definitions::{DefPath, DefPathData};
8789
use rustc_hir::hir_id::{HirIdMap, HirIdSet};
8890
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
8991
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
@@ -102,8 +104,8 @@ use rustc_middle::ty::binding::BindingMode;
102104
use rustc_middle::ty::fast_reject::SimplifiedType;
103105
use rustc_middle::ty::layout::IntegerExt;
104106
use rustc_middle::ty::{
105-
self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut,
106-
TypeVisitableExt, UintTy, UpvarCapture,
107+
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv,
108+
ParamEnvAnd, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture,
107109
};
108110
use rustc_span::hygiene::{ExpnKind, MacroKind};
109111
use rustc_span::source_map::SourceMap;
@@ -3264,3 +3266,131 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<
32643266
})
32653267
}
32663268
}
3269+
3270+
/// Produces a path from a local caller to the type of the called method. Suitable for user
3271+
/// output/suggestions.
3272+
///
3273+
/// Returned path can be either absolute (for methods defined non-locally), or relative (for local
3274+
/// methods).
3275+
pub fn get_path_from_caller_to_method_type<'tcx>(
3276+
tcx: TyCtxt<'tcx>,
3277+
from: LocalDefId,
3278+
method: DefId,
3279+
args: GenericArgsRef<'tcx>,
3280+
) -> String {
3281+
let assoc_item = tcx.associated_item(method);
3282+
let def_id = assoc_item.container_id(tcx);
3283+
match assoc_item.container {
3284+
rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id),
3285+
rustc_ty::ImplContainer => {
3286+
let ty = tcx.type_of(def_id).instantiate_identity();
3287+
get_path_to_ty(tcx, from, ty, args)
3288+
},
3289+
}
3290+
}
3291+
3292+
fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
3293+
match ty.kind() {
3294+
rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
3295+
// TODO these types need to be recursively resolved as well
3296+
rustc_ty::Array(..)
3297+
| rustc_ty::Dynamic(..)
3298+
| rustc_ty::Never
3299+
| rustc_ty::RawPtr(_)
3300+
| rustc_ty::Ref(..)
3301+
| rustc_ty::Slice(_)
3302+
| rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
3303+
_ => ty.to_string(),
3304+
}
3305+
}
3306+
3307+
/// Produce a path from some local caller to the callee. Suitable for user output/suggestions.
3308+
fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
3309+
// only search for a relative path if the call is fully local
3310+
if callee.is_local() {
3311+
let callee_path = tcx.def_path(callee);
3312+
let caller_path = tcx.def_path(from.to_def_id());
3313+
maybe_get_relative_path(&caller_path, &callee_path, 2)
3314+
} else {
3315+
tcx.def_path_str(callee)
3316+
}
3317+
}
3318+
3319+
/// Tries to produce a relative path from `from` to `to`; if such a path would contain more than
3320+
/// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in
3321+
/// the local crate.
3322+
///
3323+
/// Suitable for user output/suggestions.
3324+
///
3325+
/// This ignores use items, and assumes that the target path is visible from the source
3326+
/// path (which _should_ be a reasonable assumption since we in order to be able to use an object of
3327+
/// certain type T, T is required to be visible).
3328+
///
3329+
/// TODO make use of `use` items. Maybe we should have something more sophisticated like
3330+
/// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27>
3331+
fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
3332+
use itertools::EitherOrBoth::{Both, Left, Right};
3333+
3334+
// 1. skip the segments common for both paths (regardless of their type)
3335+
let unique_parts = to
3336+
.data
3337+
.iter()
3338+
.zip_longest(from.data.iter())
3339+
.skip_while(|el| matches!(el, Both(l, r) if l == r))
3340+
.map(|el| match el {
3341+
Both(l, r) => Both(l.data, r.data),
3342+
Left(l) => Left(l.data),
3343+
Right(r) => Right(r.data),
3344+
});
3345+
3346+
// 2. for the remaning segments, construct relative path using only mod names and `super`
3347+
let mut go_up_by = 0;
3348+
let mut path = Vec::new();
3349+
for el in unique_parts {
3350+
match el {
3351+
Both(l, r) => {
3352+
// consider:
3353+
// a::b::sym:: :: refers to
3354+
// c::d::e ::f::sym
3355+
// result should be super::super::c::d::e::f
3356+
//
3357+
// alternatively:
3358+
// a::b::c ::d::sym refers to
3359+
// e::f::sym:: ::
3360+
// result should be super::super::super::super::e::f
3361+
if let DefPathData::TypeNs(s) = l {
3362+
path.push(s.to_string());
3363+
}
3364+
if let DefPathData::TypeNs(_) = r {
3365+
go_up_by += 1;
3366+
}
3367+
},
3368+
// consider:
3369+
// a::b::sym:: :: refers to
3370+
// c::d::e ::f::sym
3371+
// when looking at `f`
3372+
Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()),
3373+
// consider:
3374+
// a::b::c ::d::sym refers to
3375+
// e::f::sym:: ::
3376+
// when looking at `d`
3377+
Right(DefPathData::TypeNs(_)) => go_up_by += 1,
3378+
_ => {},
3379+
}
3380+
}
3381+
3382+
if go_up_by > max_super {
3383+
// `super` chain would be too long, just use the absolute path instead
3384+
once(String::from("crate"))
3385+
.chain(to.data.iter().filter_map(|el| {
3386+
if let DefPathData::TypeNs(sym) = el.data {
3387+
Some(sym.to_string())
3388+
} else {
3389+
None
3390+
}
3391+
}))
3392+
.join("::")
3393+
} else {
3394+
repeat(String::from("super")).take(go_up_by).chain(path).join("::")
3395+
}
3396+
}

Diff for: ‎tests/ui/eta.fixed

+54
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,57 @@ fn _closure_with_types() {
417417
let _ = f2(|x: u32| f(x));
418418
let _ = f2(|x| -> u32 { f(x) });
419419
}
420+
421+
/// https://github.com/rust-lang/rust-clippy/issues/10854
422+
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
423+
mod issue_10854 {
424+
pub mod test_mod {
425+
pub struct Test;
426+
427+
impl Test {
428+
pub fn method(self) -> i32 {
429+
0
430+
}
431+
}
432+
433+
pub fn calls_test(test: Option<Test>) -> Option<i32> {
434+
test.map(Test::method)
435+
}
436+
437+
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
438+
test.map(super::Outer::method)
439+
}
440+
}
441+
442+
pub struct Outer;
443+
444+
impl Outer {
445+
pub fn method(self) -> i32 {
446+
0
447+
}
448+
}
449+
450+
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
451+
test.map(test_mod::Test::method)
452+
}
453+
454+
mod a {
455+
pub mod b {
456+
pub mod c {
457+
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
458+
test.map(crate::issue_10854::d::Test::method)
459+
}
460+
}
461+
}
462+
}
463+
464+
mod d {
465+
pub struct Test;
466+
467+
impl Test {
468+
pub fn method(self) -> i32 {
469+
0
470+
}
471+
}
472+
}
473+
}

Diff for: ‎tests/ui/eta.rs

+54
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,57 @@ fn _closure_with_types() {
417417
let _ = f2(|x: u32| f(x));
418418
let _ = f2(|x| -> u32 { f(x) });
419419
}
420+
421+
/// https://github.com/rust-lang/rust-clippy/issues/10854
422+
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
423+
mod issue_10854 {
424+
pub mod test_mod {
425+
pub struct Test;
426+
427+
impl Test {
428+
pub fn method(self) -> i32 {
429+
0
430+
}
431+
}
432+
433+
pub fn calls_test(test: Option<Test>) -> Option<i32> {
434+
test.map(|t| t.method())
435+
}
436+
437+
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
438+
test.map(|t| t.method())
439+
}
440+
}
441+
442+
pub struct Outer;
443+
444+
impl Outer {
445+
pub fn method(self) -> i32 {
446+
0
447+
}
448+
}
449+
450+
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
451+
test.map(|t| t.method())
452+
}
453+
454+
mod a {
455+
pub mod b {
456+
pub mod c {
457+
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
458+
test.map(|t| t.method())
459+
}
460+
}
461+
}
462+
}
463+
464+
mod d {
465+
pub struct Test;
466+
467+
impl Test {
468+
pub fn method(self) -> i32 {
469+
0
470+
}
471+
}
472+
}
473+
}

Diff for: ‎tests/ui/eta.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,29 @@ error: redundant closure
166166
LL | let _ = f(&0, |x, y| f2(x, y));
167167
| ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2`
168168

169-
error: aborting due to 27 previous errors
169+
error: redundant closure
170+
--> $DIR/eta.rs:434:22
171+
|
172+
LL | test.map(|t| t.method())
173+
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method`
174+
175+
error: redundant closure
176+
--> $DIR/eta.rs:438:22
177+
|
178+
LL | test.map(|t| t.method())
179+
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method`
180+
181+
error: redundant closure
182+
--> $DIR/eta.rs:451:18
183+
|
184+
LL | test.map(|t| t.method())
185+
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method`
186+
187+
error: redundant closure
188+
--> $DIR/eta.rs:458:30
189+
|
190+
LL | test.map(|t| t.method())
191+
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method`
192+
193+
error: aborting due to 31 previous errors
170194

0 commit comments

Comments
 (0)