Skip to content

Commit 9239760

Browse files
committed
Auto merge of rust-lang#105750 - oli-obk:valtrees, r=lcnr
Always fall back to PartialEq when a constant in a pattern is not recursively structural-eq Right now we destructure the constant as far as we can, but with this PR we just don't take it apart anymore. This is preparatory work for moving to always using valtrees, as these will just do a single conversion of the constant to a valtree at the start, and if that fails, fall back to `PartialEq`. This removes a few cases where we emitted the `unreachable pattern` lint, because we stop looking into the constant deeply enough to detect that a constant is already covered by another pattern. Previous work: rust-lang#70743 This is groundwork towards fixing rust-lang#83085 and rust-lang#105047
2 parents a673ad6 + 2282258 commit 9239760

File tree

6 files changed

+137
-94
lines changed

6 files changed

+137
-94
lines changed

compiler/rustc_mir_build/src/build/matches/test.rs

+39-10
Original file line numberDiff line numberDiff line change
@@ -380,18 +380,19 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
380380
);
381381
}
382382

383-
/// Compare two `&T` values using `<T as std::compare::PartialEq>::eq`
383+
/// Compare two values using `<T as std::compare::PartialEq>::eq`.
384+
/// If the values are already references, just call it directly, otherwise
385+
/// take a reference to the values first and then call it.
384386
fn non_scalar_compare(
385387
&mut self,
386388
block: BasicBlock,
387389
make_target_blocks: impl FnOnce(&mut Self) -> Vec<BasicBlock>,
388390
source_info: SourceInfo,
389391
value: ConstantKind<'tcx>,
390-
place: Place<'tcx>,
392+
mut val: Place<'tcx>,
391393
mut ty: Ty<'tcx>,
392394
) {
393395
let mut expect = self.literal_operand(source_info.span, value);
394-
let mut val = Operand::Copy(place);
395396

396397
// If we're using `b"..."` as a pattern, we need to insert an
397398
// unsizing coercion, as the byte string has the type `&[u8; N]`.
@@ -421,9 +422,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
421422
block,
422423
source_info,
423424
temp,
424-
Rvalue::Cast(CastKind::Pointer(PointerCast::Unsize), val, ty),
425+
Rvalue::Cast(
426+
CastKind::Pointer(PointerCast::Unsize),
427+
Operand::Copy(val),
428+
ty,
429+
),
425430
);
426-
val = Operand::Move(temp);
431+
val = temp;
427432
}
428433
if opt_ref_test_ty.is_some() {
429434
let slice = self.temp(ty, source_info.span);
@@ -438,12 +443,36 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
438443
}
439444
}
440445

441-
let ty::Ref(_, deref_ty, _) = *ty.kind() else {
442-
bug!("non_scalar_compare called on non-reference type: {}", ty);
443-
};
446+
match *ty.kind() {
447+
ty::Ref(_, deref_ty, _) => ty = deref_ty,
448+
_ => {
449+
// non_scalar_compare called on non-reference type
450+
let temp = self.temp(ty, source_info.span);
451+
self.cfg.push_assign(block, source_info, temp, Rvalue::Use(expect));
452+
let ref_ty = self.tcx.mk_imm_ref(self.tcx.lifetimes.re_erased, ty);
453+
let ref_temp = self.temp(ref_ty, source_info.span);
454+
455+
self.cfg.push_assign(
456+
block,
457+
source_info,
458+
ref_temp,
459+
Rvalue::Ref(self.tcx.lifetimes.re_erased, BorrowKind::Shared, temp),
460+
);
461+
expect = Operand::Move(ref_temp);
462+
463+
let ref_temp = self.temp(ref_ty, source_info.span);
464+
self.cfg.push_assign(
465+
block,
466+
source_info,
467+
ref_temp,
468+
Rvalue::Ref(self.tcx.lifetimes.re_erased, BorrowKind::Shared, val),
469+
);
470+
val = ref_temp;
471+
}
472+
}
444473

445474
let eq_def_id = self.tcx.require_lang_item(LangItem::PartialEq, Some(source_info.span));
446-
let method = trait_method(self.tcx, eq_def_id, sym::eq, [deref_ty, deref_ty]);
475+
let method = trait_method(self.tcx, eq_def_id, sym::eq, [ty, ty]);
447476

448477
let bool_ty = self.tcx.types.bool;
449478
let eq_result = self.temp(bool_ty, source_info.span);
@@ -463,7 +492,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
463492

464493
literal: method,
465494
})),
466-
args: vec![val, expect],
495+
args: vec![Operand::Copy(val), expect],
467496
destination: eq_result,
468497
target: Some(eq_block),
469498
unwind: UnwindAction::Continue,

compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs

+25-40
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,13 @@ struct ConstToPat<'tcx> {
6262
treat_byte_string_as_slice: bool,
6363
}
6464

