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