Skip to content

Commit a2178df

Browse files
authored
Rollup merge of #127687 - RalfJung:pattern-cleanup, r=oli-obk,lcnr
Const-to-pattern-to-MIR cleanup Now that all uses of constants without structural equality are hard errors, there's a bunch of cleanup we can do in the code that handles patterns: we can always funnel patterns through valtrees first (rather than having a fallback path for when valtree construction fails), and we can make sure that if we emit a `PartialEq` call it is not calling anything user-defined. To keep the error messages the same, I made valtree construction failures return the information of *which* type it is that cannot be valtree'd. `search_for_structural_match_violation` is now not needed any more at all, so I removed it. r? `@oli-obk`
2 parents 78fe5f7 + 303a2db commit a2178df

40 files changed

+291
-650
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4411,6 +4411,7 @@ dependencies = [
44114411
name = "rustc_mir_build"
44124412
version = "0.0.0"
44134413
dependencies = [
4414+
"either",
44144415
"itertools",
44154416
"rustc_apfloat",
44164417
"rustc_arena",

compiler/rustc_codegen_ssa/src/mir/constant.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
3737
pub fn eval_unevaluated_mir_constant_to_valtree(
3838
&self,
3939
constant: &mir::ConstOperand<'tcx>,
40-
) -> Result<Option<ty::ValTree<'tcx>>, ErrorHandled> {
40+
) -> Result<Result<ty::ValTree<'tcx>, Ty<'tcx>>, ErrorHandled> {
4141
let uv = match self.monomorphize(constant.const_) {
4242
mir::Const::Unevaluated(uv, _) => uv.shrink(),
4343
mir::Const::Ty(_, c) => match c.kind() {
4444
// A constant that came from a const generic but was then used as an argument to old-style
4545
// simd_shuffle (passing as argument instead of as a generic param).
46-
rustc_type_ir::ConstKind::Value(_, valtree) => return Ok(Some(valtree)),
46+
rustc_type_ir::ConstKind::Value(_, valtree) => return Ok(Ok(valtree)),
4747
other => span_bug!(constant.span, "{other:#?}"),
4848
},
4949
// We should never encounter `Const::Val` unless MIR opts (like const prop) evaluate
@@ -70,6 +70,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
7070
let val = self
7171
.eval_unevaluated_mir_constant_to_valtree(constant)
7272
.ok()
73+
.map(|x| x.ok())
7374
.flatten()
7475
.map(|val| {
7576
let field_ty = ty.builtin_index().unwrap();

compiler/rustc_const_eval/src/const_eval/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ pub(crate) use valtrees::{eval_to_valtree, valtree_to_const_value};
2727
// We forbid type-level constants that contain more than `VALTREE_MAX_NODES` nodes.
2828
const VALTREE_MAX_NODES: usize = 100000;
2929

30-
pub(crate) enum ValTreeCreationError {
30+
pub(crate) enum ValTreeCreationError<'tcx> {
3131
NodesOverflow,
3232
/// Values of this type, or this particular value, are not supported as valtrees.
33-
NonSupportedType,
33+
NonSupportedType(Ty<'tcx>),
3434
}
35-
pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError>;
35+
pub(crate) type ValTreeCreationResult<'tcx> = Result<ty::ValTree<'tcx>, ValTreeCreationError<'tcx>>;
3636

37-
impl From<InterpErrorInfo<'_>> for ValTreeCreationError {
38-
fn from(err: InterpErrorInfo<'_>) -> Self {
37+
impl<'tcx> From<InterpErrorInfo<'tcx>> for ValTreeCreationError<'tcx> {
38+
fn from(err: InterpErrorInfo<'tcx>) -> Self {
3939
ty::tls::with(|tcx| {
4040
bug!(
4141
"Unexpected Undefined Behavior error during valtree construction: {}",

compiler/rustc_const_eval/src/const_eval/valtrees.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,21 @@ fn const_to_valtree_inner<'tcx>(
120120
// We could allow wide raw pointers where both sides are integers in the future,
121121
// but for now we reject them.
122122
if matches!(val.layout.abi, Abi::ScalarPair(..)) {
123-
return Err(ValTreeCreationError::NonSupportedType);
123+
return Err(ValTreeCreationError::NonSupportedType(ty));
124124
}
125125
let val = val.to_scalar();
126126
// We are in the CTFE machine, so ptr-to-int casts will fail.
127127
// This can only be `Ok` if `val` already is an integer.
128128
let Ok(val) = val.try_to_scalar_int() else {
129-
return Err(ValTreeCreationError::NonSupportedType);
129+
return Err(ValTreeCreationError::NonSupportedType(ty));
130130
};
131131
// It's just a ScalarInt!
132132
Ok(ty::ValTree::Leaf(val))
133133
}
134134

135135
// Technically we could allow function pointers (represented as `ty::Instance`), but this is not guaranteed to
136136
// agree with runtime equality tests.
137-
ty::FnPtr(_) => Err(ValTreeCreationError::NonSupportedType),
137+
ty::FnPtr(_) => Err(ValTreeCreationError::NonSupportedType(ty)),
138138

139139
ty::Ref(_, _, _) => {
140140
let derefd_place = ecx.deref_pointer(place)?;
@@ -148,15 +148,15 @@ fn const_to_valtree_inner<'tcx>(
148148
// resolving their backing type, even if we can do that at const eval time. We may
149149
// hypothetically be able to allow `dyn StructuralPartialEq` trait objects in the future,
150150
// but it is unclear if this is useful.
151-
ty::Dynamic(..) => Err(ValTreeCreationError::NonSupportedType),
151+
ty::Dynamic(..) => Err(ValTreeCreationError::NonSupportedType(ty)),
152152

153153
ty::Tuple(elem_tys) => {
154154
branches(ecx, place, elem_tys.len(), None, num_nodes)
155155
}
156156

157157
ty::Adt(def, _) => {
158158
if def.is_union() {
159-
return Err(ValTreeCreationError::NonSupportedType);
159+
return Err(ValTreeCreationError::NonSupportedType(ty));
160160
} else if def.variants().is_empty() {
161161
bug!("uninhabited types should have errored and never gotten converted to valtree")
162162
}
@@ -180,7 +180,7 @@ fn const_to_valtree_inner<'tcx>(
180180
| ty::Closure(..)
181181
| ty::CoroutineClosure(..)
182182
| ty::Coroutine(..)
183-
| ty::CoroutineWitness(..) => Err(ValTreeCreationError::NonSupportedType),
183+
| ty::CoroutineWitness(..) => Err(ValTreeCreationError::NonSupportedType(ty)),
184184
}
185185
}
186186

@@ -251,7 +251,7 @@ pub(crate) fn eval_to_valtree<'tcx>(
251251
let valtree_result = const_to_valtree_inner(&ecx, &place, &mut num_nodes);
252252

253253
match valtree_result {
254-
Ok(valtree) => Ok(Some(valtree)),
254+
Ok(valtree) => Ok(Ok(valtree)),
255255
Err(err) => {
256256
let did = cid.instance.def_id();
257257
let global_const_id = cid.display(tcx);
@@ -262,7 +262,7 @@ pub(crate) fn eval_to_valtree<'tcx>(
262262
tcx.dcx().emit_err(MaxNumNodesInConstErr { span, global_const_id });
263263
Err(handled.into())
264264
}
265-
ValTreeCreationError::NonSupportedType => Ok(None),
265+
ValTreeCreationError::NonSupportedType(ty) => Ok(Err(ty)),
266266
}
267267
}
268268
}

compiler/rustc_error_codes/src/error_codes/E0158.md

+8-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
An associated `const`, `const` parameter or `static` has been referenced
2-
in a pattern.
1+
A generic parameter or `static` has been referenced in a pattern.
32

43
Erroneous code example:
54

@@ -15,25 +14,25 @@ trait Bar {
1514
1615
fn test<A: Bar>(arg: Foo) {
1716
match arg {
18-
A::X => println!("A::X"), // error: E0158: associated consts cannot be
19-
// referenced in patterns
17+
A::X => println!("A::X"), // error: E0158: constant pattern depends
18+
// on a generic parameter
2019
Foo::Two => println!("Two")
2120
}
2221
}
2322
```
2423

25-
Associated `const`s cannot be referenced in patterns because it is impossible
24+
Generic parameters cannot be referenced in patterns because it is impossible
2625
for the compiler to prove exhaustiveness (that some pattern will always match).
2726
Take the above example, because Rust does type checking in the *generic*
2827
method, not the *monomorphized* specific instance. So because `Bar` could have
29-
theoretically infinite implementations, there's no way to always be sure that
28+
theoretically arbitrary implementations, there's no way to always be sure that
3029
`A::X` is `Foo::One`. So this code must be rejected. Even if code can be
3130
proven exhaustive by a programmer, the compiler cannot currently prove this.
3231

33-
The same holds true of `const` parameters and `static`s.
32+
The same holds true of `static`s.
3433

35-
If you want to match against an associated `const`, `const` parameter or
36-
`static` consider using a guard instead:
34+
If you want to match against a `const` that depends on a generic parameter or a
35+
`static`, consider using a guard instead:
3736

3837
```
3938
trait Trait {

compiler/rustc_infer/src/infer/mod.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1427,17 +1427,17 @@ impl<'tcx> InferCtxt<'tcx> {
14271427
span: Span,
14281428
) -> Result<ty::Const<'tcx>, ErrorHandled> {
14291429
match self.const_eval_resolve(param_env, unevaluated, span) {
1430-
Ok(Some(val)) => Ok(ty::Const::new_value(
1430+
Ok(Ok(val)) => Ok(ty::Const::new_value(
14311431
self.tcx,
14321432
val,
14331433
self.tcx.type_of(unevaluated.def).instantiate(self.tcx, unevaluated.args),
14341434
)),
1435-
Ok(None) => {
1435+
Ok(Err(bad_ty)) => {
14361436
let tcx = self.tcx;
14371437
let def_id = unevaluated.def;
14381438
span_bug!(
14391439
tcx.def_span(def_id),
1440-
"unable to construct a constant value for the unevaluated constant {:?}",
1440+
"unable to construct a valtree for the unevaluated constant {:?}: type {bad_ty} is not valtree-compatible",
14411441
unevaluated
14421442
);
14431443
}

compiler/rustc_middle/src/mir/interpret/error.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,11 @@ TrivialTypeTraversalImpls! { ErrorHandled }
9090
pub type EvalToAllocationRawResult<'tcx> = Result<ConstAlloc<'tcx>, ErrorHandled>;
9191
pub type EvalStaticInitializerRawResult<'tcx> = Result<ConstAllocation<'tcx>, ErrorHandled>;
9292
pub type EvalToConstValueResult<'tcx> = Result<ConstValue<'tcx>, ErrorHandled>;
93-
/// `Ok(None)` indicates the constant was fine, but the valtree couldn't be constructed.
94-
/// This is needed in `thir::pattern::lower_inline_const`.
95-
pub type EvalToValTreeResult<'tcx> = Result<Option<ValTree<'tcx>>, ErrorHandled>;
93+
/// `Ok(Err(ty))` indicates the constant was fine, but the valtree couldn't be constructed
94+
/// because the value containts something of type `ty` that is not valtree-compatible.
95+
/// The caller can then show an appropriate error; the query does not have the
96+
/// necssary context to give good user-facing errors for this case.
97+
pub type EvalToValTreeResult<'tcx> = Result<Result<ValTree<'tcx>, Ty<'tcx>>, ErrorHandled>;
9698

9799
#[cfg(target_pointer_width = "64")]
98100
rustc_data_structures::static_assert_size!(InterpErrorInfo<'_>, 8);

compiler/rustc_middle/src/query/erase.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,10 @@ impl EraseType for Result<mir::ConstValue<'_>, mir::interpret::ErrorHandled> {
157157
type Result = [u8; size_of::<Result<mir::ConstValue<'static>, mir::interpret::ErrorHandled>>()];
158158
}
159159

160-
impl EraseType for Result<Option<ty::ValTree<'_>>, mir::interpret::ErrorHandled> {
161-
type Result =
162-
[u8; size_of::<Result<Option<ty::ValTree<'static>>, mir::interpret::ErrorHandled>>()];
160+
impl EraseType for Result<Result<ty::ValTree<'_>, Ty<'_>>, mir::interpret::ErrorHandled> {
161+
type Result = [u8; size_of::<
162+
Result<Result<ty::ValTree<'static>, Ty<'static>>, mir::interpret::ErrorHandled>,
163+
>()];
163164
}
164165

165166
impl EraseType for Result<&'_ ty::List<Ty<'_>>, ty::util::AlwaysRequiresDrop> {

compiler/rustc_middle/src/thir.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -783,16 +783,13 @@ pub enum PatKind<'tcx> {
783783
},
784784

785785
/// One of the following:
786-
/// * `&str` (represented as a valtree), which will be handled as a string pattern and thus
787-
/// exhaustiveness checking will detect if you use the same string twice in different
788-
/// patterns.
786+
/// * `&str`/`&[u8]` (represented as a valtree), which will be handled as a string/slice pattern
787+
/// and thus exhaustiveness checking will detect if you use the same string/slice twice in
788+
/// different patterns.
789789
/// * integer, bool, char or float (represented as a valtree), which will be handled by
790790
/// exhaustiveness to cover exactly its own value, similar to `&str`, but these values are
791791
/// much simpler.
792-
/// * Opaque constants (represented as `mir::ConstValue`), that must not be matched
793-
/// structurally. So anything that does not derive `PartialEq` and `Eq`.
794-
///
795-
/// These are always compared with the matched place using (the semantics of) `PartialEq`.
792+
/// * `String`, if `string_deref_patterns` is enabled.
796793
Constant {
797794
value: mir::Const<'tcx>,
798795
},

compiler/rustc_middle/src/ty/consts.rs

+38-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::middle::resolve_bound_vars as rbv;
22
use crate::mir::interpret::{ErrorHandled, LitToConstInput, Scalar};
33
use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
4+
use either::Either;
45
use rustc_data_structures::intern::Interned;
56
use rustc_error_messages::MultiSpan;
67
use rustc_hir as hir;
@@ -312,14 +313,16 @@ impl<'tcx> Const<'tcx> {
312313
Self::from_bits(tcx, n as u128, ParamEnv::empty().and(tcx.types.usize))
313314
}
314315

315-
/// Returns the evaluated constant
316+
/// Returns the evaluated constant as a valtree;
317+
/// if that fails due to a valtree-incompatible type, indicate which type that is
318+
/// by returning `Err(Left(bad_type))`.
316319
#[inline]
317-
pub fn eval(
320+
pub fn eval_valtree(
318321
self,
319322
tcx: TyCtxt<'tcx>,
320323
param_env: ParamEnv<'tcx>,
321324
span: Span,
322-
) -> Result<(Ty<'tcx>, ValTree<'tcx>), ErrorHandled> {
325+
) -> Result<(Ty<'tcx>, ValTree<'tcx>), Either<Ty<'tcx>, ErrorHandled>> {
323326
assert!(!self.has_escaping_bound_vars(), "escaping vars in {self:?}");
324327
match self.kind() {
325328
ConstKind::Unevaluated(unevaluated) => {
@@ -328,27 +331,47 @@ impl<'tcx> Const<'tcx> {
328331
let (param_env, unevaluated) = unevaluated.prepare_for_eval(tcx, param_env);
329332
// try to resolve e.g. associated constants to their definition on an impl, and then
330333
// evaluate the const.
331-
let Some(c) = tcx.const_eval_resolve_for_typeck(param_env, unevaluated, span)?
332-
else {
333-
// This can happen when we run on ill-typed code.
334-
let e = tcx.dcx().span_delayed_bug(
335-
span,
336-
"`ty::Const::eval` called on a non-valtree-compatible type",
337-
);
338-
return Err(e.into());
339-
};
340-
Ok((tcx.type_of(unevaluated.def).instantiate(tcx, unevaluated.args), c))
334+
match tcx.const_eval_resolve_for_typeck(param_env, unevaluated, span) {
335+
Ok(Ok(c)) => {
336+
Ok((tcx.type_of(unevaluated.def).instantiate(tcx, unevaluated.args), c))
337+
}
338+
Ok(Err(bad_ty)) => Err(Either::Left(bad_ty)),
339+
Err(err) => Err(Either::Right(err.into())),
340+
}
341341
}
342342
ConstKind::Value(ty, val) => Ok((ty, val)),
343-
ConstKind::Error(g) => Err(g.into()),
343+
ConstKind::Error(g) => Err(Either::Right(g.into())),
344344
ConstKind::Param(_)
345345
| ConstKind::Infer(_)
346346
| ConstKind::Bound(_, _)
347347
| ConstKind::Placeholder(_)
348-
| ConstKind::Expr(_) => Err(ErrorHandled::TooGeneric(span)),
348+
| ConstKind::Expr(_) => Err(Either::Right(ErrorHandled::TooGeneric(span))),
349349
}
350350
}
351351

352+
/// Returns the evaluated constant
353+
#[inline]
354+
pub fn eval(
355+
self,
356+
tcx: TyCtxt<'tcx>,
357+
param_env: ParamEnv<'tcx>,
358+
span: Span,
359+
) -> Result<(Ty<'tcx>, ValTree<'tcx>), ErrorHandled> {
360+
self.eval_valtree(tcx, param_env, span).map_err(|err| {
361+
match err {
362+
Either::Right(err) => err,
363+
Either::Left(_bad_ty) => {
364+
// This can happen when we run on ill-typed code.
365+
let e = tcx.dcx().span_delayed_bug(
366+
span,
367+
"`ty::Const::eval` called on a non-valtree-compatible type",
368+
);
369+
e.into()
370+
}
371+
}
372+
})
373+
}
374+
352375
/// Normalizes the constant to a value or an error if possible.
353376
#[inline]
354377
pub fn normalize(self, tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>) -> Self {

compiler/rustc_mir_build/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
# tidy-alphabetical-start
8+
either = "1.5.0"
89
itertools = "0.12"
910
rustc_apfloat = "0.2.0"
1011
rustc_arena = { path = "../rustc_arena" }

compiler/rustc_mir_build/messages.ftl

-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ mir_build_already_borrowed = cannot borrow value as mutable because it is also b
44
55
mir_build_already_mut_borrowed = cannot borrow value as immutable because it is also borrowed as mutable
66
7-
mir_build_assoc_const_in_pattern = associated consts cannot be referenced in patterns
8-
97
mir_build_bindings_with_variant_name =
108
pattern binding `{$name}` is named the same as one of the variants of the type `{$ty_path}`
119
.suggestion = to match on the variant, qualify the path

0 commit comments

Comments
 (0)