65-
mod fallback_to_const_ref {
66-
#[derive(Debug)]
67-
/// This error type signals that we encountered a non-struct-eq situation behind a reference.
68-
/// We bubble this up in order to get back to the reference destructuring and make that emit
69-
/// a const pattern instead of a deref pattern. This allows us to simply call `PartialEq::eq`
70-
/// on such patterns (since that function takes a reference) and not have to jump through any
71-
/// hoops to get a reference to the value.
72-
pub(super) struct FallbackToConstRef(());
73-
74-
pub(super) fn fallback_to_const_ref(c2p: &super::ConstToPat<'_>) -> FallbackToConstRef {
75-
assert!(c2p.behind_reference.get());
76-
FallbackToConstRef(())
77-
}
78-
}
79-
use fallback_to_const_ref::{fallback_to_const_ref, FallbackToConstRef};
65+
/// This error type signals that we encountered a non-struct-eq situation.
66+
/// We bubble this up in order to get back to the reference destructuring and make that emit
67+
/// a const pattern instead of a deref pattern. This allows us to simply call `PartialEq::eq`
68+
/// on such patterns (since that function takes a reference) and not have to jump through any
69+
/// hoops to get a reference to the value.
70+
#[derive(Debug)]
71+
struct FallbackToConstRef;
8072

