Skip to content

Commit e27f838

Browse files
committed
Rework redundant_closure
* Better track when a early-bound region appears when a late-bound region is required * Don't lint when the closure gives explicit types.
1 parent c7e6863 commit e27f838

File tree

5 files changed

+300
-111
lines changed

5 files changed

+300
-111
lines changed

clippy_lints/src/eta_reduction.rs

+178-110
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
22
use clippy_utils::higher::VecArgs;
33
use clippy_utils::source::snippet_opt;
4-
use clippy_utils::ty::is_type_diagnostic_item;
4+
use clippy_utils::ty::type_diagnostic_name;
55
use clippy_utils::usage::local_used_after_expr;
66
use clippy_utils::{get_enclosing_loop_or_closure, higher, path_to_local, path_to_local_id};
7-
use if_chain::if_chain;
87
use rustc_errors::Applicability;
98
use rustc_hir::def_id::DefId;
10-
use rustc_hir::{Expr, ExprKind, Param, PatKind, Unsafety};
9+
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, TyKind, Unsafety};
1110
use rustc_lint::{LateContext, LateLintPass};
12-
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow};
13-
use rustc_middle::ty::binding::BindingMode;
14-
use rustc_middle::ty::subst::Subst;
15-
use rustc_middle::ty::{self, ClosureKind, Ty, TypeFoldable};
11+
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
12+
use rustc_middle::ty::{self, ClosureKind, ClosureSubsts, FnSig, Region, RegionKind, Ty, TypeFoldable, TypeckResults};
1613
use rustc_session::{declare_lint_pass, declare_tool_lint};
1714
use rustc_span::symbol::sym;
15+
use rustc_target::spec::abi::Abi;
1816

1917
declare_clippy_lint! {
2018
/// ### What it does
@@ -78,7 +76,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
7876
return;
7977
}
8078
let body = match expr.kind {
81-
ExprKind::Closure(_, _, id, _, _) => cx.tcx.hir().body(id),
79+
ExprKind::Closure(_, decl, id, _, _)
80+
if decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer))
81+
&& matches!(decl.output, FnRetTy::DefaultReturn(_)) =>
82+
{
83+
cx.tcx.hir().body(id)
84+
},
8285
_ => return,
8386
};
8487
if body.value.span.from_expansion() {
@@ -100,122 +103,187 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
100103
return;
101104
}
102105

103-
let closure_ty = cx.typeck_results().expr_ty(expr);
104-
105-
if_chain!(
106-
if let ExprKind::Call(callee, args) = body.value.kind;
107-
if let ExprKind::Path(_) = callee.kind;
108-
if check_inputs(cx, body.params, args);
109-
let callee_ty = cx.typeck_results().expr_ty_adjusted(callee);
110-
let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id)
111-
.map_or(callee_ty, |id| cx.tcx.type_of(id));
112-
if check_sig(cx, closure_ty, call_ty);
113-
let substs = cx.typeck_results().node_substs(callee.hir_id);
114-
// This fixes some false positives that I don't entirely understand
115-
if substs.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions();
116-
// A type param function ref like `T::f` is not 'static, however
117-
// it is if cast like `T::f as fn()`. This seems like a rustc bug.
118-
if !substs.types().any(|t| matches!(t.kind(), ty::Param(_)));
119-
let callee_ty_unadjusted = cx.typeck_results().expr_ty(callee).peel_refs();
120-
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc);
121-
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc);
122-
then {
123-
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
124-
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
125-
if_chain! {
126-
if let ty::Closure(_, substs) = callee_ty.peel_refs().kind();
127-
if substs.as_closure().kind() == ClosureKind::FnMut;
128-
if get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
129-
|| path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, callee));
106+
let typeck = cx.typeck_results();
107+
let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() {
108+
closure_subs.as_closure()
109+
} else {
110+
return;
111+
};
130112

