Skip to content

Commit 622b947

Browse files
committed
Checkpoint status. This now nicely errors instead of ICE'ing on matthewjasper's example.
specific example is from [comment][]: [comment]: rust-lang#61188 (comment) ```rust #![deny(indirect_structural_match)] #[derive(PartialEq, Eq)] enum O<T> { Some(*const T), // Can also use PhantomData<T> None, } struct B; const C: &[O<B>] = &[O::None]; pub fn foo() { let x = O::None; match &[x][..] { C => (), _ => (), } } ```
1 parent 28d339f commit 622b947

File tree

1 file changed

+217
-81
lines changed
  • src/librustc_mir/hair/pattern

1 file changed

+217
-81
lines changed

src/librustc_mir/hair/pattern/mod.rs

+217-81
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ use crate::hair::util::UserAnnotatedTyHelpers;
1111
use crate::hair::constant::*;
1212

1313
use rustc::lint;
14+
use rustc::infer::InferCtxt;
1415
use rustc::mir::{Field, BorrowKind, Mutability};
1516
use rustc::mir::{UserTypeProjection};
1617
use rustc::mir::interpret::{GlobalId, ConstValue, get_slice_bytes, sign_extend};
1718
use rustc::traits::{self, ConstPatternStructural, TraitEngine};
1819
use rustc::traits::{ObligationCause, PredicateObligation};
19-
use rustc::ty::{self, Region, TyCtxt, AdtDef, Ty, UserType, DefIdTree};
20+
use rustc::ty::{self, Region, TyCtxt, AdtDef, Ty, UserType, DefIdTree, ToPredicate};
2021
use rustc::ty::{CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations};
2122
use rustc::ty::subst::{SubstsRef, GenericArg};
2223
use rustc::ty::layout::VariantIdx;
@@ -1006,66 +1007,46 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
10061007
return inlined_const_as_pat;
10071008
}
10081009

1009-
// double-check there even *is* a semantic PartialEq to dispatch to.
1010-
let ty_is_partial_eq: bool = {
1011-
let partial_eq_trait_id = self.tcx.lang_items().eq_trait().unwrap();
1012-
let obligation: PredicateObligation<'_> =
1013-
self.tcx.predicate_for_trait_def(self.param_env,
1014-
ObligationCause::misc(span, id),
1015-
partial_eq_trait_id,
1016-
0,
1017-
cv.ty,
1018-
&[]);
1019-
// FIXME: should this call a `predicate_must_hold` variant instead?
1020-
self.tcx
1021-
.infer_ctxt()
1022-
.enter(|infcx| infcx.predicate_may_hold(&obligation))
1023-
};
1010+
let tcx = self.tcx;
1011+
let param_env = self.param_env;
1012+
let include_lint_checks = self.include_lint_checks;
10241013

1025-
// Don't bother wtih remaining checks if the type is `PartialEq` and the lint is off.
1026-
if ty_is_partial_eq && !self.include_lint_checks {
1027-
return inlined_const_as_pat;
1028-
}
1014+
let check = tcx.infer_ctxt().enter(|infcx| -> CheckConstForStructuralPattern {
1015+
// double-check there even *is* a semantic PartialEq to dispatch to.
1016+
let ty_is_partial_eq: bool = is_partial_eq(tcx, param_env, &infcx, cv.ty, id, span);
10291017

1030-
// If we were able to successfully convert the const to some pat, double-check
1031-
// that all types in the const implement `Structural`.
1032-
if let Some(adt_def) =
1033-
search_for_adt_without_structural_match(self.tcx, cv.ty, id, span)
1034-
{
1035-
let path = self.tcx.def_path_str(adt_def.did);
1036-
let msg = format!(
1037-
"to use a constant of type `{}` in a pattern, \
1038-
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
1039-
path,
1040-
path,
1041-
);
1018+
// Don't bother wtih remaining checks if the type is `PartialEq` and the lint is off.
1019+
if ty_is_partial_eq && !include_lint_checks {
1020+
return CheckConstForStructuralPattern::Ok;
1021+
}
10421022

1043-
if !ty_is_partial_eq {
1044-
// span_fatal avoids ICE from resolution of non-existent method (rare case).
1045-
self.tcx.sess.span_fatal(span, &msg);
1046-
} else {
1047-
self.tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg);
1023+
// If we were able to successfully convert the const to some pat,
1024+
// double-check that all ADT types in the const implement
1025+
// `Structural`.
1026+
check_const_is_okay_for_structural_pattern(tcx, param_env, infcx, cv, id, span)
1027+
});
1028+
1029+
match check {
1030+
// For all three of these cases, we should return the pattern
1031+
// structure created from the constant (because we may need to go
1032+
// through with code generation for it).
1033+
CheckConstForStructuralPattern::Ok |
1034+
CheckConstForStructuralPattern::ReportedRecoverableLint |
1035+
CheckConstForStructuralPattern::UnreportedNonpartialEq => {
1036+
// FIXME: we should probably start reporting the currently
1037+
// unreported case, either here or in the
1038+
// `check_const_is_okay_for_structural_pattern` code.
1039+
inlined_const_as_pat
10481040
}
1049-
} else if !ty_is_partial_eq {
1050-
// if all ADTs in the const were structurally matchable, then we
1051-
// really should have had a type that implements `PartialEq`. But
1052-
// cases like rust-lang/rust#61188 show cases where this did not
1053-
// hold, because the structural_match analysis will not necessariy
1054-
// observe the type parameters, while deriving `PartialEq` always
1055-
// requires the parameters to themselves implement `PartialEq`.
1056-
//
1057-
// So: Just report a hard error in this case.
1058-
let msg = format!(
1059-
"to use a constant of type `{}` in a pattern, \
1060-
all of its types must be annotated with `#[derive(PartialEq, Eq)]`",
1061-
cv.ty,
1062-
);
10631041

1064-
// span_fatal avoids ICE from resolution of non-existent method (rare case).
1065-
self.tcx.sess.span_fatal(span, &msg);
1042+
// For *this* case, trying to codegen the pattern structure from the
1043+
// constant is almost certainly going to ICE. Luckily, we already
1044+
// reported an error (or will report a delayed one in the future),
1045+
// so we do not have to worry about erroneous code generation; so
1046+
// just return a wild pattern instead.
1047+
CheckConstForStructuralPattern::ReportedNonrecoverableError =>
1048+
Pat { kind: Box::new(PatKind::Wild), ..inlined_const_as_pat },
10661049
}
1067-
1068-
inlined_const_as_pat
10691050
}
10701051

