]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir_build/hair/pattern/const_to_pat.rs
structural_match: non-structural-match ty closures
[rust.git] / src / librustc_mir_build / hair / pattern / const_to_pat.rs
1 use rustc_hir as hir;
2 use rustc_hir::lang_items::EqTraitLangItem;
3 use rustc_index::vec::Idx;
4 use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
5 use rustc_middle::mir::Field;
6 use rustc_middle::ty::{self, Ty, TyCtxt};
7 use rustc_session::lint;
8 use rustc_span::Span;
9 use rustc_trait_selection::traits::predicate_for_trait_def;
10 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt;
11 use rustc_trait_selection::traits::{self, ObligationCause, PredicateObligation};
12
13 use std::cell::Cell;
14
15 use super::{FieldPat, Pat, PatCtxt, PatKind};
16
17 impl<'a, 'tcx> PatCtxt<'a, 'tcx> {
18     /// Converts an evaluated constant to a pattern (if possible).
19     /// This means aggregate values (like structs and enums) are converted
20     /// to a pattern that matches the value (as if you'd compared via structural equality).
21     pub(super) fn const_to_pat(
22         &self,
23         cv: &'tcx ty::Const<'tcx>,
24         id: hir::HirId,
25         span: Span,
26         mir_structural_match_violation: bool,
27     ) -> Pat<'tcx> {
28         debug!("const_to_pat: cv={:#?} id={:?}", cv, id);
29         debug!("const_to_pat: cv.ty={:?} span={:?}", cv.ty, span);
30
31         self.tcx.infer_ctxt().enter(|infcx| {
32             let mut convert = ConstToPat::new(self, id, span, infcx);
33             convert.to_pat(cv, mir_structural_match_violation)
34         })
35     }
36 }
37
38 struct ConstToPat<'a, 'tcx> {
39     id: hir::HirId,
40     span: Span,
41     param_env: ty::ParamEnv<'tcx>,
42
43     // This tracks if we signal some hard error for a given const value, so that
44     // we will not subsequently issue an irrelevant lint for the same const
45     // value.
46     saw_const_match_error: Cell<bool>,
47
48     // inference context used for checking `T: Structural` bounds.
49     infcx: InferCtxt<'a, 'tcx>,
50
51     include_lint_checks: bool,
52 }
53
54 impl<'a, 'tcx> ConstToPat<'a, 'tcx> {
55     fn new(
56         pat_ctxt: &PatCtxt<'_, 'tcx>,
57         id: hir::HirId,
58         span: Span,
59         infcx: InferCtxt<'a, 'tcx>,
60     ) -> Self {
61         ConstToPat {
62             id,
63             span,
64             infcx,
65             param_env: pat_ctxt.param_env,
66             include_lint_checks: pat_ctxt.include_lint_checks,
67             saw_const_match_error: Cell::new(false),
68         }
69     }
70
71     fn tcx(&self) -> TyCtxt<'tcx> {
72         self.infcx.tcx
73     }
74
75     fn search_for_structural_match_violation(
76         &self,
77         ty: Ty<'tcx>,
78     ) -> Option<traits::NonStructuralMatchTy<'tcx>> {
79         traits::search_for_structural_match_violation(self.id, self.span, self.tcx(), ty)
80     }
81
82     fn type_marked_structural(&self, ty: Ty<'tcx>) -> bool {
83         ty.is_structural_eq_shallow(self.infcx.tcx)
84     }
85
86     fn to_pat(
87         &mut self,
88         cv: &'tcx ty::Const<'tcx>,
89         mir_structural_match_violation: bool,
90     ) -> Pat<'tcx> {
91         // This method is just a wrapper handling a validity check; the heavy lifting is
92         // performed by the recursive `recur` method, which is not meant to be
93         // invoked except by this method.
94         //
95         // once indirect_structural_match is a full fledged error, this
96         // level of indirection can be eliminated
97
98         let inlined_const_as_pat = self.recur(cv);
99
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`.
103
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             );
109
110             if structural.is_none() && mir_structural_match_violation {
111                 bug!("MIR const-checker found novel structural match violation");
112             }
113
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, \
120                              `{}` 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::Opaque => {
128                         "opaque types cannot be used in patterns".to_string()
129                     }
130                     traits::NonStructuralMatchTy::Generator => {
131                         "generators cannot be used in patterns".to_string()
132                     }
133                     traits::NonStructuralMatchTy::Closure => {
134                         "closures cannot be used in patterns".to_string()
135                     }
136                     traits::NonStructuralMatchTy::Param => {
137                         bug!("use of a constant whose type is a parameter inside a pattern")
138                     }
139                     traits::NonStructuralMatchTy::Projection => {
140                         bug!("use of a constant whose type is a projection inside a pattern")
141                     }
142                     traits::NonStructuralMatchTy::Foreign => {
143                         bug!("use of a value of a foreign type inside a pattern")
144                     }
145                 };
146
147                 // double-check there even *is* a semantic `PartialEq` to dispatch to.
148                 //
149                 // (If there isn't, then we can safely issue a hard
150                 // error, because that's never worked, due to compiler
151                 // using `PartialEq::eq` in this scenario in the past.)
152                 //
153                 // Note: To fix rust-lang/rust#65466, one could lift this check
154                 // *before* any structural-match checking, and unconditionally error
155                 // if `PartialEq` is not implemented. However, that breaks stable
156                 // code at the moment, because types like `for <'a> fn(&'a ())` do
157                 // not *yet* implement `PartialEq`. So for now we leave this here.
158                 let ty_is_partial_eq: bool = {
159                     let partial_eq_trait_id =
160                         self.tcx().require_lang_item(EqTraitLangItem, Some(self.span));
161                     let obligation: PredicateObligation<'_> = predicate_for_trait_def(
162                         self.tcx(),
163                         self.param_env,
164                         ObligationCause::misc(self.span, self.id),
165                         partial_eq_trait_id,
166                         0,
167                         cv.ty,
168                         &[],
169                     );
170                     // FIXME: should this call a `predicate_must_hold` variant instead?
171                     self.infcx.predicate_may_hold(&obligation)
172                 };
173
174                 if !ty_is_partial_eq {
175                     // span_fatal avoids ICE from resolution of non-existent method (rare case).
176                     self.tcx().sess.span_fatal(self.span, &msg);
177                 } else if mir_structural_match_violation {
178                     self.tcx().struct_span_lint_hir(
179                         lint::builtin::INDIRECT_STRUCTURAL_MATCH,
180                         self.id,
181                         self.span,
182                         |lint| lint.build(&msg).emit(),
183                     );
184                 } else {
185                     debug!(
186                         "`search_for_structural_match_violation` found one, but `CustomEq` was \
187                           not in the qualifs for that `const`"
188                     );
189                 }
190             }
191         }
192
193         inlined_const_as_pat
194     }
195
196     // Recursive helper for `to_pat`; invoke that (instead of calling this directly).
197     fn recur(&self, cv: &'tcx ty::Const<'tcx>) -> Pat<'tcx> {
198         let id = self.id;
199         let span = self.span;
200         let tcx = self.tcx();
201         let param_env = self.param_env;
202
203         let field_pats = |vals: &[&'tcx ty::Const<'tcx>]| {
204             vals.iter()
205                 .enumerate()
206                 .map(|(idx, val)| {
207                     let field = Field::new(idx);
208                     FieldPat { field, pattern: self.recur(val) }
209                 })
210                 .collect()
211         };
212
213         let kind = match cv.ty.kind {
214             ty::Float(_) => {
215                 tcx.struct_span_lint_hir(
216                     lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
217                     id,
218                     span,
219                     |lint| lint.build("floating-point types cannot be used in patterns").emit(),
220                 );
221                 PatKind::Constant { value: cv }
222             }
223             ty::Adt(adt_def, _) if adt_def.is_union() => {
224                 // Matching on union fields is unsafe, we can't hide it in constants
225                 self.saw_const_match_error.set(true);
226                 tcx.sess.span_err(span, "cannot use unions in constant patterns");
227                 PatKind::Wild
228             }
229             // keep old code until future-compat upgraded to errors.
230             ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty) => {
231                 debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, cv.ty);
232                 let path = tcx.def_path_str(adt_def.did);
233                 let msg = format!(
234                     "to use a constant of type `{}` in a pattern, \
235                      `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
236                     path, path,
237                 );
238                 self.saw_const_match_error.set(true);
239                 tcx.sess.span_err(span, &msg);
240                 PatKind::Wild
241             }
242             // keep old code until future-compat upgraded to errors.
243             ty::Ref(_, adt_ty @ ty::TyS { kind: ty::Adt(_, _), .. }, _)
244                 if !self.type_marked_structural(adt_ty) =>
245             {
246                 let adt_def =
247                     if let ty::Adt(adt_def, _) = adt_ty.kind { adt_def } else { unreachable!() };
248
249                 debug!(
250                     "adt_def {:?} has !type_marked_structural for adt_ty: {:?}",
251                     adt_def, adt_ty
252                 );
253
254                 // HACK(estebank): Side-step ICE #53708, but anything other than erroring here
255                 // would be wrong. Returnging `PatKind::Wild` is not technically correct.
256                 let path = tcx.def_path_str(adt_def.did);
257                 let msg = format!(
258                     "to use a constant of type `{}` in a pattern, \
259                      `{}` must be annotated with `#[derive(PartialEq, Eq)]`",
260                     path, path,
261                 );
262                 self.saw_const_match_error.set(true);
263                 tcx.sess.span_err(span, &msg);
264                 PatKind::Wild
265             }
266             ty::Adt(adt_def, substs) if adt_def.is_enum() => {
267                 let destructured = tcx.destructure_const(param_env.and(cv));
268                 PatKind::Variant {
269                     adt_def,
270                     substs,
271                     variant_index: destructured.variant,
272                     subpatterns: field_pats(destructured.fields),
273                 }
274             }
275             ty::Adt(_, _) => {
276                 let destructured = tcx.destructure_const(param_env.and(cv));
277                 PatKind::Leaf { subpatterns: field_pats(destructured.fields) }
278             }
279             ty::Tuple(_) => {
280                 let destructured = tcx.destructure_const(param_env.and(cv));
281                 PatKind::Leaf { subpatterns: field_pats(destructured.fields) }
282             }
283             ty::Array(..) => PatKind::Array {
284                 prefix: tcx
285                     .destructure_const(param_env.and(cv))
286                     .fields
287                     .iter()
288                     .map(|val| self.recur(val))
289                     .collect(),
290                 slice: None,
291                 suffix: Vec::new(),
292             },
293             _ => PatKind::Constant { value: cv },
294         };
295
296         Pat { span, ty: cv.ty, kind: Box::new(kind) }
297     }
298 }