Skip to main content

rustc_lint/
shadowed_into_iter.rs

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