Skip to content

Commit 7768ce5

Browse files
committed
WIP: Add edition override
1 parent f836f1a commit 7768ce5

File tree

15 files changed

+338
-50
lines changed

15 files changed

+338
-50
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -795,9 +795,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
795795
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
796796
),
797797
rustc_attr!(
798-
rustc_skip_array_during_method_dispatch, Normal, template!(Word), WarnFollowing,
799-
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
800-
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
798+
rustc_skip_during_method_dispatch, Normal, template!(List: "array, boxed_slice, ..."), WarnFollowing,
799+
"the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \
800+
from method dispatch when the receiver is of the following type, for compatibility in \
801+
editions < 2021 (array) or editions < 2024 (boxed_slice)."
801802
),
802803
rustc_attr!(
803804
rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."), ErrorFollowing,

compiler/rustc_hir_analysis/src/collect.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -911,8 +911,24 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
911911

912912
let is_marker = tcx.has_attr(def_id, sym::marker);
913913
let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive);
914-
let skip_array_during_method_dispatch =
915-
tcx.has_attr(def_id, sym::rustc_skip_array_during_method_dispatch);
914+
915+
// FIXME: We could probably do way better attribute validation here.
916+
let mut skip_array_during_method_dispatch = false;
917+
let mut skip_boxed_slice_during_method_dispatch = false;
918+
for attr in tcx.get_attrs(def_id, sym::rustc_skip_during_method_dispatch) {
919+
if let Some(lst) = attr.meta_item_list() {
920+
for item in lst {
921+
if let Some(ident) = item.ident() {
922+
match ident.as_str() {
923+
"array" => skip_array_during_method_dispatch = true,
924+
"boxed_slice" => skip_boxed_slice_during_method_dispatch = true,
925+
_ => (),
926+
}
927+
}
928+
}
929+
}
930+
}
931+
916932
let specialization_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
917933
ty::trait_def::TraitSpecializationKind::Marker
918934
} else if tcx.has_attr(def_id, sym::rustc_specialization_trait) {
@@ -1047,6 +1063,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
10471063
is_marker,
10481064
is_coinductive: rustc_coinductive || is_auto,
10491065
skip_array_during_method_dispatch,
1066+
skip_boxed_slice_during_method_dispatch,
10501067
specialization_kind,
10511068
must_implement_one_of,
10521069
implement_via_object,

compiler/rustc_hir_typeck/src/method/probe.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,18 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
15971597
return ProbeResult::NoMatch;
15981598
}
15991599
}
1600+
1601+
// Some trait methods are excluded for boxed slices before 2024.
1602+
// (`boxed_slice.into_iter()` wants a slice iterator for compatibility.)
1603+
if self_ty.is_box()
1604+
&& self_ty.boxed_ty().is_slice()
1605+
&& !method_name.span.at_least_rust_2024()
1606+
{
1607+
let trait_def = self.tcx.trait_def(trait_ref.def_id);
1608+
if trait_def.skip_boxed_slice_during_method_dispatch {
1609+
return ProbeResult::NoMatch;
1610+
}
1611+
}
16001612
}
16011613
let predicate = ty::Binder::dummy(trait_ref).to_predicate(self.tcx);
16021614
parent_pred = Some(predicate);

compiler/rustc_lint/src/array_into_iter.rs

+141-41
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ use crate::{
33
LateContext, LateLintPass, LintContext,
44
};
55
use rustc_hir as hir;
6-
use rustc_middle::ty;
76
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
7+
use rustc_middle::ty::{self, Ty};
88
use rustc_session::lint::FutureIncompatibilityReason;
99
use rustc_span::edition::Edition;
1010
use rustc_span::symbol::sym;
1111
use rustc_span::Span;
12+
use std::ops::ControlFlow;
1213

