-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Always fall back to PartialEq when a constant in a pattern is not recursively structural-eq #105750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8d00f76
ad424e6
87f9f99
2282258
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,21 +62,13 @@ struct ConstToPat<'tcx> { | |
treat_byte_string_as_slice: bool, | ||
} | ||
|
||
mod fallback_to_const_ref { | ||
#[derive(Debug)] | ||
/// This error type signals that we encountered a non-struct-eq situation behind a reference. | ||
/// We bubble this up in order to get back to the reference destructuring and make that emit | ||
/// a const pattern instead of a deref pattern. This allows us to simply call `PartialEq::eq` | ||
/// on such patterns (since that function takes a reference) and not have to jump through any | ||
/// hoops to get a reference to the value. | ||
pub(super) struct FallbackToConstRef(()); | ||
|
||
pub(super) fn fallback_to_const_ref(c2p: &super::ConstToPat<'_>) -> FallbackToConstRef { | ||
assert!(c2p.behind_reference.get()); | ||
FallbackToConstRef(()) | ||
} | ||
} | ||
use fallback_to_const_ref::{fallback_to_const_ref, FallbackToConstRef}; | ||
/// This error type signals that we encountered a non-struct-eq situation. | ||
/// We bubble this up in order to get back to the reference destructuring and make that emit | ||
/// a const pattern instead of a deref pattern. This allows us to simply call `PartialEq::eq` | ||
/// on such patterns (since that function takes a reference) and not have to jump through any | ||
/// hoops to get a reference to the value. | ||
#[derive(Debug)] | ||
struct FallbackToConstRef; | ||
|
||
impl<'tcx> ConstToPat<'tcx> { | ||
fn new( | ||
|
@@ -236,13 +228,13 @@ impl<'tcx> ConstToPat<'tcx> { | |
|
||
let kind = match cv.ty().kind() { | ||
ty::Float(_) => { | ||
tcx.emit_spanned_lint( | ||
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, | ||
id, | ||
span, | ||
FloatPattern, | ||
); | ||
PatKind::Constant { value: cv } | ||
tcx.emit_spanned_lint( | ||
lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, | ||
id, | ||
span, | ||
FloatPattern, | ||
); | ||
return Err(FallbackToConstRef); | ||
} | ||
ty::Adt(adt_def, _) if adt_def.is_union() => { | ||
// Matching on union fields is unsafe, we can't hide it in constants | ||
|
@@ -289,7 +281,7 @@ impl<'tcx> ConstToPat<'tcx> { | |
// Since we are behind a reference, we can just bubble the error up so we get a | ||
// constant at reference type, making it easy to let the fallback call | ||
// `PartialEq::eq` on it. | ||
return Err(fallback_to_const_ref(self)); | ||
return Err(FallbackToConstRef); | ||
Comment on lines
-292
to
+284
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is exactly the same behaviour as on master. We're behind a reference already, so we aren't even using the new code path in |
||
} | ||
ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty()) => { | ||
debug!( | ||
|
@@ -393,11 +385,11 @@ impl<'tcx> ConstToPat<'tcx> { | |
self.behind_reference.set(old); | ||
val | ||
} | ||
// Backwards compatibility hack: support references to non-structural types. | ||
// We'll lower | ||
// this pattern to a `PartialEq::eq` comparison and `PartialEq::eq` takes a | ||
// reference. This makes the rest of the matching logic simpler as it doesn't have | ||
// to figure out how to get a reference again. | ||
// Backwards compatibility hack: support references to non-structural types, | ||
// but hard error if we aren't behind a double reference. We could just use | ||
// the fallback code path below, but that would allow *more* of this fishy | ||
// code to compile, as then it only goes through the future incompat lint | ||
// instead of a hard error. | ||
ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => { | ||
if self.behind_reference.get() { | ||
if !self.saw_const_match_error.get() | ||
|
@@ -411,7 +403,7 @@ impl<'tcx> ConstToPat<'tcx> { | |
IndirectStructuralMatch { non_sm_ty: *pointee_ty }, | ||
); | ||
} | ||
PatKind::Constant { value: cv } | ||
return Err(FallbackToConstRef); | ||
Comment on lines
-414
to
+406
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a change in behaviour. If we have struct Inner;
impl PartialEq for Inner {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl Eq for Inner {}
#[derive(PartialEq, Eq)]
enum Eek<'a> {
Foo,
Bar(&'a Inner),
}
const THE_CONST: &Eek<'static> = &Eek::Bar(&Inner); Previously Now it doesn't get destructured, but we directly see A const of value If we had something like struct Inner;
impl PartialEq for Inner {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl Eq for Inner {}
#[derive(PartialEq, Eq)]
enum Eek<'a> {
Foo,
Bar(&'a Option<Inner>),
}
const THE_CONST: &Eek<'static> = &Eek::Bar(&None); We would get If you had a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Wait, so the analysis here is value-driven, not type-driven? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I believe requiring recursive structural equality on the type of constants in order to expand them into patterns is not doable without breaking real code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I can't say I am surprised. This needs careful documentation though, to explain how this value analysis works. |
||
} else { | ||
if !self.saw_const_match_error.get() { | ||
self.saw_const_match_error.set(true); | ||
|
@@ -435,24 +427,17 @@ impl<'tcx> ConstToPat<'tcx> { | |
PatKind::Wild | ||
} else { | ||
let old = self.behind_reference.replace(true); | ||
// In case there are structural-match violations somewhere in this subpattern, | ||
// we fall back to a const pattern. If we do not do this, we may end up with | ||
// a !structural-match constant that is not of reference type, which makes it | ||
// very hard to invoke `PartialEq::eq` on it as a fallback. | ||
let val = match self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false) { | ||
Ok(subpattern) => PatKind::Deref { subpattern }, | ||
Err(_) => PatKind::Constant { value: cv }, | ||
}; | ||
let subpattern = self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false)?; | ||
self.behind_reference.set(old); | ||
val | ||
PatKind::Deref { subpattern } | ||
Comment on lines
-442
to
+432
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a more general case of the example already given, as it also works if you put something like a tuple or other builtin aggregate in between. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the fishiest reasoning of all of them, but everything I've checked out already has had tests or was just a variant using a different aggregate |
||
} | ||
} | ||
}, | ||
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => { | ||
PatKind::Constant { value: cv } | ||
} | ||
ty::RawPtr(pointee) if pointee.ty.is_sized(tcx, param_env) => { | ||
PatKind::Constant { value: cv } | ||
return Err(FallbackToConstRef); | ||
Comment on lines
-455
to
+440
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. raw pointer constants were already not participating in exhaustiveness. Even if they are just an integer behind a raw pointer type. This thus has no effect on exhaustiveness and the runtime logic is already using PartialEq. |
||
} | ||
// FIXME: these can have very surprising behaviour where optimization levels or other | ||
// compilation choices change the runtime behaviour of the match. | ||
|
@@ -469,7 +454,7 @@ impl<'tcx> ConstToPat<'tcx> { | |
PointerPattern | ||
); | ||
} | ||
PatKind::Constant { value: cv } | ||
return Err(FallbackToConstRef); | ||
Comment on lines
-472
to
+457
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one is already doing random things at runtime depending on opts. We are also already not using it for exhaustiveness. |
||
} | ||
_ => { | ||
self.saw_const_match_error.set(true); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,11 +20,12 @@ const BAR: Bar = Bar; | |
#[derive(PartialEq)] | ||
enum Baz { | ||
Baz1, | ||
Baz2 | ||
Baz2, | ||
} | ||
impl Eq for Baz {} | ||
const BAZ: Baz = Baz::Baz1; | ||
|
||
#[rustfmt::skip] | ||
fn main() { | ||
match FOO { | ||
FOO => {} | ||
|
@@ -124,8 +125,16 @@ fn main() { | |
|
||
match WRAPQUUX { | ||
Wrap(_) => {} | ||
WRAPQUUX => {} // detected unreachable because we do inspect the `Wrap` layer | ||
//~^ ERROR unreachable pattern | ||
WRAPQUUX => {} | ||
} | ||
Comment on lines
126
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't emit an |
||
|
||
match WRAPQUUX { | ||
Wrap(_) => {} | ||
} | ||
|
||
match WRAPQUUX { | ||
//~^ ERROR: non-exhaustive patterns: `Wrap(_)` not covered | ||
WRAPQUUX => {} | ||
} | ||
|
||
#[derive(PartialEq, Eq)] | ||
|
@@ -138,8 +147,7 @@ fn main() { | |
match WHOKNOWSQUUX { | ||
WHOKNOWSQUUX => {} | ||
WhoKnows::Yay(_) => {} | ||
WHOKNOWSQUUX => {} // detected unreachable because we do inspect the `WhoKnows` layer | ||
//~^ ERROR unreachable pattern | ||
WHOKNOWSQUUX => {} | ||
WhoKnows::Nope => {} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.