10711052
/// Recursive helper for `const_to_pat`; invoke that (instead of calling this directly).
@@ -1194,6 +1175,51 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
11941175
}
11951176
}
11961177

1178+
fn is_partial_eq(tcx: TyCtxt<'tcx>,
1179+
param_env: ty::ParamEnv<'tcx>,
1180+
infcx: &InferCtxt<'a, 'tcx>,
1181+
ty: Ty<'tcx>,
1182+
id: hir::HirId,
1183+
span: Span)
1184+
-> bool
1185+
{
1186+
let partial_eq_trait_id = tcx.lang_items().eq_trait().unwrap();
1187+
let obligation: PredicateObligation<'_> =
1188+
tcx.predicate_for_trait_def(param_env,
1189+
ObligationCause::misc(span, id),
1190+
partial_eq_trait_id,
1191+
0,
1192+
ty,
1193+
&[]);
1194+
// FIXME: should this call a `predicate_must_hold` variant instead?
1195+
infcx.predicate_may_hold(&obligation)
1196+
}
1197+
1198+
#[derive(Copy, Clone)]
1199+
struct SawNonScalar<'tcx>(Ty<'tcx>);
1200+
1201+
#[derive(Copy, Clone)]
1202+
enum CheckConstForStructuralPattern {
1203+
// The check succeeded. The const followed the rules for use in a pattern
1204+
// (or at least all rules from lints are currently checking), and codegen
1205+
// should be fine.
1206+
Ok,
1207+
1208+
// Reported an error that has always been unrecoverable (i.e. causes codegen
1209+
// problems). In this case, can just produce a wild pattern and move along
1210+
// (rather than ICE'ing further down).
1211+
ReportedNonrecoverableError,
1212+
1213+
// Reported a diagnostic that rustc can recover from (e.g. the current
1214+
// requirement that `const` in patterns uses structural equality)
1215+
ReportedRecoverableLint,
1216+
1217+
// The given constant does not actually implement `PartialEq`. This
1218+
// unfortunately has been allowed in certain scenarios in stable Rust, and
1219+
// therefore we cannot currently treat it as an error.
1220+
UnreportedNonpartialEq,
1221+
}
1222+
11971223
/// This method traverses the structure of `ty`, trying to find an
11981224
/// instance of an ADT (i.e. struct or enum) that does not implement
11991225
/// the `Structural` trait.
@@ -1218,23 +1244,112 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
12181244
/// For more background on why Rust has this requirement, and issues
12191245
/// that arose when the requirement was not enforced completely, see
12201246
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
1221-
fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1222-
ty: Ty<'tcx>,
1223-
id: hir::HirId,
1224-
span: Span)
1225-
-> Option<&'tcx AdtDef>
1247+
fn check_const_is_okay_for_structural_pattern(
1248+
tcx: TyCtxt<'tcx>,
1249+
param_env: ty::ParamEnv<'tcx>,
1250+
infcx: InferCtxt<'a, 'tcx>,
1251+
cv: &'tcx ty::Const<'tcx>,
1252+
id: hir::HirId,
1253+
span: Span)
1254+
-> CheckConstForStructuralPattern
12261255
{
12271256
// Import here (not mod level), because `TypeFoldable::fold_with`
12281257
// conflicts with `PatternFoldable::fold_with`
12291258
use crate::rustc::ty::fold::TypeVisitor;
12301259
use crate::rustc::ty::TypeFoldable;
12311260

1232-
let mut search = Search { tcx, id, span, found: None, seen: FxHashSet::default() };
1233-
ty.visit_with(&mut search);
1234-
return search.found;
1261+
let ty_is_partial_eq: bool = is_partial_eq(tcx, param_env, &infcx, cv.ty, id, span);
1262+
1263+
let mut search = Search {
1264+
tcx, param_env, infcx, id, span,
1265+
found: None,
1266+
seen: FxHashSet::default(),
1267+
saw_non_scalar: None,
1268+
};
1269+
1270+
// FIXME (#62614): instead of this traversal of the type, we should probably
1271+
// traverse the `const` definition and query (solely) the types that occur
1272+
// in the definition itself.
1273+
cv.ty.visit_with(&mut search);
1274+
1275+
let check_result = if let Some(adt_def) = search.found {
1276+
let path = tcx.def_path_str(adt_def.did);
1277+
let msg = format!(
1278+
"to use a constant of type `{}` in a pattern, \
1279+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
1280+
path,
1281+
path,
1282+
);
1283+
1284+
if !search.is_partial_eq(cv.ty) {
1285+
// span_fatal avoids ICE from resolution of non-existent method (rare case).
1286+
tcx.sess.span_fatal(span, &msg);
1287+
} else {
1288+
tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg);
1289+
CheckConstForStructuralPattern::ReportedRecoverableLint
1290+
}
1291+
} else if let Some(SawNonScalar(_non_scalar_ty)) = search.saw_non_scalar {
1292+
// if all ADTs in the const were structurally matchable, then we really
1293+
// should have had a type that implements `PartialEq`. But cases like
1294+
// rust-lang/rust#61188 show cases where this did not hold, because the
1295+
// structural_match analysis will not necessariy observe the type
1296+
// parameters, while deriving `PartialEq` always requires the parameters
1297+
// to themselves implement `PartialEq`.
1298+
//
1299+
// So: Just report a hard error in this case.
1300+
assert!(!ty_is_partial_eq);
1301+
1302+
// FIXME: maybe move this whole block directly to the spot where
1303+
// saw_non_scalar is set in the first place?
1304+
1305+
let cause = ObligationCause::new(span, id, ConstPatternStructural);
1306+
let mut fulfillment_cx = traits::FulfillmentContext::new();
1307+
let partial_eq_def_id = tcx.lang_items().eq_trait().unwrap();
1308+
1309+
// Note: Cannot use register_bound here, because it requires (but does
1310+
// not check) that the given trait has no type parameters apart from
1311+
// `Self`, but `PartialEq` has a type parameter that defaults to `Self`.
1312+
let trait_ref = ty::TraitRef {
1313+
def_id: partial_eq_def_id,
1314+
substs: search.infcx.tcx.mk_substs_trait(cv.ty, &[cv.ty.into()]),
1315+
};
1316+
fulfillment_cx.register_predicate_obligation(&search.infcx, traits::Obligation {
1317+
cause,
1318+
recursion_depth: 0,
1319+
param_env: search.param_env,
1320+
predicate: trait_ref.to_predicate(),
1321+
});
1322+
1323+
let err = fulfillment_cx.select_all_or_error(&search.infcx).err().unwrap();
1324+
search.infcx.report_fulfillment_errors(&err, None, false);
1325+
CheckConstForStructuralPattern::ReportedNonrecoverableError
1326+
} else {
1327+
// if all ADTs in the const were structurally matchable and all
1328+
// non-scalars implement `PartialEq`, then you would think we were
1329+
// definitely in a case where the type itself must implement
1330+
// `PartialEq` (and therefore could safely `assert!(ty_is_partial_eq)`
1331+
// here).
1332+
//
1333+
// However, exceptions to this exist (like `fn(&T)`, which is sugar
1334+
// for `for <'a> fn(&'a T)`); see rust-lang/rust#46989.
1335+
//
1336+
// For now, let compilation continue, under assumption that compiler
1337+
// will ICE if codegen is actually impossible.
1338+
1339+
if ty_is_partial_eq {
1340+
CheckConstForStructuralPattern::Ok
1341+
} else {
1342+
CheckConstForStructuralPattern::UnreportedNonpartialEq
1343+
}
1344+
};
12351345