1314
declare_lint! {
1415
/// The `array_into_iter` lint detects calling `into_iter` on arrays.
@@ -38,16 +39,119 @@ declare_lint! {
3839
reference: "<https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>",
3940
};
4041
}
42+
declare_lint! {
43+
/// The `boxed_slice_into_iter` lint detects calling `into_iter` on boxed slices.
44+
///
45+
/// ### Example
46+
///
47+
/// ```rust,edition2021
48+
/// # #![allow(unused)]
49+
/// vec![1, 2, 3].into_boxed_slice().into_iter().for_each(|n| { *n; });
50+
/// ```
51+
///
52+
/// {{produces}}
53+
///
54+
/// ### Explanation
55+
///
56+
/// Since Rust 1.??, boxed slices implement `IntoIterator`. However, to avoid
57+
/// breakage, `boxed_slice.into_iter()` in Rust 2015, 2018, and 2021 code will still
58+
/// behave as `(&boxed_slice).into_iter()`, returning an iterator over
59+
/// references, just like in Rust 1.?? and earlier.
60+
/// This only applies to the method call syntax `boxed_slice.into_iter()`, not to
61+
/// any other syntax such as `for _ in boxed_slice` or `IntoIterator::into_iter(boxed_slice)`.
62+
pub BOXED_SLICE_INTO_ITER,
63+
Warn,
64+
"detects calling `into_iter` on boxed slices in Rust 2015, 2018, and 2021",
65+
@future_incompatible = FutureIncompatibleInfo {
66+
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
67+
};
68+
}
4169

