about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Goulet <michael@errs.io>2024-04-17 16:32:17 -0400
committerMichael Goulet <michael@errs.io>2024-05-20 19:21:30 -0400
commita502e7ac1d7d117daea302717c012edf3243f361 (patch)
treee62bfa14c553fb1d876f4acae2593a9d22273c13
parent1a8109253196064e26a427d9184874ae14c55f6e (diff)
downloadrust-a502e7ac1d7d117daea302717c012edf3243f361.tar.gz
rust-a502e7ac1d7d117daea302717c012edf3243f361.zip
Implement BOXED_SLICE_INTO_ITER
-rw-r--r--compiler/rustc_ast/src/ptr.rs2
-rw-r--r--compiler/rustc_feature/src/builtin_attrs.rs9
-rw-r--r--compiler/rustc_hir_analysis/src/collect.rs21
-rw-r--r--compiler/rustc_hir_typeck/src/method/probe.rs12
-rw-r--r--compiler/rustc_lint/messages.ftl14
-rw-r--r--compiler/rustc_lint/src/array_into_iter.rs144
-rw-r--r--compiler/rustc_lint/src/lib.rs8
-rw-r--r--compiler/rustc_lint/src/lints.rs11
-rw-r--r--compiler/rustc_lint/src/shadowed_into_iter.rs157
-rw-r--r--compiler/rustc_middle/src/ty/trait_def.rs7
-rw-r--r--compiler/rustc_smir/src/rustc_smir/convert/ty.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs2
-rw-r--r--compiler/stable_mir/src/ty.rs1
-rw-r--r--library/core/src/array/iter.rs2
-rw-r--r--library/core/src/iter/traits/collect.rs3
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-2021.rs46
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-2021.stderr60
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-2024.rs20
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-lint.fixed30
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-lint.rs30
-rw-r--r--tests/ui/iterators/into-iter-on-boxed-slices-lint.stderr35
-rw-r--r--tests/ui/traits/method-on-unbounded-type-param.stderr4
22 files changed, 446 insertions, 173 deletions
diff --git a/compiler/rustc_ast/src/ptr.rs b/compiler/rustc_ast/src/ptr.rs
index e22a523dbc3..34c539ea16b 100644
--- a/compiler/rustc_ast/src/ptr.rs
+++ b/compiler/rustc_ast/src/ptr.rs
@@ -184,7 +184,7 @@ impl<'a, T> IntoIterator for &'a P<[T]> {
     type Item = &'a T;
     type IntoIter = slice::Iter<'a, T>;
     fn into_iter(self) -> Self::IntoIter {
-        self.ptr.into_iter()
+        self.ptr.iter()
     }
 }
 
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index dbb88e42a3e..ebaa9f8d950 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -899,10 +899,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         "the `#[rustc_main]` attribute is used internally to specify test entry point function",
     ),
     rustc_attr!(
-        rustc_skip_array_during_method_dispatch, Normal, template!(Word),
-        WarnFollowing, EncodeCrossCrate::No,
-        "the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
-        from method dispatch when the receiver is an array, for compatibility in editions < 2021."
+        rustc_skip_during_method_dispatch, Normal, template!(List: "array, boxed_slice"), WarnFollowing,
+        EncodeCrossCrate::No,
+        "the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \
+        from method dispatch when the receiver is of the following type, for compatibility in \
+        editions < 2021 (array) or editions < 2024 (boxed_slice)."
     ),
     rustc_attr!(
         rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."),
diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs
index aa28b2c8e2c..b760b86a7bf 100644
--- a/compiler/rustc_hir_analysis/src/collect.rs
+++ b/compiler/rustc_hir_analysis/src/collect.rs
@@ -1117,8 +1117,24 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
 
     let is_marker = tcx.has_attr(def_id, sym::marker);
     let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive);
