Auto merge of #8685 - Jarcho:redundant_closure_fixes, r=llogiq
`redundant_closure` fixes fixes #8548 A good chunk of the code is fixing false negatives. The old code banned any non late-bound regions from appearing in the callee's signature. The new version checks when the late-bound region is actually required. changelog: Better track when a early-bound region appears when a late-bound region is required in `redundant_closure`. changelog: Don't lint `redundant_closure` when the closure gives explicit types.
This commit is contained in:
commit
4c2f460dcc
@ -471,12 +471,12 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r
|
||||
if let Some(def_id) = trait_ref.trait_def_id();
|
||||
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
|
||||
let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id);
|
||||
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []);
|
||||
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]);
|
||||
// If all of our fields implement `Eq`, we can implement `Eq` too
|
||||
if adt
|
||||
.all_fields()
|
||||
.map(|f| f.ty(cx.tcx, args))
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, []));
|
||||
.all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]));
|
||||
then {
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
|
@ -1,19 +1,22 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
|
||||
use clippy_utils::higher::VecArgs;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
|
||||
use clippy_utils::usage::local_used_after_expr;
|
||||
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 if_chain::if_chain;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{Closure, Expr, ExprKind, Param, PatKind, Unsafety};
|
||||
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::adjustment::{Adjust, Adjustment, AutoBorrow};
|
||||
use rustc_middle::ty::binding::BindingMode;
|
||||
use rustc_middle::ty::{self, EarlyBinder, GenericArgsRef, Ty, TypeVisitableExt};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, BoundConstness, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind,
|
||||
GenericArgsRef, ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
|
||||
};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
@ -72,14 +75,18 @@ declare_clippy_lint! {
|
||||
declare_lint_pass!(EtaReduction => [REDUNDANT_CLOSURE, REDUNDANT_CLOSURE_FOR_METHOD_CALLS]);
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if expr.span.from_expansion() {
|
||||
let body = if let ExprKind::Closure(c) = expr.kind
|
||||
&& c.fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer))
|
||||
&& matches!(c.fn_decl.output, FnRetTy::DefaultReturn(_))
|
||||
&& !expr.span.from_expansion()
|
||||
{
|
||||
cx.tcx.hir().body(c.body)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let body = match expr.kind {
|
||||
ExprKind::Closure(&Closure { body, .. }) => cx.tcx.hir().body(body),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if body.value.span.from_expansion() {
|
||||
if body.params.is_empty() {
|
||||
if let Some(VecArgs::Vec(&[])) = higher::VecArgs::hir(cx, body.value) {
|
||||
@ -99,140 +106,209 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
|
||||
return;
|
||||
}
|
||||
|
||||
let closure_ty = cx.typeck_results().expr_ty(expr);
|
||||
let typeck = cx.typeck_results();
|
||||
let closure = if let ty::Closure(_, closure_subs) = typeck.expr_ty(expr).kind() {
|
||||
closure_subs.as_closure()
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
if_chain!(
|
||||
if !is_adjusted(cx, body.value);
|
||||
if let ExprKind::Call(callee, args) = body.value.kind;
|
||||
if let ExprKind::Path(_) = callee.kind;
|
||||
if check_inputs(cx, body.params, None, args);
|
||||
let callee_ty = cx.typeck_results().expr_ty_adjusted(callee);
|
||||
let call_ty = cx.typeck_results().type_dependent_def_id(body.value.hir_id)
|
||||
.map_or(callee_ty, |id| cx.tcx.type_of(id).instantiate_identity());
|
||||
if check_sig(cx, closure_ty, call_ty);
|
||||
let args = cx.typeck_results().node_args(callee.hir_id);
|
||||
// This fixes some false positives that I don't entirely understand
|
||||
if args.is_empty() || !cx.typeck_results().expr_ty(expr).has_late_bound_regions();
|
||||
// A type param function ref like `T::f` is not 'static, however
|
||||
// it is if cast like `T::f as fn()`. This seems like a rustc bug.
|
||||
if !args.types().any(|t| matches!(t.kind(), ty::Param(_)));
|
||||
let callee_ty_unadjusted = cx.typeck_results().expr_ty(callee).peel_refs();
|
||||
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Arc);
|
||||
if !is_type_diagnostic_item(cx, callee_ty_unadjusted, sym::Rc);
|
||||
if let ty::Closure(_, args) = *closure_ty.kind();
|
||||
// Don't lint if this is an inclusive range expression.
|
||||
// They desugar to a call to `RangeInclusiveNew` which would have odd suggestions. (#10684)
|
||||
if !matches!(higher::Range::hir(body.value), Some(higher::Range {
|
||||
start: Some(_),
|
||||
end: Some(_),
|
||||
limits: rustc_ast::RangeLimits::Closed
|
||||
}));
|
||||
then {
|
||||
span_lint_and_then(cx, REDUNDANT_CLOSURE, expr.span, "redundant closure", |diag| {
|
||||
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
|
||||
if let Some(fn_mut_id) = cx.tcx.lang_items().fn_mut_trait()
|
||||
&& let args = cx.tcx.erase_late_bound_regions(args.as_closure().sig()).inputs()
|
||||
&& implements_trait(
|
||||
cx,
|
||||
callee_ty.peel_refs(),
|
||||
fn_mut_id,
|
||||
&args.iter().copied().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
&& path_to_local(callee).map_or(false, |l| local_used_after_expr(cx, l, expr))
|
||||
if is_adjusted(cx, body.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
match body.value.kind {
|
||||
ExprKind::Call(callee, args)
|
||||
if matches!(callee.kind, ExprKind::Path(QPath::Resolved(..) | QPath::TypeRelative(..))) =>
|
||||
{
|
||||
let callee_ty = typeck.expr_ty(callee).peel_refs();
|
||||
if matches!(
|
||||
type_diagnostic_name(cx, callee_ty),
|
||||
Some(sym::Arc | sym::Rc)
|
||||
) || !check_inputs(typeck, body.params, None, args) {
|
||||
return;
|
||||
}
|
||||
let callee_ty_adjusted = typeck.expr_adjustments(callee).last().map_or(
|
||||
callee_ty,
|
||||
|a| a.target.peel_refs(),
|
||||
);
|
||||
|
||||
let sig = match callee_ty_adjusted.kind() {
|
||||
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(),
|
||||
ty::FnPtr(sig) => sig.skip_binder(),
|
||||
ty::Closure(_, subs) => cx
|
||||
.tcx
|
||||
.signature_unclosure(subs.as_closure().sig(), Unsafety::Normal)
|
||||
.skip_binder(),
|
||||
_ => {
|
||||
if typeck.type_dependent_def_id(body.value.hir_id).is_some()
|
||||
&& let subs = typeck.node_args(body.value.hir_id)
|
||||
&& let output = typeck.expr_ty(body.value)
|
||||
&& let ty::Tuple(tys) = *subs.type_at(1).kind()
|
||||
{
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
snippet = format!("&mut {snippet}");
|
||||
cx.tcx.mk_fn_sig(tys, output, false, Unsafety::Normal, Abi::Rust)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the function itself",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if_chain!(
|
||||
if !is_adjusted(cx, body.value);
|
||||
if let ExprKind::MethodCall(path, receiver, args, _) = body.value.kind;
|
||||
if check_inputs(cx, body.params, Some(receiver), args);
|
||||
let method_def_id = cx.typeck_results().type_dependent_def_id(body.value.hir_id).unwrap();
|
||||
let args = cx.typeck_results().node_args(body.value.hir_id);
|
||||
let call_ty = cx.tcx.type_of(method_def_id).instantiate(cx.tcx, args);
|
||||
if check_sig(cx, closure_ty, call_ty);
|
||||
then {
|
||||
span_lint_and_then(cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, expr.span, "redundant closure", |diag| {
|
||||
let name = get_ufcs_type_name(cx, method_def_id, args);
|
||||
diag.span_suggestion(
|
||||
},
|
||||
};
|
||||
if check_sig(cx, closure, sig)
|
||||
&& let generic_args = typeck.node_args(callee.hir_id)
|
||||
// Given some trait fn `fn f() -> ()` and some type `T: Trait`, `T::f` is not
|
||||
// `'static` unless `T: 'static`. The cast `T::f as fn()` will, however, result
|
||||
// in a type which is `'static`.
|
||||
// For now ignore all callee types which reference a type parameter.
|
||||
&& !generic_args.types().any(|t| matches!(t.kind(), ty::Param(_)))
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
REDUNDANT_CLOSURE,
|
||||
expr.span,
|
||||
"replace the closure with the method itself",
|
||||
format!("{name}::{}", path.ident.name),
|
||||
Applicability::MachineApplicable,
|
||||
"redundant closure",
|
||||
|diag| {
|
||||
if let Some(mut snippet) = snippet_opt(cx, callee.span) {
|
||||
if let Ok((ClosureKind::FnMut, _))
|
||||
= cx.tcx.infer_ctxt().build().type_implements_fn_trait(
|
||||
cx.param_env,
|
||||
Binder::bind_with_vars(callee_ty_adjusted, List::empty()),
|
||||
BoundConstness::NotConst,
|
||||
ImplPolarity::Positive,
|
||||
) && path_to_local(callee)
|
||||
.map_or(
|
||||
false,
|
||||
|l| local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr),
|
||||
)
|
||||
{
|
||||
// Mutable closure is used after current expr; we cannot consume it.
|
||||
snippet = format!("&mut {snippet}");
|
||||
}
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the function itself",
|
||||
snippet,
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
|
||||
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
|
||||
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
|
||||
{
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
REDUNDANT_CLOSURE_FOR_METHOD_CALLS,
|
||||
expr.span,
|
||||
"redundant closure",
|
||||
|diag| {
|
||||
let args = typeck.node_args(body.value.hir_id);
|
||||
let name = get_ufcs_type_name(cx, method_def_id, args);
|
||||
diag.span_suggestion(
|
||||
expr.span,
|
||||
"replace the closure with the method itself",
|
||||
format!("{}::{}", name, path.ident.name),
|
||||
Applicability::MachineApplicable,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_inputs(
|
||||
cx: &LateContext<'_>,
|
||||
typeck: &TypeckResults<'_>,
|
||||
params: &[Param<'_>],
|
||||
receiver: Option<&Expr<'_>>,
|
||||
call_args: &[Expr<'_>],
|
||||
self_arg: Option<&Expr<'_>>,
|
||||
args: &[Expr<'_>],
|
||||
) -> bool {
|
||||
if receiver.map_or(params.len() != call_args.len(), |_| params.len() != call_args.len() + 1) {
|
||||
return false;
|
||||
}
|
||||
let binding_modes = cx.typeck_results().pat_binding_modes();
|
||||
let check_inputs = |param: &Param<'_>, arg| {
|
||||
match param.pat.kind {
|
||||
PatKind::Binding(_, id, ..) if path_to_local_id(arg, id) => {},
|
||||
_ => return false,
|
||||
}
|
||||
// checks that parameters are not bound as `ref` or `ref mut`
|
||||
if let Some(BindingMode::BindByReference(_)) = binding_modes.get(param.pat.hir_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match *cx.typeck_results().expr_adjustments(arg) {
|
||||
[] => true,
|
||||
[
|
||||
Adjustment {
|
||||
kind: Adjust::Deref(None),
|
||||
..
|
||||
},
|
||||
Adjustment {
|
||||
kind: Adjust::Borrow(AutoBorrow::Ref(_, mu2)),
|
||||
..
|
||||
},
|
||||
] => {
|
||||
// re-borrow with the same mutability is allowed
|
||||
let ty = cx.typeck_results().expr_ty(arg);
|
||||
matches!(*ty.kind(), ty::Ref(.., mu1) if mu1 == mu2.into())
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
std::iter::zip(params, receiver.into_iter().chain(call_args.iter())).all(|(param, arg)| check_inputs(param, arg))
|
||||
params.len() == self_arg.map_or(0, |_| 1) + args.len()
|
||||
&& params.iter().zip(self_arg.into_iter().chain(args)).all(|(p, arg)| {
|
||||
matches!(
|
||||
p.pat.kind,PatKind::Binding(BindingAnnotation::NONE, id, _, None)
|
||||
if path_to_local_id(arg, id)
|
||||
)
|
||||
// Only allow adjustments which change regions (i.e. re-borrowing).
|
||||
&& typeck
|
||||
.expr_adjustments(arg)
|
||||
.last()
|
||||
.map_or(true, |a| a.target == typeck.expr_ty(arg))
|
||||
})
|
||||
}
|
||||
|
||||
fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure_ty: Ty<'tcx>, call_ty: Ty<'tcx>) -> bool {
|
||||
let call_sig = call_ty.fn_sig(cx.tcx);
|
||||
if call_sig.unsafety() == Unsafety::Unsafe {
|
||||
return false;
|
||||
fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig: FnSig<'_>) -> bool {
|
||||
call_sig.unsafety == Unsafety::Normal
|
||||
&& !has_late_bound_to_non_late_bound_regions(
|
||||
cx.tcx
|
||||
.signature_unclosure(closure.sig(), Unsafety::Normal)
|
||||
.skip_binder(),
|
||||
call_sig,
|
||||
)
|
||||
}
|
||||
|
||||
/// This walks through both signatures and checks for any time a late-bound region is expected by an
|
||||
/// `impl Fn` type, but the target signature does not have a late-bound region in the same position.
|
||||
///
|
||||
/// This is needed because rustc is unable to late bind early-bound regions in a function signature.
|
||||
fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'_>) -> bool {
|
||||
fn check_region(from_region: Region<'_>, to_region: Region<'_>) -> bool {
|
||||
matches!(from_region.kind(), RegionKind::ReLateBound(..))
|
||||
&& !matches!(to_region.kind(), RegionKind::ReLateBound(..))
|
||||
}
|
||||
if !closure_ty.has_late_bound_regions() {
|
||||
return true;
|
||||
|
||||
fn check_subs(from_subs: &[GenericArg<'_>], to_subs: &[GenericArg<'_>]) -> bool {
|
||||
if from_subs.len() != to_subs.len() {
|
||||
return true;
|
||||
}
|
||||
for (from_arg, to_arg) in to_subs.iter().zip(from_subs) {
|
||||
match (from_arg.unpack(), to_arg.unpack()) {
|
||||
(GenericArgKind::Lifetime(from_region), GenericArgKind::Lifetime(to_region)) => {
|
||||
if check_region(from_region, to_region) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
(GenericArgKind::Type(from_ty), GenericArgKind::Type(to_ty)) => {
|
||||
if check_ty(from_ty, to_ty) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
(GenericArgKind::Const(_), GenericArgKind::Const(_)) => (),
|
||||
_ => return true,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
let ty::Closure(_, args) = closure_ty.kind() else {
|
||||
return false;
|
||||
};
|
||||
let closure_sig = cx.tcx.signature_unclosure(args.as_closure().sig(), Unsafety::Normal);
|
||||
cx.tcx.erase_late_bound_regions(closure_sig) == cx.tcx.erase_late_bound_regions(call_sig)
|
||||
|
||||
fn check_ty(from_ty: Ty<'_>, to_ty: Ty<'_>) -> bool {
|
||||
match (from_ty.kind(), to_ty.kind()) {
|
||||
(&ty::Adt(_, from_subs), &ty::Adt(_, to_subs)) => check_subs(from_subs, to_subs),
|
||||
(&ty::Array(from_ty, _), &ty::Array(to_ty, _)) | (&ty::Slice(from_ty), &ty::Slice(to_ty)) => {
|
||||
check_ty(from_ty, to_ty)
|
||||
},
|
||||
(&ty::Ref(from_region, from_ty, _), &ty::Ref(to_region, to_ty, _)) => {
|
||||
check_region(from_region, to_region) || check_ty(from_ty, to_ty)
|
||||
},
|
||||
(&ty::Tuple(from_tys), &ty::Tuple(to_tys)) => {
|
||||
from_tys.len() != to_tys.len()
|
||||
|| from_tys
|
||||
.iter()
|
||||
.zip(to_tys)
|
||||
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
|
||||
},
|
||||
_ => from_ty.has_late_bound_regions(),
|
||||
}
|
||||
}
|
||||
|
||||
assert!(from_sig.inputs_and_output.len() == to_sig.inputs_and_output.len());
|
||||
from_sig
|
||||
.inputs_and_output
|
||||
.iter()
|
||||
.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 {
|
||||
@ -241,7 +317,7 @@ fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args:
|
||||
match assoc_item.container {
|
||||
ty::TraitContainer => cx.tcx.def_path_str(def_id),
|
||||
ty::ImplContainer => {
|
||||
let ty = cx.tcx.type_of(def_id).skip_binder();
|
||||
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(..)
|
||||
|
@ -189,12 +189,7 @@ impl LateLintPass<'_> for IncorrectImpls {
|
||||
.diagnostic_items(trait_impl.def_id.krate)
|
||||
.name_to_id
|
||||
.get(&sym::Ord)
|
||||
&& implements_trait(
|
||||
cx,
|
||||
hir_ty_to_ty(cx.tcx, imp.self_ty),
|
||||
*ord_def_id,
|
||||
trait_impl.args,
|
||||
)
|
||||
&& implements_trait(cx, hir_ty_to_ty(cx.tcx, imp.self_ty), *ord_def_id, &[])
|
||||
{
|
||||
// If the `cmp` call likely needs to be fully qualified in the suggestion
|
||||
// (like `std::cmp::Ord::cmp`). It's unfortunate we must put this here but we can't
|
||||
|
@ -109,7 +109,7 @@ fn is_ref_iterable<'tcx>(
|
||||
&& let sig = cx.tcx.liberate_late_bound_regions(fn_id, cx.tcx.fn_sig(fn_id).skip_binder())
|
||||
&& let &[req_self_ty, req_res_ty] = &**sig.inputs_and_output
|
||||
&& let param_env = cx.tcx.param_env(fn_id)
|
||||
&& implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, [])
|
||||
&& implements_trait_with_env(cx.tcx, param_env, req_self_ty, trait_id, &[])
|
||||
&& let Some(into_iter_ty) =
|
||||
make_normalized_projection_with_regions(cx.tcx, param_env, trait_id, sym!(IntoIter), [req_self_ty])
|
||||
&& let req_res_ty = normalize_with_regions(cx.tcx, param_env, req_res_ty)
|
||||
|
@ -57,7 +57,7 @@ pub(super) fn check<'tcx>(
|
||||
cx.tcx
|
||||
.get_diagnostic_item(sym::Default)
|
||||
.map_or(false, |default_trait_id| {
|
||||
implements_trait(cx, output_ty, default_trait_id, args)
|
||||
implements_trait(cx, output_ty, default_trait_id, &[])
|
||||
})
|
||||
} else {
|
||||
false
|
||||
|
@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
|
||||
use clippy_utils::ptr::get_spans;
|
||||
use clippy_utils::source::{snippet, snippet_opt};
|
||||
use clippy_utils::ty::{
|
||||
implements_trait, implements_trait_with_env, is_copy, is_type_diagnostic_item, is_type_lang_item,
|
||||
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
|
||||
};
|
||||
use clippy_utils::{get_trait_def_id, is_self, paths};
|
||||
use if_chain::if_chain;
|
||||
@ -182,7 +182,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
|
||||
if !ty.is_mutable_ptr();
|
||||
if !is_copy(cx, ty);
|
||||
if ty.is_sized(cx.tcx, cx.param_env);
|
||||
if !allowed_traits.iter().any(|&t| implements_trait_with_env(cx.tcx, cx.param_env, ty, t, [None]));
|
||||
if !allowed_traits.iter().any(|&t| implements_trait_with_env_from_iter(
|
||||
cx.tcx,
|
||||
cx.param_env,
|
||||
ty,
|
||||
t,
|
||||
[Option::<ty::GenericArg<'tcx>>::None],
|
||||
));
|
||||
if !implements_borrow_trait;
|
||||
if !all_borrowable_trait;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
|
||||
use core::ops::ControlFlow;
|
||||
use itertools::Itertools;
|
||||
use rustc_ast::ast::Mutability;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_hir as hir;
|
||||
@ -13,17 +14,19 @@ use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKi
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir::interpret::{ConstValue, Scalar};
|
||||
use rustc_middle::traits::EvaluationResult;
|
||||
use rustc_middle::ty::layout::ValidityRequirement;
|
||||
use rustc_middle::ty::{
|
||||
self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, IntTy,
|
||||
List, ParamEnv, Region, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
|
||||
UintTy, VariantDef, VariantDiscr,
|
||||
self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
|
||||
GenericParamDefKind, IntTy, List, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt,
|
||||
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr,
|
||||
};
|
||||
use rustc_span::symbol::Ident;
|
||||
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
|
||||
use rustc_target::abi::{Size, VariantIdx};
|
||||
use rustc_trait_selection::infer::InferCtxtExt;
|
||||
use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use rustc_trait_selection::traits::query::normalize::QueryNormalizeExt;
|
||||
use rustc_trait_selection::traits::{Obligation, ObligationCause};
|
||||
use std::iter;
|
||||
|
||||
use crate::{match_def_path, path_res, paths};
|
||||
@ -207,15 +210,9 @@ pub fn implements_trait<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
trait_id: DefId,
|
||||
ty_params: &[GenericArg<'tcx>],
|
||||
args: &[GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
implements_trait_with_env(
|
||||
cx.tcx,
|
||||
cx.param_env,
|
||||
ty,
|
||||
trait_id,
|
||||
ty_params.iter().map(|&arg| Some(arg)),
|
||||
)
|
||||
implements_trait_with_env_from_iter(cx.tcx, cx.param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
|
||||
}
|
||||
|
||||
/// Same as `implements_trait` but allows using a `ParamEnv` different from the lint context.
|
||||
@ -224,7 +221,18 @@ pub fn implements_trait_with_env<'tcx>(
|
||||
param_env: ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
trait_id: DefId,
|
||||
ty_params: impl IntoIterator<Item = Option<GenericArg<'tcx>>>,
|
||||
args: &[GenericArg<'tcx>],
|
||||
) -> bool {
|
||||
implements_trait_with_env_from_iter(tcx, param_env, ty, trait_id, args.iter().map(|&x| Some(x)))
|
||||
}
|
||||
|
||||
/// Same as `implements_trait_from_env` but takes the arguments as an iterator.
|
||||
pub fn implements_trait_with_env_from_iter<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ParamEnv<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
trait_id: DefId,
|
||||
args: impl IntoIterator<Item = impl Into<Option<GenericArg<'tcx>>>>,
|
||||
) -> bool {
|
||||
// Clippy shouldn't have infer types
|
||||
assert!(!ty.has_infer());
|
||||
@ -233,19 +241,37 @@ pub fn implements_trait_with_env<'tcx>(
|
||||
if ty.has_escaping_bound_vars() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let infcx = tcx.infer_ctxt().build();
|
||||
let orig = TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: DUMMY_SP,
|
||||
};
|
||||
let ty_params = tcx.mk_args_from_iter(
|
||||
ty_params
|
||||
let trait_ref = TraitRef::new(
|
||||
tcx,
|
||||
trait_id,
|
||||
Some(GenericArg::from(ty))
|
||||
.into_iter()
|
||||
.map(|arg| arg.unwrap_or_else(|| infcx.next_ty_var(orig).into())),
|
||||
.chain(args.into_iter().map(|arg| {
|
||||
arg.into().unwrap_or_else(|| {
|
||||
let orig = TypeVariableOrigin {
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
span: DUMMY_SP,
|
||||
};
|
||||
infcx.next_ty_var(orig).into()
|
||||
})
|
||||
})),
|
||||
);
|
||||
|
||||
debug_assert_eq!(tcx.def_kind(trait_id), DefKind::Trait);
|
||||
#[cfg(debug_assertions)]
|
||||
assert_generic_args_match(tcx, trait_id, trait_ref.args);
|
||||
|
||||
let obligation = Obligation {
|
||||
cause: ObligationCause::dummy(),
|
||||
param_env,
|
||||
recursion_depth: 0,
|
||||
predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx),
|
||||
};
|
||||
infcx
|
||||
.type_implements_trait(trait_id, [ty.into()].into_iter().chain(ty_params), param_env)
|
||||
.must_apply_modulo_regions()
|
||||
.evaluate_obligation(&obligation)
|
||||
.is_ok_and(EvaluationResult::must_apply_modulo_regions)
|
||||
}
|
||||
|
||||
/// Checks whether this type implements `Drop`.
|
||||
@ -393,6 +419,11 @@ pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangI
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the diagnostic name of the type, if it has one
|
||||
pub fn type_diagnostic_name(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<Symbol> {
|
||||
ty.ty_adt_def().and_then(|adt| cx.tcx.get_diagnostic_name(adt.did()))
|
||||
}
|
||||
|
||||
/// Return `true` if the passed `typ` is `isize` or `usize`.
|
||||
pub fn is_isize_or_usize(typ: Ty<'_>) -> bool {
|
||||
matches!(typ.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
|
||||
@ -1014,12 +1045,60 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that the given arguments match the generic parameters of the given item.
|
||||
#[allow(dead_code)]
|
||||
fn assert_generic_args_match<'tcx>(tcx: TyCtxt<'tcx>, did: DefId, args: &[GenericArg<'tcx>]) {
|
||||
let g = tcx.generics_of(did);
|
||||
let parent = g.parent.map(|did| tcx.generics_of(did));
|
||||
let count = g.parent_count + g.params.len();
|
||||
let params = parent
|
||||
.map_or([].as_slice(), |p| p.params.as_slice())
|
||||
.iter()
|
||||
.chain(&g.params)
|
||||
.map(|x| &x.kind);
|
||||
|
||||
assert!(
|
||||
count == args.len(),
|
||||
"wrong number of arguments for `{did:?}`: expected `{count}`, found {}\n\
|
||||
note: the expected arguments are: `[{}]`\n\
|
||||
the given arguments are: `{args:#?}`",
|
||||
args.len(),
|
||||
params.clone().map(GenericParamDefKind::descr).format(", "),
|
||||
);
|
||||
|
||||
if let Some((idx, (param, arg))) =
|
||||
params
|
||||
.clone()
|
||||
.zip(args.iter().map(|&x| x.unpack()))
|
||||
.enumerate()
|
||||
.find(|(_, (param, arg))| match (param, arg) {
|
||||
(GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
|
||||
| (GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
|
||||
| (GenericParamDefKind::Const { .. }, GenericArgKind::Const(_)) => false,
|
||||
(
|
||||
GenericParamDefKind::Lifetime
|
||||
| GenericParamDefKind::Type { .. }
|
||||
| GenericParamDefKind::Const { .. },
|
||||
_,
|
||||
) => true,
|
||||
})
|
||||
{
|
||||
panic!(
|
||||
"incorrect argument for `{did:?}` at index `{idx}`: expected a {}, found `{arg:?}`\n\
|
||||
note: the expected arguments are `[{}]`\n\
|
||||
the given arguments are `{args:#?}`",
|
||||
param.descr(),
|
||||
params.clone().map(GenericParamDefKind::descr).format(", "),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes the projection type for the named associated type in the given impl or trait impl.
|
||||
///
|
||||
/// This function is for associated types which are "known" to exist, and as such, will only return
|
||||
/// `None` when debug assertions are disabled in order to prevent ICE's. With debug assertions
|
||||
/// enabled this will check that the named associated type exists, the correct number of
|
||||
/// substitutions are given, and that the correct kinds of substitutions are given (lifetime,
|
||||
/// arguments are given, and that the correct kinds of arguments are given (lifetime,
|
||||
/// constant or type). This will not check if type normalization would succeed.
|
||||
pub fn make_projection<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
@ -1043,49 +1122,7 @@ pub fn make_projection<'tcx>(
|
||||
return None;
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let generics = tcx.generics_of(assoc_item.def_id);
|
||||
let generic_count = generics.parent_count + generics.params.len();
|
||||
let params = generics
|
||||
.parent
|
||||
.map_or([].as_slice(), |id| &*tcx.generics_of(id).params)
|
||||
.iter()
|
||||
.chain(&generics.params)
|
||||
.map(|x| &x.kind);
|
||||
|
||||
debug_assert!(
|
||||
generic_count == args.len(),
|
||||
"wrong number of args for `{:?}`: found `{}` expected `{generic_count}`.\n\
|
||||
note: the expected parameters are: {:#?}\n\
|
||||
the given arguments are: `{args:#?}`",
|
||||
assoc_item.def_id,
|
||||
args.len(),
|
||||
params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
if let Some((idx, (param, arg))) = params
|
||||
.clone()
|
||||
.zip(args.iter().map(GenericArg::unpack))
|
||||
.enumerate()
|
||||
.find(|(_, (param, arg))| {
|
||||
!matches!(
|
||||
(param, arg),
|
||||
(ty::GenericParamDefKind::Lifetime, GenericArgKind::Lifetime(_))
|
||||
| (ty::GenericParamDefKind::Type { .. }, GenericArgKind::Type(_))
|
||||
| (ty::GenericParamDefKind::Const { .. }, GenericArgKind::Const(_))
|
||||
)
|
||||
})
|
||||
{
|
||||
debug_assert!(
|
||||
false,
|
||||
"mismatched subst type at index {idx}: expected a {}, found `{arg:?}`\n\
|
||||
note: the expected parameters are {:#?}\n\
|
||||
the given arguments are {args:#?}",
|
||||
param.descr(),
|
||||
params.map(ty::GenericParamDefKind::descr).collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
assert_generic_args_match(tcx, assoc_item.def_id, args);
|
||||
|
||||
Some(tcx.mk_alias_ty(assoc_item.def_id, args))
|
||||
}
|
||||
@ -1100,7 +1137,7 @@ pub fn make_projection<'tcx>(
|
||||
/// Normalizes the named associated type in the given impl or trait impl.
|
||||
///
|
||||
/// This function is for associated types which are "known" to be valid with the given
|
||||
/// substitutions, and as such, will only return `None` when debug assertions are disabled in order
|
||||
/// arguments, and as such, will only return `None` when debug assertions are disabled in order
|
||||
/// to prevent ICE's. With debug assertions enabled this will check that type normalization
|
||||
/// succeeds as well as everything checked by `make_projection`.
|
||||
pub fn make_normalized_projection<'tcx>(
|
||||
@ -1112,17 +1149,12 @@ pub fn make_normalized_projection<'tcx>(
|
||||
) -> Option<Ty<'tcx>> {
|
||||
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some((i, subst)) = ty
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, subst)| subst.has_late_bound_regions())
|
||||
{
|
||||
if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) {
|
||||
debug_assert!(
|
||||
false,
|
||||
"args contain late-bound region at index `{i}` which can't be normalized.\n\
|
||||
use `TyCtxt::erase_late_bound_regions`\n\
|
||||
note: subst is `{subst:#?}`",
|
||||
note: arg is `{arg:#?}`",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
@ -1190,17 +1222,12 @@ pub fn make_normalized_projection_with_regions<'tcx>(
|
||||
) -> Option<Ty<'tcx>> {
|
||||
fn helper<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: AliasTy<'tcx>) -> Option<Ty<'tcx>> {
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some((i, subst)) = ty
|
||||
.args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, subst)| subst.has_late_bound_regions())
|
||||
{
|
||||
if let Some((i, arg)) = ty.args.iter().enumerate().find(|(_, arg)| arg.has_late_bound_regions()) {
|
||||
debug_assert!(
|
||||
false,
|
||||
"args contain late-bound region at index `{i}` which can't be normalized.\n\
|
||||
use `TyCtxt::erase_late_bound_regions`\n\
|
||||
note: subst is `{subst:#?}`",
|
||||
note: arg is `{arg:#?}`",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
|
||||
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend, Visitable};
|
||||
use crate::{self as utils, get_enclosing_loop_or_multi_call_closure};
|
||||
use core::ops::ControlFlow;
|
||||
use hir::def::Res;
|
||||
use rustc_hir::intravisit::{self, Visitor};
|
||||
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node};
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, HirIdSet};
|
||||
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::hir::nested_filter;
|
||||
use rustc_middle::mir::FakeReadCause;
|
||||
use rustc_middle::ty;
|
||||
use {crate as utils, rustc_hir as hir};
|
||||
|
||||
/// Returns a set of mutated local variable IDs, or `None` if mutations could not be determined.
|
||||
pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> Option<HirIdSet> {
|
||||
@ -154,6 +154,17 @@ pub fn contains_return_break_continue_macro(expression: &Expr<'_>) -> bool {
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn local_used_in<'tcx>(cx: &LateContext<'tcx>, local_id: HirId, v: impl Visitable<'tcx>) -> bool {
|
||||
for_each_expr_with_closures(cx, v, |e| {
|
||||
if utils::path_to_local_id(e, local_id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr<'_>) -> bool {
|
||||
let Some(block) = utils::get_enclosing_block(cx, local_id) else {
|
||||
return false;
|
||||
@ -166,32 +177,21 @@ pub fn local_used_after_expr(cx: &LateContext<'_>, local_id: HirId, after: &Expr
|
||||
// let closure = || local;
|
||||
// closure();
|
||||
// closure();
|
||||
let in_loop_or_closure = cx
|
||||
.tcx
|
||||
.hir()
|
||||
.parent_iter(after.hir_id)
|
||||
.take_while(|&(id, _)| id != block.hir_id)
|
||||
.any(|(_, node)| {
|
||||
matches!(
|
||||
node,
|
||||
Node::Expr(Expr {
|
||||
kind: ExprKind::Loop(..) | ExprKind::Closure { .. },
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
if in_loop_or_closure {
|
||||
return true;
|
||||
}
|
||||
let loop_start = get_enclosing_loop_or_multi_call_closure(cx, after).map(|e| e.hir_id);
|
||||
|
||||
let mut past_expr = false;
|
||||
for_each_expr_with_closures(cx, block, |e| {
|
||||
if e.hir_id == after.hir_id {
|
||||
if past_expr {
|
||||
if utils::path_to_local_id(e, local_id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
} else if e.hir_id == after.hir_id {
|
||||
past_expr = true;
|
||||
ControlFlow::Continue(Descend::No)
|
||||
} else if past_expr && utils::path_to_local_id(e, local_id) {
|
||||
ControlFlow::Break(())
|
||||
} else {
|
||||
past_expr = Some(e.hir_id) == loop_start;
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
}
|
||||
})
|
||||
|
@ -52,6 +52,16 @@ pub trait Visitable<'tcx> {
|
||||
/// Calls the corresponding `visit_*` function on the visitor.
|
||||
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V);
|
||||
}
|
||||
impl<'tcx, T> Visitable<'tcx> for &'tcx [T]
|
||||
where
|
||||
&'tcx T: Visitable<'tcx>,
|
||||
{
|
||||
fn visit<V: Visitor<'tcx>>(self, visitor: &mut V) {
|
||||
for x in self {
|
||||
x.visit(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
macro_rules! visitable_ref {
|
||||
($t:ident, $f:ident) => {
|
||||
impl<'tcx> Visitable<'tcx> for &'tcx $t<'tcx> {
|
||||
|
@ -345,3 +345,58 @@ fn angle_brackets_and_args() {
|
||||
let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct);
|
||||
dyn_opt.map(<dyn TestTrait>::method_on_dyn);
|
||||
}
|
||||
|
||||
fn _late_bound_to_early_bound_regions() {
|
||||
struct Foo<'a>(&'a u32);
|
||||
impl<'a> Foo<'a> {
|
||||
fn f(x: &'a u32) -> Self {
|
||||
Foo(x)
|
||||
}
|
||||
}
|
||||
fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f(|x| Foo::f(x));
|
||||
|
||||
struct Bar;
|
||||
impl<'a> From<&'a u32> for Bar {
|
||||
fn from(x: &'a u32) -> Bar {
|
||||
Bar
|
||||
}
|
||||
}
|
||||
fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f2(|x| <Bar>::from(x));
|
||||
|
||||
struct Baz<'a>(&'a u32);
|
||||
fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f3(|x| Baz(x));
|
||||
}
|
||||
|
||||
fn _mixed_late_bound_and_early_bound_regions() {
|
||||
fn f<T>(t: T, f: impl Fn(T, &u32) -> u32) -> u32 {
|
||||
f(t, &0)
|
||||
}
|
||||
fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 {
|
||||
*y
|
||||
}
|
||||
let _ = f(&0, f2);
|
||||
}
|
||||
|
||||
fn _closure_with_types() {
|
||||
fn f<T>(x: T) -> T {
|
||||
x
|
||||
}
|
||||
fn f2<T: Default>(f: impl Fn(T) -> T) -> T {
|
||||
f(T::default())
|
||||
}
|
||||
|
||||
let _ = f2(|x: u32| f(x));
|
||||
let _ = f2(|x| -> u32 { f(x) });
|
||||
}
|
||||
|
@ -345,3 +345,58 @@ fn angle_brackets_and_args() {
|
||||
let dyn_opt: Option<&dyn TestTrait> = Some(&test_struct);
|
||||
dyn_opt.map(|d| d.method_on_dyn());
|
||||
}
|
||||
|
||||
fn _late_bound_to_early_bound_regions() {
|
||||
struct Foo<'a>(&'a u32);
|
||||
impl<'a> Foo<'a> {
|
||||
fn f(x: &'a u32) -> Self {
|
||||
Foo(x)
|
||||
}
|
||||
}
|
||||
fn f(f: impl for<'a> Fn(&'a u32) -> Foo<'a>) -> Foo<'static> {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f(|x| Foo::f(x));
|
||||
|
||||
struct Bar;
|
||||
impl<'a> From<&'a u32> for Bar {
|
||||
fn from(x: &'a u32) -> Bar {
|
||||
Bar
|
||||
}
|
||||
}
|
||||
fn f2(f: impl for<'a> Fn(&'a u32) -> Bar) -> Bar {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f2(|x| <Bar>::from(x));
|
||||
|
||||
struct Baz<'a>(&'a u32);
|
||||
fn f3(f: impl Fn(&u32) -> Baz<'_>) -> Baz<'static> {
|
||||
f(&0)
|
||||
}
|
||||
|
||||
let _ = f3(|x| Baz(x));
|
||||
}
|
||||
|
||||
fn _mixed_late_bound_and_early_bound_regions() {
|
||||
fn f<T>(t: T, f: impl Fn(T, &u32) -> u32) -> u32 {
|
||||
f(t, &0)
|
||||
}
|
||||
fn f2<'a, T: 'a>(_: &'a T, y: &u32) -> u32 {
|
||||
*y
|
||||
}
|
||||
let _ = f(&0, |x, y| f2(x, y));
|
||||
}
|
||||
|
||||
fn _closure_with_types() {
|
||||
fn f<T>(x: T) -> T {
|
||||
x
|
||||
}
|
||||
fn f2<T: Default>(f: impl Fn(T) -> T) -> T {
|
||||
f(T::default())
|
||||
}
|
||||
|
||||
let _ = f2(|x: u32| f(x));
|
||||
let _ = f2(|x| -> u32 { f(x) });
|
||||
}
|
||||
|
@ -158,5 +158,11 @@ error: redundant closure
|
||||
LL | dyn_opt.map(|d| d.method_on_dyn());
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn`
|
||||
|
||||
error: aborting due to 26 previous errors
|
||||
error: redundant closure
|
||||
--> $DIR/eta.rs:389:19
|
||||
|
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user