1236-
struct Search<'tcx> {
1346+
return check_result;
1347+
1348+
struct Search<'a, 'tcx> {
12371349
tcx: TyCtxt<'tcx>,
1350+
param_env: ty::ParamEnv<'tcx>,
1351+
infcx: InferCtxt<'a, 'tcx>,
1352+
12381353
id: hir::HirId,
12391354
span: Span,
12401355

@@ -1244,9 +1359,23 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
12441359
// tracks ADT's previously encountered during search, so that
12451360
// we will not recur on them again.
12461361
seen: FxHashSet<&'tcx AdtDef>,
1362+
1363+
// records first non-PartialEq non-ADT non-scalar we encounter during
1364+
// our search for a non-structural ADT.
1365+
//
1366+
// Codegen for non-scalars dispatches to `PartialEq::eq`, which means it
1367+
// is (and has always been) a hard-error to leave `PartialEq`
1368+
// unimplemented for this case.
1369+
saw_non_scalar: Option<SawNonScalar<'tcx>>,
1370+
}
1371+
1372+
impl<'a, 'tcx> Search<'a, 'tcx> {
1373+
fn is_partial_eq(&self, ty: Ty<'tcx>) -> bool {
1374+
is_partial_eq(self.tcx, self.param_env, &self.infcx, ty, self.id, self.span)
1375+
}
12471376
}
12481377

