Skip to content

Commit c6a9e76

Browse files
committed
Auto merge of #62339 - pnkfelix:issue-61188-use-visitor-for-structural-match-check, r=nikomatsakis
use visitor for #[structural_match] check This changes the code so that we recur down the structure of a type of a const (rather than just inspecting at a shallow one or two levels) when we are looking to see if it has an ADT that did not derive `PartialEq` and `Eq`. Fix #61188 Fix #62307 Cc #62336
2 parents d4e1565 + b0b64dd commit c6a9e76

30 files changed

+686
-6
lines changed

src/librustc/lint/builtin.rs

+6
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ declare_lint! {
348348
"outlives requirements can be inferred"
349349
}
350350

351+
declare_lint! {
352+
pub INDIRECT_STRUCTURAL_MATCH,
353+
Warn,
354+
"pattern with const indirectly referencing non-`#[structural_match]` type"
355+
}
356+
351357
/// Some lints that are buffered from `libsyntax`. See `syntax::early_buffered_lints`.
352358
pub mod parser {
353359
declare_lint! {

src/librustc/middle/lang_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ language_item_table! {
326326
UnpinTraitLangItem, "unpin", unpin_trait, Target::Trait;
327327
PinTypeLangItem, "pin", pin_type, Target::Struct;
328328

329+
// Don't be fooled by the naming here: this lang item denotes `PartialEq`, not `Eq`.
329330
EqTraitLangItem, "eq", eq_trait, Target::Trait;
330331
PartialOrdTraitLangItem, "partial_ord", partial_ord_trait, Target::Trait;
331332
OrdTraitLangItem, "ord", ord_trait, Target::Trait;

src/librustc_lint/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,11 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
429429
id: LintId::of(MUTABLE_BORROW_RESERVATION_CONFLICT),
430430
reference: "issue #59159 <https://github.com/rust-lang/rust/issues/59159>",
431431
edition: None,
432+
},
433+
FutureIncompatibleInfo {
434+
id: LintId::of(INDIRECT_STRUCTURAL_MATCH),
435+
reference: "issue #62411 <https://github.com/rust-lang/rust/issues/62411>",
436+
edition: None,
432437
}
433438
]);
434439

src/librustc_mir/hair/pattern/check_match.rs

