Skip to content

Commit 3e8fa17

Browse files
authored
Rollup merge of rust-lang#137045 - BoxyUwU:defer_repeat_expr_checks, r=compiler-errors
Defer repeat expr `Copy` checks to end of type checking Fixes rust-lang#110443 Defers repeat expr checks that the element type is `Copy` when the length is > 1 (or generic) to end of typeck so that under `generic_arg_infer` repeat exprs are able to have an inferred count, e.g. `let a: [_; 1] = [String::new(); _];`. Currently the deferring is gated under `generic_arg_infer` though I intend to separately types FCP deferring the checks even outside of `generic_arg_infer` if we wind up not going with an alternative.
2 parents 2f58193 + 6c3243f commit 3e8fa17

16 files changed

+286
-19
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -1853,12 +1853,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18531853
return Ty::new_error(tcx, guar);
18541854
}
18551855

1856+
// We defer checking whether the element type is `Copy` as it is possible to have
1857+
// an inference variable as a repeat count and it seems unlikely that `Copy` would
1858+
// have inference side effects required for type checking to succeed.
1859+
if tcx.features().generic_arg_infer() {
1860+
self.deferred_repeat_expr_checks.borrow_mut().push((element, element_ty, count));
18561861
// If the length is 0, we don't create any elements, so we don't copy any.
18571862
// If the length is 1, we don't copy that one element, we move it. Only check
18581863
// for `Copy` if the length is larger, or unevaluated.
1859-
// FIXME(min_const_generic_exprs): We could perhaps defer this check so that
1860-
// we don't require `<?0t as Tr>::CONST` doesn't unnecessarily require `Copy`.
1861-
if count.try_to_target_usize(tcx).is_none_or(|x| x > 1) {
1864+
} else if count.try_to_target_usize(self.tcx).is_none_or(|x| x > 1) {
18621865
self.enforce_repeat_element_needs_copy_bound(element, element_ty);
18631866
}
18641867

@@ -1868,7 +1871,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18681871
}
18691872

18701873
/// Requires that `element_ty` is `Copy` (unless it's a const expression itself).
1871-
fn enforce_repeat_element_needs_copy_bound(
1874+
pub(super) fn enforce_repeat_element_needs_copy_bound(
18721875
&self,
18731876
element: &hir::Expr<'_>,
18741877
element_ty: Ty<'tcx>,

compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs

+40-11
Original file line numberDiff line numberDiff line change
@@ -85,33 +85,36 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8585
})
8686
}
8787

88-
/// Resolves type and const variables in `ty` if possible. Unlike the infcx
88+
/// Resolves type and const variables in `t` if possible. Unlike the infcx
8989
/// version (resolve_vars_if_possible), this version will
9090
/// also select obligations if it seems useful, in an effort
9191
/// to get more type information.
9292
// FIXME(-Znext-solver): A lot of the calls to this method should
9393
// probably be `try_structurally_resolve_type` or `structurally_resolve_type` instead.
9494
#[instrument(skip(self), level = "debug", ret)]
95-
pub(crate) fn resolve_vars_with_obligations(&self, mut ty: Ty<'tcx>) -> Ty<'tcx> {
95+
pub(crate) fn resolve_vars_with_obligations<T: TypeFoldable<TyCtxt<'tcx>>>(
96+
&self,
97+
mut t: T,
98+
) -> T {
9699
// No Infer()? Nothing needs doing.
97-
if !ty.has_non_region_infer() {
100+
if !t.has_non_region_infer() {
98101
debug!("no inference var, nothing needs doing");
99-
return ty;
102+
return t;
100103
}
101104

102-
// If `ty` is a type variable, see whether we already know what it is.
103-
ty = self.resolve_vars_if_possible(ty);
104-
if !ty.has_non_region_infer() {
105-
debug!(?ty);
106-
return ty;
105+
// If `t` is a type variable, see whether we already know what it is.
106+
t = self.resolve_vars_if_possible(t);
107+
if !t.has_non_region_infer() {
108+
debug!(?t);
109+
return t;
107110
}
108111

109112
// If not, try resolving pending obligations as much as
110113
// possible. This can help substantially when there are
111114
// indirect dependencies that don't seem worth tracking
112115
// precisely.
113116
self.select_obligations_where_possible(|_| {});
114-
self.resolve_vars_if_possible(ty)
117+
self.resolve_vars_if_possible(t)
115118
}
116119

117120
pub(crate) fn record_deferred_call_resolution(
@@ -1454,7 +1457,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14541457
sp: Span,
14551458
ct: ty::Const<'tcx>,
14561459
) -> ty::Const<'tcx> {
1457-
// FIXME(min_const_generic_exprs): We could process obligations here if `ct` is a var.
1460+
let ct = self.resolve_vars_with_obligations(ct);
14581461

14591462
if self.next_trait_solver()
14601463
&& let ty::ConstKind::Unevaluated(..) = ct.kind()
@@ -1510,6 +1513,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15101513
}
15111514
}
15121515

1516+
pub(crate) fn structurally_resolve_const(
1517+
&self,
1518+
sp: Span,
1519+
ct: ty::Const<'tcx>,
1520+
) -> ty::Const<'tcx> {
1521+
let ct = self.try_structurally_resolve_const(sp, ct);
1522+
1523+
if !ct.is_ct_infer() {
1524+
ct
1525+
} else {
1526+
let e = self.tainted_by_errors().unwrap_or_else(|| {
1527+
self.err_ctxt()
1528+
.emit_inference_failure_err(
1529+
self.body_id,
1530+
sp,
1531+
ct.into(),
1532+
TypeAnnotationNeeded::E0282,
1533+
true,
1534+
)
1535+
.emit()
1536+
});
1537+
// FIXME: Infer `?ct = {const error}`?
1538+
ty::Const::new_error(self.tcx, e)
1539+
}
1540+
}
1541+
15131542
pub(crate) fn with_breakable_ctxt<F: FnOnce() -> R, R>(
15141543
&self,
15151544
id: HirId,

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+25
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
115115
}
116116
}
117117

