Skip to content

Commit

Permalink
Auto merge of rust-lang#84147 - cuviper:array-method-dispatch, r=niko…
Browse files Browse the repository at this point in the history
…matsakis,m-ou-se

Cautiously add IntoIterator for arrays by value

Add the attribute described in rust-lang#84133, `#[rustc_skip_array_during_method_dispatch]`, which effectively hides a trait from method dispatch when the receiver type is an array.

Then cherry-pick `IntoIterator for [T; N]` from rust-lang#65819 and gate it with that attribute. Arrays can now be used as `IntoIterator` normally, but `array.into_iter()` has edition-dependent behavior, returning `slice::Iter` for 2015 and 2018 editions, or `array::IntoIter` for 2021 and later.

r? `@nikomatsakis`
cc `@LukasKalbertodt` `@rust-lang/libs`
  • Loading branch information
bors committed Apr 25, 2021
2 parents 5da10c0 + f6a90ca commit 13a2615
Show file tree
Hide file tree
Showing 18 changed files with 237 additions and 171 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,11 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
rustc_main, Normal, template!(Word),
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
),
rustc_attr!(
rustc_skip_array_during_method_dispatch, Normal, template!(Word),
"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."
),

// ==========================================================================
// Internal attributes, Testing:
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_metadata/src/rmeta/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
data.paren_sugar,
data.has_auto_impl,
data.is_marker,
data.skip_array_during_method_dispatch,
data.specialization_kind,
self.def_path_hash(item_id),
)
Expand All @@ -767,6 +768,7 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
false,
false,
false,
false,
ty::trait_def::TraitSpecializationKind::None,
self.def_path_hash(item_id),
),
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,7 @@ impl EncodeContext<'a, 'tcx> {
paren_sugar: trait_def.paren_sugar,
has_auto_impl: self.tcx.trait_is_auto(def_id),
is_marker: trait_def.is_marker,
skip_array_during_method_dispatch: trait_def.skip_array_during_method_dispatch,
specialization_kind: trait_def.specialization_kind,
};

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ struct TraitData {
paren_sugar: bool,
has_auto_impl: bool,
is_marker: bool,
skip_array_during_method_dispatch: bool,
specialization_kind: ty::trait_def::TraitSpecializationKind,
}

Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_middle/src/ty/trait_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ pub struct TraitDef {
/// and thus `impl`s of it are allowed to overlap.
pub is_marker: bool,

/// If `true`, then this trait has the `#[rustc_skip_array_during_method_dispatch]`
/// 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,

/// Used to determine whether the standard library is allowed to specialize
/// on this trait.
pub specialization_kind: TraitSpecializationKind,
Expand Down Expand Up @@ -82,6 +87,7 @@ impl<'tcx> TraitDef {
paren_sugar: bool,
has_auto_impl: bool,
is_marker: bool,
skip_array_during_method_dispatch: bool,
specialization_kind: TraitSpecializationKind,
def_path_hash: DefPathHash,
) -> TraitDef {
Expand All @@ -91,6 +97,7 @@ impl<'tcx> TraitDef {
paren_sugar,
has_auto_impl,
is_marker,
skip_array_during_method_dispatch,
specialization_kind,
def_path_hash,
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ symbols! {
rustc_regions,
rustc_reservation_impl,
rustc_serialize,
rustc_skip_array_during_method_dispatch,
rustc_specialization_trait,
rustc_stable,
rustc_std_internal_symbol,
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_typeck/src/check/method/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1461,6 +1461,16 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
}

TraitCandidate(trait_ref) => {
if let Some(method_name) = self.method_name {
// Some trait methods are excluded for arrays before 2021.
// (`array.into_iter()` wants a slice iterator for compatibility.)
if self_ty.is_array() && !method_name.span.rust_2021() {
let trait_def = self.tcx.trait_def(trait_ref.def_id);
if trait_def.skip_array_during_method_dispatch {
return ProbeResult::NoMatch;
}
}
}
let predicate = trait_ref.without_const().to_predicate(self.tcx);
let obligation = traits::Obligation::new(cause, self.param_env, predicate);
if !self.predicate_may_hold(&obligation) {
Expand Down
13 changes: 12 additions & 1 deletion compiler/rustc_typeck/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,8 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
}

let is_marker = tcx.has_attr(def_id, sym::marker);
let skip_array_during_method_dispatch =
tcx.has_attr(def_id, sym::rustc_skip_array_during_method_dispatch);
let spec_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) {
Expand All @@ -1199,7 +1201,16 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: DefId) -> ty::TraitDef {
ty::trait_def::TraitSpecializationKind::None
};
let def_path_hash = tcx.def_path_hash(def_id);
ty::TraitDef::new(def_id, unsafety, paren_sugar, is_auto, is_marker, spec_kind, def_path_hash)
ty::TraitDef::new(
def_id,
unsafety,
paren_sugar,
is_auto,
is_marker,
skip_array_during_method_dispatch,
spec_kind,
def_path_hash,
)
}

