From 7768ce5f334f92117f40da35d4d4d3593cac9880 Mon Sep 17 00:00:00 2001 From: ltdk Date: Thu, 2 Nov 2023 22:39:47 -0400 Subject: [PATCH] WIP: Add edition override --- compiler/rustc_feature/src/builtin_attrs.rs | 7 +- compiler/rustc_hir_analysis/src/collect.rs | 21 +- compiler/rustc_hir_typeck/src/method/probe.rs | 12 ++ compiler/rustc_lint/src/array_into_iter.rs | 182 ++++++++++++++---- compiler/rustc_lint/src/lib.rs | 1 + compiler/rustc_middle/src/ty/trait_def.rs | 7 +- compiler/rustc_smir/src/rustc_smir/mod.rs | 1 + compiler/rustc_span/src/symbol.rs | 2 +- compiler/stable_mir/src/ty.rs | 1 + library/alloc/src/boxed.rs | 42 ++++ library/core/src/array/iter.rs | 2 +- library/core/src/iter/traits/collect.rs | 3 +- .../into-iter-on-boxed-slices-2021.rs | 46 +++++ .../into-iter-on-boxed-slices-2024.rs | 31 +++ .../into-iter-on-boxed-slices-lint.rs | 30 +++ 15 files changed, 338 insertions(+), 50 deletions(-) create mode 100644 tests/ui/iterators/into-iter-on-boxed-slices-2021.rs create mode 100644 tests/ui/iterators/into-iter-on-boxed-slices-2024.rs create mode 100644 tests/ui/iterators/into-iter-on-boxed-slices-lint.rs diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index e808e4815fe0b..1f4214d10118e 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -795,9 +795,10 @@ 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, - "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, + "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, ..."), ErrorFollowing, diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 9636c6144461b..a40b7629b04d6 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -911,8 +911,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) { @@ -1047,6 +1063,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 74f469cb39cbf..da8b439fc5e38 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1597,6 +1597,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(trait_ref.def_id); + if trait_def.skip_boxed_slice_during_method_dispatch { + return ProbeResult::NoMatch; + } + } } let predicate = ty::Binder::dummy(trait_ref).to_predicate(self.tcx); parent_pred = Some(predicate); diff --git a/compiler/rustc_lint/src/array_into_iter.rs b/compiler/rustc_lint/src/array_into_iter.rs index 814991cd8c971..b8a60746101c2 100644 --- a/compiler/rustc_lint/src/array_into_iter.rs +++ b/compiler/rustc_lint/src/array_into_iter.rs @@ -3,12 +3,13 @@ use crate::{ LateContext, LateLintPass, LintContext, }; use rustc_hir as hir; -use rustc_middle::ty; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; +use rustc_middle::ty::{self, Ty}; use rustc_session::lint::FutureIncompatibilityReason; use rustc_span::edition::Edition; use rustc_span::symbol::sym; use rustc_span::Span; +use std::ops::ControlFlow; declare_lint! { /// The `array_into_iter` lint detects calling `into_iter` on arrays. @@ -38,16 +39,119 @@ declare_lint! { reference: "", }; } +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 1.??, 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 1.?? 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, Default)] -pub struct ArrayIntoIter { +#[derive(Copy, Clone)] +pub struct CommonIntoIter { for_expr_span: Span, + filter: F, + namer: N, +} + +#[derive(Copy, Clone)] +pub struct ArrayIntoIter(CommonIntoIter); +impl Default for ArrayIntoIter { + fn default() -> ArrayIntoIter { + ArrayIntoIter(CommonIntoIter { + for_expr_span: Span::default(), + filter: array_filter, + namer: array_namer, + }) + } +} + +#[derive(Copy, Clone)] +pub struct BoxedSliceIntoIter(CommonIntoIter); +impl Default for BoxedSliceIntoIter { + fn default() -> BoxedSliceIntoIter { + BoxedSliceIntoIter(CommonIntoIter { + for_expr_span: Span::default(), + filter: boxed_slice_filter, + namer: boxed_slice_namer, + }) + } } impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]); +impl_lint_pass!(BoxedSliceIntoIter => [BOXED_SLICE_INTO_ITER]); -impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { +type ArrayFilter = impl Copy + FnMut(Ty<'_>) -> ControlFlow; +type BoxedSliceFilter = impl Copy + FnMut(Ty<'_>) -> ControlFlow; +type ArrayNamer = impl Copy + FnMut(Ty<'_>) -> &'static str; +type BoxedSliceNamer = impl Copy + FnMut(Ty<'_>) -> &'static str; + +fn array_filter(ty: Ty<'_>) -> ControlFlow { + 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() => ControlFlow::Break(false), + ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => { + ControlFlow::Break(false) + } + // Found an actual array type without matching a &[T; N] first. + // This is the problematic case. + ty::Array(..) => ControlFlow::Break(true), + _ => ControlFlow::Continue(()), + } +} + +fn boxed_slice_filter(_ty: Ty<'_>) -> ControlFlow { + todo!() +} + +fn array_namer(ty: Ty<'_>) -> &'static str { + match *ty.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"), + } +} + +fn boxed_slice_namer(_ty: Ty<'_>) -> &'static str { + todo!() +} + +impl CommonIntoIter +where + F: FnMut(Ty<'_>) -> ControlFlow, + N: FnMut(Ty<'_>) -> &'static str, +{ + fn check_expr<'tcx>( + &mut self, + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Option<(Span, ArrayIntoIterDiag<'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 { @@ -65,7 +169,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { // 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; + return None; } // Check if the method call actually calls the libcore @@ -73,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { 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, + _ => return None, }; // As this is a method call expression, we have at least one argument. @@ -81,45 +185,27 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { let adjustments = cx.typeck_results().expr_adjustments(receiver_arg); let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else { - return; + return None; }; 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; + let found_it = 'outer: { + for ty in types { + match (self.filter)(ty) { + ControlFlow::Break(b) => break 'outer b, + ControlFlow::Continue(()) => (), } - _ => {} } - } - - if !found_array { - return; + false + }; + if !found_it { + return None; } // 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 target = (self.namer)(*target); 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()), @@ -132,11 +218,25 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { } else { None }; - cx.emit_spanned_lint( - ARRAY_INTO_ITER, - call.ident.span, - ArrayIntoIterDiag { target, suggestion: call.ident.span, sub }, - ); + + Some((call.ident.span, ArrayIntoIterDiag { target, suggestion: call.ident.span, sub })) + } else { + None + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + if let Some((span, decorator)) = self.0.check_expr(cx, expr) { + cx.emit_spanned_lint(ARRAY_INTO_ITER, span, decorator); + } + } +} +impl<'tcx> LateLintPass<'tcx> for BoxedSliceIntoIter { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) { + if let Some((span, decorator)) = self.0.check_expr(cx, expr) { + cx.emit_spanned_lint(BOXED_SLICE_INTO_ITER, span, decorator); } } } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index d61c59af1e05f..8a0b198c4a4c2 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -39,6 +39,7 @@ #![feature(min_specialization)] #![feature(never_type)] #![feature(rustc_attrs)] +#![feature(type_alias_impl_trait)] #![recursion_limit = "256"] #![deny(rustc::untranslatable_diagnostic)] #![deny(rustc::diagnostic_outside_of_impl)] diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index bf9b244936fc6..5d5866a67cca8 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -40,11 +40,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 2021 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/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index 25bd82bf1ef52..4a7fb06162fd6 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -1400,6 +1400,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 3f99d2a4b1ffb..f25da43eb4db6 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1416,7 +1416,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 5dfaa0fd89158..08a0d4a454617 100644 --- a/compiler/stable_mir/src/ty.rs +++ b/compiler/stable_mir/src/ty.rs @@ -478,6 +478,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>, pub implement_via_object: bool, diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index cae860ccf328e..4e3eef8fe5612 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -134,6 +134,45 @@ //! is not allowed. For more guidance on working with box from unsafe code, see //! [rust-lang/unsafe-code-guidelines#326][ucg#326]. //! +//! # Editions +//! +//! A special case exists for the implementation of `IntoIterator` for arrays on the Rust 2021 +//! edition, as documented [here][array]. Unfortunately, it was later found that a similar +//! workaround should be added for boxed slices, and this was applied in the 2024 edition. +//! +//! Specifically, `IntoIterator` is implemented for `Box<[T]>` on all editions, but specific calls +//! to `into_iter()` for boxed slices will defer to the slice implementation on editions before +//! 2024: +//! +#![cfg_attr(bootstrap, doc = "```rust,edition2021,ignore")] +#![cfg_attr(not(bootstrap), doc = "```rust,edition2021")] +//! // Rust 2015, 2018, and 2021: +//! +//! # #![allow(boxed_slice_into_iter)] // override our `deny(warnings)` +//! let boxed_slice: Box<[i32]> = vec![0; 3].into_boxed_slice(); +//! +//! // This creates a slice iterator, producing references to each value. +//! for item in boxed_slice.into_iter().enumerate() { +//! let (i, x): (usize, &i32) = item; +//! println!("boxed_slice[{i}] = {x}"); +//! } +//! +//! // The `boxed_slice_into_iter` lint suggests this change for future compatibility: +//! for item in boxed_slice.iter().enumerate() { +//! let (i, x): (usize, &i32) = item; +//! println!("boxed_slice[{i}] = {x}"); +//! } +//! +//! // You can explicitly iterate a boxed slice by value using `IntoIterator::into_iter` +//! for item in IntoIterator::into_iter(boxed_slice).enumerate() { +//! let (i, x): (usize, i32) = item; +//! println!("boxed_slice[{i}] = {x}"); +//! } +//! ``` +//! +//! Similar to the array implementation, this may be modified in the future to remove this override, +//! and it's best to avoid relying on this edition-dependent behavior if you wish to preserve +//! compatibility with future versions of the compiler. //! //! [ucg#198]: https://github.com/rust-lang/unsafe-code-guidelines/issues/198 //! [ucg#326]: https://github.com/rust-lang/unsafe-code-guidelines/issues/326 @@ -2041,6 +2080,9 @@ impl FromIterator for Box<[I]> { #[stable(feature = "boxed_slice_into_iter", since = "CURRENT_RUSTC_VERSION")] impl !Iterator for Box<[I], A> {} +// Note: the `#[rustc_skip_during_method_dispatch(boxed_slice)]` on `trait IntoIterator` +// hides this implementation from explicit `.into_iter()` calls on editions < 2024, +// so those calls will still resolve to the slice implementation, by reference. #[stable(feature = "boxed_slice_into_iter", since = "CURRENT_RUSTC_VERSION")] impl IntoIterator for Box<[I], A> { type IntoIter = vec::IntoIter; diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index 321357a15bf04..b3b68356d3150 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -38,7 +38,7 @@ pub struct IntoIter { 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 0d1cf7941fb69..29189c3f7e460 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -235,7 +235,8 @@ pub trait FromIterator: Sized { /// } /// ``` #[rustc_diagnostic_item = "IntoIterator"] -#[rustc_skip_array_during_method_dispatch] +#[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 0000000000000..533a62705848f --- /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 &[T; N]`, + // which we continue to support for compatibility. + let _: Iter<'_, i32> = boxed_slice.into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + + let _: Iter<'_, i32> = Box::new(boxed_slice).into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + + let _: Iter<'_, i32> = Rc::new(boxed_slice).into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + let _: Iter<'_, i32> = Array(boxed_slice).into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + + // But you can always use the trait method explicitly as an boxed_slice. + let _: IntoIter = IntoIterator::into_iter(boxed_slice); + + for _ in [1, 2, 3].into_iter() {} + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning +} + +/// User type that dereferences to a boxed slice. +struct Array(Box); + +impl Deref for Array { + type Target = Box; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} 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 0000000000000..4797fe26dcdb3 --- /dev/null +++ b/tests/ui/iterators/into-iter-on-boxed-slices-2024.rs @@ -0,0 +1,31 @@ +// check-pass +// edition:2024 + +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 = boxed_slice.into_iter(); + + // The `boxed_slice_into_iter` lint doesn't cover other wrappers that deref to a boxed_slice. + let _: IntoIter = Rc::new(boxed_slice).into_iter(); + let _: IntoIter = Array(boxed_slice).into_iter(); + + // You can always use the trait method explicitly as a boxed_slice. + let _: IntoIter = IntoIterator::into_iter(boxed_slice); +} + +/// User type that dereferences to a boxed slice. +struct Array(Box); + +impl Deref for Array { + type Target = Box; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} 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 0000000000000..8c73fe228c1ad --- /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 `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + Box::new(boxed).into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] as IntoIterator>::into_iter` + //~| WARNING this changes meaning + Box::new(Box::new(boxed)).into_iter(); + //~^ WARNING this method call resolves to `<&[T; N] 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(); +}