118+
pub(in super::super) fn check_repeat_exprs(&self) {
119+
let mut deferred_repeat_expr_checks = self.deferred_repeat_expr_checks.borrow_mut();
120+
debug!("FnCtxt::check_repeat_exprs: {} deferred checks", deferred_repeat_expr_checks.len());
121+
for (element, element_ty, count) in deferred_repeat_expr_checks.drain(..) {
122+
// We want to emit an error if the const is not structurally resolveable as otherwise
123+
// we can find up conservatively proving `Copy` which may infer the repeat expr count
124+
// to something that never required `Copy` in the first place.
125+
let count =
126+
self.structurally_resolve_const(element.span, self.normalize(element.span, count));
127+
128+
// Avoid run on "`NotCopy: Copy` is not implemented" errors when the repeat expr count
129+
// is erroneous/unknown. The user might wind up specifying a repeat count of 0/1.
130+
if count.references_error() {
131+
continue;
132+
}
133+
134+
// If the length is 0, we don't create any elements, so we don't copy any.
135+
// If the length is 1, we don't copy that one element, we move it. Only check
136+
// for `Copy` if the length is larger.
137+
if count.try_to_target_usize(self.tcx).is_none_or(|x| x > 1) {
138+
self.enforce_repeat_element_needs_copy_bound(element, element_ty);
139+
}
140+
}
141+
}
142+
118143
pub(in super::super) fn check_method_argument_types(
119144
&self,
120145
sp: Span,

compiler/rustc_hir_typeck/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,15 @@ fn typeck_with_inspect<'tcx>(
199199
fcx.write_ty(id, expected_type);
200200
};
201201

202+
// Whether to check repeat exprs before/after inference fallback is somewhat arbitrary of a decision
203+
// as neither option is strictly more permissive than the other. However, we opt to check repeat exprs
204+
// first as errors from not having inferred array lengths yet seem less confusing than errors from inference
205+
// fallback arbitrarily inferring something incompatible with `Copy` inference side effects.
206+
//
207+
// This should also be forwards compatible with moving repeat expr checks to a custom goal kind or using
208+
// marker traits in the future.
209+
fcx.check_repeat_exprs();
210+
202211
fcx.type_inference_fallback();
203212

204213
// Even though coercion casts provide type hints, we check casts after fallback for

compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ pub(crate) struct TypeckRootCtxt<'tcx> {
6262

