Skip to content

Commit a4783de

Browse files
committed
const evaluatable: improve TooGeneric handling
1 parent 535d27a commit a4783de

File tree

8 files changed

+199
-75
lines changed

8 files changed

+199
-75
lines changed

compiler/rustc_trait_selection/src/traits/const_evaluatable.rs

+138-15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ use rustc_session::lint;
2323
use rustc_span::def_id::{DefId, LocalDefId};
2424
use rustc_span::Span;
2525

26+
use std::cmp;
27+
28+
/// Check if a given constant can be evaluated.
2629
pub fn is_const_evaluatable<'cx, 'tcx>(
2730
infcx: &InferCtxt<'cx, 'tcx>,
2831
def: ty::WithOptConstParam<DefId>,
@@ -32,23 +35,87 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
3235
) -> Result<(), ErrorHandled> {
3336
debug!("is_const_evaluatable({:?}, {:?})", def, substs);
3437
if infcx.tcx.features().const_evaluatable_checked {
35-
if let Some(ct) = AbstractConst::new(infcx.tcx, def, substs)? {
36-
for pred in param_env.caller_bounds() {
37-
match pred.skip_binders() {
38-
ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => {
39-
debug!("is_const_evaluatable: caller_bound={:?}, {:?}", b_def, b_substs);
40-
if b_def == def && b_substs == substs {
41-
debug!("is_const_evaluatable: caller_bound ~~> ok");
42-
return Ok(());
43-
} else if AbstractConst::new(infcx.tcx, b_def, b_substs)?
44-
.map_or(false, |b_ct| try_unify(infcx.tcx, ct, b_ct))
45-
{
46-
debug!("is_const_evaluatable: abstract_const ~~> ok");
47-
return Ok(());
38+
let tcx = infcx.tcx;
39+
match AbstractConst::new(tcx, def, substs)? {
40+
// We are looking at a generic abstract constant.
41+
Some(ct) => {
42+
for pred in param_env.caller_bounds() {
43+
match pred.skip_binders() {
44+
ty::PredicateAtom::ConstEvaluatable(b_def, b_substs) => {
45+
debug!(
46+
"is_const_evaluatable: caller_bound={:?}, {:?}",
47+
b_def, b_substs
48+
);
49+
if b_def == def && b_substs == substs {
50+
debug!("is_const_evaluatable: caller_bound ~~> ok");
51+
return Ok(());
52+
} else if AbstractConst::new(tcx, b_def, b_substs)?
53+
.map_or(false, |b_ct| try_unify(tcx, ct, b_ct))
54+
{
55+
debug!("is_const_evaluatable: abstract_const ~~> ok");
56+
return Ok(());
57+
}
4858
}
59+
_ => {} // don't care
4960
}
50-
_ => {} // don't care
5161
}
62+
63+
// We were unable to unify the abstract constant with
64+
// a constant found in the caller bounds, there are
65+
// now three possible cases here.
66+
//
67+
// - The substs are concrete enough that we can simply
68+
// try and evaluate the given constant.
69+
// - The abstract const still references an inference
70+
// variable, in this case we return `TooGeneric`.
71+
// - The abstract const references a generic parameter,
72+
// this means that we emit an error here.
73+
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
74+
enum FailureKind {
75+
MentionsInfer,
76+
MentionsParam,
77+
Concrete,
78+
}
79+
let mut failure_kind = FailureKind::Concrete;
80+
walk_abstract_const(tcx, ct, |node| match node {
81+
Node::Leaf(leaf) => {
82+
let leaf = leaf.subst(tcx, ct.substs);
83+
if leaf.has_infer_types_or_consts() {
84+
failure_kind = FailureKind::MentionsInfer;
85+
} else if leaf.has_param_types_or_consts() {
86+
failure_kind = cmp::min(failure_kind, FailureKind::MentionsParam);
87+
}
88+
}
89+
Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => (),
90+
});
91+
92+
match failure_kind {
93+
FailureKind::MentionsInfer => {
94+
return Err(ErrorHandled::TooGeneric);
95+
}
96+
FailureKind::MentionsParam => {
97+
// FIXME(const_evaluatable_checked): Better error message.
98+
infcx
99+
.tcx
100+
.sess
101+
.struct_span_err(span, "unconstrained generic constant")
102+
.span_help(
103+
tcx.def_span(def.did),
104+
"consider adding a `where` bound for this expression",
105+
)
106+
.emit();
107+
return Err(ErrorHandled::Reported(ErrorReported));
108+
}
109+
FailureKind::Concrete => {
110+
// Dealt with below by the same code which handles this
111+
// without the feature gate.
112+
}
113+
}
114+
}
115+
None => {
116+
// If we are dealing with a concrete constant, we can
117+
// reuse the old code path and try to evaluate
118+
// the constant.
52119
}
53120
}
54121
}
@@ -95,7 +162,36 @@ pub fn is_const_evaluatable<'cx, 'tcx>(
95162
}
96163

97164
debug!(?concrete, "is_const_evaluatable");
98-
concrete.map(drop)
165+
match concrete {
166+
Err(ErrorHandled::TooGeneric) if !substs.has_infer_types_or_consts() => {
167+
// FIXME(const_evaluatable_checked): We really should move
168+
// emitting this error message to fulfill instead. For
169+
// now this is easier.
170+
//
171+
// This is not a problem without `const_evaluatable_checked` as
172+
// all `ConstEvaluatable` predicates have to be fulfilled for compilation
173+
// to succeed.
174+
//
175+
// @lcnr: We already emit an error for things like
176+
// `fn test<const N: usize>() -> [0 - N]` eagerly here,
177+
// so until we fix this I don't really care.
178+
179+
let mut err = infcx
180+
.tcx
181+
.sess
182+
.struct_span_err(span, "constant expression depends on a generic parameter");
183+
// FIXME(const_generics): we should suggest to the user how they can resolve this
184+
// issue. However, this is currently not actually possible
185+
// (see https://github.com/rust-lang/rust/issues/66962#issuecomment-575907083).
186+
//
187+
// Note that with `feature(const_evaluatable_checked)` this case should not
188+
// be reachable.
189+
err.note("this may fail depending on what value the parameter takes");
190+
err.emit();
191+
Err(ErrorHandled::Reported(ErrorReported))
192+
}
193+
c => c.map(drop),
194+
}
99195
}
100196

101197
/// A tree representing an anonymous constant.
@@ -421,6 +517,33 @@ pub(super) fn try_unify_abstract_consts<'tcx>(
421517
// on `ErrorReported`.
422518
}
423519

520+
fn walk_abstract_const<'tcx, F>(tcx: TyCtxt<'tcx>, ct: AbstractConst<'tcx>, mut f: F)
521+
where
522+
F: FnMut(Node<'tcx>),
523+
{
524+
recurse(tcx, ct, &mut f);
525+
fn recurse<'tcx>(tcx: TyCtxt<'tcx>, ct: AbstractConst<'tcx>, f: &mut dyn FnMut(Node<'tcx>)) {
526+
let root = ct.root();
527+
f(root);
528+
match root {
529+
Node::Leaf(_) => (),
530+
Node::Binop(_, l, r) => {
531+
recurse(tcx, ct.subtree(l), f);
532+
recurse(tcx, ct.subtree(r), f);
533+
}
534+
Node::UnaryOp(_, v) => {
535+
recurse(tcx, ct.subtree(v), f);
536+
}
537+
Node::FunctionCall(func, args) => {
538+
recurse(tcx, ct.subtree(func), f);
539+
for &arg in args {
540+
recurse(tcx, ct.subtree(arg), f);
541+
}
542+
}
543+
}
544+
}
545+
}
546+
424547
/// Tries to unify two abstract constants using structural equality.
425548
pub(super) fn try_unify<'tcx>(
426549
tcx: TyCtxt<'tcx>,

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+1-17
Original file line numberDiff line numberDiff line change
@@ -745,25 +745,9 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
745745
let violations = self.tcx.object_safety_violations(did);
746746
report_object_safety_error(self.tcx, span, did, violations)
747747
}
748-
749748
ConstEvalFailure(ErrorHandled::TooGeneric) => {
750-
// In this instance, we have a const expression containing an unevaluated
751-
// generic parameter. We have no idea whether this expression is valid or
752-
// not (e.g. it might result in an error), but we don't want to just assume
753-
// that it's okay, because that might result in post-monomorphisation time
754-
// errors. The onus is really on the caller to provide values that it can
755-
// prove are well-formed.
756-
let mut err = self
757-
.tcx
758-
.sess
759-
.struct_span_err(span, "constant expression depends on a generic parameter");
760-
// FIXME(const_generics): we should suggest to the user how they can resolve this
761-
// issue. However, this is currently not actually possible
762-
// (see https://github.com/rust-lang/rust/issues/66962#issuecomment-575907083).
763-
err.note("this may fail depending on what value the parameter takes");
764-
err
749+
bug!("too generic should have been handled in `is_const_evaluatable`");
765750
}
766-
767751
// Already reported in the query.
768752
ConstEvalFailure(ErrorHandled::Reported(ErrorReported)) => {
769753
// FIXME(eddyb) remove this once `ErrorReported` becomes a proof token.

compiler/rustc_trait_selection/src/traits/fulfill.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,13 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
496496
obligation.cause.span,
497497
) {
498498
Ok(()) => ProcessResult::Changed(vec![]),
499+
Err(ErrorHandled::TooGeneric) => {
500+
pending_obligation.stalled_on = substs
501+
.iter()
502+
.filter_map(|ty| TyOrConstInferVar::maybe_from_generic_arg(ty))
503+
.collect();
504+
ProcessResult::Unchanged
505+
}
499506
Err(e) => ProcessResult::Error(CodeSelectionError(ConstEvalFailure(e))),
500507
}
501508
}
@@ -537,8 +544,10 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
537544
Err(ErrorHandled::TooGeneric) => {
538545
stalled_on.append(
539546
&mut substs
540-
.types()
541-
.filter_map(|ty| TyOrConstInferVar::maybe_from_ty(ty))
547+
.iter()
548+
.filter_map(|arg| {
549+
TyOrConstInferVar::maybe_from_generic_arg(arg)
550+
})
542551
.collect(),
543552
);
544553
Err(ErrorHandled::TooGeneric)

src/test/ui/const-generics/const_evaluatable_checked/cross_crate_predicate.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ extern crate const_evaluatable_lib;
55

66
fn user<T>() {
77
let _ = const_evaluatable_lib::test1::<T>();
8-
//~^ ERROR constant expression depends
9-
//~| ERROR constant expression depends
10-
//~| ERROR constant expression depends
11-
//~| ERROR constant expression depends
8+
//~^ ERROR unconstrained generic constant
9+
//~| ERROR unconstrained generic constant
10+
//~| ERROR unconstrained generic constant
11+
//~| ERROR unconstrained generic constant
1212
}
1313

1414
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,50 @@
1-
error: constant expression depends on a generic parameter
1+
error: unconstrained generic constant
22
--> $DIR/cross_crate_predicate.rs:7:13
33
|
44
LL | let _ = const_evaluatable_lib::test1::<T>();
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6-
|
7-
::: $DIR/auxiliary/const_evaluatable_lib.rs:6:10
86
|
9-
LL | [u8; std::mem::size_of::<T>() - 1]: Sized,
10-
| ---------------------------- required by this bound in `test1`
7+
help: consider adding a `where` bound for this expression
8+
--> $DIR/auxiliary/const_evaluatable_lib.rs:6:10
119
|
12-
= note: this may fail depending on what value the parameter takes
10+
LL | [u8; std::mem::size_of::<T>() - 1]: Sized,
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1312

14-
error: constant expression depends on a generic parameter
13+
error: unconstrained generic constant
1514
--> $DIR/cross_crate_predicate.rs:7:13
1615
|
1716
LL | let _ = const_evaluatable_lib::test1::<T>();
1817
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19-
|
20-
::: $DIR/auxiliary/const_evaluatable_lib.rs:4:27
2118
|
22-
LL | pub fn test1<T>() -> [u8; std::mem::size_of::<T>() - 1]
23-
| ---------------------------- required by this bound in `test1`
19+
help: consider adding a `where` bound for this expression
20+
--> $DIR/auxiliary/const_evaluatable_lib.rs:4:27
2421
|
25-
= note: this may fail depending on what value the parameter takes
22+
LL | pub fn test1<T>() -> [u8; std::mem::size_of::<T>() - 1]
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2624

27-
error: constant expression depends on a generic parameter
25+
error: unconstrained generic constant
2826
--> $DIR/cross_crate_predicate.rs:7:13
2927
|
3028
LL | let _ = const_evaluatable_lib::test1::<T>();
3129
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32-
|
33-
::: $DIR/auxiliary/const_evaluatable_lib.rs:6:10
3430
|
35-
LL | [u8; std::mem::size_of::<T>() - 1]: Sized,
36-
| ---------------------------- required by this bound in `test1`
31+
help: consider adding a `where` bound for this expression
32+
--> $DIR/auxiliary/const_evaluatable_lib.rs:6:10
3733
|
38-
= note: this may fail depending on what value the parameter takes
34+
LL | [u8; std::mem::size_of::<T>() - 1]: Sized,
35+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3936

40-
error: constant expression depends on a generic parameter
37+
error: unconstrained generic constant
4138
--> $DIR/cross_crate_predicate.rs:7:13
4239
|
4340
LL | let _ = const_evaluatable_lib::test1::<T>();
4441
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45-
|
46-
::: $DIR/auxiliary/const_evaluatable_lib.rs:4:27
4742
|
48-
LL | pub fn test1<T>() -> [u8; std::mem::size_of::<T>() - 1]
49-
| ---------------------------- required by this bound in `test1`
43+
help: consider adding a `where` bound for this expression
44+
--> $DIR/auxiliary/const_evaluatable_lib.rs:4:27
5045
|
51-
= note: this may fail depending on what value the parameter takes
46+
LL | pub fn test1<T>() -> [u8; std::mem::size_of::<T>() - 1]
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5248

5349
error: aborting due to 4 previous errors
5450

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// run-pass
2+
#![feature(const_generics, const_evaluatable_checked)]
3+
#![allow(incomplete_features)]
4+
5+
use std::{mem, ptr};
6+
7+
fn split_first<T, const N: usize>(arr: [T; N]) -> (T, [T; N - 1])
8+
where
9+
[T; N - 1]: Sized,
10+
{
11+
let arr = mem::ManuallyDrop::new(arr);
12+
unsafe {
13+
let head = ptr::read(&arr[0]);
14+
let tail = ptr::read(&arr[1..] as *const [T] as *const [T; N - 1]);
15+
(head, tail)
16+
}
17+
}
18+
19+
fn main() {
20+
let arr = [0, 1, 2, 3, 4];
21+
let (head, tail) = split_first(arr);
22+
assert_eq!(head, 0);
23+
assert_eq!(tail, [1, 2, 3, 4]);
24+
}

src/test/ui/const-generics/issues/issue-76595.rs

-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ fn test<T, const P: usize>() where Bool<{core::mem::size_of::<T>() > 4}>: True {
1414
fn main() {
1515
test::<2>();
1616
//~^ ERROR wrong number of type
17-
//~| ERROR constant expression depends
1817
}

src/test/ui/const-generics/issues/issue-76595.stderr

+1-12
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,6 @@ error[E0107]: wrong number of type arguments: expected 1, found 0
44
LL | test::<2>();
55
| ^^^^^^^^^ expected 1 type argument
66

7-
error: constant expression depends on a generic parameter
8-
--> $DIR/issue-76595.rs:15:5
9-
|
10-
LL | fn test<T, const P: usize>() where Bool<{core::mem::size_of::<T>() > 4}>: True {
11-
| ------------------------------- required by this bound in `test`
12-
...
13-
LL | test::<2>();
14-
| ^^^^^^^^^
15-
|
16-
= note: this may fail depending on what value the parameter takes
17-
18-
error: aborting due to 2 previous errors
7+
error: aborting due to previous error
198

209
For more information about this error, try `rustc --explain E0107`.

0 commit comments

Comments
 (0)