131-
then {
132-
// Mutable closure is used after current expr; we cannot consume it.
133-
snippet = format!("&mut {}", snippet);
134-
}
113+
match body.value.kind {
114+
ExprKind::Call(callee, args)
115+
if matches!(callee.kind, ExprKind::Path(..))
116+
&& !matches!(
117+
type_diagnostic_name(cx, typeck.expr_ty(callee).peel_refs()),
118+
Some(sym::Arc | sym::Rc)
119+
)
120+
&& check_inputs(typeck, body.params, args) =>
121+
{
122+
let sig = match *typeck.expr_ty_adjusted(callee).kind() {
123+
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder(),
124+
ty::FnPtr(sig) => sig.skip_binder(),
125+
ty::Closure(_, subs) => cx
126+
.tcx
127+
.signature_unclosure(subs.as_closure().sig(), Unsafety::Normal)
128+
.skip_binder(),
129+
_ => {
130+
if typeck.type_dependent_def_id(body.value.hir_id).is_some()
131+
&& let subs = typeck.node_substs(body.value.hir_id)
132+
&& let output = typeck.expr_ty(&body.value)
133+
&& let ty::Tuple(tys) = *subs.type_at(1).kind()
134+
{
135+
cx.tcx.mk_fn_sig(tys.into_iter(), output, false, Unsafety::Normal, Abi::Rust)
136+
} else {
137+
return;
135138
}
136-
diag.span_suggestion(
137-
expr.span,
138-
"replace the closure with the function itself",
139-
snippet,
140-
Applicability::MachineApplicable,
141-
);
142-
}
143-
});
144-
}
145-
);
146-
147-
if_chain!(
148-
if let ExprKind::MethodCall(path, args, _) = body.value.kind;
149-
if check_inputs(cx, body.params, args);
150-
let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();
151-
let substs = cx.typeck_results().node_substs(body.value.hir_id);
152-
let call_ty = cx.tcx.type_of(method_def_id).subst(cx.tcx, substs);
153-
if check_sig(cx, closure_ty, call_ty);
154-
then {
155-
span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
156-
let name = get_ufcs_type_name(cx, method_def_id);
157-
diag.span_suggestion(
139+
},
140+
};
141+
if check_sig(cx, closure, sig)
142+
// Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
143+
// `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
144+
// in a type which is `'static`.
145+
// For now ignore all callee types which reference a type parameter.
146+
&& !typeck.node_substs(callee.hir_id).types().any(|t| matches!(t.kind(), ty::Param(_)))
147+
{
148+
span_lint_and_then(
149+
cx,
150+
REDUNDANT_CLOSURE,
158151
expr.span,
159-
"replace the closure with the method itself",
160-
format!("{}::{}", name, path.ident.name),
161-
Applicability::MachineApplicable,
152+
"redundant closure",
153+
|diag| {
154+
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
155+
if let ty::Closure(_, substs) = *typeck.expr_ty(callee).peel_refs().kind()
156+
&& substs.as_closure().kind() == ClosureKind::FnMut
157+
&& (
158+
get_enclosing_loop_or_closure(cx.tcx, expr).is_some()
159+
|| path_to_local(callee)
160+
.map_or(false, |l| local_used_after_expr(cx, l, callee))
161+
)
162+
{
163+
// Mutable closure is used after current expr; we cannot consume it.
164+
snippet = format!("&mut {}", snippet);
165+
}
166+
diag.span_suggestion(
167+
expr.span,
168+
"replace the closure with the function itself",
169+
snippet,
170+
Applicability::MachineApplicable,
171+
);
172+
}
173+
}
162174
);
163-
})
164-
}
165-
);
175+
}
176+
},
177+
ExprKind::MethodCall(path, args, _) if check_inputs(typeck, body.params, args) => {
178+
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
179+
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder())
180+
{
181+
span_lint_and_then(
182+
cx,
183+
REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
184+
expr.span,
185+
"redundant closure",
186+
|diag| {
187+
let name = get_ufcs_type_name(cx, method_def_id);
188+
diag.span_suggestion(
189+
expr.span,
190+
"replace the closure with the method itself",
191+
format!("{}::{}", name, path.ident.name),
192+
Applicability::MachineApplicable,
193+
);
194+
},
195+
)
196+
}
197+
},
198+
_ => return,
199+
}
166200
}
167201
}
168202

