Skip to content
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

Cautiously add IntoIterator for arrays by value #84147

Merged
merged 9 commits into from
Apr 25, 2021
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 @@ -540,6 +540,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 @@ -1030,6 +1030,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.
cuviper marked this conversation as resolved.
Show resolved Hide resolved
// (`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")]
cuviper marked this conversation as resolved.
Show resolved Hide resolved
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
cuviper marked this conversation as resolved.
Show resolved Hide resolved
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