Skip to content

Commit e390944

Browse files
Check all types for PartialEq while lowering consts to patterns
1 parent dd927a5 commit e390944

File tree

1 file changed

+133
-71
lines changed

1 file changed

+133
-71
lines changed

src/librustc_mir_build/hair/pattern/const_to_pat.rs

+133-71
Original file line numberDiff line numberDiff line change
@@ -97,82 +97,67 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
9797

9898
let inlined_const_as_pat = self.recur(cv);
9999

100-
if self.include_lint_checks && !self.saw_const_match_error.get() {
101-
// If we were able to successfully convert the const to some pat,
102-
// double-check that all types in the const implement `Structural`.
100+
if self.saw_const_match_error.get() {
101+
return inlined_const_as_pat;
102+
}
103103

104-
let structural = self.search_for_structural_match_violation(cv.ty);
105-
debug!(
106-
"search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
107-
cv.ty, structural
108-
);
104+
// We eventually lower to a call to `PartialEq::eq` for this type, so ensure that this
105+
// method actually exists.
106+
if !self.ty_has_partial_eq_impl(cv.ty) {
107+
let msg = if cv.ty.is_trait() {
108+
"trait objects cannot be used in patterns".to_string()
109+
} else {
110+
format!("`{:?}` must implement `PartialEq` to be used in a pattern", cv.ty)
111+
};
109112

110-
if structural.is_none() && mir_structural_match_violation {
111-
bug!("MIR const-checker found novel structural match violation");
112-
}
113+
// Codegen will ICE if we continue compilation, so abort here.
114+
self.tcx().sess.span_fatal(self.span, &msg);
115+
}
113116

114-
if let Some(non_sm_ty) = structural {
115-
let msg = match non_sm_ty {
116-
traits::NonStructuralMatchTy::Adt(adt_def) => {
117-
let path = self.tcx().def_path_str(adt_def.did);
118-
format!(
119-
"to use a constant of type `{}` in a pattern, \
117+
if !self.include_lint_checks {
118+
return inlined_const_as_pat;
119+
}
120+
121+
// If we were able to successfully convert the const to some pat,
122+
// double-check that all types in the const implement `Structural`.
123+
124+
let ty_violation = self.search_for_structural_match_violation(cv.ty);
125+
debug!(
126+
"search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
127+
cv.ty, ty_violation,
128+
);
129+
130+
if mir_structural_match_violation {
131+
let non_sm_ty =
132+
ty_violation.expect("MIR const-checker found novel structural match violation");
133+
let msg = match non_sm_ty {
134+
traits::NonStructuralMatchTy::Adt(adt_def) => {
135+
let path = self.tcx().def_path_str(adt_def.did);
136+
format!(
137+
"to use a constant of type `{}` in a pattern, \
120138
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
121-
path, path,
122-
)
123-
}
124-
traits::NonStructuralMatchTy::Dynamic => {
125-
"trait objects cannot be used in patterns".to_string()
126-
}
127-
traits::NonStructuralMatchTy::Param => {
128-
bug!("use of constant whose type is a parameter inside a pattern")
129-
}
130-
};
131-
132-
// double-check there even *is* a semantic `PartialEq` to dispatch to.
133-
//
134-
// (If there isn't, then we can safely issue a hard
135-
// error, because that's never worked, due to compiler
136-
// using `PartialEq::eq` in this scenario in the past.)
137-
//
138-
// Note: To fix rust-lang/rust#65466, one could lift this check
139-
// *before* any structural-match checking, and unconditionally error
140-
// if `PartialEq` is not implemented. However, that breaks stable
141-
// code at the moment, because types like `for <'a> fn(&'a ())` do
142-
// not *yet* implement `PartialEq`. So for now we leave this here.
143-
let ty_is_partial_eq: bool = {
144-
let partial_eq_trait_id =
145-
self.tcx().require_lang_item(EqTraitLangItem, Some(self.span));
146-
let obligation: PredicateObligation<'_> = predicate_for_trait_def(
147-
self.tcx(),
148-
self.param_env,
149-
ObligationCause::misc(self.span, self.id),
150-
partial_eq_trait_id,
151-
0,
152-
cv.ty,
153-
&[],
154-
);
155-
// FIXME: should this call a `predicate_must_hold` variant instead?
156-
self.infcx.predicate_may_hold(&obligation)
157-
};
158-
159-
if !ty_is_partial_eq {
160-
// span_fatal avoids ICE from resolution of non-existent method (rare case).
161-
self.tcx().sess.span_fatal(self.span, &msg);
162-
} else if mir_structural_match_violation {
163-
self.tcx().struct_span_lint_hir(
164-
lint::builtin::INDIRECT_STRUCTURAL_MATCH,
165-
self.id,
166-
self.span,
167-
|lint| lint.build(&msg).emit(),
168-
);
169-
} else {
170-
debug!(
171-
"`search_for_structural_match_violation` found one, but `CustomEq` was \
172-
not in the qualifs for that `const`"
173-
);
139+
path, path,
140+
)
174141
}
175-
}
142+
traits::NonStructuralMatchTy::Dynamic => {
143+
"trait objects cannot be used in patterns".to_string()
144+
}
145+
traits::NonStructuralMatchTy::Param => {
146+
bug!("use of constant whose type is a parameter inside a pattern")
147+
}
148+
};
149+
150+
self.tcx().struct_span_lint_hir(
151+
lint::builtin::INDIRECT_STRUCTURAL_MATCH,
152+
self.id,
153+
self.span,
154+
|lint| lint.build(&msg).emit(),
155+
);
156+
} else if ty_violation.is_some() {
157+
debug!(
158+
"`search_for_structural_match_violation` found one, but `CustomEq` was \
159+
not in the qualifs for that `const`"
160+
);
176161
}
177162

178163
inlined_const_as_pat
@@ -280,4 +265,81 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
280265

281266
Pat { span, ty: cv.ty, kind: Box::new(kind) }
282267
}
268+
269+
fn ty_has_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool {
270+
let tcx = self.tcx();
271+
272+
let is_partial_eq = |ty| {
273+
let partial_eq_trait_id = tcx.require_lang_item(EqTraitLangItem, Some(self.span));
274+
let obligation: PredicateObligation<'_> = predicate_for_trait_def(
275+
tcx,
276+
self.param_env,
277+
ObligationCause::misc(self.span, self.id),
278+
partial_eq_trait_id,
279+
0,
280+
ty,
281+
&[],
282+
);
283+
284+
// FIXME: should this call a `predicate_must_hold` variant instead?
285+
self.infcx.predicate_may_hold(&obligation)
286+
};
287+
288+
// Higher-ranked function pointers, such as `for<'r> fn(&'r i32)` are allowed in patterns
289+
// but do not satisfy `Self: PartialEq` due to shortcomings in the trait solver.
290+
// Check for bare function pointers first since it is cheap to do so.
291+
if let ty::FnPtr(_) = ty.kind {
292+
return true;
293+
}
294+
295+
// In general, types that appear in patterns need to implement `PartialEq`.
296+
if is_partial_eq(ty) {
297+
return true;
298+
}
299+
300+
// HACK: The check for bare function pointers will miss generic types that are instantiated
301+
// with a higher-ranked type (`for<'r> fn(&'r i32)`) as a parameter. To preserve backwards
302+
// compatibility in this case, we must continue to allow types such as `Option<fn(&i32)>`.
303+
//
304+
//
305+
// We accomplish this by replacing *all* late-bound lifetimes in the type with concrete
306+
// ones. This leverages the fact that function pointers with no late-bound lifetimes do
307+
// satisfy `PartialEq`. In other words, we transform `Option<for<'r> fn(&'r i32)>` to
308+
// `Option<fn(&'erased i32)>` and again check whether `PartialEq` is satisfied.
309+
// Obviously this is too permissive, but it is better than the old behavior, which
310+
// allowed *all* types to reach codegen and caused issues like #65466.
311+
let erased_ty = erase_all_late_bound_regions(tcx, ty);
312+
if is_partial_eq(erased_ty) {
313+
warn!("Non-function pointer only satisfied `PartialEq` after regions were erased");
314+
return true;
315+
}
316+
317+
false
318+
}
319+
}
320+
321+
/// Erase *all* late bound regions, ignoring their debruijn index.
322+
///
323+
/// This is a terrible hack. Do not use it elsewhere.
324+
fn erase_all_late_bound_regions<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
325+
use ty::fold::TypeFoldable;
326+
327+
struct Eraser<'tcx> {
328+
tcx: TyCtxt<'tcx>,
329+
}
330+
331+
impl<'tcx> ty::fold::TypeFolder<'tcx> for Eraser<'tcx> {
332+
fn tcx(&self) -> TyCtxt<'tcx> {
333+
self.tcx
334+
}
335+
336+
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
337+
match r {
338+
ty::ReLateBound(_, _) => &ty::ReErased,
339+
r => r.super_fold_with(self),
340+
}
341+
}
342+
}
343+
344+
ty.fold_with(&mut Eraser { tcx })
283345
}

0 commit comments

Comments
 (0)