-    let skip_array_during_method_dispatch =
-        tcx.has_attr(def_id, sym::rustc_skip_array_during_method_dispatch);
+
+    // FIXME: We could probably do way better attribute validation here.
+    let mut skip_array_during_method_dispatch = false;
+    let mut skip_boxed_slice_during_method_dispatch = false;
+    for attr in tcx.get_attrs(def_id, sym::rustc_skip_during_method_dispatch) {
+        if let Some(lst) = attr.meta_item_list() {
+            for item in lst {
+                if let Some(ident) = item.ident() {
+                    match ident.as_str() {
+                        "array" => skip_array_during_method_dispatch = true,
+                        "boxed_slice" => skip_boxed_slice_during_method_dispatch = true,
+                        _ => (),
+                    }
+                }
+            }
+        }
+    }
+
     let specialization_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
         ty::trait_def::TraitSpecializationKind::Marker
     } else if tcx.has_attr(def_id, sym::rustc_specialization_trait) {
@@ -1253,6 +1269,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
         is_marker,
         is_coinductive: rustc_coinductive || is_auto,
         skip_array_during_method_dispatch,
+        skip_boxed_slice_during_method_dispatch,
         specialization_kind,
         must_implement_one_of,
         implement_via_object,
diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs
index 60a63d72d40..e0a60337c3b 100644
--- a/compiler/rustc_hir_typeck/src/method/probe.rs
+++ b/compiler/rustc_hir_typeck/src/method/probe.rs
@@ -1444,6 +1444,18 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
                                 return ProbeResult::NoMatch;
                             }
                         }