+2
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
170170
let mut patcx = PatternContext::new(self.tcx,
171171
self.param_env.and(self.identity_substs),
172172
self.tables);
173+
patcx.include_lint_checks();
173174
let pattern = expand_pattern(cx, patcx.lower_pattern(&pat));
174175
if !patcx.errors.is_empty() {
175176
patcx.report_inlining_errors(pat.span);
@@ -266,6 +267,7 @@ impl<'a, 'tcx> MatchVisitor<'a, 'tcx> {
266267
let mut patcx = PatternContext::new(self.tcx,
267268
self.param_env.and(self.identity_substs),
268269
self.tables);
270+
patcx.include_lint_checks();
269271
let pattern = patcx.lower_pattern(pat);
270272
let pattern_ty = pattern.ty;
271273
let pats: Matrix<'_, '_> = vec![smallvec![

src/librustc_mir/hair/pattern/mod.rs

+207-6
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ use crate::const_eval::const_variant_index;
1010
use crate::hair::util::UserAnnotatedTyHelpers;
1111
use crate::hair::constant::*;
1212

13+
use rustc::lint;
1314
use rustc::mir::{Field, BorrowKind, Mutability};
1415
use rustc::mir::{UserTypeProjection};
1516
use rustc::mir::interpret::{GlobalId, ConstValue, sign_extend, AllocId, Pointer};
17+
use rustc::traits::{ObligationCause, PredicateObligation};
1618
use rustc::ty::{self, Region, TyCtxt, AdtDef, Ty, UserType, DefIdTree};
1719
use rustc::ty::{CanonicalUserType, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations};
1820
use rustc::ty::subst::{SubstsRef, Kind};
@@ -23,6 +25,7 @@ use rustc::hir::pat_util::EnumerateAndAdjustIterator;
2325
use rustc::hir::ptr::P;
2426

2527
use rustc_data_structures::indexed_vec::Idx;
28+
use rustc_data_structures::fx::FxHashSet;
2629

2730
use std::cmp::Ordering;
2831
use std::fmt;
@@ -332,6 +335,7 @@ pub struct PatternContext<'a, 'tcx> {
332335
pub tables: &'a ty::TypeckTables<'tcx>,
333336
pub substs: SubstsRef<'tcx>,
334337
pub errors: Vec<PatternError>,
338+
include_lint_checks: bool,
335339
}
336340

337341
impl<'a, 'tcx> Pattern<'tcx> {
@@ -363,10 +367,16 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
363367
param_env: param_env_and_substs.param_env,
364368
tables,
365369
substs: param_env_and_substs.value,
366-
errors: vec![]
370+
errors: vec![],
371+
include_lint_checks: false,
367372
}
368373
}
369374

375+
pub fn include_lint_checks(&mut self) -> &mut Self {
376+
self.include_lint_checks = true;
377+
self
378+
}
379+
370380
pub fn lower_pattern(&mut self, pat: &'tcx hir::Pat) -> Pattern<'tcx> {
371381
// When implicit dereferences have been inserted in this pattern, the unadjusted lowered
372382
// pattern has the type that results *after* dereferencing. For example, in this code:
@@ -942,23 +952,94 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
942952

943953
/// Converts an evaluated constant to a pattern (if possible).
944954
/// This means aggregate values (like structs and enums) are converted
945-
/// to a pattern that matches the value (as if you'd compared via equality).
955+
/// to a pattern that matches the value (as if you'd compared via structural equality).
946956
fn const_to_pat(
947957
&self,
948958
instance: ty::Instance<'tcx>,
949959
cv: &'tcx ty::Const<'tcx>,
950960
id: hir::HirId,
951961
span: Span,
952962
) -> Pattern<'tcx> {
963+
// This method is just a warpper handling a validity check; the heavy lifting is
964+
// performed by the recursive const_to_pat_inner method, which is not meant to be
965+
// invoked except by this method.
966+
//
967+
// once indirect_structural_match is a full fledged error, this
968+
// level of indirection can be eliminated
969+
953970
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
954-
let adt_subpattern = |i, variant_opt| {
971+
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
972+
973+
let mut saw_error = false;
974+
let inlined_const_as_pat = self.const_to_pat_inner(instance, cv, id, span, &mut saw_error);
975+
976+
if self.include_lint_checks && !saw_error {
977+
// If we were able to successfully convert the const to some pat, double-check
978+
// that the type of the const obeys `#[structural_match]` constraint.
979+
if let Some(adt_def) = search_for_adt_without_structural_match(self.tcx, cv.ty) {
980+
981+
let path = self.tcx.def_path_str(adt_def.did);
982+
let msg = format!(
983+
"to use a constant of type `{}` in a pattern, \
984+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
985+
path,
986+
path,
987+
);
988+
989+
// before issuing lint, double-check there even *is* a
990+
// semantic PartialEq for us to dispatch to.
991+
//
992+
// (If there isn't, then we can safely issue a hard
993+
// error, because that's never worked, due to compiler
994+
// using PartialEq::eq in this scenario in the past.)
995+
996+
let ty_is_partial_eq: bool = {
997+
let partial_eq_trait_id = self.tcx.lang_items().eq_trait().unwrap();
998+
let obligation: PredicateObligation<'_> =
999+
self.tcx.predicate_for_trait_def(self.param_env,
1000+
ObligationCause::misc(span, id),
1001+
partial_eq_trait_id,
1002+
0,
1003+
cv.ty,
1004+
&[]);
1005+
self.tcx
1006+
.infer_ctxt()
1007+
.enter(|infcx| infcx.predicate_may_hold(&obligation))
1008+
};
1009+
1010+
if !ty_is_partial_eq {
1011+
// span_fatal avoids ICE from resolution of non-existent method (rare case).
1012+
self.tcx.sess.span_fatal(span, &msg);
1013+
} else {
1014+
self.tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg);
1015+
}
1016+
}
1017+
}
1018+
1019+
inlined_const_as_pat
1020+
}
1021+
1022+
/// Recursive helper for `const_to_pat`; invoke that (instead of calling this directly).
1023+
fn const_to_pat_inner(
1024+
&self,
1025+
instance: ty::Instance<'tcx>,
1026+
cv: &'tcx ty::Const<'tcx>,
1027+
id: hir::HirId,
1028+
span: Span,
1029+
// This tracks if we signal some hard error for a given const
1030+
// value, so that we will not subsequently issue an irrelevant
1031+
// lint for the same const value.
1032+
saw_const_match_error: &mut bool,
1033+
) -> Pattern<'tcx> {
1034+
1035+
let mut adt_subpattern = |i, variant_opt| {
9551036
let field = Field::new(i);
9561037
let val = crate::const_eval::const_field(
9571038
self.tcx, self.param_env, variant_opt, field, cv
9581039
);
959-
self.const_to_pat(instance, val, id, span)
1040+
self.const_to_pat_inner(instance, val, id, span, saw_const_match_error)
9601041
};
961-
let adt_subpatterns = |n, variant_opt| {
1042+
let mut adt_subpatterns = |n, variant_opt| {
9621043
(0..n).map(|i| {
9631044
let field = Field::new(i);
9641045
FieldPattern {
@@ -967,7 +1048,8 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
9671048
}
9681049
}).collect::<Vec<_>>()
9691050
};
970-
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
1051+
1052+
9711053
let kind = match cv.ty.sty {
9721054
ty::Float(_) => {
9731055
self.tcx.lint_hir(
@@ -982,9 +1064,11 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
9821064
}
9831065
ty::Adt(adt_def, _) if adt_def.is_union() => {
9841066
// Matching on union fields is unsafe, we can't hide it in constants
1067+
*saw_const_match_error = true;
9851068
self.tcx.sess.span_err(span, "cannot use unions in constant patterns");
9861069
PatternKind::Wild
9871070
}
1071+
// keep old code until future-compat upgraded to errors.
9881072
ty::Adt(adt_def, _) if !self.tcx.has_attr(adt_def.did, sym::structural_match) => {
9891073
let path = self.tcx.def_path_str(adt_def.did);
9901074
let msg = format!(
@@ -993,9 +1077,11 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
9931077
path,
9941078
path,
9951079
);
1080+
*saw_const_match_error = true;
9961081
self.tcx.sess.span_err(span, &msg);
9971082
PatternKind::Wild
9981083
}
1084+
// keep old code until future-compat upgraded to errors.
9991085
ty::Ref(_, ty::TyS { sty: ty::Adt(adt_def, _), .. }, _)
10001086
if !self.tcx.has_attr(adt_def.did, sym::structural_match) => {
10011087
// HACK(estebank): Side-step ICE #53708, but anything other than erroring here
@@ -1007,6 +1093,7 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
10071093
path,
10081094
path,
10091095
);
1096+
*saw_const_match_error = true;
10101097
self.tcx.sess.span_err(span, &msg);
10111098
PatternKind::Wild
10121099
}
@@ -1058,6 +1145,120 @@ impl<'a, 'tcx> PatternContext<'a, 'tcx> {
10581145
}
10591146
}
10601147

1148+
/// This method traverses the structure of `ty`, trying to find an
1149+
/// instance of an ADT (i.e. struct or enum) that was declared without
1150+
/// the `#[structural_match]` attribute.
1151+
///
1152+
/// The "structure of a type" includes all components that would be
1153+
/// considered when doing a pattern match on a constant of that
1154+
/// type.
1155+
///
1156+
/// * This means this method descends into fields of structs/enums,
1157+
/// and also descends into the inner type `T` of `&T` and `&mut T`
1158+
///
1159+
/// * The traversal doesn't dereference unsafe pointers (`*const T`,
1160+
/// `*mut T`), and it does not visit the type arguments of an
1161+
/// instantiated generic like `PhantomData<T>`.
1162+
///
1163+
/// The reason we do this search is Rust currently require all ADT's
1164+
/// reachable from a constant's type to be annotated with
1165+
/// `#[structural_match]`, an attribute which essentially says that
1166+
/// the implementation of `PartialEq::eq` behaves *equivalently* to a
1167+
/// comparison against the unfolded structure.
1168+
///
1169+
/// For more background on why Rust has this requirement, and issues
1170+
/// that arose when the requirement was not enforced completely, see
1171+
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
1172+
fn search_for_adt_without_structural_match<'tcx>(tcx: TyCtxt<'tcx>,
1173+
ty: Ty<'tcx>)
1174+
-> Option<&'tcx AdtDef>
1175+
{
1176+
// Import here (not mod level), because `TypeFoldable::fold_with`
1177+
// conflicts with `PatternFoldable::fold_with`
1178+
use crate::rustc::ty::fold::TypeVisitor;
1179+
use crate::rustc::ty::TypeFoldable;
1180+
1181+
let mut search = Search { tcx, found: None, seen: FxHashSet::default() };
1182+
ty.visit_with(&mut search);
1183+
return search.found;
1184+
1185+
struct Search<'tcx> {
1186+
tcx: TyCtxt<'tcx>,
1187+
1188+
// records the first ADT we find without `#[structural_match`
1189+
found: Option<&'tcx AdtDef>,
1190+
1191+
// tracks ADT's previously encountered during search, so that
1192+
// we will not recur on them again.
1193+
seen: FxHashSet<&'tcx AdtDef>,
1194+
}
1195+
1196+
impl<'tcx> TypeVisitor<'tcx> for Search<'tcx> {
1197+
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
1198+
debug!("Search visiting ty: {:?}", ty);
1199+
1200+
let (adt_def, substs) = match ty.sty {
1201+
ty::Adt(adt_def, substs) => (adt_def, substs),
1202+
ty::RawPtr(..) => {
1203+
// `#[structural_match]` ignores substructure of
1204+
// `*const _`/`*mut _`, so skip super_visit_with
1205+
1206+
// (But still tell caller to continue search.)
1207+
return false;
1208+
}
1209+
ty::Array(_, n) if n.assert_usize(self.tcx) == Some(0) => {
1210+
// rust-lang/rust#62336: ignore type of contents
1211+
// for empty array.
1212+
return false;
1213+
}
1214+
_ => {
1215+
ty.super_visit_with(self);
1216+
return false;
1217+
}
1218+
};
1219+
1220+
if !self.tcx.has_attr(adt_def.did, sym::structural_match) {
1221+
self.found = Some(&adt_def);
1222+
debug!("Search found adt_def: {:?}", adt_def);
1223+
return true // Halt visiting!
1224+
}
1225+
1226+
if self.seen.contains(adt_def) {
1227+
debug!("Search already seen adt_def: {:?}", adt_def);
1228+
// let caller continue its search
1229+
return false;
1230+
}
1231+
1232+
self.seen.insert(adt_def);
1233+
1234+
// `#[structural_match]` does not care about the
1235+
// instantiation of the generics in an ADT (it
1236+
// instead looks directly at its fields outside
1237+
// this match), so we skip super_visit_with.
1238+
//
1239+
// (Must not recur on substs for `PhantomData<T>` cf
1240+
// rust-lang/rust#55028 and rust-lang/rust#55837; but also
1241+
// want to skip substs when only uses of generic are
1242+
// behind unsafe pointers `*const T`/`*mut T`.)
1243+
1244+
// even though we skip super_visit_with, we must recur on
1245+
// fields of ADT.
1246+
let tcx = self.tcx;
1247+
for field_ty in adt_def.all_fields().map(|field| field.ty(tcx, substs)) {
1248+
if field_ty.visit_with(self) {
1249+
// found an ADT without `#[structural_match]`; halt visiting!
1250+
assert!(self.found.is_some());
1251+
return true;
1252+
}
1253+
}
1254+
1255+
// Even though we do not want to recur on substs, we do
1256+
// want our caller to continue its own search.
1257+
false
1258+
}
1259+
}
1260+
}
1261+
10611262
impl UserAnnotatedTyHelpers<'tcx> for PatternContext<'_, 'tcx> {
10621263
fn tcx(&self) -> TyCtxt<'tcx> {
10631264
self.tcx

src/test/ui/issues/issue-55511.rs

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ fn main() {
1414
//~^ ERROR `a` does not live long enough [E0597]
1515
match b {
1616
<() as Foo<'static>>::C => { }
17+
//~^ WARN must be annotated with `#[derive(PartialEq, Eq)]`
18+
//~| WARN will become a hard error in a future release
1719
_ => { }
1820
}
1921
}

src/test/ui/issues/issue-55511.stderr

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
warning: to use a constant of type `std::cell::Cell` in a pattern, `std::cell::Cell` must be annotated with `#[derive(PartialEq, Eq)]`
2+
--> $DIR/issue-55511.rs:16:9
3+
|
4+
LL | <() as Foo<'static>>::C => { }
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: #[warn(indirect_structural_match)] on by default
8+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
9+
= note: for more information, see issue #62411 <https://github.com/rust-lang/rust/issues/62411>
10+
111
error[E0597]: `a` does not live long enough
212
--> $DIR/issue-55511.rs:13:28
313
|

0 commit comments

Comments
 (0)