rustc_lint/
shadowed_into_iter.rs

1use rustc_hir::{self as hir, LangItem};
2use rustc_middle::ty::{self, Ty};
3use rustc_session::lint::fcw;
4use rustc_session::{declare_lint, impl_lint_pass};
5
6use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub};
7use crate::{LateContext, LateLintPass, LintContext};
8
9declare_lint! {
10    /// The `array_into_iter` lint detects calling `into_iter` on arrays.
11    ///
12    /// ### Example
13    ///
14    /// ```rust,edition2018
15    /// # #![allow(unused)]
16    /// [1, 2, 3].into_iter().for_each(|n| { *n; });
17    /// ```
18    ///
19    /// {{produces}}
20    ///
21    /// ### Explanation
22    ///
23    /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
24    /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
25    /// behave as `(&array).into_iter()`, returning an iterator over
26    /// references, just like in Rust 1.52 and earlier.
27    /// This only applies to the method call syntax `array.into_iter()`, not to
28    /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
29    pub ARRAY_INTO_ITER,
30    Warn,
31    "detects calling `into_iter` on arrays in Rust 2015 and 2018",
32    @future_incompatible = FutureIncompatibleInfo {
33        reason: fcw!(EditionSemanticsChange 2021 "IntoIterator-for-arrays"),
34    };
35}
36
37declare_lint! {
38    /// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
39    ///
40    /// ### Example
41    ///
42    /// ```rust,edition2021
43    /// # #![allow(unused)]
44    /// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
45    /// ```
46    ///
47    /// {{produces}}
48    ///
49    /// ### Explanation
50    ///
51    /// Since Rust 1.80.0, boxed slices implement `IntoIterator`. However, to avoid
52    /// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
53    /// behave as `(&boxed_slice).into_iter()`, returning an iterator over
54    /// references, just like in Rust 1.79.0 and earlier.
55    /// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
56    /// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
57    pub BOXED_SLICE_INTO_ITER,
58    Warn,
59    "detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
60    @future_incompatible = FutureIncompatibleInfo {
61        reason: fcw!(EditionSemanticsChange 2024 "intoiterator-box-slice"),
62    };
63}
64
65#[derive(Copy, Clone)]
66pub(crate) struct ShadowedIntoIter;
67
68impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]);
69
70impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter {
71    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
72        let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else {
73            return;
74        };
75
76        // Check if the method call actually calls the libcore
77        // `IntoIterator::into_iter`.
78        let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
79            return;
80        };
81        if !cx.tcx.is_lang_item(method_def_id, LangItem::IntoIterIntoIter) {
82            return;
83        }
84
85        // As this is a method call expression, we have at least one argument.
86        let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
87        let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
88
89        let adjusted_receiver_tys: Vec<_> =
90            [receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect();
91
92        fn is_ref_to_array(ty: Ty<'_>) -> bool {
93            if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false }
94        }
95        fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool {
96            if let ty::Ref(_, pointee_ty, _) = *ty.kind() {
97                pointee_ty.boxed_ty().is_some_and(Ty::is_slice)
98            } else {
99                false
100            }
101        }
102
103        let (lint, target, edition, can_suggest_ufcs) =
104            if is_ref_to_array(*adjusted_receiver_tys.last().unwrap())
105                && let Some(idx) = adjusted_receiver_tys
106                    .iter()
107                    .copied()
108                    .take_while(|ty| !is_ref_to_array(*ty))
109                    .position(|ty| ty.is_array())
110            {
111                (ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0)
112            } else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap())
113                && let Some(idx) = adjusted_receiver_tys
114                    .iter()
115                    .copied()
116                    .take_while(|ty| !is_ref_to_boxed_slice(*ty))
117                    .position(|ty| ty.boxed_ty().is_some_and(Ty::is_slice))
118            {
119                (BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0)
120            } else {
121                return;
122            };
123
124        // This check needs to avoid ICE from when `receiver_arg` is from macro expansion
125        // Which leads to empty span in span arithmetic below
126        // cc: https://github.com/rust-lang/rust/issues/147408
127        let span = receiver_arg.span.find_ancestor_in_same_ctxt(expr.span);
128
129        // If this expression comes from the `IntoIter::into_iter` inside of a for loop,
130        // we should just suggest removing the `.into_iter()` or changing it to `.iter()`
131        // to disambiguate if we want to iterate by-value or by-ref.
132        let sub = if let Some((_, hir::Node::Expr(parent_expr))) =
133            cx.tcx.hir_parent_iter(expr.hir_id).nth(1)
134            && let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) =
135                &parent_expr.kind
136            && let hir::ExprKind::Call(path, [_]) = &arg.kind
137            && let hir::ExprKind::Path(qpath) = path.kind
138            && cx.tcx.qpath_is_lang_item(qpath, LangItem::IntoIterIntoIter)
139            && let Some(span) = span
140        {
141            Some(ShadowedIntoIterDiagSub::RemoveIntoIter {
142                span: span.shrink_to_hi().to(expr.span.shrink_to_hi()),
143            })
144        } else if can_suggest_ufcs && let Some(span) = span {
145            Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter {
146                start_span: expr.span.shrink_to_lo(),
147                end_span: span.shrink_to_hi().to(expr.span.shrink_to_hi()),
148            })
149        } else {
150            None
151        };
152
153        cx.emit_span_lint(
154            lint,
155            call.ident.span,
156            ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub },
157        );
158    }
159}