6363
pub(super) deferred_coroutine_interiors: RefCell<Vec<(LocalDefId, hir::BodyId, Ty<'tcx>)>>,
6464

65+
pub(super) deferred_repeat_expr_checks:
66+
RefCell<Vec<(&'tcx hir::Expr<'tcx>, Ty<'tcx>, ty::Const<'tcx>)>>,
67+
6568
/// Whenever we introduce an adjustment from `!` into a type variable,
6669
/// we record that type variable here. This is later used to inform
6770
/// fallback. See the `fallback` module for details.
@@ -96,6 +99,7 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
9699
deferred_transmute_checks: RefCell::new(Vec::new()),
97100
deferred_asm_checks: RefCell::new(Vec::new()),
98101
deferred_coroutine_interiors: RefCell::new(Vec::new()),
102+
deferred_repeat_expr_checks: RefCell::new(Vec::new()),
99103
diverging_type_vars: RefCell::new(Default::default()),
100104
infer_var_info: RefCell::new(Default::default()),
101105
}

tests/ui/consts/const-fn-in-vec.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
static _MAYBE_STRINGS: [Option<String>; 5] = [None; 5];
22
//~^ ERROR the trait bound `String: Copy` is not satisfied
33

4-
fn main() {
5-
// should hint to create an inline `const` block
6-
// or to create a new `const` item
4+
// should hint to create an inline `const` block
5+
// or to create a new `const` item
6+
fn foo() {
77
let _strings: [String; 5] = [String::new(); 5];
88
//~^ ERROR the trait bound `String: Copy` is not satisfied
9+
}
10+
11+
fn bar() {
912
let _maybe_strings: [Option<String>; 5] = [None; 5];
1013
//~^ ERROR the trait bound `String: Copy` is not satisfied
1114
}
15+
16+
fn main() {}

tests/ui/consts/const-fn-in-vec.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ LL | let _strings: [String; 5] = [String::new(); 5];
2222
= note: the `Copy` trait is required because this value will be copied for each element of the array
2323

2424
error[E0277]: the trait bound `String: Copy` is not satisfied
25-
--> $DIR/const-fn-in-vec.rs:9:48
25+
--> $DIR/const-fn-in-vec.rs:12:48
2626
|
2727
LL | let _maybe_strings: [Option<String>; 5] = [None; 5];
2828
| ^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![feature(generic_arg_infer)]
2+
3+
// Test that would start passing if we defer repeat expr copy checks to end of
4+
// typechecking and they're checked after integer fallback occurs. We accomplish
5+
// this by contriving a situation where integer fallback allows progress to be
6+
// made on a trait goal that infers the length of a repeat expr.
7+
8+
use std::marker::PhantomData;
9+
10+
struct NotCopy;
11+
12+
trait Trait<const N: usize> {}
13+
14+
impl Trait<2> for u32 {}
15+
impl Trait<1> for i32 {}
16+
17+
fn make_goal<T: Trait<N>, const N: usize>(_: &T, _: [NotCopy; N]) {}
18+
19+
fn main() {
20+
let a = 1;
21+
let b = [NotCopy; _];
22+
//~^ ERROR: type annotations needed
23+
24+
// a is of type `?y`
25+
// b is of type `[NotCopy; ?x]`
26+
// there is a goal ?y: Trait<?x>` with two candidates:
27+
// - `i32: Trait<1>`, ?y=i32 ?x=1 which doesnt require `NotCopy: Copy`
28+
// - `u32: Trait<2>` ?y=u32 ?x=2 which requires `NotCopy: Copy`
29+
make_goal(&a, b);
30+
31+
// final repeat expr checks:
32+
//
33+
// `NotCopy; ?x`
34+
// - succeeds if fallback happens before repeat exprs as `i32: Trait<?x>` infers `?x=1`
35+
// - fails if repeat expr checks happen first as `?x` is unconstrained so cannot be
36+
// structurally resolved
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0282]: type annotations needed for `[NotCopy; _]`
2+
--> $DIR/copy-check-deferred-after-fallback.rs:21:9
3+
|
4+
LL | let b = [NotCopy; _];
5+
| ^ ------- type must be known at this point
6+
|
7+
help: consider giving `b` an explicit type, where the value of const parameter `N` is specified
8+
|
9+
LL | let b: [_; N] = [NotCopy; _];
10+
| ++++++++
11+
12+
error: aborting due to 1 previous error
13+
14+
For more information about this error, try `rustc --explain E0282`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//@ check-pass
2+
3+
#![feature(generic_arg_infer)]
4+
5+
// Test that if we defer repeat expr copy checks to end of typechecking they're
6+
// checked before integer fallback occurs. We accomplish this by contriving a
7+
// situation where we have a goal that can be proven either via another repeat expr
8+
// check or by integer fallback. In the integer fallback case an array length would
9+
// be inferred to `2` requiring `NotCopy: Copy`, and in the repeat expr case it would
10+
// be inferred to `1`.
11+
12+
use std::marker::PhantomData;
13+
14+
struct NotCopy;
15+
16+
struct Foo<T>(PhantomData<T>);
17+
18+
impl Clone for Foo<u32> {
19+
fn clone(&self) -> Self {
20+
Foo(PhantomData)
21+
}
22+
}
23+
24+
impl Copy for Foo<u32> {}
25+
26+
fn tie<T>(_: &T, _: [Foo<T>; 2]) {}
27+
28+
trait Trait<const N: usize> {}
29+
30+
impl Trait<2> for i32 {}
31+
impl Trait<1> for u32 {}
32+
33+
fn make_goal<T: Trait<N>, const N: usize>(_: &T, _: [NotCopy; N]) {}
34+
35+
fn main() {
36+
let a = 1;
37+
let b: [Foo<_>; 2] = [Foo(PhantomData); _];
38+
tie(&a, b);
39+
let c = [NotCopy; _];
40+
41+
// a is of type `?y`
42+
// b is of type `[Foo<?y>; 2]`
43+
// c is of type `[NotCopy; ?x]`
44+
// there is a goal ?y: Trait<?x>` with two candidates:
45+
// - `i32: Trait<2>`, ?y=i32 ?x=2 which requires `NotCopy: Copy` when expr checks happen
46+
// - `u32: Trait<1>` ?y=u32 ?x=1 which doesnt require `NotCopy: Copy`
47+
make_goal(&a, c);
48+
49+
// final repeat expr checks:
50+
//
51+
// `Foo<?y>; 2`
52+
// - Foo<?y>: Copy
53+
// - requires ?y=u32
54+
//
55+
// `NotCopy; ?x`
56+
// - fails if fallback happens before repeat exprs as `i32: Trait<?x>` infers `?x=2`
57+
// - succeeds if repeat expr checks happen first as `?y=u32` means `u32: Trait<?x>`
58+
// infers `?x=1`
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0282]: type annotations needed for `[Foo<_>; 2]`
2+
--> $DIR/copy-inference-side-effects-are-lazy.rs:22:9
3+
|
4+
LL | let x = [Foo(PhantomData); 2];
5+
| ^
6+
LL |
7+
LL | _ = extract(x).max(2);
8+
| ---------- type must be known at this point
9+
|
10+
help: consider giving `x` an explicit type, where the type for type parameter `T` is specified
11+
|
12+
LL | let x: [Foo<T>; 2] = [Foo(PhantomData); 2];
13+
| +++++++++++++
14+
15+
error: aborting due to 1 previous error
16+
17+
For more information about this error, try `rustc --explain E0282`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//@revisions: current gai
2+
//@[current] check-pass
3+
4+
#![cfg_attr(gai, feature(generic_arg_infer))]
5+
6+
use std::marker::PhantomData;
7+
8+
struct Foo<T>(PhantomData<T>);
9+
10+
impl Clone for Foo<u8> {
11+
fn clone(&self) -> Self {
12+
Foo(PhantomData)
13+
}
14+
}
15+
impl Copy for Foo<u8> {}
16+
17+
fn extract<T, const N: usize>(_: [Foo<T>; N]) -> T {
18+
loop {}
19+
}
20+
21+
fn main() {
22+
let x = [Foo(PhantomData); 2];
23+
//[gai]~^ ERROR: type annotations needed
24+
_ = extract(x).max(2);
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//@ check-pass
2+
#![feature(generic_arg_infer)]
3+
4+
fn main() {
5+
let a: [_; 1] = [String::new(); _];
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![feature(generic_arg_infer)]
2+
3+
struct Foo<const N: usize>;
4+
5+
impl Clone for Foo<1> {
6+
fn clone(&self) -> Self {
7+
Foo
8+
}
9+
}
10+
impl Copy for Foo<1> {}
11+
12+
fn unify<const N: usize>(_: &[Foo<N>; N]) {
13+
loop {}
14+
}
15+
16+
fn main() {
17+
let x = &[Foo::<_>; _];
18+
//~^ ERROR: type annotations needed for `&[Foo<_>; _]`
19+
_ = unify(x);
20+
}

0 commit comments

Comments
 (0)