]>
Commit | Line | Data |
---|---|---|
fc512014 XL |
1 | use super::usefulness::Usefulness::*; |
2 | use super::usefulness::{ | |
3 | compute_match_usefulness, expand_pattern, MatchArm, MatchCheckCtxt, UsefulnessReport, | |
4 | }; | |
e74abb32 | 5 | use super::{PatCtxt, PatKind, PatternError}; |
223e47cc | 6 | |
f035d41b | 7 | use rustc_arena::TypedArena; |
3dfed10e | 8 | use rustc_ast::Mutability; |
dfeec247 XL |
9 | use rustc_errors::{error_code, struct_span_err, Applicability, DiagnosticBuilder}; |
10 | use rustc_hir as hir; | |
11 | use rustc_hir::def::*; | |
12 | use rustc_hir::def_id::DefId; | |
13 | use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; | |
14 | use rustc_hir::{HirId, Pat}; | |
ba9703b0 | 15 | use rustc_middle::ty::{self, Ty, TyCtxt}; |
dfeec247 XL |
16 | use rustc_session::lint::builtin::BINDINGS_WITH_VARIANT_NAME; |
17 | use rustc_session::lint::builtin::{IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS}; | |
18 | use rustc_session::parse::feature_err; | |
19 | use rustc_session::Session; | |
74b04a01 | 20 | use rustc_span::{sym, Span}; |
ff7c6d11 | 21 | use std::slice; |
1a4d82fc | 22 | |
e74abb32 | 23 | crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) { |
f9f354fc | 24 | let body_id = match def_id.as_local() { |
e74abb32 | 25 | None => return, |
3dfed10e | 26 | Some(id) => tcx.hir().body_owned_by(tcx.hir().local_def_id_to_hir_id(id)), |
ff7c6d11 XL |
27 | }; |
28 | ||
f9f354fc XL |
29 | let mut visitor = MatchVisitor { |
30 | tcx, | |
3dfed10e | 31 | typeck_results: tcx.typeck_body(body_id), |
f9f354fc XL |
32 | param_env: tcx.param_env(def_id), |
33 | pattern_arena: TypedArena::default(), | |
34 | }; | |
416331ca | 35 | visitor.visit_body(tcx.hir().body(body_id)); |
ff7c6d11 XL |
36 | } |
37 | ||
416331ca | 38 | fn create_e0004(sess: &Session, sp: Span, error_message: String) -> DiagnosticBuilder<'_> { |
c30ab7b3 | 39 | struct_span_err!(sess, sp, E0004, "{}", &error_message) |
223e47cc LB |
40 | } |
41 | ||
dc9dc135 XL |
42 | struct MatchVisitor<'a, 'tcx> { |
43 | tcx: TyCtxt<'tcx>, | |
3dfed10e | 44 | typeck_results: &'a ty::TypeckResults<'tcx>, |
7cac9316 | 45 | param_env: ty::ParamEnv<'tcx>, |
f9f354fc | 46 | pattern_arena: TypedArena<super::Pat<'tcx>>, |
1a4d82fc JJ |
47 | } |
48 | ||
e1599b0c | 49 | impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> { |
ba9703b0 | 50 | type Map = intravisit::ErasedMap<'tcx>; |
dfeec247 | 51 | |
ba9703b0 | 52 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
32a655c1 | 53 | NestedVisitorMap::None |
476ff2be SL |
54 | } |
55 | ||
dfeec247 | 56 | fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { |
c30ab7b3 SL |
57 | intravisit::walk_expr(self, ex); |
58 | ||
e74abb32 | 59 | if let hir::ExprKind::Match(ref scrut, ref arms, source) = ex.kind { |
416331ca | 60 | self.check_match(scrut, arms, source); |
c30ab7b3 | 61 | } |
1a4d82fc | 62 | } |
c30ab7b3 | 63 | |
dfeec247 | 64 | fn visit_local(&mut self, loc: &'tcx hir::Local<'tcx>) { |
c30ab7b3 SL |
65 | intravisit::walk_local(self, loc); |
66 | ||
e74abb32 XL |
67 | let (msg, sp) = match loc.source { |
68 | hir::LocalSource::Normal => ("local binding", Some(loc.span)), | |
69 | hir::LocalSource::ForLoopDesugar => ("`for` loop binding", None), | |
70 | hir::LocalSource::AsyncFn => ("async fn binding", None), | |
71 | hir::LocalSource::AwaitDesugar => ("`await` future binding", None), | |
29967ef6 | 72 | hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None), |
e74abb32 XL |
73 | }; |
74 | self.check_irrefutable(&loc.pat, msg, sp); | |
29967ef6 | 75 | self.check_patterns(&loc.pat); |
1a4d82fc | 76 | } |
c30ab7b3 | 77 | |
dfeec247 XL |
78 | fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { |
79 | intravisit::walk_param(self, param); | |
80 | self.check_irrefutable(¶m.pat, "function argument", None); | |
29967ef6 | 81 | self.check_patterns(¶m.pat); |
c30ab7b3 | 82 | } |
1a4d82fc JJ |
83 | } |
84 | ||
e74abb32 | 85 | impl PatCtxt<'_, '_> { |
cc61c64b XL |
86 | fn report_inlining_errors(&self, pat_span: Span) { |
87 | for error in &self.errors { | |
88 | match *error { | |
c30ab7b3 | 89 | PatternError::StaticInPattern(span) => { |
2c00a5a8 XL |
90 | self.span_e0158(span, "statics cannot be referenced in patterns") |
91 | } | |
dc9dc135 | 92 | PatternError::AssocConstInPattern(span) => { |
2c00a5a8 | 93 | self.span_e0158(span, "associated consts cannot be referenced in patterns") |
c30ab7b3 | 94 | } |
ba9703b0 XL |
95 | PatternError::ConstParamInPattern(span) => { |
96 | self.span_e0158(span, "const parameters cannot be referenced in patterns") | |
97 | } | |
0531ce1d XL |
98 | PatternError::FloatBug => { |
99 | // FIXME(#31407) this is only necessary because float parsing is buggy | |
29967ef6 | 100 | rustc_middle::mir::interpret::struct_error( |
94b46f34 | 101 | self.tcx.at(pat_span), |
0531ce1d | 102 | "could not evaluate float literal (see issue #31407)", |
e74abb32 XL |
103 | ) |
104 | .emit(); | |
0531ce1d XL |
105 | } |
106 | PatternError::NonConstPath(span) => { | |
29967ef6 | 107 | rustc_middle::mir::interpret::struct_error( |
94b46f34 | 108 | self.tcx.at(span), |
0531ce1d | 109 | "runtime values cannot be referenced in patterns", |
e74abb32 XL |
110 | ) |
111 | .emit(); | |
1a4d82fc JJ |
112 | } |
113 | } | |
c30ab7b3 SL |
114 | } |
115 | } | |
2c00a5a8 XL |
116 | |
117 | fn span_e0158(&self, span: Span, text: &str) { | |
dfeec247 | 118 | struct_span_err!(self.tcx.sess, span, E0158, "{}", text).emit(); |
2c00a5a8 | 119 | } |
cc61c64b XL |
120 | } |
121 | ||
e1599b0c | 122 | impl<'tcx> MatchVisitor<'_, 'tcx> { |
29967ef6 | 123 | fn check_patterns(&mut self, pat: &Pat<'_>) { |
74b04a01 | 124 | pat.walk_always(|pat| check_borrow_conflicts_in_at_patterns(self, pat)); |
dfeec247 XL |
125 | if !self.tcx.features().bindings_after_at { |
126 | check_legality_of_bindings_in_at_patterns(self, pat); | |
127 | } | |
128 | check_for_bindings_named_same_as_variants(self, pat); | |
cc61c64b | 129 | } |
1a4d82fc | 130 | |
dfeec247 XL |
131 | fn lower_pattern<'p>( |
132 | &self, | |
133 | cx: &mut MatchCheckCtxt<'p, 'tcx>, | |
134 | pat: &'tcx hir::Pat<'tcx>, | |
135 | have_errors: &mut bool, | |
136 | ) -> (&'p super::Pat<'tcx>, Ty<'tcx>) { | |
3dfed10e | 137 | let mut patcx = PatCtxt::new(self.tcx, self.param_env, self.typeck_results); |
dfeec247 XL |
138 | patcx.include_lint_checks(); |
139 | let pattern = patcx.lower_pattern(pat); | |
140 | let pattern_ty = pattern.ty; | |
29967ef6 | 141 | let pattern: &_ = cx.pattern_arena.alloc(expand_pattern(pattern)); |
dfeec247 XL |
142 | if !patcx.errors.is_empty() { |
143 | *have_errors = true; | |
144 | patcx.report_inlining_errors(pat.span); | |
145 | } | |
146 | (pattern, pattern_ty) | |
147 | } | |
148 | ||
f9f354fc XL |
149 | fn new_cx(&self, hir_id: HirId) -> MatchCheckCtxt<'_, 'tcx> { |
150 | MatchCheckCtxt { | |
151 | tcx: self.tcx, | |
152 | param_env: self.param_env, | |
153 | module: self.tcx.parent_module(hir_id).to_def_id(), | |
154 | pattern_arena: &self.pattern_arena, | |
155 | } | |
dfeec247 XL |
156 | } |
157 | ||
158 | fn check_match( | |
159 | &mut self, | |
160 | scrut: &hir::Expr<'_>, | |
161 | arms: &'tcx [hir::Arm<'tcx>], | |
162 | source: hir::MatchSource, | |
163 | ) { | |
c30ab7b3 | 164 | for arm in arms { |
dfeec247 | 165 | // Check the arm for some things unrelated to exhaustiveness. |
29967ef6 | 166 | self.check_patterns(&arm.pat); |
fc512014 XL |
167 | if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard { |
168 | self.check_patterns(pat); | |
169 | } | |
c30ab7b3 | 170 | } |
1a4d82fc | 171 | |
f9f354fc | 172 | let mut cx = self.new_cx(scrut.hir_id); |
1a4d82fc | 173 | |
fc512014 XL |
174 | for arm in arms { |
175 | if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard { | |
176 | let tpat = self.lower_pattern(&mut cx, pat, &mut false).0; | |
177 | check_if_let_guard(&mut cx, &tpat, pat.hir_id); | |
178 | } | |
179 | } | |
180 | ||
f9f354fc | 181 | let mut have_errors = false; |
1a4d82fc | 182 | |
fc512014 | 183 | let arms: Vec<_> = arms |
f9f354fc | 184 | .iter() |
fc512014 XL |
185 | .map(|hir::Arm { pat, guard, .. }| MatchArm { |
186 | pat: self.lower_pattern(&mut cx, pat, &mut have_errors).0, | |
187 | hir_id: pat.hir_id, | |
188 | has_guard: guard.is_some(), | |
f9f354fc XL |
189 | }) |
190 | .collect(); | |
1a4d82fc | 191 | |
fc512014 | 192 | // Bail out early if lowering failed. |
f9f354fc XL |
193 | if have_errors { |
194 | return; | |
195 | } | |
1a4d82fc | 196 | |
fc512014 XL |
197 | let scrut_ty = self.typeck_results.expr_ty_adjusted(scrut); |
198 | let report = compute_match_usefulness(&cx, &arms, scrut.hir_id, scrut_ty); | |
199 | ||
200 | // Report unreachable arms. | |
201 | report_arm_reachability(&cx, &report, source); | |
f9f354fc | 202 | |
fc512014 | 203 | // Check if the match is exhaustive. |
f9f354fc XL |
204 | // Note: An empty match isn't the same as an empty matrix for diagnostics purposes, |
205 | // since an empty matrix can occur when there are arms, if those arms all have guards. | |
fc512014 XL |
206 | let is_empty_match = arms.is_empty(); |
207 | let witnesses = report.non_exhaustiveness_witnesses; | |
208 | if !witnesses.is_empty() { | |
209 | non_exhaustive_match(&cx, scrut_ty, scrut.span, witnesses, is_empty_match); | |
210 | } | |
c30ab7b3 SL |
211 | } |
212 | ||
dfeec247 | 213 | fn check_irrefutable(&self, pat: &'tcx Pat<'tcx>, origin: &str, sp: Option<Span>) { |
f9f354fc | 214 | let mut cx = self.new_cx(pat.hir_id); |
e74abb32 | 215 | |
f9f354fc | 216 | let (pattern, pattern_ty) = self.lower_pattern(&mut cx, pat, &mut false); |
fc512014 XL |
217 | let arms = vec![MatchArm { pat: pattern, hir_id: pat.hir_id, has_guard: false }]; |
218 | let report = compute_match_usefulness(&cx, &arms, pat.hir_id, pattern_ty); | |
219 | ||
220 | // Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We | |
221 | // only care about exhaustiveness here. | |
222 | let witnesses = report.non_exhaustiveness_witnesses; | |
223 | if witnesses.is_empty() { | |
224 | // The pattern is irrefutable. | |
225 | return; | |
226 | } | |
f9f354fc XL |
227 | |
228 | let joined_patterns = joined_uncovered_patterns(&witnesses); | |
229 | let mut err = struct_span_err!( | |
230 | self.tcx.sess, | |
231 | pat.span, | |
232 | E0005, | |
233 | "refutable pattern in {}: {} not covered", | |
234 | origin, | |
235 | joined_patterns | |
236 | ); | |
237 | let suggest_if_let = match &pat.kind { | |
238 | hir::PatKind::Path(hir::QPath::Resolved(None, path)) | |
239 | if path.segments.len() == 1 && path.segments[0].args.is_none() => | |
240 | { | |
241 | const_not_var(&mut err, cx.tcx, pat, path); | |
242 | false | |
243 | } | |
244 | _ => { | |
245 | err.span_label(pat.span, pattern_not_covered_label(&witnesses, &joined_patterns)); | |
246 | true | |
247 | } | |
248 | }; | |
249 | ||
250 | if let (Some(span), true) = (sp, suggest_if_let) { | |
251 | err.note( | |
252 | "`let` bindings require an \"irrefutable pattern\", like a `struct` or \ | |
253 | an `enum` with only one variant", | |
254 | ); | |
255 | if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(span) { | |
256 | err.span_suggestion( | |
257 | span, | |
258 | "you might want to use `if let` to ignore the variant that isn't matched", | |
259 | format!("if {} {{ /* */ }}", &snippet[..snippet.len() - 1]), | |
260 | Applicability::HasPlaceholders, | |
e74abb32 XL |
261 | ); |
262 | } | |
f9f354fc XL |
263 | err.note( |
264 | "for more information, visit \ | |
265 | https://doc.rust-lang.org/book/ch18-02-refutability.html", | |
266 | ); | |
267 | } | |
e74abb32 | 268 | |
f9f354fc XL |
269 | adt_defined_here(&cx, &mut err, pattern_ty, &witnesses); |
270 | err.note(&format!("the matched value is of type `{}`", pattern_ty)); | |
271 | err.emit(); | |
1a4d82fc JJ |
272 | } |
273 | } | |
274 | ||
e74abb32 XL |
275 | /// A path pattern was interpreted as a constant, not a new variable. |
276 | /// This caused an irrefutable match failure in e.g. `let`. | |
dfeec247 XL |
277 | fn const_not_var( |
278 | err: &mut DiagnosticBuilder<'_>, | |
279 | tcx: TyCtxt<'_>, | |
280 | pat: &Pat<'_>, | |
281 | path: &hir::Path<'_>, | |
282 | ) { | |
e74abb32 XL |
283 | let descr = path.res.descr(); |
284 | err.span_label( | |
285 | pat.span, | |
286 | format!("interpreted as {} {} pattern, not a new variable", path.res.article(), descr,), | |
287 | ); | |
288 | ||
289 | err.span_suggestion( | |
290 | pat.span, | |
291 | "introduce a variable instead", | |
292 | format!("{}_var", path.segments[0].ident).to_lowercase(), | |
293 | // Cannot use `MachineApplicable` as it's not really *always* correct | |
294 | // because there may be such an identifier in scope or the user maybe | |
295 | // really wanted to match against the constant. This is quite unlikely however. | |
296 | Applicability::MaybeIncorrect, | |
297 | ); | |
298 | ||
299 | if let Some(span) = tcx.hir().res_span(path.res) { | |
300 | err.span_label(span, format!("{} defined here", descr)); | |
301 | } | |
302 | } | |
303 | ||
dfeec247 XL |
304 | fn check_for_bindings_named_same_as_variants(cx: &MatchVisitor<'_, '_>, pat: &Pat<'_>) { |
305 | pat.walk_always(|p| { | |
e74abb32 | 306 | if let hir::PatKind::Binding(_, _, ident, None) = p.kind { |
dfeec247 | 307 | if let Some(ty::BindByValue(hir::Mutability::Not)) = |
3dfed10e | 308 | cx.typeck_results.extract_binding_mode(cx.tcx.sess, p.hir_id, p.span) |
dfeec247 | 309 | { |
3dfed10e | 310 | let pat_ty = cx.typeck_results.pat_ty(p).peel_refs(); |
1b1a35ee | 311 | if let ty::Adt(edef, _) = pat_ty.kind() { |
e74abb32 XL |
312 | if edef.is_enum() |
313 | && edef.variants.iter().any(|variant| { | |
314 | variant.ident == ident && variant.ctor_kind == CtorKind::Const | |
315 | }) | |
316 | { | |
74b04a01 XL |
317 | cx.tcx.struct_span_lint_hir( |
318 | BINDINGS_WITH_VARIANT_NAME, | |
319 | p.hir_id, | |
320 | p.span, | |
321 | |lint| { | |
322 | let ty_path = cx.tcx.def_path_str(edef.did); | |
323 | lint.build(&format!( | |
dfeec247 | 324 | "pattern binding `{}` is named the same as one \ |
74b04a01 | 325 | of the variants of the type `{}`", |
dfeec247 | 326 | ident, ty_path |
74b04a01 XL |
327 | )) |
328 | .code(error_code!(E0170)) | |
329 | .span_suggestion( | |
330 | p.span, | |
331 | "to match on the variant, qualify the path", | |
332 | format!("{}::{}", ty_path, ident), | |
333 | Applicability::MachineApplicable, | |
334 | ) | |
335 | .emit(); | |
336 | }, | |
337 | ) | |
8faf50e0 | 338 | } |
1a4d82fc JJ |
339 | } |
340 | } | |
1a4d82fc | 341 | } |
1a4d82fc JJ |
342 | }); |
343 | } | |
344 | ||
c30ab7b3 | 345 | /// Checks for common cases of "catchall" patterns that may not be intended as such. |
dfeec247 XL |
346 | fn pat_is_catchall(pat: &super::Pat<'_>) -> bool { |
347 | use super::PatKind::*; | |
348 | match &*pat.kind { | |
349 | Binding { subpattern: None, .. } => true, | |
350 | Binding { subpattern: Some(s), .. } | Deref { subpattern: s } => pat_is_catchall(s), | |
351 | Leaf { subpatterns: s } => s.iter().all(|p| pat_is_catchall(&p.pattern)), | |
e74abb32 | 352 | _ => false, |
c30ab7b3 | 353 | } |
1a4d82fc JJ |
354 | } |
355 | ||
dfeec247 | 356 | fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<Span>) { |
74b04a01 XL |
357 | tcx.struct_span_lint_hir(UNREACHABLE_PATTERNS, id, span, |lint| { |
358 | let mut err = lint.build("unreachable pattern"); | |
359 | if let Some(catchall) = catchall { | |
360 | // We had a catchall pattern, hint at that. | |
361 | err.span_label(span, "unreachable pattern"); | |
362 | err.span_label(catchall, "matches any value"); | |
363 | } | |
364 | err.emit(); | |
365 | }); | |
dfeec247 XL |
366 | } |
367 | ||
368 | fn irrefutable_let_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, source: hir::MatchSource) { | |
74b04a01 XL |
369 | tcx.struct_span_lint_hir(IRREFUTABLE_LET_PATTERNS, id, span, |lint| { |
370 | let msg = match source { | |
371 | hir::MatchSource::IfLetDesugar { .. } => "irrefutable if-let pattern", | |
372 | hir::MatchSource::WhileLetDesugar => "irrefutable while-let pattern", | |
fc512014 | 373 | hir::MatchSource::IfLetGuardDesugar => "irrefutable if-let guard", |
74b04a01 XL |
374 | _ => bug!(), |
375 | }; | |
376 | lint.build(msg).emit() | |
377 | }); | |
dfeec247 XL |
378 | } |
379 | ||
fc512014 | 380 | fn check_if_let_guard<'p, 'tcx>( |
60c5eb7d | 381 | cx: &mut MatchCheckCtxt<'p, 'tcx>, |
fc512014 XL |
382 | pat: &'p super::Pat<'tcx>, |
383 | pat_id: HirId, | |
384 | ) { | |
385 | let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }]; | |
386 | let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty); | |
387 | report_arm_reachability(&cx, &report, hir::MatchSource::IfLetGuardDesugar); | |
388 | ||
389 | if report.non_exhaustiveness_witnesses.is_empty() { | |
390 | // The match is exhaustive, i.e. the if let pattern is irrefutable. | |
391 | irrefutable_let_pattern(cx.tcx, pat.span, pat_id, hir::MatchSource::IfLetGuardDesugar) | |
392 | } | |
393 | } | |
394 | ||
395 | /// Report unreachable arms, if any. | |
396 | fn report_arm_reachability<'p, 'tcx>( | |
397 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
398 | report: &UsefulnessReport<'p, 'tcx>, | |
532ac7d7 | 399 | source: hir::MatchSource, |
fc512014 | 400 | ) { |
c30ab7b3 | 401 | let mut catchall = None; |
fc512014 XL |
402 | for (arm_index, (arm, is_useful)) in report.arm_usefulness.iter().enumerate() { |
403 | match is_useful { | |
60c5eb7d XL |
404 | NotUseful => { |
405 | match source { | |
406 | hir::MatchSource::IfDesugar { .. } | hir::MatchSource::WhileDesugar => bug!(), | |
407 | ||
408 | hir::MatchSource::IfLetDesugar { .. } | hir::MatchSource::WhileLetDesugar => { | |
dfeec247 | 409 | // Check which arm we're on. |
60c5eb7d XL |
410 | match arm_index { |
411 | // The arm with the user-specified pattern. | |
fc512014 | 412 | 0 => unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, None), |
60c5eb7d | 413 | // The arm with the wildcard pattern. |
fc512014 | 414 | 1 => irrefutable_let_pattern(cx.tcx, arm.pat.span, arm.hir_id, source), |
60c5eb7d | 415 | _ => bug!(), |
9fa01778 | 416 | } |
60c5eb7d | 417 | } |
54a0048b | 418 | |
fc512014 XL |
419 | hir::MatchSource::IfLetGuardDesugar => { |
420 | assert_eq!(arm_index, 0); | |
421 | unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, None); | |
422 | } | |
423 | ||
60c5eb7d | 424 | hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => { |
fc512014 | 425 | unreachable_pattern(cx.tcx, arm.pat.span, arm.hir_id, catchall); |
1a4d82fc | 426 | } |
60c5eb7d XL |
427 | |
428 | // Unreachable patterns in try and await expressions occur when one of | |
429 | // the arms are an uninhabited type. Which is OK. | |
430 | hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {} | |
1a4d82fc | 431 | } |
1a4d82fc | 432 | } |
fc512014 XL |
433 | Useful(unreachables) if unreachables.is_empty() => {} |
434 | // The arm is reachable, but contains unreachable subpatterns (from or-patterns). | |
29967ef6 | 435 | Useful(unreachables) => { |
fc512014 | 436 | let mut unreachables: Vec<_> = unreachables.iter().collect(); |
29967ef6 XL |
437 | // Emit lints in the order in which they occur in the file. |
438 | unreachables.sort_unstable(); | |
439 | for span in unreachables { | |
fc512014 | 440 | unreachable_pattern(cx.tcx, span, arm.hir_id, None); |
c30ab7b3 | 441 | } |
223e47cc | 442 | } |
60c5eb7d XL |
443 | UsefulWithWitness(_) => bug!(), |
444 | } | |
fc512014 XL |
445 | if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) { |
446 | catchall = Some(arm.pat.span); | |
223e47cc LB |
447 | } |
448 | } | |
449 | } | |
450 | ||
fc512014 XL |
451 | /// Report that a match is not exhaustive. |
452 | fn non_exhaustive_match<'p, 'tcx>( | |
453 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
532ac7d7 XL |
454 | scrut_ty: Ty<'tcx>, |
455 | sp: Span, | |
fc512014 | 456 | witnesses: Vec<super::Pat<'tcx>>, |
60c5eb7d | 457 | is_empty_match: bool, |
532ac7d7 | 458 | ) { |
1b1a35ee | 459 | let non_empty_enum = match scrut_ty.kind() { |
60c5eb7d XL |
460 | ty::Adt(def, _) => def.is_enum() && !def.variants.is_empty(), |
461 | _ => false, | |
462 | }; | |
463 | // In the case of an empty match, replace the '`_` not covered' diagnostic with something more | |
464 | // informative. | |
465 | let mut err; | |
466 | if is_empty_match && !non_empty_enum { | |
467 | err = create_e0004( | |
468 | cx.tcx.sess, | |
469 | sp, | |
470 | format!("non-exhaustive patterns: type `{}` is non-empty", scrut_ty), | |
471 | ); | |
472 | } else { | |
473 | let joined_patterns = joined_uncovered_patterns(&witnesses); | |
474 | err = create_e0004( | |
475 | cx.tcx.sess, | |
476 | sp, | |
477 | format!("non-exhaustive patterns: {} not covered", joined_patterns), | |
478 | ); | |
479 | err.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns)); | |
480 | }; | |
481 | ||
e1599b0c XL |
482 | adt_defined_here(cx, &mut err, scrut_ty, &witnesses); |
483 | err.help( | |
484 | "ensure that all possible cases are being handled, \ | |
f035d41b | 485 | possibly by adding wildcards or more match arms", |
60c5eb7d | 486 | ); |
ba9703b0 | 487 | err.note(&format!("the matched value is of type `{}`", scrut_ty)); |
f035d41b XL |
488 | if (scrut_ty == cx.tcx.types.usize || scrut_ty == cx.tcx.types.isize) |
489 | && !is_empty_match | |
490 | && witnesses.len() == 1 | |
491 | && witnesses[0].is_wildcard() | |
492 | { | |
493 | err.note(&format!( | |
494 | "`{}` does not have a fixed maximum value, \ | |
495 | so a wildcard `_` is necessary to match exhaustively", | |
496 | scrut_ty, | |
497 | )); | |
fc512014 | 498 | if cx.tcx.sess.is_nightly_build() { |
f035d41b XL |
499 | err.help(&format!( |
500 | "add `#![feature(precise_pointer_size_matching)]` \ | |
501 | to the crate attributes to enable precise `{}` matching", | |
502 | scrut_ty, | |
503 | )); | |
504 | } | |
505 | } | |
60c5eb7d | 506 | err.emit(); |
e1599b0c | 507 | } |
7cac9316 | 508 | |
e74abb32 | 509 | fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String { |
e1599b0c XL |
510 | const LIMIT: usize = 3; |
511 | match witnesses { | |
512 | [] => bug!(), | |
513 | [witness] => format!("`{}`", witness), | |
514 | [head @ .., tail] if head.len() < LIMIT => { | |
515 | let head: Vec<_> = head.iter().map(<_>::to_string).collect(); | |
516 | format!("`{}` and `{}`", head.join("`, `"), tail) | |
1a4d82fc | 517 | } |
e1599b0c XL |
518 | _ => { |
519 | let (head, tail) = witnesses.split_at(LIMIT); | |
520 | let head: Vec<_> = head.iter().map(<_>::to_string).collect(); | |
521 | format!("`{}` and {} more", head.join("`, `"), tail.len()) | |
9fa01778 | 522 | } |
1a4d82fc JJ |
523 | } |
524 | } | |
525 | ||
e74abb32 | 526 | fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String { |
60c5eb7d | 527 | format!("pattern{} {} not covered", rustc_errors::pluralize!(witnesses.len()), joined_patterns) |
e1599b0c XL |
528 | } |
529 | ||
530 | /// Point at the definition of non-covered `enum` variants. | |
531 | fn adt_defined_here( | |
532 | cx: &MatchCheckCtxt<'_, '_>, | |
533 | err: &mut DiagnosticBuilder<'_>, | |
534 | ty: Ty<'_>, | |
e74abb32 | 535 | witnesses: &[super::Pat<'_>], |
e1599b0c XL |
536 | ) { |
537 | let ty = ty.peel_refs(); | |
1b1a35ee | 538 | if let ty::Adt(def, _) = ty.kind() { |
e1599b0c XL |
539 | if let Some(sp) = cx.tcx.hir().span_if_local(def.did) { |
540 | err.span_label(sp, format!("`{}` defined here", ty)); | |
541 | } | |
542 | ||
543 | if witnesses.len() < 4 { | |
544 | for sp in maybe_point_at_variant(ty, &witnesses) { | |
545 | err.span_label(sp, "not covered"); | |
546 | } | |
547 | } | |
548 | } | |
549 | } | |
550 | ||
e74abb32 | 551 | fn maybe_point_at_variant(ty: Ty<'_>, patterns: &[super::Pat<'_>]) -> Vec<Span> { |
532ac7d7 | 552 | let mut covered = vec![]; |
1b1a35ee | 553 | if let ty::Adt(def, _) = ty.kind() { |
532ac7d7 | 554 | // Don't point at variants that have already been covered due to other patterns to avoid |
e1599b0c | 555 | // visual clutter. |
532ac7d7 | 556 | for pattern in patterns { |
e74abb32 | 557 | use PatKind::{AscribeUserType, Deref, Leaf, Or, Variant}; |
e1599b0c XL |
558 | match &*pattern.kind { |
559 | AscribeUserType { subpattern, .. } | Deref { subpattern } => { | |
560 | covered.extend(maybe_point_at_variant(ty, slice::from_ref(&subpattern))); | |
561 | } | |
562 | Variant { adt_def, variant_index, subpatterns, .. } if adt_def.did == def.did => { | |
532ac7d7 XL |
563 | let sp = def.variants[*variant_index].ident.span; |
564 | if covered.contains(&sp) { | |
565 | continue; | |
566 | } | |
567 | covered.push(sp); | |
e1599b0c | 568 | |
e74abb32 XL |
569 | let pats = subpatterns |
570 | .iter() | |
532ac7d7 | 571 | .map(|field_pattern| field_pattern.pattern.clone()) |
e1599b0c XL |
572 | .collect::<Box<[_]>>(); |
573 | covered.extend(maybe_point_at_variant(ty, &pats)); | |
532ac7d7 | 574 | } |
e1599b0c | 575 | Leaf { subpatterns } => { |
e74abb32 XL |
576 | let pats = subpatterns |
577 | .iter() | |
e1599b0c XL |
578 | .map(|field_pattern| field_pattern.pattern.clone()) |
579 | .collect::<Box<[_]>>(); | |
580 | covered.extend(maybe_point_at_variant(ty, &pats)); | |
581 | } | |
582 | Or { pats } => { | |
583 | let pats = pats.iter().cloned().collect::<Box<[_]>>(); | |
584 | covered.extend(maybe_point_at_variant(ty, &pats)); | |
585 | } | |
586 | _ => {} | |
532ac7d7 XL |
587 | } |
588 | } | |
589 | } | |
590 | covered | |
591 | } | |
592 | ||
74b04a01 XL |
593 | /// Check if a by-value binding is by-value. That is, check if the binding's type is not `Copy`. |
594 | fn is_binding_by_move(cx: &MatchVisitor<'_, '_>, hir_id: HirId, span: Span) -> bool { | |
3dfed10e | 595 | !cx.typeck_results.node_type(hir_id).is_copy_modulo_regions(cx.tcx.at(span), cx.param_env) |
74b04a01 XL |
596 | } |
597 | ||
74b04a01 | 598 | /// Check that there are no borrow or move conflicts in `binding @ subpat` patterns. |
dfeec247 XL |
599 | /// |
600 | /// For example, this would reject: | |
601 | /// - `ref x @ Some(ref mut y)`, | |
74b04a01 XL |
602 | /// - `ref mut x @ Some(ref y)`, |
603 | /// - `ref mut x @ Some(ref mut y)`, | |
604 | /// - `ref mut? x @ Some(y)`, and | |
605 | /// - `x @ Some(ref mut? y)`. | |
dfeec247 XL |
606 | /// |
607 | /// This analysis is *not* subsumed by NLL. | |
608 | fn check_borrow_conflicts_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat<'_>) { | |
74b04a01 XL |
609 | // Extract `sub` in `binding @ sub`. |
610 | let (name, sub) = match &pat.kind { | |
611 | hir::PatKind::Binding(.., name, Some(sub)) => (*name, sub), | |
612 | _ => return, | |
dfeec247 | 613 | }; |
74b04a01 | 614 | let binding_span = pat.span.with_hi(name.span.hi()); |
dfeec247 | 615 | |
3dfed10e | 616 | let typeck_results = cx.typeck_results; |
74b04a01 | 617 | let sess = cx.tcx.sess; |
1a4d82fc | 618 | |
74b04a01 | 619 | // Get the binding move, extract the mutability if by-ref. |
3dfed10e | 620 | let mut_outer = match typeck_results.extract_binding_mode(sess, pat.hir_id, pat.span) { |
74b04a01 XL |
621 | Some(ty::BindByValue(_)) if is_binding_by_move(cx, pat.hir_id, pat.span) => { |
622 | // We have `x @ pat` where `x` is by-move. Reject all borrows in `pat`. | |
623 | let mut conflicts_ref = Vec::new(); | |
624 | sub.each_binding(|_, hir_id, span, _| { | |
3dfed10e | 625 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
626 | Some(ty::BindByValue(_)) | None => {} |
627 | Some(ty::BindByReference(_)) => conflicts_ref.push(span), | |
dfeec247 | 628 | } |
74b04a01 XL |
629 | }); |
630 | if !conflicts_ref.is_empty() { | |
631 | let occurs_because = format!( | |
632 | "move occurs because `{}` has type `{}` which does not implement the `Copy` trait", | |
633 | name, | |
3dfed10e | 634 | typeck_results.node_type(pat.hir_id), |
74b04a01 XL |
635 | ); |
636 | sess.struct_span_err(pat.span, "borrow of moved value") | |
637 | .span_label(binding_span, format!("value moved into `{}` here", name)) | |
638 | .span_label(binding_span, occurs_because) | |
639 | .span_labels(conflicts_ref, "value borrowed here after move") | |
640 | .emit(); | |
dfeec247 | 641 | } |
74b04a01 XL |
642 | return; |
643 | } | |
644 | Some(ty::BindByValue(_)) | None => return, | |
645 | Some(ty::BindByReference(m)) => m, | |
646 | }; | |
dfeec247 | 647 | |
74b04a01 XL |
648 | // We now have `ref $mut_outer binding @ sub` (semantically). |
649 | // Recurse into each binding in `sub` and find mutability or move conflicts. | |
650 | let mut conflicts_move = Vec::new(); | |
651 | let mut conflicts_mut_mut = Vec::new(); | |
652 | let mut conflicts_mut_ref = Vec::new(); | |
653 | sub.each_binding(|_, hir_id, span, name| { | |
3dfed10e | 654 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
655 | Some(ty::BindByReference(mut_inner)) => match (mut_outer, mut_inner) { |
656 | (Mutability::Not, Mutability::Not) => {} // Both sides are `ref`. | |
657 | (Mutability::Mut, Mutability::Mut) => conflicts_mut_mut.push((span, name)), // 2x `ref mut`. | |
658 | _ => conflicts_mut_ref.push((span, name)), // `ref` + `ref mut` in either direction. | |
659 | }, | |
660 | Some(ty::BindByValue(_)) if is_binding_by_move(cx, hir_id, span) => { | |
661 | conflicts_move.push((span, name)) // `ref mut?` + by-move conflict. | |
dfeec247 | 662 | } |
74b04a01 | 663 | Some(ty::BindByValue(_)) | None => {} // `ref mut?` + by-copy is fine. |
dfeec247 XL |
664 | } |
665 | }); | |
74b04a01 XL |
666 | |
667 | // Report errors if any. | |
668 | if !conflicts_mut_mut.is_empty() { | |
669 | // Report mutability conflicts for e.g. `ref mut x @ Some(ref mut y)`. | |
670 | let mut err = sess | |
671 | .struct_span_err(pat.span, "cannot borrow value as mutable more than once at a time"); | |
672 | err.span_label(binding_span, format!("first mutable borrow, by `{}`, occurs here", name)); | |
673 | for (span, name) in conflicts_mut_mut { | |
674 | err.span_label(span, format!("another mutable borrow, by `{}`, occurs here", name)); | |
675 | } | |
676 | for (span, name) in conflicts_mut_ref { | |
677 | err.span_label(span, format!("also borrowed as immutable, by `{}`, here", name)); | |
678 | } | |
679 | for (span, name) in conflicts_move { | |
680 | err.span_label(span, format!("also moved into `{}` here", name)); | |
681 | } | |
682 | err.emit(); | |
683 | } else if !conflicts_mut_ref.is_empty() { | |
684 | // Report mutability conflicts for e.g. `ref x @ Some(ref mut y)` or the converse. | |
685 | let (primary, also) = match mut_outer { | |
686 | Mutability::Mut => ("mutable", "immutable"), | |
687 | Mutability::Not => ("immutable", "mutable"), | |
688 | }; | |
689 | let msg = | |
690 | format!("cannot borrow value as {} because it is also borrowed as {}", also, primary); | |
691 | let mut err = sess.struct_span_err(pat.span, &msg); | |
692 | err.span_label(binding_span, format!("{} borrow, by `{}`, occurs here", primary, name)); | |
693 | for (span, name) in conflicts_mut_ref { | |
694 | err.span_label(span, format!("{} borrow, by `{}`, occurs here", also, name)); | |
695 | } | |
696 | for (span, name) in conflicts_move { | |
697 | err.span_label(span, format!("also moved into `{}` here", name)); | |
698 | } | |
699 | err.emit(); | |
700 | } else if !conflicts_move.is_empty() { | |
701 | // Report by-ref and by-move conflicts, e.g. `ref x @ y`. | |
702 | let mut err = | |
703 | sess.struct_span_err(pat.span, "cannot move out of value because it is borrowed"); | |
704 | err.span_label(binding_span, format!("value borrowed, by `{}`, here", name)); | |
705 | for (span, name) in conflicts_move { | |
706 | err.span_label(span, format!("value moved into `{}` here", name)); | |
707 | } | |
708 | err.emit(); | |
709 | } | |
1a4d82fc JJ |
710 | } |
711 | ||
dfeec247 XL |
712 | /// Forbids bindings in `@` patterns. This used to be is necessary for memory safety, |
713 | /// because of the way rvalues were handled in the borrow check. (See issue #14587.) | |
714 | fn check_legality_of_bindings_in_at_patterns(cx: &MatchVisitor<'_, '_>, pat: &Pat<'_>) { | |
715 | AtBindingPatternVisitor { cx, bindings_allowed: true }.visit_pat(pat); | |
716 | ||
717 | struct AtBindingPatternVisitor<'a, 'b, 'tcx> { | |
718 | cx: &'a MatchVisitor<'b, 'tcx>, | |
719 | bindings_allowed: bool, | |
476ff2be SL |
720 | } |
721 | ||
dfeec247 | 722 | impl<'v> Visitor<'v> for AtBindingPatternVisitor<'_, '_, '_> { |
ba9703b0 | 723 | type Map = intravisit::ErasedMap<'v>; |
dfeec247 | 724 | |
ba9703b0 | 725 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
dfeec247 XL |
726 | NestedVisitorMap::None |
727 | } | |
3157f602 | 728 | |
dfeec247 XL |
729 | fn visit_pat(&mut self, pat: &Pat<'_>) { |
730 | match pat.kind { | |
731 | hir::PatKind::Binding(.., ref subpat) => { | |
732 | if !self.bindings_allowed { | |
733 | feature_err( | |
734 | &self.cx.tcx.sess.parse_sess, | |
735 | sym::bindings_after_at, | |
736 | pat.span, | |
737 | "pattern bindings after an `@` are unstable", | |
738 | ) | |
739 | .emit(); | |
740 | } | |
741 | ||
742 | if subpat.is_some() { | |
743 | let bindings_were_allowed = self.bindings_allowed; | |
744 | self.bindings_allowed = false; | |
745 | intravisit::walk_pat(self, pat); | |
746 | self.bindings_allowed = bindings_were_allowed; | |
747 | } | |
3157f602 | 748 | } |
dfeec247 | 749 | _ => intravisit::walk_pat(self, pat), |
1a4d82fc | 750 | } |
223e47cc | 751 | } |
223e47cc LB |
752 | } |
753 | } |