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    /// The `array_into_iter` lint detects calling `into_iter` on arrays.
12    ///
13    /// ### Example
14    ///
15    /// ```rust,edition2018
16    /// # #![allow(unused)]
17    /// [1, 2, 3].into_iter().for_each(|n| { *n; });
18    /// ```
19    ///
20    /// {{produces}}
21    ///
22    /// ### Explanation
23    ///
24    /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
25    /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
26    /// behave as `(&array).into_iter()`, returning an iterator over
27    /// references, just like in Rust 1.52 and earlier.
28    /// This only applies to the method call syntax `array.into_iter()`, not to
29    /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
30    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    /// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
41    ///
42    /// ### Example
43    ///
44    /// ```rust,edition2021
45    /// # #![allow(unused)]
46    /// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
47    /// ```
48    ///
49    /// {{produces}}
50    ///
51    /// ### Explanation
52    ///
53    /// Since Rust 1.80.0, boxed slices implement `IntoIterator`. However, to avoid
54    /// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
55    /// behave as `(&boxed_slice).into_iter()`, returning an iterator over
56    /// references, just like in Rust 1.79.0 and earlier.
57    /// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
58    /// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
59    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        // Check if the method call actually calls the libcore
80        // `IntoIterator::into_iter`.
81        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        // As this is a method call expression, we have at least one argument.
89        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        // If this expression comes from the `IntoIter::into_iter` inside of a for loop,
128        // we should just suggest removing the `.into_iter()` or changing it to `.iter()`
129        // to disambiguate if we want to iterate by-value or by-ref.
130        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}