Skip to content

Commit e4d3e40

Browse files
committed
HIR-based (rather than type-based) structural-match checking of consts occurring in patterns.
Incorporated review feedback: moved the HIR traversal to `rustc_mir::hair::structural_match`.
1 parent 7b482cd commit e4d3e40

File tree

8 files changed

+380
-109
lines changed

8 files changed

+380
-109
lines changed

src/librustc/ty/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ pub use self::context::{
8585

8686
pub use self::instance::{Instance, InstanceDef};
8787

88-
pub use self::structural_match::search_for_structural_match_violation;
88+
pub use self::structural_match::search_type_for_structural_match_violation;
8989
pub use self::structural_match::type_marked_structural;
90+
pub use self::structural_match::register_structural_match_bounds;
91+
pub use self::structural_match::report_structural_match_violation;
9092
pub use self::structural_match::NonStructuralMatchTy;
9193

9294
pub use self::trait_def::TraitDef;

src/librustc/ty/structural_match.rs

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::hir;
22
use rustc::infer::InferCtxt;
33
use rustc::traits::{self, ConstPatternStructural, TraitEngine};
44
use rustc::traits::ObligationCause;
5+
use rustc::lint;
56

67
use rustc_data_structures::fx::{FxHashSet};
78

@@ -16,6 +17,30 @@ pub enum NonStructuralMatchTy<'tcx> {
1617
Param,
1718
}
1819

20+
pub fn report_structural_match_violation(tcx: TyCtxt<'tcx>,
21+
non_sm_ty: NonStructuralMatchTy<'tcx>,
22+
id: hir::HirId,
23+
span: Span,
24+
warn_instead_of_hard_error: bool) {
25+
let adt_def = match non_sm_ty {
26+
ty::NonStructuralMatchTy::Adt(adt_def) => adt_def,
27+
ty::NonStructuralMatchTy::Param =>
28+
bug!("use of constant whose type is a parameter inside a pattern"),
29+
};
30+
let path = tcx.def_path_str(adt_def.did);
31+
let msg = format!("to use a constant of type `{}` in a pattern, \
32+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
33+
path, path);
34+
35+
36+
if warn_instead_of_hard_error {
37+
tcx.lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, &msg);
38+
} else {
39+
// span_fatal avoids ICE from resolution of non-existent method (rare case).
40+
tcx.sess.span_fatal(span, &msg);
41+
}
42+
}
43+
1944
/// This method traverses the structure of `ty`, trying to find an
2045
/// instance of an ADT (i.e. struct or enum) that was declared without
2146
/// the `#[structural_match]` attribute, or a generic type parameter
@@ -41,20 +66,39 @@ pub enum NonStructuralMatchTy<'tcx> {
4166
/// For more background on why Rust has this requirement, and issues
4267
/// that arose when the requirement was not enforced completely, see
4368
/// Rust RFC 1445, rust-lang/rust#61188, and rust-lang/rust#62307.
44-
pub fn search_for_structural_match_violation<'tcx>(
69+
pub fn search_type_for_structural_match_violation<'tcx>(
4570
id: hir::HirId,
4671
span: Span,
4772
tcx: TyCtxt<'tcx>,
4873
ty: Ty<'tcx>,
4974
) -> Option<NonStructuralMatchTy<'tcx>> {
50-
// FIXME: we should instead pass in an `infcx` from the outside.
75+
// FIXME: consider passing in an `infcx` from the outside.
5176
tcx.infer_ctxt().enter(|infcx| {
52-
let mut search = Search { id, span, infcx, found: None, seen: FxHashSet::default() };
77+
let mut search = SearchTy { id, span, infcx, found: None, seen: FxHashSet::default() };
5378
ty.visit_with(&mut search);
5479
search.found
5580
})
5681
}
5782

