From 66d44ce5f4be413d9bae6cad80bf26bf567096c1 Mon Sep 17 00:00:00 2001 From: Pavel Grigorenko Date: Mon, 2 Sep 2024 03:05:37 +0300 Subject: [PATCH] rustc_skip_during_method_dispatch: decouple receiver & edition --- Cargo.lock | 1 + compiler/rustc_feature/src/builtin_attrs.rs | 9 +- compiler/rustc_hir_analysis/Cargo.toml | 1 + compiler/rustc_hir_analysis/src/collect.rs | 87 +++++++++++++++---- compiler/rustc_hir_typeck/src/method/probe.rs | 21 +---- compiler/rustc_middle/src/ty/trait_def.rs | 58 +++++++++++-- .../rustc_smir/src/rustc_smir/convert/ty.rs | 33 ++++++- compiler/stable_mir/src/edition.rs | 10 +++ compiler/stable_mir/src/lib.rs | 1 + compiler/stable_mir/src/ty.rs | 11 ++- library/alloc/src/boxed.rs | 2 +- library/core/src/array/iter.rs | 2 +- library/core/src/iter/traits/collect.rs | 7 +- 13 files changed, 191 insertions(+), 52 deletions(-) create mode 100644 compiler/stable_mir/src/edition.rs diff --git a/Cargo.lock b/Cargo.lock index 7244ae14fb8da..19f3a75ba8af1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3745,6 +3745,7 @@ dependencies = [ "rustc_trait_selection", "rustc_type_ir", "smallvec", + "thin-vec", "tracing", ] diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index e2491922b8df6..f5eb87448446e 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -969,11 +969,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "the `#[rustc_main]` attribute is used internally to specify test entry point function", ), rustc_attr!( - rustc_skip_during_method_dispatch, Normal, template!(List: "array, boxed_slice"), WarnFollowing, - EncodeCrossCrate::No, + rustc_skip_during_method_dispatch, Normal, + template!(List: r#"receiver = "name", before_edition = "N""#), + DuplicatesOk, 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)." + from method dispatch when the receiver is of the type `receiver`, \ + for compatibility in editions < `before_edition`." ), rustc_attr!( rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), diff --git a/compiler/rustc_hir_analysis/Cargo.toml b/compiler/rustc_hir_analysis/Cargo.toml index 04ca7f123d3e0..560d9c2f52e23 100644 --- a/compiler/rustc_hir_analysis/Cargo.toml +++ b/compiler/rustc_hir_analysis/Cargo.toml @@ -30,5 +30,6 @@ rustc_target = { path = "../rustc_target" } rustc_trait_selection = { path = "../rustc_trait_selection" } rustc_type_ir = { path = "../rustc_type_ir" } smallvec = { version = "1.8.1", features = ["union", "may_dangle"] } +thin-vec = "0.2.12" tracing = "0.1" # tidy-alphabetical-end diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 53105f337c4a8..6f4aeefafc5f0 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -18,7 +18,9 @@ use std::cell::Cell; use std::iter; use std::ops::Bound; -use rustc_ast::Recovered; +use rustc_ast::{ + self as ast, Attribute, MetaItem, MetaItemKind, MetaItemLit, NestedMetaItem, Recovered, +}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::unord::UnordMap; @@ -33,15 +35,18 @@ use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::ObligationCause; use rustc_middle::hir::nested_filter; use rustc_middle::query::Providers; +use rustc_middle::ty::trait_def::GatedReceiver; use rustc_middle::ty::util::{Discr, IntTypeExt}; use rustc_middle::ty::{self, AdtKind, Const, IsSuggestable, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; +use rustc_span::edition::Edition; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; use rustc_target::spec::abi; use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::ObligationCtxt; +use thin_vec::ThinVec; use tracing::{debug, instrument}; use crate::check::intrinsic::intrinsic_operation_unsafety; @@ -1205,6 +1210,60 @@ fn adt_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::AdtDef<'_> { tcx.mk_adt_def(def_id.to_def_id(), kind, variants, repr, is_anonymous) } +fn parse_rustc_skip_during_method_dispatch( + dcx: DiagCtxtHandle<'_>, + attr: &Attribute, +) -> Result<(GatedReceiver, Edition), ErrorGuaranteed> { + debug_assert!(attr.has_name(sym::rustc_skip_during_method_dispatch)); + let mut receiver: Option = None; + let mut before: Option = None; + for arg in attr.meta_item_list().unwrap_or_default() { + let arg_span = arg.span(); + if let NestedMetaItem::MetaItem(MetaItem { + path: ast::Path { segments, span: key_span, .. }, + kind: MetaItemKind::NameValue(MetaItemLit { symbol: value, span: value_span, .. }), + .. + }) = arg + && let [ast::PathSegment { ident: key, .. }] = segments.as_slice() + { + match key.as_str() { + "receiver" => { + if receiver + .replace(value.as_str().parse().map_err(|()| { + dcx.span_err(value_span, "Expected `array` or `boxed_slice`") + })?) + .is_some() + { + Err(dcx.span_err(arg_span, "`receiver` should be specified only once"))? + } + } + + "before" => { + if before + .replace( + value.as_str().parse().map_err(|()| { + dcx.span_err(value_span, "Could not parse edition") + })?, + ) + .is_some() + { + Err(dcx.span_err(arg_span, "`before` should be specified only once"))? + } + } + _ => Err(dcx.span_err(key_span, "Expected either `receiver` or `before`"))?, + } + } else { + Err(dcx + .span_err(arg_span, "Expected either `receiver = \"...\"` or `before = \"...\"`"))? + }; + } + + Ok(( + receiver.ok_or_else(|| dcx.span_err(attr.span, "Missing `receiver`"))?, + before.ok_or_else(|| dcx.span_err(attr.span, "Missing `before`"))?, + )) +} + fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let item = tcx.hir().expect_item(def_id); @@ -1234,20 +1293,19 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive); let is_fundamental = tcx.has_attr(def_id, sym::fundamental); - // 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; + let mut skip_during_method_dispatch: ThinVec<(GatedReceiver, Edition)> = ThinVec::new(); 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, - _ => (), - } - } + if let Ok(parsed) = parse_rustc_skip_during_method_dispatch(tcx.dcx(), attr) { + if skip_during_method_dispatch.iter().any(|prev| prev.0 == parsed.0) { + tcx.dcx().span_err( + attr.span, + format!( + "Duplicate `#[rustc_skip_during_method_dispatch(receiver = \"{}\")]`", + parsed.0 + ), + ); } + skip_during_method_dispatch.push(parsed); } } @@ -1386,8 +1444,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { is_marker, is_coinductive: rustc_coinductive || is_auto, is_fundamental, - skip_array_during_method_dispatch, - skip_boxed_slice_during_method_dispatch, + skip_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 0cf5403b3c085..7d0761dde936e 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1473,24 +1473,11 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { )); } TraitCandidate(poly_trait_ref) => { - // Some trait methods are excluded for arrays before 2021. - // (`array.into_iter()` wants a slice iterator for compatibility.) + // Handle `#[rustc_skip_during_method_dispatch]` if let Some(method_name) = self.method_name { - if self_ty.is_array() && !method_name.span.at_least_rust_2021() { - let trait_def = self.tcx.trait_def(poly_trait_ref.def_id()); - if trait_def.skip_array_during_method_dispatch { - 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 { + let trait_def = self.tcx.trait_def(poly_trait_ref.def_id()); + for &(receiver, edition) in &trait_def.skip_during_method_dispatch { + if method_name.span.edition() < edition && receiver.matches(self_ty) { return ProbeResult::NoMatch; } } diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index dfb137f738f1e..9e253aad2f120 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -1,4 +1,5 @@ use std::iter; +use std::str::FromStr; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::ErrorGuaranteed; @@ -6,6 +7,8 @@ use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_macros::{Decodable, Encodable, HashStable}; +use rustc_span::edition::Edition; +use thin_vec::ThinVec; use tracing::debug; use crate::query::LocalCrate; @@ -13,6 +16,48 @@ use crate::traits::specialization_graph; use crate::ty::fast_reject::{self, SimplifiedType, TreatParams}; use crate::ty::{Ident, Ty, TyCtxt}; +/// The `receiver` from the `#[rustc_skip_during_method_dispatch]` attribute. +/// Effectively represents a set of types. +#[derive(Copy, Clone, PartialEq, Eq, HashStable, Encodable, Decodable)] +pub enum GatedReceiver { + Array, + BoxedSlice, +} + +impl GatedReceiver { + #[must_use] + /// Checks whether a method call with a receiver of the given type + /// falls under `#[rustc_skip_during_method_dispatch]`'s jurisdiction + /// and should be dispatched only past a certain edition. + pub fn matches(self, ty: Ty<'_>) -> bool { + match self { + GatedReceiver::Array => ty.is_array(), + GatedReceiver::BoxedSlice => ty.is_box() && ty.boxed_ty().is_slice(), + } + } +} + +impl std::fmt::Display for GatedReceiver { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Array => "array", + Self::BoxedSlice => "boxed_slice", + }) + } +} + +impl FromStr for GatedReceiver { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "array" => Ok(Self::Array), + "boxed_slice" => Ok(Self::BoxedSlice), + _ => Err(()), + } + } +} + /// A trait's definition with type information. #[derive(HashStable, Encodable, Decodable)] pub struct TraitDef { @@ -50,15 +95,10 @@ pub struct TraitDef { /// added in the future. pub is_fundamental: bool, - /// 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, + /// If not empty, then this trait has the `#[rustc_skip_during_method_dispatch]` attribute, + /// indicating it should not be considered during method dispatch + /// for certain receivers before a specific edition. + pub skip_during_method_dispatch: ThinVec<(GatedReceiver, Edition)>, /// Used to determine whether the standard library is allowed to specialize /// on this trait. diff --git a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs index 9afd732486c28..d848329ab8e05 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/ty.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/ty.rs @@ -1,7 +1,9 @@ //! Conversion of internal Rust compiler `ty` items to stable ones. +use rustc_middle::ty::trait_def::GatedReceiver; use rustc_middle::ty::Ty; use rustc_middle::{mir, ty}; +use rustc_span::edition::Edition; use stable_mir::ty::{ AdtKind, FloatTy, GenericArgs, GenericParamDef, IntTy, Region, RigidTy, TyKind, UintTy, }; @@ -532,6 +534,30 @@ impl<'tcx> Stable<'tcx> for ty::trait_def::TraitSpecializationKind { } } +impl Stable<'_> for Edition { + type T = stable_mir::edition::Edition; + + fn stable(&self, _: &mut Tables<'_>) -> Self::T { + match self { + Self::Edition2015 => Self::T::Edition2015, + Self::Edition2018 => Self::T::Edition2018, + Self::Edition2021 => Self::T::Edition2021, + Self::Edition2024 => Self::T::Edition2024, + } + } +} + +impl Stable<'_> for GatedReceiver { + type T = stable_mir::ty::GatedReceiver; + + fn stable(&self, _: &mut Tables<'_>) -> Self::T { + match self { + Self::Array => Self::T::Array, + Self::BoxedSlice => Self::T::BoxedSlice, + } + } +} + impl<'tcx> Stable<'tcx> for ty::TraitDef { type T = stable_mir::ty::TraitDecl; fn stable(&self, tables: &mut Tables<'_>) -> Self::T { @@ -545,8 +571,11 @@ impl<'tcx> Stable<'tcx> for ty::TraitDef { has_auto_impl: self.has_auto_impl, 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, + skip_during_method_dispatch: self + .skip_during_method_dispatch + .iter() + .map(|x| x.stable(tables)) + .collect(), specialization_kind: self.specialization_kind.stable(tables), must_implement_one_of: self .must_implement_one_of diff --git a/compiler/stable_mir/src/edition.rs b/compiler/stable_mir/src/edition.rs new file mode 100644 index 0000000000000..701d4db11cdf5 --- /dev/null +++ b/compiler/stable_mir/src/edition.rs @@ -0,0 +1,10 @@ +use serde::Serialize; + +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub enum Edition { + Edition2015, + Edition2018, + Edition2021, + Edition2024, +} diff --git a/compiler/stable_mir/src/lib.rs b/compiler/stable_mir/src/lib.rs index b523e949cde3e..d1e012c4384a2 100644 --- a/compiler/stable_mir/src/lib.rs +++ b/compiler/stable_mir/src/lib.rs @@ -32,6 +32,7 @@ pub mod abi; #[macro_use] pub mod crate_def; pub mod compiler_interface; +pub mod edition; #[macro_use] pub mod error; pub mod mir; diff --git a/compiler/stable_mir/src/ty.rs b/compiler/stable_mir/src/ty.rs index 2f36aa5182965..aae0e5f9d4a61 100644 --- a/compiler/stable_mir/src/ty.rs +++ b/compiler/stable_mir/src/ty.rs @@ -7,6 +7,7 @@ use super::mir::{Body, Mutability, Safety}; use super::{with, DefId, Error, Symbol}; use crate::abi::{FnAbi, Layout}; use crate::crate_def::{CrateDef, CrateDefType}; +use crate::edition::Edition; use crate::mir::alloc::{read_target_int, read_target_uint, AllocId}; use crate::mir::mono::StaticDef; use crate::target::MachineInfo; @@ -1317,6 +1318,13 @@ pub enum TraitSpecializationKind { AlwaysApplicable, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +#[non_exhaustive] +pub enum GatedReceiver { + Array, + BoxedSlice, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct TraitDecl { pub def_id: TraitDef, @@ -1325,8 +1333,7 @@ pub struct TraitDecl { pub has_auto_impl: bool, pub is_marker: bool, pub is_coinductive: bool, - pub skip_array_during_method_dispatch: bool, - pub skip_boxed_slice_during_method_dispatch: bool, + pub skip_during_method_dispatch: Vec<(GatedReceiver, Edition)>, 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 38b1766c17440..34fca96abb7ce 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -2304,7 +2304,7 @@ impl<'a, I, A: Allocator> !Iterator for &'a Box<[I], A> {} #[stable(feature = "boxed_slice_into_iter", since = "1.80.0")] impl<'a, I, A: Allocator> !Iterator for &'a mut Box<[I], A> {} -// Note: the `#[rustc_skip_during_method_dispatch(boxed_slice)]` on `trait IntoIterator` +// Note: the `rustc_skip_during_method_dispatch` attribute 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 = "1.80.0")] diff --git a/library/core/src/array/iter.rs b/library/core/src/array/iter.rs index 2d19e4876f680..867cbcdb1f76d 100644 --- a/library/core/src/array/iter.rs +++ b/library/core/src/array/iter.rs @@ -35,7 +35,7 @@ pub struct IntoIter { alive: IndexRange, } -// Note: the `#[rustc_skip_during_method_dispatch(array)]` on `trait IntoIterator` +// Note: the `rustc_skip_during_method_dispatch` attribute 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 86660f2e375c3..85c358e04d503 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -313,7 +313,12 @@ where label = "`{Self}` is not an iterator", message = "`{Self}` is not an iterator" )] -#[rustc_skip_during_method_dispatch(array, boxed_slice)] +#[cfg_attr(bootstrap, rustc_skip_during_method_dispatch(array, boxed_slice))] +#[cfg_attr(not(bootstrap), rustc_skip_during_method_dispatch(receiver = "array", before = "2021"))] +#[cfg_attr( + not(bootstrap), + rustc_skip_during_method_dispatch(receiver = "boxed_slice", before = "2024") +)] #[stable(feature = "rust1", since = "1.0.0")] pub trait IntoIterator { /// The type of the elements being iterated over.