Fix iter_on_single_items FP on function pointers and let stmts (#15013)

Closes rust-lang/rust-clippy#14981

changelog: [`iter_on_single_items`] fix FP on function pointers and let
stmts
This commit is contained in:
Alejandra González 2025-07-30 16:42:12 +00:00 committed by GitHub
commit 445d41909e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 34 deletions

View file

@ -2,14 +2,15 @@ use std::iter::once;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig};
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core, sym};
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::def_id::DefId;
use rustc_hir::hir_id::HirId;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::Binder;
use rustc_span::Symbol;
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
@ -32,24 +33,34 @@ impl IterType {
fn is_arg_ty_unified_in_fn<'tcx>(
cx: &LateContext<'tcx>,
fn_id: DefId,
fn_sig: ExprFnSig<'tcx>,
arg_id: HirId,
args: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
is_method: bool,
) -> bool {
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity();
let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap();
let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder();
let Some(arg_ty_in_args) = fn_sig.input(arg_id_in_args).map(Binder::skip_binder) else {
return false;
};
cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| {
clause
.as_projection_clause()
.and_then(|p| p.map_bound(|p| p.term.as_type()).transpose())
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
}) || fn_sig
.inputs()
.iter()
.enumerate()
.any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args)))
fn_sig
.predicates_id()
.map(|def_id| cx.tcx.predicates_of(def_id))
.is_some_and(|generics| {
generics.predicates.iter().any(|(clause, _)| {
clause
.as_projection_clause()
.and_then(|p| p.map_bound(|p| p.term.as_type()).transpose())
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
})
})
|| (!is_method
&& fn_sig.input(arg_id_in_args).is_some_and(|binder| {
binder
.skip_binder()
.walk()
.any(|arg| arg.as_type() == Some(arg_ty_in_args))
}))
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: Symbol, recv: &'tcx Expr<'tcx>) {
@ -70,25 +81,16 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method
let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
Some((Node::Expr(parent), child_id)) => match parent.kind {
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
ExprKind::Call(
Expr {
kind: ExprKind::Path(path),
hir_id,
..
},
args,
) => cx
ExprKind::Call(recv, args) => {
expr_sig(cx, recv).is_some_and(|fn_sig| is_arg_ty_unified_in_fn(cx, fn_sig, child_id, args, false))
},
ExprKind::MethodCall(_name, recv, args, _span) => cx
.typeck_results()
.qpath_res(path, *hir_id)
.opt_def_id()
.filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like())
.is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)),
ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn(
cx,
cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(),
child_id,
once(recv).chain(args.iter()),
),
.type_dependent_def_id(parent.hir_id)
.and_then(|def_id| ty_sig(cx, cx.tcx.type_of(def_id).instantiate_identity()))
.is_some_and(|fn_sig| {
is_arg_ty_unified_in_fn(cx, fn_sig, child_id, once(recv).chain(args.iter()), true)
}),
ExprKind::If(_, _, _)
| ExprKind::Match(_, _, _)
| ExprKind::Closure(_)
@ -96,7 +98,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method
| ExprKind::Break(_, _) => true,
_ => false,
},
Some((Node::Stmt(_) | Node::LetStmt(_), _)) => false,
Some((Node::LetStmt(let_stmt), _)) => let_stmt.ty.is_some(),
Some((Node::Stmt(_), _)) => false,
_ => true,
};

View file

@ -582,7 +582,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator<Item = &(t
}
/// A signature for a function like type.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum ExprFnSig<'tcx> {
Sig(Binder<'tcx, FnSig<'tcx>>, Option<DefId>),
Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>),

View file

@ -66,3 +66,27 @@ fn main() {
custom_option::custom_option();
in_macros!();
}
mod issue14981 {
use std::option::IntoIter;
fn takes_into_iter(_: impl IntoIterator<Item = i32>) {}
fn let_stmt() {
macro_rules! x {
($e:expr) => {
let _: IntoIter<i32> = $e;
};
}
x!(Some(5).into_iter());
}
fn fn_ptr() {
fn some_func(_: IntoIter<i32>) -> IntoIter<i32> {
todo!()
}
some_func(Some(5).into_iter());
const C: fn(IntoIter<i32>) -> IntoIter<i32> = <IntoIter<i32> as IntoIterator>::into_iter;
C(Some(5).into_iter());
}
}

View file

@ -66,3 +66,27 @@ fn main() {
custom_option::custom_option();
in_macros!();
}
mod issue14981 {
use std::option::IntoIter;
fn takes_into_iter(_: impl IntoIterator<Item = i32>) {}
fn let_stmt() {
macro_rules! x {
($e:expr) => {
let _: IntoIter<i32> = $e;
};
}
x!(Some(5).into_iter());
}
fn fn_ptr() {
fn some_func(_: IntoIter<i32>) -> IntoIter<i32> {
todo!()
}
some_func(Some(5).into_iter());
const C: fn(IntoIter<i32>) -> IntoIter<i32> = <IntoIter<i32> as IntoIterator>::into_iter;
C(Some(5).into_iter());
}
}