1249-
impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> {
1378+
impl<'a, 'tcx> TypeVisitor<'tcx> for Search<'a, 'tcx> {
12501379
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
12511380
debug!("Search visiting ty: {:?}", ty);
12521381

@@ -1272,7 +1401,14 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
12721401
return false;
12731402
}
12741403
_ => {
1404+
if self.saw_non_scalar.is_none() && !ty.is_scalar() {
1405+
if !self.is_partial_eq(ty) {
1406+
self.saw_non_scalar = Some(SawNonScalar(ty));
1407+
}
1408+
}
1409+
12751410
ty.super_visit_with(self);
1411+
12761412
return false;
12771413
}
12781414
};
@@ -1283,24 +1419,23 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
12831419
return false;
12841420
}
12851421

1286-
let non_structural = self.tcx.infer_ctxt().enter(|infcx| {
1422+
{
12871423
let cause = ObligationCause::new(self.span, self.id, ConstPatternStructural);
12881424
let mut fulfillment_cx = traits::FulfillmentContext::new();
12891425
let structural_def_id = self.tcx.lang_items().structural_trait().unwrap();
12901426
fulfillment_cx.register_bound(
1291-
&infcx, ty::ParamEnv::empty(), ty, structural_def_id, cause);
1292-
if let Err(err) = fulfillment_cx.select_all_or_error(&infcx) {
1293-
infcx.report_fulfillment_errors(&err, None, false);
1294-
true
1295-
} else {
1296-
false
1427+
&self.infcx, self.param_env, ty, structural_def_id, cause);
1428+
1429+
// We deliberately do not report fulfillment errors related to
1430+
// this check, becase we want the diagnostics to be controlled
1431+
// by a future-compatibility lint. (Also current implementation
1432+
// is conservative and would flag too many false positives; see
1433+
// e.g. rust-lang/rust#62614.)
1434+
if fulfillment_cx.select_all_or_error(&self.infcx).is_err() {
1435+
debug!("Search found ty: {:?}", ty);
1436+
self.found = Some(&adt_def);
1437+
return true // Halt visiting!
12971438
}
1298-
});
1299-
1300-
if non_structural {
1301-
debug!("Search found ty: {:?}", ty);
1302-
self.found = Some(&adt_def);
1303-
return true // Halt visiting!
13041439
}
13051440

13061441
self.seen.insert(adt_def);
@@ -1315,12 +1450,13 @@ fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
13151450
// want to skip substs when only uses of generic are
13161451
// behind unsafe pointers `*const T`/`*mut T`.)
13171452

1318-
// even though we skip super_visit_with, we must recur on
1319-
// fields of ADT.
1453+
// even though we skip super_visit_with, we must recur on fields of
1454+
// ADT (at least while we traversing type structure rather than
1455+
// const definition).
13201456
let tcx = self.tcx;
13211457
for field_ty in adt_def.all_fields().map(|field| field.ty(tcx, substs)) {
13221458
if field_ty.visit_with(self) {
1323-
// found an ADT without `#[structural_match]`; halt visiting!
1459+
// found a non-structural ADT; halt visiting!
13241460
assert!(self.found.is_some());
13251461
return true;
13261462
}

0 commit comments

Comments
 (0)