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: "", - }; -} - -#[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, + pub sub: Option, } #[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: "", + }; +} + +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>, 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 { 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", @@ -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 = 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 ` 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 ` 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 ` 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 ` 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 ` 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 = boxed_slice.clone().into_iter(); + + // And through other boxes. + let _: IntoIter = Box::new(boxed_slice.clone()).into_iter(); + + // You can always use the trait method explicitly as a boxed_slice. + let _: IntoIter = 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 ` 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 ` 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 ` 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