169-
fn check_inputs(cx: &LateContext<'_>, params: &[Param<'_>], call_args: &[Expr<'_>]) -> bool {
170-
if params.len() != call_args.len() {
171-
return false;
203+
fn check_inputs(typeck: &TypeckResults<'_>, params: &[Param<'_>], call_args: &[Expr<'_>]) -> bool {
204+
params.len() == call_args.len()
205+
&& params.iter().zip(call_args).all(|(p, arg)| {
206+
matches!(
207+
p.pat.kind,PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
208+
if path_to_local_id(arg, id)
209+
)
210+
// Only allow adjustments which change regions (i.e. re-borrowing).
211+
&& typeck
212+
.expr_adjustments(arg)
213+
.last()
214+
.map_or(true, |a| a.target == typeck.expr_ty(arg))
215+
})
216+
}
217+
218+
fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureSubsts<'tcx>, call_sig: FnSig<'_>) -> bool {
219+
call_sig.unsafety == Unsafety::Normal
220+
&& !has_late_bound_to_non_late_bound_regions(
221+
cx.tcx
222+
.signature_unclosure(closure.sig(), Unsafety::Normal)
223+
.skip_binder(),
224+
call_sig,
225+
)
226+
}
227+
228+
/// This walks through both signatures and checks for any time a late-bound region is expected by an
229+
/// `impl Fn` type, but the target signature does not have a late-bound region in the same position.
230+
///
231+
/// This is needed because rustc is unable to late bind early-bound regions in a function signature.
232+
fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool {
233+
fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
234+
matches!(from_region.kind(), RegionKind::ReLateBound(..))
235+
&& !matches!(to_region.kind(), RegionKind::ReLateBound(..))
172236
}
173-
let binding_modes = cx.typeck_results().pat_binding_modes();
174-
std::iter::zip(params, call_args).all(|(param, arg)| {
175-
match param.pat.kind {
176-
PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {},
177-
_ => return false,
178-
}
179-
// checks that parameters are not bound as `ref` or `ref mut`
180-
if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) {
181-
return false;
182-
}
183237

184-
match *cx.typeck_results().expr_adjustments(arg) {
185-
[] => true,
186-
[
187-
Adjustment {
188-
kind: Adjust::Deref(None),
189-
..
238+
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool {
239+
if from_subs.len() != to_subs.len() {
240+
return true;
241+
}
242+
for (from_arg, to_arg) in to_subs.iter().zip(from_subs) {
243+
match (from_arg.unpack(), to_arg.unpack()) {
244+
(GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => {
245+
if check_region(from_region, to_region) {
246+
return true;
247+
}
190248
},
191-
Adjustment {
192-
kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)),
193-
..
249+
(GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) => {
250+
if check_ty(from_ty, to_ty) {
251+
return true;
252+
}
194253
},
195-
] => {
196-
// re-borrow with the same mutability is allowed
197-
let ty = cx.typeck_results().expr_ty(arg);
198-
matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into())
199-
},
200-
_ => false,
254+
(GenericArgKind::Const(_), GenericArgKind::Const(_)) => (),
255+
_ => return true,
256+
}
201257
}
202-
})
203-
}
204-
205-
fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool {
206-
let call_sig = call_ty.fn_sig(cx.tcx);
207-
if call_sig.unsafety() == Unsafety::Unsafe {
208-
return false;
258+
false
209259
}
210-
if !closure_ty.has_late_bound_regions() {
211-
return true;
260+
261+
fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool {
262+
match (from_ty.kind(), to_ty.kind()) {
263+
(&ty::Adt(_, from_subs), &ty::Adt(_, to_subs)) => check_subs(from_subs, to_subs),
264+
(&ty::Array(from_ty, _), &ty::Array(to_ty, _)) | (&ty::Slice(from_ty), &ty::Slice(to_ty)) => {
265+
check_ty(from_ty, to_ty)
266+
},
267+
(&ty::Ref(from_region, from_ty, _), &ty::Ref(to_region, to_ty, _)) => {
268+
check_region(from_region, to_region) || check_ty(from_ty, to_ty)
269+
},
270+
(&ty::Tuple(from_tys), &ty::Tuple(to_tys)) => {
271+
from_tys.len() != to_tys.len()
272+
|| from_tys
273+
.iter()
274+
.zip(to_tys)
275+
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
276+
},
277+
_ => from_ty.has_late_bound_regions(),
278+
}
212279
}
213-
let substs = match closure_ty.kind() {
214-
ty::Closure(_, substs) => substs,
215-
_ => return false,
216-
};
217-
let closure_sig = cx.tcx.signature_unclosure(substs.as_closure().sig(), Unsafety::Normal);
218-
cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)
280+
281+
assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len());
282+
from_sig
283+
.inputs_and_output
284+
.iter()
285+
.zip(to_sig.inputs_and_output)
286+
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
219287
}
220288

221289
fn get_ufcs_type_name(cx: &LateContext<'_>, method_def_id: DefId) -> String {

clippy_utils/src/ty.rs

+5
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,11 @@ pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangI
303303
}
304304
}
305305

306+
/// Gets the diagnostic name of the type, if it has one
307+
pub fn type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> {
308+
ty.ty_adt_def().and_then(|adt| cx.tcx.get_diagnostic_name(adt.did()))
309+
}
310+
306311
/// Return `true` if the passed `typ` is `isize` or `usize`.
307312
pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
308313
matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))

0 commit comments

Comments
 (0)