fn has_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, node: Node<'tcx>) -> Option<Span> {
Expand Down
22 changes: 22 additions & 0 deletions library/core/src/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,28 @@ impl<T: fmt::Debug, const N: usize> fmt::Debug for [T; N] {
}
}

// Note: the `#[rustc_skip_array_during_method_dispatch]` 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.
#[cfg(not(bootstrap))]
#[stable(feature = "array_into_iter_impl", since = "1.53.0")]
impl<T, const N: usize> IntoIterator for [T; N] {
type Item = T;
type IntoIter = IntoIter<T, N>;

/// Creates a consuming iterator, that is, one that moves each value out of
/// the array (from start to end). The array cannot be used after calling
/// this unless `T` implements `Copy`, so the whole array is copied.
///
/// Arrays have special behavior when calling `.into_iter()` prior to the
/// 2021 edition -- see the [array] Editions section for more information.
///
/// [array]: prim@array
fn into_iter(self) -> Self::IntoIter {
IntoIter::new(self)
}
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T, const N: usize> IntoIterator for &'a [T; N] {
type Item = &'a T;
Expand Down
1 change: 1 addition & 0 deletions library/core/src/iter/traits/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ pub trait FromIterator<A>: Sized {
/// }
/// ```
#[rustc_diagnostic_item = "IntoIterator"]
#[cfg_attr(not(bootstrap), rustc_skip_array_during_method_dispatch)]
#[stable(feature = "rust1", since = "1.0.0")]
pub trait IntoIterator {
/// The type of the elements being iterated over.
Expand Down
77 changes: 57 additions & 20 deletions library/std/src/primitive_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ mod prim_pointer {}
/// - [`Copy`]
/// - [`Clone`]
/// - [`Debug`]
/// - [`IntoIterator`] (implemented for `&[T; N]` and `&mut [T; N]`)
/// - [`IntoIterator`] (implemented for `[T; N]`, `&[T; N]` and `&mut [T; N]`)
/// - [`PartialEq`], [`PartialOrd`], [`Eq`], [`Ord`]
/// - [`Hash`]
/// - [`AsRef`], [`AsMut`]
Expand All @@ -517,7 +517,8 @@ mod prim_pointer {}
///
/// # Examples
///
/// ```
#[cfg_attr(bootstrap, doc = "```ignore")]
#[cfg_attr(not(bootstrap), doc = "```")]
/// let mut array: [i32; 3] = [0; 3];
///
/// array[1] = 1;
Expand All @@ -526,31 +527,16 @@ mod prim_pointer {}
/// assert_eq!([1, 2], &array[1..]);
///
/// // This loop prints: 0 1 2
/// for x in &array {
/// for x in array {
/// print!("{} ", x);
/// }
/// ```
///
/// An array itself is not iterable:
///
/// ```compile_fail,E0277
/// let array: [i32; 3] = [0; 3];
///
/// for x in array { }
/// // error: the trait bound `[i32; 3]: std::iter::Iterator` is not satisfied
/// ```
///
/// The solution is to coerce the array to a slice by calling a slice method:
/// You can also iterate over reference to the array's elements:
///
/// ```
/// # let array: [i32; 3] = [0; 3];
/// for x in array.iter() { }
/// ```
///
/// You can also use the array reference's [`IntoIterator`] implementation:
/// let array: [i32; 3] = [0; 3];
///
/// ```
/// # let array: [i32; 3] = [0; 3];
/// for x in &array { }
/// ```
///
Expand All @@ -564,6 +550,57 @@ mod prim_pointer {}
/// move_away(roa);
/// ```
///
/// # Editions
///
/// Prior to Rust 1.53, arrays did not implement `IntoIterator` by value, so the method call
/// `array.into_iter()` auto-referenced into a slice iterator. That behavior is preserved in the
/// 2015 and 2018 editions of Rust for compatability, ignoring `IntoIterator` by value.
///
#[cfg_attr(bootstrap, doc = "```rust,edition2018,ignore")]
#[cfg_attr(not(bootstrap), doc = "```rust,edition2018")]
/// # #![allow(array_into_iter)] // override our `deny(warnings)`
/// let array: [i32; 3] = [0; 3];
///
/// // This creates a slice iterator, producing references to each value.
/// for item in array.into_iter().enumerate() {
/// let (i, x): (usize, &i32) = item;
/// println!("array[{}] = {}", i, x);
/// }
///
/// // The `array_into_iter` lint suggests this change for future compatibility:
/// for item in array.iter().enumerate() {
/// let (i, x): (usize, &i32) = item;
/// println!("array[{}] = {}", i, x);
/// }
///
/// // You can explicitly iterate an array by value using
/// // `IntoIterator::into_iter` or `std::array::IntoIter::new`:
/// for item in IntoIterator::into_iter(array).enumerate() {
/// let (i, x): (usize, i32) = item;
/// println!("array[{}] = {}", i, x);
/// }
/// ```
///
/// Starting in the 2021 edition, `array.into_iter()` will use `IntoIterator` normally to iterate
/// by value, and `iter()` should be used to iterate by reference like previous editions.
///
/// ```rust,edition2021,ignore
/// # // FIXME: ignored because 2021 testing is still unstable
/// let array: [i32; 3] = [0; 3];
///
/// // This iterates by reference:
/// for item in array.iter().enumerate() {
/// let (i, x): (usize, &i32) = item;
/// println!("array[{}] = {}", i, x);
/// }
///
/// // This iterates by value:
/// for item in array.into_iter().enumerate() {
/// let (i, x): (usize, i32) = item;
/// println!("array[{}] = {}", i, x);
/// }
/// ```
///
/// [slice]: prim@slice
/// [`Debug`]: fmt::Debug
/// [`Hash`]: hash::Hash
Expand Down
11 changes: 2 additions & 9 deletions src/test/ui/iterators/array-of-ranges.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
// check-pass

fn main() {
for _ in [0..1] {}
//~^ ERROR is not an iterator
for _ in [0..=1] {}
//~^ ERROR is not an iterator
for _ in [0..] {}
//~^ ERROR is not an iterator
for _ in [..1] {}
//~^ ERROR is not an iterator
for _ in [..=1] {}
//~^ ERROR is not an iterator
let start = 0;
let end = 0;
for _ in [start..end] {}
//~^ ERROR is not an iterator
let array_of_range = [start..end];
for _ in array_of_range {}
//~^ ERROR is not an iterator
for _ in [0..1, 2..3] {}
//~^ ERROR is not an iterator
for _ in [0..=1] {}
//~^ ERROR is not an iterator
}
Loading

0 comments on commit 13a2615

Please sign in to comment.