Skip to content

rustc_skip_during_method_dispatch: decouple receiver & edition #129871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3745,6 +3745,7 @@ dependencies = [
"rustc_trait_selection",
"rustc_type_ir",
"smallvec",
"thin-vec",
"tracing",
]

Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ..."),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_hir_analysis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
87 changes: 72 additions & 15 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<GatedReceiver> = None;
let mut before: Option<Edition> = 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);

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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,
Expand Down
21 changes: 4 additions & 17 deletions compiler/rustc_hir_typeck/src/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
58 changes: 49 additions & 9 deletions compiler/rustc_middle/src/ty/trait_def.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,63 @@
use std::iter;
use std::str::FromStr;

use rustc_data_structures::fx::FxIndexMap;
use rustc_errors::ErrorGuaranteed;
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;
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<Self, Self::Err> {
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 {
Expand Down Expand Up @@ -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.
Expand Down
33 changes: 31 additions & 2 deletions compiler/rustc_smir/src/rustc_smir/convert/ty.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions compiler/stable_mir/src/edition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::Serialize;

#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub enum Edition {
Edition2015,
Edition2018,
Edition2021,
Edition2024,
}
1 change: 1 addition & 0 deletions compiler/stable_mir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 9 additions & 2 deletions compiler/stable_mir/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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<Vec<Ident>>,
pub implement_via_object: bool,
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
Loading
Loading