rustc_lint/
shadowed_into_iter.rs
1use rustc_hir as hir;
2use rustc_middle::ty::{self, Ty};
3use rustc_session::lint::FutureIncompatibilityReason;
4use rustc_session::{declare_lint, impl_lint_pass};
5use rustc_span::edition::Edition;
6
7use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub};
8use crate::{LateContext, LateLintPass, LintContext};
9
10declare_lint! {
11 pub ARRAY_INTO_ITER,
31 Warn,
32 "detects calling `into_iter` on arrays in Rust 2015 and 2018",
33 @future_incompatible = FutureIncompatibleInfo {
34 reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
35 reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
36 };
37}
38
39declare_lint! {
40 pub BOXED_SLICE_INTO_ITER,
60 Warn,
61 "detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
62 @future_incompatible = FutureIncompatibleInfo {
63 reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
64 reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2024/intoiterator-box-slice.html>"
65 };
66}
67
68#[derive(Copy, Clone)]
69pub(crate) struct ShadowedIntoIter;
70
71impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]);
72
73impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter {
74 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
75 let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else {
76 return;
77 };
78
79 let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
82 return;
83 };
84 if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() {
85 return;
86 }
87
88 let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
90 let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
91
92 let adjusted_receiver_tys: Vec<_> =
93 [receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect();
94
95 fn is_ref_to_array(ty: Ty<'_>) -> bool {
96 if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false }
97 }
98 fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool {
99 if let ty::Ref(_, pointee_ty, _) = *ty.kind() {
100 pointee_ty.boxed_ty().is_some_and(Ty::is_slice)
101 } else {
102 false
103 }
104 }
105
106 let (lint, target, edition, can_suggest_ufcs) =
107 if is_ref_to_array(*adjusted_receiver_tys.last().unwrap())
108 && let Some(idx) = adjusted_receiver_tys
109 .iter()
110 .copied()
111 .take_while(|ty| !is_ref_to_array(*ty))
112 .position(|ty| ty.is_array())
113 {
114 (ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0)
115 } else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap())
116 && let Some(idx) = adjusted_receiver_tys
117 .iter()
118 .copied()
119 .take_while(|ty| !is_ref_to_boxed_slice(*ty))
120 .position(|ty| ty.boxed_ty().is_some_and(Ty::is_slice))
121 {
122 (BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0)
123 } else {
124 return;
125 };
126
127 let sub = if let Some((_, hir::Node::Expr(parent_expr))) =
131 cx.tcx.hir_parent_iter(expr.hir_id).nth(1)
132 && let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) =
133 &parent_expr.kind
134 && let hir::ExprKind::Call(path, [_]) = &arg.kind
135 && let hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoIterIntoIter, ..)) =
136 &path.kind
137 {
138 Some(ShadowedIntoIterDiagSub::RemoveIntoIter {
139 span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
140 })
141 } else if can_suggest_ufcs {
142 Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter {
143 start_span: expr.span.shrink_to_lo(),
144 end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
145 })
146 } else {
147 None
148 };
149
150 cx.emit_span_lint(
151 lint,
152 call.ident.span,
153 ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub },
154 );
155 }
156}