+
+                        // Some trait methods are excluded for boxed slices before 2024.
+                        // (`boxed_slice.into_iter()` wants a slice iterator for compatibility.)
+                        if self_ty.is_box()
+                            && self_ty.boxed_ty().is_slice()
+                            && !method_name.span.at_least_rust_2024()
+                        {
+                            let trait_def = self.tcx.trait_def(poly_trait_ref.def_id());
+                            if trait_def.skip_boxed_slice_during_method_dispatch {
+                                return ProbeResult::NoMatch;
+                            }
+                        }
                     }
 
                     let trait_ref = self.instantiate_binder_with_fresh_vars(
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index c717269d201..cf9d089ff62 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -2,13 +2,6 @@ lint_ambiguous_wide_pointer_comparisons = ambiguous wide pointer comparison, the
     .addr_metadata_suggestion = use explicit `std::ptr::eq` method to compare metadata and addresses
     .addr_suggestion = use `std::ptr::addr_eq` or untyped pointers to only compare their addresses
 
-lint_array_into_iter =
-    this method call resolves to `<&{$target} as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<{$target} as IntoIterator>::into_iter` in Rust 2021
-    .use_iter_suggestion = use `.iter()` instead of `.into_iter()` to avoid ambiguity
-    .remove_into_iter_suggestion = or remove `.into_iter()` to iterate by value
-    .use_explicit_into_iter_suggestion =
-        or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
-
 lint_async_fn_in_trait = use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
     .note = you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
     .suggestion = you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change
@@ -565,6 +558,13 @@ lint_renamed_lint = lint `{$name}` has been renamed to `{$replace}`
 
 lint_requested_level = requested on the command line with `{$level} {$lint_name}`
 
+lint_shadowed_into_iter =
+    this method call resolves to `<&{$target} as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<{$target} as IntoIterator>::into_iter` in Rust {$edition}
+    .use_iter_suggestion = use `.iter()` instead of `.into_iter()` to avoid ambiguity
+    .remove_into_iter_suggestion = or remove `.into_iter()` to iterate by value
+    .use_explicit_into_iter_suggestion =
+        or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
+
 lint_span_use_eq_ctxt = use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
 
 lint_supertrait_as_deref_target = this `Deref` implementation is covered by an implicit supertrait coercion
diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs
deleted file mode 100644
index 8f4bae33957..00000000000
--- a/compiler/rustc_lint/src/array_into_iter.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use crate::{
-    lints::{ArrayIntoIterDiag, ArrayIntoIterDiagSub},
-    LateContext, LateLintPass, LintContext,
-};
-use rustc_hir as hir;
-use rustc_middle::bug;
-use rustc_middle::ty;
-use rustc_middle::ty::adjustment::{Adjust, Adjustment};
-use rustc_session::lint::FutureIncompatibilityReason;
-use rustc_session::{declare_lint, impl_lint_pass};
-use rustc_span::edition::Edition;
-use rustc_span::symbol::sym;
-use rustc_span::Span;
-
-declare_lint! {
-    /// The `array_into_iter` lint detects calling `into_iter` on arrays.
-    ///
-    /// ### Example
-    ///
-    /// ```rust,edition2018
-    /// # #![allow(unused)]
-    /// [1, 2, 3].into_iter().for_each(|n| { *n; });
-    /// ```
-    ///
-    /// {{produces}}
-    ///
-    /// ### Explanation
-    ///
-    /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
-    /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
-    /// behave as `(&array).into_iter()`, returning an iterator over
-    /// references, just like in Rust 1.52 and earlier.
-    /// This only applies to the method call syntax `array.into_iter()`, not to
-    /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
-    pub ARRAY_INTO_ITER,
-    Warn,
-    "detects calling `into_iter` on arrays in Rust 2015 and 2018",
-    @future_incompatible = FutureIncompatibleInfo {
-        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
-        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
-    };
-}
-
-#[derive(Copy, Clone, Default)]
-pub struct ArrayIntoIter {
-    for_expr_span: Span,
-}
-
-impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]);
-
-impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
-    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
-        // Save the span of expressions in `for _ in expr` syntax,
-        // so we can give a better suggestion for those later.
-        if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind {
-            if let hir::ExprKind::Call(path, [arg]) = &arg.kind {
-                if let hir::ExprKind::Path(hir::QPath::LangItem(
-                    hir::LangItem::IntoIterIntoIter,
-                    ..,
-                )) = &path.kind
-                {
-                    self.for_expr_span = arg.span;
-                }
-            }
-        }
-
-        // We only care about method call expressions.
-        if let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind {
-            if call.ident.name != sym::into_iter {
-                return;
-            }
-
-            // Check if the method call actually calls the libcore
-            // `IntoIterator::into_iter`.
-            let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
-            match cx.tcx.trait_of_item(def_id) {
-                Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {}
-                _ => return,
-            };
-
-            // As this is a method call expression, we have at least one argument.
-            let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
-            let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
-
-            let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else {
-                return;
-            };
-
-            let types =
-                std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target));
-
-            let mut found_array = false;
-
-            for ty in types {
-                match ty.kind() {
-                    // If we run into a &[T; N] or &[T] first, there's nothing to warn about.
-                    // It'll resolve to the reference version.
-                    ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return,
-                    ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return,
-                    // Found an actual array type without matching a &[T; N] first.
-                    // This is the problematic case.
-                    ty::Array(..) => {
-                        found_array = true;
-                        break;
-                    }
-                    _ => {}
-                }
-            }
-
-            if !found_array {
-                return;
-            }
-
-            // Emit lint diagnostic.
-            let target = match *target.kind() {
-                ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]",
-                ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]",
-                // We know the original first argument type is an array type,
-                // we know that the first adjustment was an autoref coercion
-                // and we know that `IntoIterator` is the trait involved. The
-                // array cannot be coerced to something other than a reference
-                // to an array or to a slice.
-                _ => bug!("array type coerced to something other than array or slice"),
-            };
-            let sub = if self.for_expr_span == expr.span {
-                Some(ArrayIntoIterDiagSub::RemoveIntoIter {
-                    span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
-                })
-            } else if receiver_ty.is_array() {
-                Some(ArrayIntoIterDiagSub::UseExplicitIntoIter {
-                    start_span: expr.span.shrink_to_lo(),
-                    end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
-                })
-            } else {
-                None
-            };
-            cx.emit_span_lint(
-                ARRAY_INTO_ITER,
-                call.ident.span,
-                ArrayIntoIterDiag { target, suggestion: call.ident.span, sub },
-            );
-        }
-    }
-}
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index d93edadcfbc..10c2557baa7 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -42,7 +42,6 @@
 #[macro_use]
 extern crate tracing;
 
-mod array_into_iter;
 mod async_fn_in_trait;
 pub mod builtin;
 mod context;
@@ -76,18 +75,18 @@ mod passes;
 mod ptr_nulls;
 mod redundant_semicolon;
 mod reference_casting;
+mod shadowed_into_iter;
 mod traits;
 mod types;
 mod unit_bindings;
 mod unused;
 
-pub use array_into_iter::ARRAY_INTO_ITER;
+pub use shadowed_into_iter::ARRAY_INTO_ITER;
 
 use rustc_hir::def_id::LocalModDefId;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
 
-use array_into_iter::ArrayIntoIter;
 use async_fn_in_trait::AsyncFnInTrait;
 use builtin::*;
 use deref_into_dyn_supertrait::*;