42-
#[derive(Copy, Clone, Default)]
43-
pub struct ArrayIntoIter {
70+
#[derive(Copy, Clone)]
71+
pub struct CommonIntoIter<F, N> {
4472
for_expr_span: Span,
73+
filter: F,
74+
namer: N,
75+
}
76+
77+
#[derive(Copy, Clone)]
78+
pub struct ArrayIntoIter(CommonIntoIter<ArrayFilter, ArrayNamer>);
79+
impl Default for ArrayIntoIter {
80+
fn default() -> ArrayIntoIter {
81+
ArrayIntoIter(CommonIntoIter {
82+
for_expr_span: Span::default(),
83+
filter: array_filter,
84+
namer: array_namer,
85+
})
86+
}
87+
}
88+
89+
#[derive(Copy, Clone)]
90+
pub struct BoxedSliceIntoIter(CommonIntoIter<BoxedSliceFilter, BoxedSliceNamer>);
91+
impl Default for BoxedSliceIntoIter {
92+
fn default() -> BoxedSliceIntoIter {
93+
BoxedSliceIntoIter(CommonIntoIter {
94+
for_expr_span: Span::default(),
95+
filter: boxed_slice_filter,
96+
namer: boxed_slice_namer,
97+
})
98+
}
4599
}
46100

47101
impl_lint_pass!(ArrayIntoIter => [ARRAY_INTO_ITER]);
102+
impl_lint_pass!(BoxedSliceIntoIter => [BOXED_SLICE_INTO_ITER]);
48103

49-
impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
50-
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
104+
type ArrayFilter = impl Copy + FnMut(Ty<'_>) -> ControlFlow<bool>;
105+
type BoxedSliceFilter = impl Copy + FnMut(Ty<'_>) -> ControlFlow<bool>;
106+
type ArrayNamer = impl Copy + FnMut(Ty<'_>) -> &'static str;
107+
type BoxedSliceNamer = impl Copy + FnMut(Ty<'_>) -> &'static str;
108+
109+
fn array_filter(ty: Ty<'_>) -> ControlFlow<bool> {
110+
match ty.kind() {
111+
// If we run into a &[T; N] or &[T] first, there's nothing to warn about.
112+
// It'll resolve to the reference version.
113+
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => ControlFlow::Break(false),
114+
ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => {
115+
ControlFlow::Break(false)
116+
}
117+
// Found an actual array type without matching a &[T; N] first.
118+
// This is the problematic case.
119+
ty::Array(..) => ControlFlow::Break(true),
120+
_ => ControlFlow::Continue(()),
121+
}
122+
}
123+
124+
fn boxed_slice_filter(_ty: Ty<'_>) -> ControlFlow<bool> {
125+
todo!()
126+
}
127+
128+
fn array_namer(ty: Ty<'_>) -> &'static str {
129+
match *ty.kind() {
130+
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]",
131+
ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]",
132+
// We know the original first argument type is an array type,
133+
// we know that the first adjustment was an autoref coercion
134+
// and we know that `IntoIterator` is the trait involved. The
135+
// array cannot be coerced to something other than a reference
136+
// to an array or to a slice.
137+
_ => bug!("array type coerced to something other than array or slice"),
138+
}
139+
}
140+
141+
fn boxed_slice_namer(_ty: Ty<'_>) -> &'static str {
142+
todo!()
143+
}
144+
145+
impl<F, N> CommonIntoIter<F, N>
146+
where
147+
F: FnMut(Ty<'_>) -> ControlFlow<bool>,
148+
N: FnMut(Ty<'_>) -> &'static str,
149+
{
150+
fn check_expr<'tcx>(
151+
&mut self,
152+
cx: &LateContext<'tcx>,
153+
expr: &'tcx hir::Expr<'tcx>,
154+
) -> Option<(Span, ArrayIntoIterDiag<'tcx>)> {
51155
// Save the span of expressions in `for _ in expr` syntax,
52156
// so we can give a better suggestion for those later.
53157
if let hir::ExprKind::Match(arg, [_], hir::MatchSource::ForLoopDesugar) = &expr.kind {
@@ -65,61 +169,43 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
65169
// We only care about method call expressions.
66170
if let hir::ExprKind::MethodCall(call, receiver_arg, ..) = &expr.kind {
67171
if call.ident.name != sym::into_iter {
68-
return;
172+
return None;
69173
}
70174

71175
// Check if the method call actually calls the libcore
72176
// `IntoIterator::into_iter`.
73177
let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
74178
match cx.tcx.trait_of_item(def_id) {
75179
Some(trait_id) if cx.tcx.is_diagnostic_item(sym::IntoIterator, trait_id) => {}
76-
_ => return,
180+
_ => return None,
77181
};
78182

79183
// As this is a method call expression, we have at least one argument.
80184
let receiver_ty = cx.typeck_results().expr_ty(receiver_arg);
81185
let adjustments = cx.typeck_results().expr_adjustments(receiver_arg);
82186

83187
let Some(Adjustment { kind: Adjust::Borrow(_), target }) = adjustments.last() else {
84-
return;
188+
return None;
85189
};
86190

87191
let types =
88192
std::iter::once(receiver_ty).chain(adjustments.iter().map(|adj| adj.target));
89193

90-
let mut found_array = false;
91-
92-
for ty in types {
93-
match ty.kind() {
94-
// If we run into a &[T; N] or &[T] first, there's nothing to warn about.
95-
// It'll resolve to the reference version.
96-
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => return,
97-
ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => return,
98-
// Found an actual array type without matching a &[T; N] first.
99-
// This is the problematic case.
100-
ty::Array(..) => {
101-
found_array = true;
102-
break;
194+
let found_it = 'outer: {
195+
for ty in types {
196+
match (self.filter)(ty) {
197+
ControlFlow::Break(b) => break 'outer b,
198+
ControlFlow::Continue(()) => (),
103199
}
104-
_ => {}
105200
}
106-
}
107-
108-
if !found_array {
109-
return;
201+
false
202+
};
203+
if !found_it {
204+
return None;
110205
}
111206

112207
// Emit lint diagnostic.
113-
let target = match *target.kind() {
114-
ty::Ref(_, inner_ty, _) if inner_ty.is_array() => "[T; N]",
115-
ty::Ref(_, inner_ty, _) if matches!(inner_ty.kind(), ty::Slice(..)) => "[T]",
116-
// We know the original first argument type is an array type,
117-
// we know that the first adjustment was an autoref coercion
118-
// and we know that `IntoIterator` is the trait involved. The
119-
// array cannot be coerced to something other than a reference
120-
// to an array or to a slice.
121-
_ => bug!("array type coerced to something other than array or slice"),
122-
};
208+
let target = (self.namer)(*target);
123209
let sub = if self.for_expr_span == expr.span {
124210
Some(ArrayIntoIterDiagSub::RemoveIntoIter {
125211
span: receiver_arg.span.shrink_to_hi().to(expr.span.shrink_to_hi()),
@@ -132,11 +218,25 @@ impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
132218
} else {
133219
None
134220
};
135-
cx.emit_spanned_lint(
136-
ARRAY_INTO_ITER,
137-
call.ident.span,
138-
ArrayIntoIterDiag { target, suggestion: call.ident.span, sub },
139-
);
221+
222+
Some((call.ident.span, ArrayIntoIterDiag { target, suggestion: call.ident.span, sub }))
223+
} else {
224+
None
225+
}
226+
}
227+
}
228+
229+
impl<'tcx> LateLintPass<'tcx> for ArrayIntoIter {
230+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
231+
if let Some((span, decorator)) = self.0.check_expr(cx, expr) {
232+
cx.emit_spanned_lint(ARRAY_INTO_ITER, span, decorator);
233+
}
234+
}
235+
}
236+
impl<'tcx> LateLintPass<'tcx> for BoxedSliceIntoIter {
237+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
238+
if let Some((span, decorator)) = self.0.check_expr(cx, expr) {
239+
cx.emit_spanned_lint(BOXED_SLICE_INTO_ITER, span, decorator);
140240
}
141241
}
142242
}

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#![feature(min_specialization)]
4040
#![feature(never_type)]
4141
#![feature(rustc_attrs)]
42+
#![feature(type_alias_impl_trait)]
4243
#![recursion_limit = "256"]
4344
#![deny(rustc::untranslatable_diagnostic)]
4445
#![deny(rustc::diagnostic_outside_of_impl)]

compiler/rustc_middle/src/ty/trait_def.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,16 @@ pub struct TraitDef {
4040
/// also have already switched to the new trait solver.
4141
pub is_coinductive: bool,
4242

43-
/// If `true`, then this trait has the `#[rustc_skip_array_during_method_dispatch]`
43+
/// If `true`, then this trait has the `#[rustc_skip_during_method_dispatch(array)]`
4444
/// attribute, indicating that editions before 2021 should not consider this trait
4545
/// during method dispatch if the receiver is an array.
4646
pub skip_array_during_method_dispatch: bool,
4747

48+
/// If `true`, then this trait has the `#[rustc_skip_during_method_dispatch(boxed_slice)]`
49+
/// attribute, indicating that editions before 2021 should not consider this trait
50+
/// during method dispatch if the receiver is a boxed slice.
51+
pub skip_boxed_slice_during_method_dispatch: bool,
52+
4853
/// Used to determine whether the standard library is allowed to specialize
4954
/// on this trait.
5055
pub specialization_kind: TraitSpecializationKind,

compiler/rustc_smir/src/rustc_smir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,7 @@ impl<'tcx> Stable<'tcx> for ty::TraitDef {
14001400
is_marker: self.is_marker,
14011401
is_coinductive: self.is_coinductive,
14021402
skip_array_during_method_dispatch: self.skip_array_during_method_dispatch,
1403+
skip_boxed_slice_during_method_dispatch: self.skip_boxed_slice_during_method_dispatch,
14031404
specialization_kind: self.specialization_kind.stable(tables),
14041405
must_implement_one_of: self
14051406
.must_implement_one_of

compiler/rustc_span/src/symbol.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1416,7 +1416,7 @@ symbols! {
14161416
rustc_reservation_impl,
14171417
rustc_safe_intrinsic,
14181418
rustc_serialize,
1419-
rustc_skip_array_during_method_dispatch,
1419+
rustc_skip_during_method_dispatch,
14201420
rustc_specialization_trait,
14211421
rustc_std_internal_symbol,
14221422
rustc_strict_coherence,

compiler/stable_mir/src/ty.rs

+1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ pub struct TraitDecl {
478478
pub is_marker: bool,
479479
pub is_coinductive: bool,
480480
pub skip_array_during_method_dispatch: bool,
481+
pub skip_boxed_slice_during_method_dispatch: bool,
481482
pub specialization_kind: TraitSpecializationKind,
482483
pub must_implement_one_of: Option<Vec<Ident>>,
483484
pub implement_via_object: bool,

0 commit comments

Comments
 (0)