Skip to content

Commit 761974c

Browse files
committed
WIP: Add edition override
1 parent a9a6e8b commit 761974c

File tree

15 files changed

+901
-52
lines changed

15 files changed

+901
-52
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -892,10 +892,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
892892
"the `#[rustc_main]` attribute is used internally to specify test entry point function",
893893
),
894894
rustc_attr!(
895-
rustc_skip_array_during_method_dispatch, Normal, template!(Word),
896-
WarnFollowing, EncodeCrossCrate::No,
897-
"the `#[rustc_skip_array_during_method_dispatch]` attribute is used to exclude a trait \
898-
from method dispatch when the receiver is an array, for compatibility in editions < 2021."
895+
rustc_skip_during_method_dispatch, Normal, template!(List: "array, boxed_slice, ..."), WarnFollowing,
896+
"the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \
897+
from method dispatch when the receiver is of the following type, for compatibility in \
898+
editions < 2021 (array) or editions < 2024 (boxed_slice)."
899899
),
900900
rustc_attr!(
901901
rustc_must_implement_one_of, Normal, template!(List: "function1, function2, ..."),

compiler/rustc_hir_analysis/src/collect.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,8 +1110,24 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
11101110

11111111
let is_marker = tcx.has_attr(def_id, sym::marker);
11121112
let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive);
1113-
let skip_array_during_method_dispatch =
1114-
tcx.has_attr(def_id, sym::rustc_skip_array_during_method_dispatch);
1113+
1114+
// FIXME: We could probably do way better attribute validation here.
1115+
let mut skip_array_during_method_dispatch = false;
1116+
let mut skip_boxed_slice_during_method_dispatch = false;
1117+
for attr in tcx.get_attrs(def_id, sym::rustc_skip_during_method_dispatch) {
1118+
if let Some(lst) = attr.meta_item_list() {
1119+
for item in lst {
1120+
if let Some(ident) = item.ident() {
1121+
match ident.as_str() {
1122+
"array" => skip_array_during_method_dispatch = true,
1123+
"boxed_slice" => skip_boxed_slice_during_method_dispatch = true,
1124+
_ => (),
1125+
}
1126+
}
1127+
}
1128+
}
1129+
}
1130+
11151131
let specialization_kind = if tcx.has_attr(def_id, sym::rustc_unsafe_specialization_marker) {
11161132
ty::trait_def::TraitSpecializationKind::Marker
11171133
} else if tcx.has_attr(def_id, sym::rustc_specialization_trait) {
@@ -1246,6 +1262,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
12461262
is_marker,
12471263
is_coinductive: rustc_coinductive || is_auto,
12481264
skip_array_during_method_dispatch,
1265+
skip_boxed_slice_during_method_dispatch,
12491266
specialization_kind,
12501267
must_implement_one_of,
12511268
implement_via_object,

compiler/rustc_hir_typeck/src/method/probe.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,18 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
15921592
return ProbeResult::NoMatch;
15931593
}
15941594
}
1595+
1596+
// Some trait methods are excluded for boxed slices before 2024.
1597+
// (`boxed_slice.into_iter()` wants a slice iterator for compatibility.)
1598+
if self_ty.is_box()
1599+
&& self_ty.boxed_ty().is_slice()
1600+
&& !method_name.span.at_least_rust_2024()
1601+
{
1602+
let trait_def = self.tcx.trait_def(trait_ref.def_id);
1603+
if trait_def.skip_boxed_slice_during_method_dispatch {
1604+
return ProbeResult::NoMatch;
1605+
}
1606+
}
15951607
}
15961608
let predicate = ty::Binder::dummy(trait_ref).to_predicate(self.tcx);
15971609
parent_pred = Some(predicate);

compiler/rustc_lint/src/array_into_iter.rs

Lines changed: 141 additions & 41 deletions
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_span_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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
#![feature(let_chains)]
3939
#![feature(trait_upcasting)]
4040
#![feature(rustc_attrs)]
41+
#![feature(type_alias_impl_trait)]
42+
#![recursion_limit = "256"]
43+
#![deny(rustc::untranslatable_diagnostic)]
44+
#![deny(rustc::diagnostic_outside_of_impl)]
4145
#![allow(internal_features)]
4246

4347
#[macro_use]

compiler/rustc_middle/src/ty/trait_def.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ pub struct TraitDef {
3939
/// also have already switched to the new trait solver.
4040
pub is_coinductive: bool,
4141

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

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

0 commit comments

Comments
 (0)