83+
pub fn register_structural_match_bounds(fulfillment_cx: &mut traits::FulfillmentContext<'tcx>,
84+
id: hir::HirId,
85+
span: Span,
86+
infcx: &InferCtxt<'_, 'tcx>,
87+
adt_ty: Ty<'tcx>)
88+
{
89+
let cause = ObligationCause::new(span, id, ConstPatternStructural);
90+
// require `#[derive(PartialEq)]`
91+
let structural_peq_def_id = infcx.tcx.lang_items().structural_peq_trait().unwrap();
92+
fulfillment_cx.register_bound(
93+
infcx, ty::ParamEnv::empty(), adt_ty, structural_peq_def_id, cause);
94+
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
95+
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
96+
let cause = ObligationCause::new(span, id, ConstPatternStructural);
97+
let structural_teq_def_id = infcx.tcx.lang_items().structural_teq_trait().unwrap();
98+
fulfillment_cx.register_bound(
99+
infcx, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id, cause);
100+
}
101+
58102
/// This method returns true if and only if `adt_ty` itself has been marked as
59103
/// eligible for structural-match: namely, if it implements both
60104
/// `StructuralPartialEq` and `StructuralEq` (which are respectively injected by
@@ -69,17 +113,8 @@ pub fn type_marked_structural(id: hir::HirId,
69113
-> bool
70114
{
71115
let mut fulfillment_cx = traits::FulfillmentContext::new();
72-
let cause = ObligationCause::new(span, id, ConstPatternStructural);
73-
// require `#[derive(PartialEq)]`
74-
let structural_peq_def_id = infcx.tcx.lang_items().structural_peq_trait().unwrap();
75-
fulfillment_cx.register_bound(
76-
infcx, ty::ParamEnv::empty(), adt_ty, structural_peq_def_id, cause);
77-
// for now, require `#[derive(Eq)]`. (Doing so is a hack to work around
78-
// the type `for<'a> fn(&'a ())` failing to implement `Eq` itself.)
79-
let cause = ObligationCause::new(span, id, ConstPatternStructural);
80-
let structural_teq_def_id = infcx.tcx.lang_items().structural_teq_trait().unwrap();
81-
fulfillment_cx.register_bound(
82-
infcx, ty::ParamEnv::empty(), adt_ty, structural_teq_def_id, cause);
116+
117+
register_structural_match_bounds(&mut fulfillment_cx, id, span, infcx, adt_ty);
83118

84119
// We deliberately skip *reporting* fulfillment errors (via
85120
// `report_fulfillment_errors`), for two reasons:
@@ -96,7 +131,7 @@ pub fn type_marked_structural(id: hir::HirId,
96131
/// This implements the traversal over the structure of a given type to try to
97132
/// find instances of ADTs (specifically structs or enums) that do not implement
98133
/// the structural-match traits (`StructuralPartialEq` and `StructuralEq`).
99-
struct Search<'a, 'tcx> {
134+
struct SearchTy<'a, 'tcx> {
100135
id: hir::HirId,
101136
span: Span,
102137

@@ -110,7 +145,7 @@ struct Search<'a, 'tcx> {
110145
seen: FxHashSet<hir::def_id::DefId>,
111146
}
112147

113-
impl Search<'a, 'tcx> {
148+
impl SearchTy<'a, 'tcx> {
114149
fn tcx(&self) -> TyCtxt<'tcx> {
115150
self.infcx.tcx
116151
}
@@ -120,9 +155,9 @@ impl Search<'a, 'tcx> {
120155
}
121156
}
122157

123-
impl<'a, 'tcx> TypeVisitor<'tcx> for Search<'a, 'tcx> {
158+
impl<'a, 'tcx> TypeVisitor<'tcx> for SearchTy<'a, 'tcx> {
124159
fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
125-
debug!("Search visiting ty: {:?}", ty);
160+
debug!("SearchTy visiting ty: {:?}", ty);
126161

127162
let (adt_def, substs) = match ty.kind {
128163
ty::Adt(adt_def, substs) => (adt_def, substs),
@@ -170,13 +205,13 @@ impl<'a, 'tcx> TypeVisitor<'tcx> for Search<'a, 'tcx> {
170205
};
171206

172207
if !self.seen.insert(adt_def.did) {
173-
debug!("Search already seen adt_def: {:?}", adt_def);
208+
debug!("SearchTy already seen adt_def: {:?}", adt_def);
174209
// let caller continue its search
175210
return false;
176211
}
177212

178213
if !self.type_marked_structural(ty) {
179-
debug!("Search found ty: {:?}", ty);
214+
debug!("SearchTy found ty: {:?}", ty);
180215
self.found = Some(NonStructuralMatchTy::Adt(&adt_def));
181216
return true; // Halt visiting!
182217
}

src/librustc_mir/hair/pattern/const_to_pat.rs

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::const_eval::const_variant_index;
22

33
use rustc::hir;
4-
use rustc::lint;
4+
use rustc::hir::def_id::DefId;
55
use rustc::mir::Field;
66
use rustc::infer::InferCtxt;
77
use rustc::traits::{ObligationCause, PredicateObligation};
@@ -15,23 +15,28 @@ use syntax_pos::Span;
1515
use std::cell::Cell;
1616

1717
use super::{FieldPat, Pat, PatCtxt, PatKind};
18+
use super::structural_match::search_const_rhs_for_structural_match_violation;
1819

1920
impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
2021
/// Converts an evaluated constant to a pattern (if possible).
2122
/// This means aggregate values (like structs and enums) are converted
2223
/// to a pattern that matches the value (as if you'd compared via structural equality).
24+
///
25+
/// For literals, pass `None` as the `opt_const_def_id`; for a const
26+
/// identifier, pass its `DefId`.
2327
pub(super) fn const_to_pat(
2428
&self,
2529
cv: &'tcx ty::Const<'tcx>,
30+
opt_const_def_id: Option<DefId>,
2631
id: hir::HirId,
2732
span: Span,
2833
) -> Pat<'tcx> {
29-
debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
30-
debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
34+
debug!("const_def_to_pat: cv={:#?} const_def_id: {:?} id={:?}", cv, opt_const_def_id, id);
35+
debug!("const_def_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
3136

3237
self.tcx.infer_ctxt().enter(|infcx| {
3338
let mut convert = ConstToPat::new(self, id, span, infcx);
34-
convert.to_pat(cv)
39+
convert.to_pat(cv, opt_const_def_id)
3540
})
3641
}
3742
}
@@ -67,85 +72,77 @@ impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
6772

6873
fn tcx(&self) -> TyCtxt<'tcx> { self.infcx.tcx }
6974

70-
fn search_for_structural_match_violation(&self,
71-
ty: Ty<'tcx>)
72-
-> Option<ty::NonStructuralMatchTy<'tcx>>
75+
fn search_const_def_for_structural_match_violation(&self, const_def_id: DefId)
7376
{
74-
ty::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty)
77+
assert!(const_def_id.is_local());
78+
self.tcx().infer_ctxt().enter(|infcx| {
79+
search_const_rhs_for_structural_match_violation(
80+
&infcx, self.param_env, const_def_id, self.id, self.span);
81+
});
82+
}
83+
84+
fn search_ty_for_structural_match_violation(&self, ty: Ty<'tcx>)
85+
{
86+
let structural = ty::search_type_for_structural_match_violation(
87+
self.id, self.span, self.tcx(), ty);
88+
debug!("search_ty_for_structural_match_violation ty: {:?} returned: {:?}", ty, structural);
89+
if let Some(non_sm_ty) = structural {
90+
91+
// double-check there even *is* a semantic `PartialEq` to dispatch to.
92+
//
93+
// (If there isn't, then we can safely issue a hard
94+
// error, because that's never worked, due to compiler
95+
// using `PartialEq::eq` in this scenario in the past.)
96+
//
97+
// Note: To fix rust-lang/rust#65466, one could lift this check
98+
// *before* any structural-match checking, and unconditionally error
99+
// if `PartialEq` is not implemented. However, that breaks stable
100+
// code at the moment, because types like `for <'a> fn(&'a ())` do
101+
// not *yet* implement `PartialEq`. So for now we leave this here.
102+
let warn_instead_of_hard_error: bool = {
103+
let partial_eq_trait_id = self.tcx().lang_items().eq_trait().unwrap();
104+
let obligation: PredicateObligation<'_> =
105+
self.tcx().predicate_for_trait_def(
106+
self.param_env,
107+
ObligationCause::misc(self.span, self.id),
108+
partial_eq_trait_id,
109+
0,
110+
ty,
111+
&[]);
112+
// FIXME: should this call a `predicate_must_hold` variant instead?
113+
self.infcx.predicate_may_hold(&obligation)
114+
};
115+
116+
debug!("call report_structural_match_violation non_sm_ty: {:?} id: {:?} warn: {:?}",
117+
non_sm_ty, self.id, warn_instead_of_hard_error);
118+
ty::report_structural_match_violation(
119+
self.tcx(), non_sm_ty, self.id, self.span, warn_instead_of_hard_error);
120+
}
75121
}
76122

77123
fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
78124
ty::type_marked_structural(self.id, self.span, &self.infcx, ty)
79125
}
80126

81-
fn to_pat(&mut self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
82-
// This method is just a wrapper handling a validity check; the heavy lifting is
83-
// performed by the recursive `recur` method, which is not meant to be
84-
// invoked except by this method.
85-
//
86-
// once indirect_structural_match is a full fledged error, this
87-
// level of indirection can be eliminated
88-
127+
fn to_pat(&mut self,
128+
cv: &'tcx ty::Const<'tcx>,
129+
opt_const_def_id: Option<DefId>)
130+
-> Pat<'tcx>
131+
{
89132
let inlined_const_as_pat = self.recur(cv);
90133

91134
if self.include_lint_checks && !self.saw_const_match_error.get() {
92135
// If we were able to successfully convert the const to some pat,
93136
// double-check that all types in the const implement `Structural`.
94-
95-
let structural = self.search_for_structural_match_violation(cv.ty);
96-
debug!("search_for_structural_match_violation cv.ty: {:?} returned: {:?}",
97-
cv.ty, structural);
98-
if let Some(non_sm_ty) = structural {
99-
let adt_def = match non_sm_ty {
100-
ty::NonStructuralMatchTy::Adt(adt_def) => adt_def,
101-
ty::NonStructuralMatchTy::Param =>
102-
bug!("use of constant whose type is a parameter inside a pattern"),
103-
};
104-
let path = self.tcx().def_path_str(adt_def.did);
105-
let msg = format!(
106-
"to use a constant of type `{}` in a pattern, \
107-
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
108-
path,
109-
path,
110-
);
111-
112-
// double-check there even *is* a semantic `PartialEq` to dispatch to.
113-
//
114-
// (If there isn't, then we can safely issue a hard
115-
// error, because that's never worked, due to compiler
116-
// using `PartialEq::eq` in this scenario in the past.)
117-
//
118-
// Note: To fix rust-lang/rust#65466, one could lift this check
119-
// *before* any structural-match checking, and unconditionally error
120-
// if `PartialEq` is not implemented. However, that breaks stable
121-
// code at the moment, because types like `for <'a> fn(&'a ())` do
122-
// not *yet* implement `PartialEq`. So for now we leave this here.
123-
let ty_is_partial_eq: bool = {
124-
let partial_eq_trait_id = self.tcx().lang_items().eq_trait().unwrap();
125-
let obligation: PredicateObligation<'_> =
126-
self.tcx().predicate_for_trait_def(
127-
self.param_env,
128-
ObligationCause::misc(self.span, self.id),
129-
partial_eq_trait_id,
130-
0,
131-
cv.ty,
132-
&[]);
133-
// FIXME: should this call a `predicate_must_hold` variant instead?
134-
self.infcx.predicate_may_hold(&obligation)
135-
};
136-
137-
if !ty_is_partial_eq {
138-
// span_fatal avoids ICE from resolution of non-existent method (rare case).
139-
self.tcx().sess.span_fatal(self.span, &msg);
140-
} else {
141-
self.tcx().lint_hir(lint::builtin::INDIRECT_STRUCTURAL_MATCH,
142-
self.id,
143-
self.span,
144-
&msg);
137+
match opt_const_def_id {
138+
Some(const_def_id) if const_def_id.is_local() => {
139+
self.search_const_def_for_structural_match_violation(const_def_id);
140+
}
141+
_ => {
142+
self.search_ty_for_structural_match_violation(cv.ty);
145143
}
146144
}
147145
}
148-
149146
inlined_const_as_pat
150147
}
151148

src/librustc_mir/hair/pattern/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
mod _match;
44
mod check_match;
55
mod const_to_pat;
6+
mod structural_match;
67

78
pub(crate) use self::check_match::check_match;
89

@@ -867,7 +868,8 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
867868
};
868869
match self.tcx.at(span).const_eval(self.param_env.and(cid)) {
869870
Ok(value) => {
870-
let pattern = self.const_to_pat(value, id, span);
871+
let const_def_id = Some(instance.def_id());
872+
let pattern = self.const_to_pat(value, const_def_id, id, span);
871873
if !is_associated_const {
872874
return pattern;
873875
}
@@ -934,7 +936,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
934936
let ty = self.tables.expr_ty(expr);
935937
match lit_to_const(&lit.node, self.tcx, ty, false) {
936938
Ok(val) => {
937-
*self.const_to_pat(val, expr.hir_id, lit.span).kind
939+
*self.const_to_pat(val, None, expr.hir_id, lit.span).kind
938940
},
939941
Err(LitToConstError::UnparseableFloat) => {
940942
self.errors.push(PatternError::FloatBug);
@@ -952,7 +954,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
952954
};
953955
match lit_to_const(&lit.node, self.tcx, ty, true) {
954956
Ok(val) => {
955-
*self.const_to_pat(val, expr.hir_id, lit.span).kind
957+
*self.const_to_pat(val, None, expr.hir_id, lit.span).kind
956958
},
957959
Err(LitToConstError::UnparseableFloat) => {
958960
self.errors.push(PatternError::FloatBug);

0 commit comments

Comments
 (0)