8173
impl<'tcx> ConstToPat<'tcx> {
8274
fn new(
@@ -236,13 +228,13 @@ impl<'tcx> ConstToPat<'tcx> {
236228

237229
let kind = match cv.ty().kind() {
238230
ty::Float(_) => {
239-
tcx.emit_spanned_lint(
240-
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
241-
id,
242-
span,
243-
FloatPattern,
244-
);
245-
PatKind::Constant { value: cv }
231+
tcx.emit_spanned_lint(
232+
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
233+
id,
234+
span,
235+
FloatPattern,
236+
);
237+
return Err(FallbackToConstRef);
246238
}
247239
ty::Adt(adt_def, _) if adt_def.is_union() => {
248240
// Matching on union fields is unsafe, we can't hide it in constants
@@ -289,7 +281,7 @@ impl<'tcx> ConstToPat<'tcx> {
289281
// Since we are behind a reference, we can just bubble the error up so we get a
290282
// constant at reference type, making it easy to let the fallback call
291283
// `PartialEq::eq` on it.
292-
return Err(fallback_to_const_ref(self));
284+
return Err(FallbackToConstRef);
293285
}
294286
ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty()) => {
295287
debug!(
@@ -393,11 +385,11 @@ impl<'tcx> ConstToPat<'tcx> {
393385
self.behind_reference.set(old);
394386
val
395387
}
396-
// Backwards compatibility hack: support references to non-structural types.
397-
// We'll lower
398-
// this pattern to a `PartialEq::eq` comparison and `PartialEq::eq` takes a
399-
// reference. This makes the rest of the matching logic simpler as it doesn't have
400-
// to figure out how to get a reference again.
388+
// Backwards compatibility hack: support references to non-structural types,
389+
// but hard error if we aren't behind a double reference. We could just use
390+
// the fallback code path below, but that would allow *more* of this fishy
391+
// code to compile, as then it only goes through the future incompat lint
392+
// instead of a hard error.
401393
ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => {
402394
if self.behind_reference.get() {
403395
if !self.saw_const_match_error.get()
@@ -411,7 +403,7 @@ impl<'tcx> ConstToPat<'tcx> {
411403
IndirectStructuralMatch { non_sm_ty: *pointee_ty },
412404
);
413405
}
414-
PatKind::Constant { value: cv }
406+
return Err(FallbackToConstRef);
415407
} else {
416408
if !self.saw_const_match_error.get() {
417409
self.saw_const_match_error.set(true);
@@ -435,24 +427,17 @@ impl<'tcx> ConstToPat<'tcx> {
435427
PatKind::Wild
436428
} else {
437429
let old = self.behind_reference.replace(true);
438-
// In case there are structural-match violations somewhere in this subpattern,
439-
// we fall back to a const pattern. If we do not do this, we may end up with
440-
// a !structural-match constant that is not of reference type, which makes it
441-
// very hard to invoke `PartialEq::eq` on it as a fallback.
442-
let val = match self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false) {
443-
Ok(subpattern) => PatKind::Deref { subpattern },
444-
Err(_) => PatKind::Constant { value: cv },
445-
};
430+
let subpattern = self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false)?;
446431
self.behind_reference.set(old);
447-
val
432+
PatKind::Deref { subpattern }
448433
}
449434
}
450435
},
451436
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => {
452437
PatKind::Constant { value: cv }
453438
}
454439
ty::RawPtr(pointee) if pointee.ty.is_sized(tcx, param_env) => {
455-
PatKind::Constant { value: cv }
440+
return Err(FallbackToConstRef);
456441
}
457442
// FIXME: these can have very surprising behaviour where optimization levels or other
458443
// compilation choices change the runtime behaviour of the match.
@@ -469,7 +454,7 @@ impl<'tcx> ConstToPat<'tcx> {
469454
PointerPattern
470455
);
471456
}
472-
PatKind::Constant { value: cv }
457+
return Err(FallbackToConstRef);
473458
}
474459
_ => {
475460
self.saw_const_match_error.set(true);

compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,8 @@ impl<'tcx> Constructor<'tcx> {
844844
}
845845

846846
/// Faster version of `is_covered_by` when applied to many constructors. `used_ctors` is
847-
/// assumed to be built from `matrix.head_ctors()` with wildcards filtered out, and `self` is
848-
/// assumed to have been split from a wildcard.
847+
/// assumed to be built from `matrix.head_ctors()` with wildcards and opaques filtered out,
848+
/// and `self` is assumed to have been split from a wildcard.
849849
fn is_covered_by_any<'p>(
850850
&self,
851851
pcx: &PatCtxt<'_, 'p, 'tcx>,
@@ -894,7 +894,7 @@ impl<'tcx> Constructor<'tcx> {
894894
/// in `to_ctors`: in some cases we only return `Missing`.
895895
#[derive(Debug)]
896896
pub(super) struct SplitWildcard<'tcx> {
897-
/// Constructors seen in the matrix.
897+
/// Constructors (other than wildcards and opaques) seen in the matrix.
898898
matrix_ctors: Vec<Constructor<'tcx>>,
899899
/// All the constructors for this type
900900
all_ctors: SmallVec<[Constructor<'tcx>; 1]>,
@@ -1037,7 +1037,7 @@ impl<'tcx> SplitWildcard<'tcx> {
10371037
// Since `all_ctors` never contains wildcards, this won't recurse further.
10381038
self.all_ctors =
10391039
self.all_ctors.iter().flat_map(|ctor| ctor.split(pcx, ctors.clone())).collect();
1040-
self.matrix_ctors = ctors.filter(|c| !c.is_wildcard()).cloned().collect();
1040+
self.matrix_ctors = ctors.filter(|c| !matches!(c, Wildcard | Opaque)).cloned().collect();
10411041
}
10421042

10431043
/// Whether there are any value constructors for this type that are not present in the matrix.

compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

+16
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,22 @@
288288
//!
289289
//! The details are not necessary to understand this file, so we explain them in
290290
//! [`super::deconstruct_pat`]. Splitting is done by the [`Constructor::split`] function.
291+
//!
292+
//! # Constants in patterns
293+
//!
294+
//! There are two kinds of constants in patterns:
295+
//!
296+
//! * literals (`1`, `true`, `"foo"`)
297+
//! * named or inline consts (`FOO`, `const { 5 + 6 }`)
298+
//!
299+
//! The latter are converted into other patterns with literals at the leaves. For example
300+
//! `const_to_pat(const { [1, 2, 3] })` becomes an `Array(vec![Const(1), Const(2), Const(3)])`
301+
//! pattern. This gets problematic when comparing the constant via `==` would behave differently
302+
//! from matching on the constant converted to a pattern. Situations like that can occur, when
303+
//! the user implements `PartialEq` manually, and thus could make `==` behave arbitrarily different.
304+
//! In order to honor the `==` implementation, constants of types that implement `PartialEq` manually
305+
//! stay as a full constant and become an `Opaque` pattern. These `Opaque` patterns do not participate
306+
//! in exhaustiveness, specialization or overlap checking.
291307
292308
use self::ArmType::*;
293309
use self::Usefulness::*;

tests/ui/pattern/usefulness/consts-opaque.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ const BAR: Bar = Bar;
2020
#[derive(PartialEq)]
2121
enum Baz {
2222
Baz1,
23-
Baz2
23+
Baz2,
2424
}
2525
impl Eq for Baz {}
2626
const BAZ: Baz = Baz::Baz1;
2727

28+
#[rustfmt::skip]
2829
fn main() {
2930
match FOO {
3031
FOO => {}
@@ -124,8 +125,16 @@ fn main() {
124125

125126
match WRAPQUUX {
126127
Wrap(_) => {}
127-
WRAPQUUX => {} // detected unreachable because we do inspect the `Wrap` layer
128-
//~^ ERROR unreachable pattern
128+
WRAPQUUX => {}
129+
}
130+
131+
match WRAPQUUX {
132+
Wrap(_) => {}
133+
}
134+
135+
match WRAPQUUX {
136+
//~^ ERROR: non-exhaustive patterns: `Wrap(_)` not covered
137+
WRAPQUUX => {}
129138
}
130139

131140
#[derive(PartialEq, Eq)]
@@ -138,8 +147,7 @@ fn main() {
138147
match WHOKNOWSQUUX {
139148
WHOKNOWSQUUX => {}
140149
WhoKnows::Yay(_) => {}
141-
WHOKNOWSQUUX => {} // detected unreachable because we do inspect the `WhoKnows` layer
142-
//~^ ERROR unreachable pattern
150+
WHOKNOWSQUUX => {}
143151
WhoKnows::Nope => {}
144152
}
145153
}

0 commit comments

Comments
 (0)