@@ -112,6 +111,7 @@ use pass_by_value::*;
 use ptr_nulls::*;
 use redundant_semicolon::*;
 use reference_casting::*;
+use shadowed_into_iter::ShadowedIntoIter;
 use traits::*;
 use types::*;
 use unit_bindings::*;
@@ -215,7 +215,7 @@ late_lint_methods!(
             DerefNullPtr: DerefNullPtr,
             UnstableFeatures: UnstableFeatures,
             UngatedAsyncFnTrackCaller: UngatedAsyncFnTrackCaller,
-            ArrayIntoIter: ArrayIntoIter::default(),
+            ShadowedIntoIter: ShadowedIntoIter,
             DropTraitConstraints: DropTraitConstraints,
             TemporaryCStringAsPtr: TemporaryCStringAsPtr,
             NonPanicFmt: NonPanicFmt,
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 7efa5245baa..bc0c8cf85d8 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -22,17 +22,18 @@ use crate::{
 
 // array_into_iter.rs
 #[derive(LintDiagnostic)]
-#[diag(lint_array_into_iter)]
-pub struct ArrayIntoIterDiag<'a> {
-    pub target: &'a str,
+#[diag(lint_shadowed_into_iter)]
+pub struct ShadowedIntoIterDiag {
+    pub target: &'static str,
+    pub edition: &'static str,
     #[suggestion(lint_use_iter_suggestion, code = "iter", applicability = "machine-applicable")]
     pub suggestion: Span,
     #[subdiagnostic]
-    pub sub: Option<ArrayIntoIterDiagSub>,
+    pub sub: Option<ShadowedIntoIterDiagSub>,
 }
 
 #[derive(Subdiagnostic)]
-pub enum ArrayIntoIterDiagSub {
+pub enum ShadowedIntoIterDiagSub {
     #[suggestion(lint_remove_into_iter_suggestion, code = "", applicability = "maybe-incorrect")]
     RemoveIntoIter {
         #[primary_span]
diff --git a/compiler/rustc_lint/src/shadowed_into_iter.rs b/compiler/rustc_lint/src/shadowed_into_iter.rs
new file mode 100644
index 00000000000..41ec84faa78
--- /dev/null
+++ b/compiler/rustc_lint/src/shadowed_into_iter.rs
@@ -0,0 +1,157 @@
+use crate::lints::{ShadowedIntoIterDiag, ShadowedIntoIterDiagSub};
+use crate::{LateContext, LateLintPass, LintContext};
+use rustc_hir as hir;
+use rustc_middle::ty::{self, Ty};
+use rustc_session::lint::FutureIncompatibilityReason;
+use rustc_session::{declare_lint, impl_lint_pass};
+use rustc_span::edition::Edition;
+
+declare_lint! {
+    /// The `array_into_iter` lint detects calling `into_iter` on arrays.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2018
+    /// # #![allow(unused)]
+    /// [1, 2, 3].into_iter().for_each(|n| { *n; });
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Since Rust 1.53, arrays implement `IntoIterator`. However, to avoid
+    /// breakage, `array.into_iter()` in Rust 2015 and 2018 code will still
+    /// behave as `(&array).into_iter()`, returning an iterator over
+    /// references, just like in Rust 1.52 and earlier.
+    /// This only applies to the method call syntax `array.into_iter()`, not to
+    /// any other syntax such as `for _ in array` or `IntoIterator::into_iter(array)`.
+    pub ARRAY_INTO_ITER,
+    Warn,
+    "detects calling `into_iter` on arrays in Rust 2015 and 2018",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2021),
+        reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
+    };
+}
+
+declare_lint! {
+    /// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2021
+    /// # #![allow(unused)]
+    /// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Since Rust CURRENT_RUSTC_VERSION, boxed slices implement `IntoIterator`. However, to avoid
+    /// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
+    /// behave as `(&boxed_slice).into_iter()`, returning an iterator over
+    /// references, just like in Rust CURRENT_RUSTC_VERSION and earlier.
+    /// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
+    /// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
+    pub BOXED_SLICE_INTO_ITER,
+    Warn,
+    "detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
+    };
+}
+
+#[derive(Copy, Clone)]
+pub struct ShadowedIntoIter;
+
+impl_lint_pass!(ShadowedIntoIter => [ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER]);
+
+impl<'tcx> LateLintPass<'tcx> for ShadowedIntoIter {
+    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
+        let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind else {
+            return;
+        };
+
+        // Check if the method call actually calls the libcore
+        // `IntoIterator::into_iter`.
+        let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
+            return;
+        };
+        if Some(method_def_id) != cx.tcx.lang_items().into_iter_fn() {
+            return;
+        }
+
+        // As this is a method call expression, we have at least one argument.
+        let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
+        let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
+
+        let adjusted_receiver_tys: Vec<_> =
+            [receiver_ty].into_iter().chain(adjustments.iter().map(|adj| adj.target)).collect();
+
+        fn is_ref_to_array(ty: Ty<'_>) -> bool {
+            if let ty::Ref(_, pointee_ty, _) = *ty.kind() { pointee_ty.is_array() } else { false }
+        }
+        fn is_boxed_slice(ty: Ty<'_>) -> bool {
+            ty.is_box() && ty.boxed_ty().is_slice()
+        }
+        fn is_ref_to_boxed_slice(ty: Ty<'_>) -> bool {
+            if let ty::Ref(_, pointee_ty, _) = *ty.kind() {
+                is_boxed_slice(pointee_ty)
+            } else {
+                false
+            }
+        }
+
+        let (lint, target, edition, can_suggest_ufcs) =
+            if is_ref_to_array(*adjusted_receiver_tys.last().unwrap())
+                && let Some(idx) = adjusted_receiver_tys
+                    .iter()
+                    .copied()
+                    .take_while(|ty| !is_ref_to_array(*ty))
+                    .position(|ty| ty.is_array())
+            {
+                (ARRAY_INTO_ITER, "[T; N]", "2021", idx == 0)
+            } else if is_ref_to_boxed_slice(*adjusted_receiver_tys.last().unwrap())
+                && let Some(idx) = adjusted_receiver_tys
+                    .iter()
+                    .copied()
+                    .take_while(|ty| !is_ref_to_boxed_slice(*ty))
+                    .position(|ty| is_boxed_slice(ty))
+            {
+                (BOXED_SLICE_INTO_ITER, "Box<[T]>", "2024", idx == 0)
+            } else {
+                return;
+            };
+
+        // If this expression comes from the `IntoIter::into_iter` inside of a for loop,
+        // we should just suggest removing the `.into_iter()` or changing it to `.iter()`
+        // to disambiguate if we want to iterate by-value or by-ref.
+        let sub = if let Some((_, hir::Node::Expr(parent_expr))) =
+            cx.tcx.hir().parent_iter(expr.hir_id).nth(1)
+            && let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) =
+                &parent_expr.kind
+            && let hir::ExprKind::Call(path, [_]) = &arg.kind
+            && let hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoIterIntoIter, ..)) =
+                &path.kind
+        {
+            Some(ShadowedIntoIterDiagSub::RemoveIntoIter {
+                span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+            })
+        } else if can_suggest_ufcs {
+            Some(ShadowedIntoIterDiagSub::UseExplicitIntoIter {
+                start_span: expr.span.shrink_to_lo(),
+                end_span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
+            })
+        } else {
+            None
+        };
+
+        cx.emit_span_lint(
+            lint,
+            call.ident.span,
+            ShadowedIntoIterDiag { target, edition, suggestion: call.ident.span, sub },
+        );
+    }
+}
diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs
index c5b3de17bcb..cf1cbb93410 100644
--- a/compiler/rustc_middle/src/ty/trait_def.rs
+++ b/compiler/rustc_middle/src/ty/trait_def.rs
@@ -39,11 +39,16 @@ pub struct TraitDef {
     /// also have already switched to the new trait solver.
     pub is_coinductive: bool,
 
-    /// If `true`, then this trait has the `#[rustc_skip_array_during_method_dispatch]`
+    /// If `true`, then this trait has the `#[rustc_skip_during_method_dispatch(array)]`
     /// attribute, indicating that editions before 2021 should not consider this trait
     /// during method dispatch if the receiver is an array.
     pub skip_array_during_method_dispatch: bool,
 
+    /// If `true`, then this trait has the `#[rustc_skip_during_method_dispatch(boxed_slice)]`
+    /// attribute, indicating that editions before 2024 should not consider this trait
+    /// during method dispatch if the receiver is a boxed slice.
+    pub skip_boxed_slice_during_method_dispatch: bool,
+
     /// Used to determine whether the standard library is allowed to specialize
     /// on this trait.
     pub specialization_kind: TraitSpecializationKind,
diff --git a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs
index d59318be720..15447983abb 100644
--- a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs
+++ b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs
@@ -505,6 +505,7 @@ impl<'tcx> Stable<'tcx> for ty::TraitDef {
             is_marker: self.is_marker,
             is_coinductive: self.is_coinductive,
             skip_array_during_method_dispatch: self.skip_array_during_method_dispatch,
+            skip_boxed_slice_during_method_dispatch: self.skip_boxed_slice_during_method_dispatch,
             specialization_kind: self.specialization_kind.stable(tables),
             must_implement_one_of: self
                 .must_implement_one_of
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index e453c03e7ce..1869daf7e12 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1633,7 +1633,7 @@ symbols! {
         rustc_reservation_impl,
         rustc_safe_intrinsic,
         rustc_serialize,
-        rustc_skip_array_during_method_dispatch,
+        rustc_skip_during_method_dispatch,
         rustc_specialization_trait,
         rustc_std_internal_symbol,
         rustc_strict_coherence,
diff --git a/compiler/stable_mir/src/ty.rs b/compiler/stable_mir/src/ty.rs
index 759e3f166bd..321c56b623a 100644
--- a/compiler/stable_mir/src/ty.rs
+++ b/compiler/stable_mir/src/ty.rs
@@ -1205,6 +1205,7 @@ pub struct TraitDecl {
     pub is_marker: bool,
     pub is_coinductive: bool,
     pub skip_array_during_method_dispatch: bool,
+    pub skip_boxed_slice_during_method_dispatch: bool,
     pub specialization_kind: TraitSpecializationKind,
     pub must_implement_one_of: Option<Vec<Ident>>,
     pub implement_via_object: bool,
diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs
index e3d2cd2a31f..b314d0536a3 100644
--- a/library/core/src/array/iter.rs
+++ b/library/core/src/array/iter.rs
@@ -38,7 +38,7 @@ pub struct IntoIter<T, const N: usize> {
     alive: IndexRange,
 }
 
-// Note: the `#[rustc_skip_array_during_method_dispatch]` on `trait IntoIterator`
+// Note: the `#[rustc_skip_during_method_dispatch(array)]` on `trait IntoIterator`
 // hides this implementation from explicit `.into_iter()` calls on editions < 2021,
 // so those calls will still resolve to the slice implementation, by reference.
 #[stable(feature = "array_into_iter_impl", since = "1.53.0")]
diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs
index 563781230c0..d9d860c7b6c 100644
--- a/library/core/src/iter/traits/collect.rs
+++ b/library/core/src/iter/traits/collect.rs
@@ -268,7 +268,6 @@ where
 /// }
 /// ```
 #[rustc_diagnostic_item = "IntoIterator"]
-#[rustc_skip_array_during_method_dispatch]
 #[rustc_on_unimplemented(
     on(
         _Self = "core::ops::range::RangeTo<Idx>",
@@ -312,6 +311,8 @@ where
     label = "`{Self}` is not an iterator",
     message = "`{Self}` is not an iterator"
 )]
+#[cfg_attr(bootstrap, rustc_skip_array_during_method_dispatch)]
+#[cfg_attr(not(bootstrap), rustc_skip_during_method_dispatch(array, boxed_slice))]
 #[stable(feature = "rust1", since = "1.0.0")]
 pub trait IntoIterator {
     /// The type of the elements being iterated over.
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-2021.rs b/tests/ui/iterators/into-iter-on-boxed-slices-2021.rs
new file mode 100644
index 00000000000..3d9d131e59c
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-2021.rs
@@ -0,0 +1,46 @@
+//@ check-pass
+//@ edition:2018
+
+use std::ops::Deref;
+use std::rc::Rc;
+use std::slice::Iter;
+use std::vec::IntoIter;
+
+fn main() {
+    let boxed_slice = vec![0; 10].into_boxed_slice();
+
+    // Before 2024, the method dispatched to `IntoIterator for Box<[T]>`,
+    // which we continue to support for compatibility.
+    let _: Iter<'_, i32> = boxed_slice.into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+
+    let _: Iter<'_, i32> = Box::new(boxed_slice.clone()).into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+
+    let _: Iter<'_, i32> = Rc::new(boxed_slice.clone()).into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+    let _: Iter<'_, i32> = Array(boxed_slice.clone()).into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+
+    // But you can always use the trait method explicitly as an boxed_slice.
+    let _: IntoIter<i32> = IntoIterator::into_iter(boxed_slice);
+
+    for _ in (Box::new([1, 2, 3]) as Box<[_]>).into_iter() {}
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+}
+
+/// User type that dereferences to a boxed slice.
+struct Array(Box<[i32]>);
+
+impl Deref for Array {
+    type Target = Box<[i32]>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-2021.stderr b/tests/ui/iterators/into-iter-on-boxed-slices-2021.stderr
new file mode 100644
index 00000000000..d7f38a23725
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-2021.stderr
@@ -0,0 +1,60 @@
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-2021.rs:14:40
+   |
+LL |     let _: Iter<'_, i32> = boxed_slice.into_iter();
+   |                                        ^^^^^^^^^
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: `#[warn(boxed_slice_into_iter)]` on by default
+help: use `.iter()` instead of `.into_iter()` to avoid ambiguity
+   |
+LL |     let _: Iter<'_, i32> = boxed_slice.iter();
+   |                                        ~~~~
+help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
+   |
+LL |     let _: Iter<'_, i32> = IntoIterator::into_iter(boxed_slice);
+   |                            ++++++++++++++++++++++++           ~
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-2021.rs:18:58
+   |
+LL |     let _: Iter<'_, i32> = Box::new(boxed_slice.clone()).into_iter();
+   |                                                          ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
+   |
+   = warning: this changes meaning in Rust 2024
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-2021.rs:22:57
+   |
+LL |     let _: Iter<'_, i32> = Rc::new(boxed_slice.clone()).into_iter();
+   |                                                         ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
+   |
+   = warning: this changes meaning in Rust 2024
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-2021.rs:25:55
+   |
+LL |     let _: Iter<'_, i32> = Array(boxed_slice.clone()).into_iter();
+   |                                                       ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
+   |
+   = warning: this changes meaning in Rust 2024
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-2021.rs:32:48
+   |
+LL |     for _ in (Box::new([1, 2, 3]) as Box<[_]>).into_iter() {}
+   |                                                ^^^^^^^^^
+   |
+   = warning: this changes meaning in Rust 2024
+help: use `.iter()` instead of `.into_iter()` to avoid ambiguity
+   |
+LL |     for _ in (Box::new([1, 2, 3]) as Box<[_]>).iter() {}
+   |                                                ~~~~
+help: or remove `.into_iter()` to iterate by value
+   |
+LL -     for _ in (Box::new([1, 2, 3]) as Box<[_]>).into_iter() {}
+LL +     for _ in (Box::new([1, 2, 3]) as Box<[_]>) {}
+   |
+
+warning: 5 warnings emitted
+
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-2024.rs b/tests/ui/iterators/into-iter-on-boxed-slices-2024.rs
new file mode 100644
index 00000000000..ffd6f022bc6
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-2024.rs
@@ -0,0 +1,20 @@
+//@ check-pass
+//@ edition:2024
+//@ compile-flags: -Zunstable-options
+
+use std::ops::Deref;
+use std::rc::Rc;
+use std::vec::IntoIter;
+
+fn main() {
+    let boxed_slice = vec![0; 10].into_boxed_slice();
+
+    // In 2021, the method dispatches to `IntoIterator for [T; N]`.
+    let _: IntoIter<i32> = boxed_slice.clone().into_iter();
+
+    // And through other boxes.
+    let _: IntoIter<i32> = Box::new(boxed_slice.clone()).into_iter();
+
+    // You can always use the trait method explicitly as a boxed_slice.
+    let _: IntoIter<i32> = IntoIterator::into_iter(boxed_slice.clone());
+}
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-lint.fixed b/tests/ui/iterators/into-iter-on-boxed-slices-lint.fixed
new file mode 100644
index 00000000000..265a6c764d2
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-lint.fixed
@@ -0,0 +1,30 @@
+//@ run-pass
+//@ run-rustfix
+//@ rustfix-only-machine-applicable
+
+#[allow(unused_must_use, unused_allocation)]
+fn main() {
+    let boxed = vec![1, 2].into_boxed_slice();
+
+    // Expressions that should trigger the lint
+    boxed.iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+    Box::new(boxed.clone()).iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+    Box::new(Box::new(boxed.clone())).iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+
+    // Expressions that should not
+    (&boxed).into_iter();
+
+    for _ in &boxed {}
+    (&boxed as &[_]).into_iter();
+    boxed[..].into_iter();
+    std::iter::IntoIterator::into_iter(&boxed);
+
+    #[allow(boxed_slice_into_iter)]
+    boxed.into_iter();
+}
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-lint.rs b/tests/ui/iterators/into-iter-on-boxed-slices-lint.rs
new file mode 100644
index 00000000000..dd78e481e0e
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-lint.rs
@@ -0,0 +1,30 @@
+//@ run-pass
+//@ run-rustfix
+//@ rustfix-only-machine-applicable
+
+#[allow(unused_must_use, unused_allocation)]
+fn main() {
+    let boxed = vec![1, 2].into_boxed_slice();
+
+    // Expressions that should trigger the lint
+    boxed.into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+    Box::new(boxed.clone()).into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+    Box::new(Box::new(boxed.clone())).into_iter();
+    //~^ WARNING this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter`
+    //~| WARNING this changes meaning
+
+    // Expressions that should not
+    (&boxed).into_iter();
+
+    for _ in &boxed {}
+    (&boxed as &[_]).into_iter();
+    boxed[..].into_iter();
+    std::iter::IntoIterator::into_iter(&boxed);
+
+    #[allow(boxed_slice_into_iter)]
+    boxed.into_iter();
+}
diff --git a/tests/ui/iterators/into-iter-on-boxed-slices-lint.stderr b/tests/ui/iterators/into-iter-on-boxed-slices-lint.stderr
new file mode 100644
index 00000000000..b73faf0dbd3
--- /dev/null
+++ b/tests/ui/iterators/into-iter-on-boxed-slices-lint.stderr
@@ -0,0 +1,35 @@
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-lint.rs:10:11
+   |
+LL |     boxed.into_iter();
+   |           ^^^^^^^^^
+   |
+   = warning: this changes meaning in Rust 2024
+   = note: `#[warn(boxed_slice_into_iter)]` on by default
+help: use `.iter()` instead of `.into_iter()` to avoid ambiguity
+   |
+LL |     boxed.iter();
+   |           ~~~~
+help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value
+   |
+LL |     IntoIterator::into_iter(boxed);
+   |     ++++++++++++++++++++++++     ~
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-lint.rs:13:29
+   |
+LL |     Box::new(boxed.clone()).into_iter();
+   |                             ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
+   |
+   = warning: this changes meaning in Rust 2024
+
+warning: this method call resolves to `<&Box<[T]> as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<Box<[T]> as IntoIterator>::into_iter` in Rust 2024
+  --> $DIR/into-iter-on-boxed-slices-lint.rs:16:39
+   |
+LL |     Box::new(Box::new(boxed.clone())).into_iter();
+   |                                       ^^^^^^^^^ help: use `.iter()` instead of `.into_iter()` to avoid ambiguity: `iter`
+   |
+   = warning: this changes meaning in Rust 2024
+
+warning: 3 warnings emitted
+
diff --git a/tests/ui/traits/method-on-unbounded-type-param.stderr b/tests/ui/traits/method-on-unbounded-type-param.stderr
index 4d968e7bee1..0b1f83a9de9 100644
--- a/tests/ui/traits/method-on-unbounded-type-param.stderr
+++ b/tests/ui/traits/method-on-unbounded-type-param.stderr
@@ -76,8 +76,8 @@ LL |     x.cmp(&x);
            which is required by `&mut dyn T: Iterator`
    = help: items from traits can only be used if the trait is implemented and in scope
    = note: the following traits define an item `cmp`, perhaps you need to implement one of them:
-           candidate #1: `Iterator`
-           candidate #2: `Ord`
+           candidate #1: `Ord`
+   = note: the trait `Iterator` defines an item `cmp`, but is explicitly unimplemented
 
 error: aborting due to 4 previous errors