]>
Commit | Line | Data |
---|---|---|
c295e0f8 | 1 | use super::deconstruct_pat::{Constructor, DeconstructedPat}; |
fc512014 | 2 | use super::usefulness::{ |
c295e0f8 | 3 | compute_match_usefulness, MatchArm, MatchCheckCtxt, Reachability, UsefulnessReport, |
fc512014 | 4 | }; |
17df50a5 | 5 | use super::{PatCtxt, 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}; | |
c295e0f8 XL |
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 | }; | |
dfeec247 | 19 | use rustc_session::Session; |
94222f64 | 20 | use rustc_span::{DesugaringKind, ExpnKind, Span}; |
1a4d82fc | 21 | |
e74abb32 | 22 | crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) { |
f9f354fc | 23 | let body_id = match def_id.as_local() { |
e74abb32 | 24 | None => return, |
3dfed10e | 25 | Some(id) => tcx.hir().body_owned_by(tcx.hir().local_def_id_to_hir_id(id)), |
ff7c6d11 XL |
26 | }; |
27 | ||
c295e0f8 | 28 | let pattern_arena = TypedArena::default(); |
f9f354fc XL |
29 | let mut visitor = MatchVisitor { |
30 | tcx, | |
3dfed10e | 31 | typeck_results: tcx.typeck_body(body_id), |
f9f354fc | 32 | param_env: tcx.param_env(def_id), |
c295e0f8 | 33 | pattern_arena: &pattern_arena, |
f9f354fc | 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 | ||
c295e0f8 XL |
42 | #[derive(PartialEq)] |
43 | enum RefutableFlag { | |
44 | Irrefutable, | |
45 | Refutable, | |
46 | } | |
47 | use RefutableFlag::*; | |
48 | ||
49 | struct MatchVisitor<'a, 'p, 'tcx> { | |
dc9dc135 | 50 | tcx: TyCtxt<'tcx>, |
3dfed10e | 51 | typeck_results: &'a ty::TypeckResults<'tcx>, |
7cac9316 | 52 | param_env: ty::ParamEnv<'tcx>, |
c295e0f8 | 53 | pattern_arena: &'p TypedArena<DeconstructedPat<'p, 'tcx>>, |
1a4d82fc JJ |
54 | } |
55 | ||
c295e0f8 | 56 | impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> { |
ba9703b0 | 57 | type Map = intravisit::ErasedMap<'tcx>; |
dfeec247 | 58 | |
ba9703b0 | 59 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { |
32a655c1 | 60 | NestedVisitorMap::None |
476ff2be SL |
61 | } |
62 | ||
dfeec247 | 63 | fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { |
c30ab7b3 | 64 | intravisit::walk_expr(self, ex); |
94222f64 XL |
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 | _ => {} | |
c30ab7b3 | 69 | } |
1a4d82fc | 70 | } |
c30ab7b3 | 71 | |
dfeec247 | 72 | fn visit_local(&mut self, loc: &'tcx hir::Local<'tcx>) { |
c30ab7b3 SL |
73 | intravisit::walk_local(self, loc); |
74 | ||
e74abb32 XL |
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), | |
29967ef6 | 80 | hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None), |
e74abb32 XL |
81 | }; |
82 | self.check_irrefutable(&loc.pat, msg, sp); | |
c295e0f8 | 83 | self.check_patterns(&loc.pat, Irrefutable); |
1a4d82fc | 84 | } |
c30ab7b3 | 85 | |
dfeec247 XL |
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); | |
c295e0f8 | 89 | self.check_patterns(¶m.pat, Irrefutable); |
c30ab7b3 | 90 | } |
1a4d82fc JJ |
91 | } |
92 | ||
e74abb32 | 93 | impl PatCtxt<'_, '_> { |
136023e0 | 94 | fn report_inlining_errors(&self) { |
cc61c64b XL |
95 | for error in &self.errors { |
96 | match *error { | |
c30ab7b3 | 97 | PatternError::StaticInPattern(span) => { |
2c00a5a8 XL |
98 | self.span_e0158(span, "statics cannot be referenced in patterns") |
99 | } | |
dc9dc135 | 100 | PatternError::AssocConstInPattern(span) => { |
2c00a5a8 | 101 | self.span_e0158(span, "associated consts cannot be referenced in patterns") |
c30ab7b3 | 102 | } |
ba9703b0 XL |
103 | PatternError::ConstParamInPattern(span) => { |
104 | self.span_e0158(span, "const parameters cannot be referenced in patterns") | |
105 | } | |
0531ce1d | 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 | ||
c295e0f8 XL |
122 | impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { |
123 | fn check_patterns(&self, pat: &Pat<'_>, rf: RefutableFlag) { | |
74b04a01 | 124 | pat.walk_always(|pat| check_borrow_conflicts_in_at_patterns(self, pat)); |
c295e0f8 | 125 | check_for_bindings_named_same_as_variants(self, pat, rf); |
cc61c64b | 126 | } |
1a4d82fc | 127 | |
c295e0f8 | 128 | fn lower_pattern( |
dfeec247 XL |
129 | &self, |
130 | cx: &mut MatchCheckCtxt<'p, 'tcx>, | |
131 | pat: &'tcx hir::Pat<'tcx>, | |
132 | have_errors: &mut bool, | |
c295e0f8 | 133 | ) -> &'p DeconstructedPat<'p, 'tcx> { |
3dfed10e | 134 | let mut patcx = PatCtxt::new(self.tcx, self.param_env, self.typeck_results); |
dfeec247 XL |
135 | patcx.include_lint_checks(); |
136 | let pattern = patcx.lower_pattern(pat); | |
c295e0f8 | 137 | let pattern: &_ = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern)); |
dfeec247 XL |
138 | if !patcx.errors.is_empty() { |
139 | *have_errors = true; | |
136023e0 | 140 | patcx.report_inlining_errors(); |
dfeec247 | 141 | } |
c295e0f8 | 142 | pattern |
dfeec247 XL |
143 | } |
144 | ||
c295e0f8 | 145 | fn new_cx(&self, hir_id: HirId) -> MatchCheckCtxt<'p, 'tcx> { |
f9f354fc XL |
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 | } | |
dfeec247 XL |
152 | } |
153 | ||
94222f64 | 154 | fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, expr: &hir::Expr<'_>, span: Span) { |
c295e0f8 | 155 | self.check_patterns(pat, Refutable); |
94222f64 | 156 | let mut cx = self.new_cx(expr.hir_id); |
c295e0f8 XL |
157 | let tpat = self.lower_pattern(&mut cx, pat, &mut false); |
158 | check_let_reachability(&mut cx, pat.hir_id, tpat, span); | |
94222f64 XL |
159 | } |
160 | ||
dfeec247 XL |
161 | fn check_match( |
162 | &mut self, | |
163 | scrut: &hir::Expr<'_>, | |
164 | arms: &'tcx [hir::Arm<'tcx>], | |
165 | source: hir::MatchSource, | |
166 | ) { | |
94222f64 XL |
167 | let mut cx = self.new_cx(scrut.hir_id); |
168 | ||
c30ab7b3 | 169 | for arm in arms { |
dfeec247 | 170 | // Check the arm for some things unrelated to exhaustiveness. |
c295e0f8 | 171 | self.check_patterns(&arm.pat, Refutable); |
fc512014 | 172 | if let Some(hir::Guard::IfLet(ref pat, _)) = arm.guard { |
c295e0f8 XL |
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()); | |
fc512014 XL |
176 | } |
177 | } | |
178 | ||
f9f354fc | 179 | let mut have_errors = false; |
1a4d82fc | 180 | |
fc512014 | 181 | let arms: Vec<_> = arms |
f9f354fc | 182 | .iter() |
fc512014 | 183 | .map(|hir::Arm { pat, guard, .. }| MatchArm { |
c295e0f8 | 184 | pat: self.lower_pattern(&mut cx, pat, &mut have_errors), |
fc512014 XL |
185 | hir_id: pat.hir_id, |
186 | has_guard: guard.is_some(), | |
f9f354fc XL |
187 | }) |
188 | .collect(); | |
1a4d82fc | 189 | |
fc512014 | 190 | // Bail out early if lowering failed. |
f9f354fc XL |
191 | if have_errors { |
192 | return; | |
193 | } | |
1a4d82fc | 194 | |
fc512014 XL |
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 | ||
c295e0f8 XL |
198 | match source { |
199 | hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => { | |
200 | report_arm_reachability(&cx, &report) | |
94222f64 | 201 | } |
c295e0f8 XL |
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 | } | |
f9f354fc | 206 | |
fc512014 | 207 | // Check if the match is exhaustive. |
fc512014 XL |
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 | } | |
c30ab7b3 SL |
213 | } |
214 | ||
dfeec247 | 215 | fn check_irrefutable(&self, pat: &'tcx Pat<'tcx>, origin: &str, sp: Option<Span>) { |
f9f354fc | 216 | let mut cx = self.new_cx(pat.hir_id); |
e74abb32 | 217 | |
c295e0f8 XL |
218 | let pattern = self.lower_pattern(&mut cx, pat, &mut false); |
219 | let pattern_ty = pattern.ty(); | |
fc512014 XL |
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 | } | |
f9f354fc | 230 | |
c295e0f8 | 231 | let joined_patterns = joined_uncovered_patterns(&cx, &witnesses); |
f9f354fc XL |
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, | |
e74abb32 XL |
264 | ); |
265 | } | |
f9f354fc XL |
266 | err.note( |
267 | "for more information, visit \ | |
268 | https://doc.rust-lang.org/book/ch18-02-refutability.html", | |
269 | ); | |
270 | } | |
e74abb32 | 271 | |
f9f354fc XL |
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(); | |
1a4d82fc JJ |
275 | } |
276 | } | |
277 | ||
e74abb32 XL |
278 | /// A path pattern was interpreted as a constant, not a new variable. |
279 | /// This caused an irrefutable match failure in e.g. `let`. | |
dfeec247 XL |
280 | fn const_not_var( |
281 | err: &mut DiagnosticBuilder<'_>, | |
282 | tcx: TyCtxt<'_>, | |
283 | pat: &Pat<'_>, | |
284 | path: &hir::Path<'_>, | |
285 | ) { | |
e74abb32 XL |
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 | ||
c295e0f8 XL |
307 | fn check_for_bindings_named_same_as_variants( |
308 | cx: &MatchVisitor<'_, '_, '_>, | |
309 | pat: &Pat<'_>, | |
310 | rf: RefutableFlag, | |
311 | ) { | |
dfeec247 | 312 | pat.walk_always(|p| { |
e74abb32 | 313 | if let hir::PatKind::Binding(_, _, ident, None) = p.kind { |
dfeec247 | 314 | if let Some(ty::BindByValue(hir::Mutability::Not)) = |
3dfed10e | 315 | cx.typeck_results.extract_binding_mode(cx.tcx.sess, p.hir_id, p.span) |
dfeec247 | 316 | { |
3dfed10e | 317 | let pat_ty = cx.typeck_results.pat_ty(p).peel_refs(); |
1b1a35ee | 318 | if let ty::Adt(edef, _) = pat_ty.kind() { |
e74abb32 XL |
319 | if edef.is_enum() |
320 | && edef.variants.iter().any(|variant| { | |
321 | variant.ident == ident && variant.ctor_kind == CtorKind::Const | |
322 | }) | |
323 | { | |
c295e0f8 | 324 | let variant_count = edef.variants.len(); |
74b04a01 XL |
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); | |
c295e0f8 | 331 | let mut err = lint.build(&format!( |
dfeec247 | 332 | "pattern binding `{}` is named the same as one \ |
c295e0f8 | 333 | of the variants of the type `{}`", |
dfeec247 | 334 | ident, ty_path |
c295e0f8 XL |
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(); | |
74b04a01 XL |
349 | }, |
350 | ) | |
8faf50e0 | 351 | } |
1a4d82fc JJ |
352 | } |
353 | } | |
1a4d82fc | 354 | } |
1a4d82fc JJ |
355 | }); |
356 | } | |
357 | ||
c30ab7b3 | 358 | /// Checks for common cases of "catchall" patterns that may not be intended as such. |
c295e0f8 XL |
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)), | |
e74abb32 | 364 | _ => false, |
c30ab7b3 | 365 | } |
1a4d82fc JJ |
366 | } |
367 | ||
dfeec247 | 368 | fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<Span>) { |
74b04a01 XL |
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 | }); | |
dfeec247 XL |
378 | } |
379 | ||
94222f64 XL |
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)); | |
6a06907d | 391 | diag.emit() |
94222f64 XL |
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 | ); | |
6a06907d | 411 | } |
94222f64 XL |
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 | ); | |
6a06907d | 419 | } |
94222f64 XL |
420 | LetSource::LetElse(..) => { |
421 | emit_diag!( | |
422 | lint, | |
423 | "`let...else`", | |
424 | "`else` clause is useless", | |
425 | "removing the `else` clause" | |
426 | ); | |
6a06907d | 427 | } |
94222f64 XL |
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 | ); | |
6a06907d | 435 | } |
74b04a01 | 436 | }); |
dfeec247 XL |
437 | } |
438 | ||
94222f64 | 439 | fn check_let_reachability<'p, 'tcx>( |
60c5eb7d | 440 | cx: &mut MatchCheckCtxt<'p, 'tcx>, |
fc512014 | 441 | pat_id: HirId, |
c295e0f8 | 442 | pat: &'p DeconstructedPat<'p, 'tcx>, |
94222f64 | 443 | span: Span, |
fc512014 XL |
444 | ) { |
445 | let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }]; | |
c295e0f8 XL |
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); | |
fc512014 XL |
452 | |
453 | if report.non_exhaustiveness_witnesses.is_empty() { | |
6a06907d | 454 | // The match is exhaustive, i.e. the `if let` pattern is irrefutable. |
94222f64 | 455 | irrefutable_let_pattern(cx.tcx, pat_id, span); |
fc512014 XL |
456 | } |
457 | } | |
458 | ||
459 | /// Report unreachable arms, if any. | |
c295e0f8 | 460 | fn report_arm_reachability<'p, 'tcx>( |
fc512014 XL |
461 | cx: &MatchCheckCtxt<'p, 'tcx>, |
462 | report: &UsefulnessReport<'p, 'tcx>, | |
c295e0f8 | 463 | ) { |
6a06907d | 464 | use Reachability::*; |
c30ab7b3 | 465 | let mut catchall = None; |
c295e0f8 | 466 | for (arm, is_useful) in report.arm_usefulness.iter() { |
fc512014 | 467 | match is_useful { |
c295e0f8 | 468 | Unreachable => unreachable_pattern(cx.tcx, arm.pat.span(), arm.hir_id, catchall), |
6a06907d | 469 | Reachable(unreachables) if unreachables.is_empty() => {} |
fc512014 | 470 | // The arm is reachable, but contains unreachable subpatterns (from or-patterns). |
6a06907d XL |
471 | Reachable(unreachables) => { |
472 | let mut unreachables = unreachables.clone(); | |
29967ef6 XL |
473 | // Emit lints in the order in which they occur in the file. |
474 | unreachables.sort_unstable(); | |
475 | for span in unreachables { | |
fc512014 | 476 | unreachable_pattern(cx.tcx, span, arm.hir_id, None); |
c30ab7b3 | 477 | } |
223e47cc | 478 | } |
60c5eb7d | 479 | } |
fc512014 | 480 | if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) { |
c295e0f8 | 481 | catchall = Some(arm.pat.span()); |
223e47cc LB |
482 | } |
483 | } | |
484 | } | |
485 | ||
fc512014 XL |
486 | /// Report that a match is not exhaustive. |
487 | fn non_exhaustive_match<'p, 'tcx>( | |
488 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
532ac7d7 XL |
489 | scrut_ty: Ty<'tcx>, |
490 | sp: Span, | |
c295e0f8 | 491 | witnesses: Vec<DeconstructedPat<'p, 'tcx>>, |
60c5eb7d | 492 | is_empty_match: bool, |
532ac7d7 | 493 | ) { |
1b1a35ee | 494 | let non_empty_enum = match scrut_ty.kind() { |
60c5eb7d XL |
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 { | |
c295e0f8 | 508 | let joined_patterns = joined_uncovered_patterns(cx, &witnesses); |
60c5eb7d XL |
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 | ||
17df50a5 XL |
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 | ||
e1599b0c XL |
522 | adt_defined_here(cx, &mut err, scrut_ty, &witnesses); |
523 | err.help( | |
524 | "ensure that all possible cases are being handled, \ | |
f035d41b | 525 | possibly by adding wildcards or more match arms", |
60c5eb7d | 526 | ); |
17df50a5 XL |
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 | )); | |
f035d41b XL |
532 | if (scrut_ty == cx.tcx.types.usize || scrut_ty == cx.tcx.types.isize) |
533 | && !is_empty_match | |
534 | && witnesses.len() == 1 | |
c295e0f8 | 535 | && matches!(witnesses[0].ctor(), Constructor::NonExhaustive) |
f035d41b XL |
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 | )); | |
fc512014 | 542 | if cx.tcx.sess.is_nightly_build() { |
f035d41b XL |
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 | } | |
5869c6ff XL |
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 | } | |
60c5eb7d | 555 | err.emit(); |
e1599b0c | 556 | } |
7cac9316 | 557 | |
c295e0f8 XL |
558 | crate fn joined_uncovered_patterns<'p, 'tcx>( |
559 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
560 | witnesses: &[DeconstructedPat<'p, 'tcx>], | |
561 | ) -> String { | |
e1599b0c | 562 | const LIMIT: usize = 3; |
c295e0f8 | 563 | let pat_to_str = |pat: &DeconstructedPat<'p, 'tcx>| pat.to_pat(cx).to_string(); |
e1599b0c XL |
564 | match witnesses { |
565 | [] => bug!(), | |
c295e0f8 | 566 | [witness] => format!("`{}`", witness.to_pat(cx)), |
e1599b0c | 567 | [head @ .., tail] if head.len() < LIMIT => { |
c295e0f8 XL |
568 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); |
569 | format!("`{}` and `{}`", head.join("`, `"), tail.to_pat(cx)) | |
1a4d82fc | 570 | } |
e1599b0c XL |
571 | _ => { |
572 | let (head, tail) = witnesses.split_at(LIMIT); | |
c295e0f8 | 573 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); |
e1599b0c | 574 | format!("`{}` and {} more", head.join("`, `"), tail.len()) |
9fa01778 | 575 | } |
1a4d82fc JJ |
576 | } |
577 | } | |
578 | ||
c295e0f8 XL |
579 | crate fn pattern_not_covered_label( |
580 | witnesses: &[DeconstructedPat<'_, '_>], | |
581 | joined_patterns: &str, | |
582 | ) -> String { | |
60c5eb7d | 583 | format!("pattern{} {} not covered", rustc_errors::pluralize!(witnesses.len()), joined_patterns) |
e1599b0c XL |
584 | } |
585 | ||
586 | /// Point at the definition of non-covered `enum` variants. | |
c295e0f8 XL |
587 | fn adt_defined_here<'p, 'tcx>( |
588 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
e1599b0c | 589 | err: &mut DiagnosticBuilder<'_>, |
c295e0f8 XL |
590 | ty: Ty<'tcx>, |
591 | witnesses: &[DeconstructedPat<'p, 'tcx>], | |
e1599b0c XL |
592 | ) { |
593 | let ty = ty.peel_refs(); | |
1b1a35ee | 594 | if let ty::Adt(def, _) = ty.kind() { |
e1599b0c XL |
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 { | |
c295e0f8 | 600 | for sp in maybe_point_at_variant(cx, def, witnesses.iter()) { |
e1599b0c XL |
601 | err.span_label(sp, "not covered"); |
602 | } | |
603 | } | |
604 | } | |
605 | } | |
606 | ||
c295e0f8 XL |
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::*; | |
532ac7d7 | 613 | let mut covered = vec![]; |
c295e0f8 XL |
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; | |
e1599b0c | 619 | } |
532ac7d7 | 620 | } |
c295e0f8 XL |
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); | |
532ac7d7 | 628 | } |
c295e0f8 | 629 | covered.extend(maybe_point_at_variant(cx, def, pattern.iter_fields())); |
532ac7d7 XL |
630 | } |
631 | covered | |
632 | } | |
633 | ||
74b04a01 | 634 | /// Check if a by-value binding is by-value. That is, check if the binding's type is not `Copy`. |
c295e0f8 | 635 | fn is_binding_by_move(cx: &MatchVisitor<'_, '_, '_>, hir_id: HirId, span: Span) -> bool { |
3dfed10e | 636 | !cx.typeck_results.node_type(hir_id).is_copy_modulo_regions(cx.tcx.at(span), cx.param_env) |
74b04a01 XL |
637 | } |
638 | ||
74b04a01 | 639 | /// Check that there are no borrow or move conflicts in `binding @ subpat` patterns. |
dfeec247 XL |
640 | /// |
641 | /// For example, this would reject: | |
642 | /// - `ref x @ Some(ref mut y)`, | |
74b04a01 XL |
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)`. | |
dfeec247 XL |
647 | /// |
648 | /// This analysis is *not* subsumed by NLL. | |
c295e0f8 | 649 | fn check_borrow_conflicts_in_at_patterns(cx: &MatchVisitor<'_, '_, '_>, pat: &Pat<'_>) { |
74b04a01 XL |
650 | // Extract `sub` in `binding @ sub`. |
651 | let (name, sub) = match &pat.kind { | |
652 | hir::PatKind::Binding(.., name, Some(sub)) => (*name, sub), | |
653 | _ => return, | |
dfeec247 | 654 | }; |
74b04a01 | 655 | let binding_span = pat.span.with_hi(name.span.hi()); |
dfeec247 | 656 | |
3dfed10e | 657 | let typeck_results = cx.typeck_results; |
74b04a01 | 658 | let sess = cx.tcx.sess; |
1a4d82fc | 659 | |
74b04a01 | 660 | // Get the binding move, extract the mutability if by-ref. |
3dfed10e | 661 | let mut_outer = match typeck_results.extract_binding_mode(sess, pat.hir_id, pat.span) { |
74b04a01 XL |
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, _| { | |
3dfed10e | 666 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
667 | Some(ty::BindByValue(_)) | None => {} |
668 | Some(ty::BindByReference(_)) => conflicts_ref.push(span), | |
dfeec247 | 669 | } |
74b04a01 XL |
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, | |
3dfed10e | 675 | typeck_results.node_type(pat.hir_id), |
74b04a01 XL |
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(); | |
dfeec247 | 682 | } |
74b04a01 XL |
683 | return; |
684 | } | |
685 | Some(ty::BindByValue(_)) | None => return, | |
686 | Some(ty::BindByReference(m)) => m, | |
687 | }; | |
dfeec247 | 688 | |
74b04a01 XL |
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| { | |
3dfed10e | 695 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
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. | |
dfeec247 | 703 | } |
74b04a01 | 704 | Some(ty::BindByValue(_)) | None => {} // `ref mut?` + by-copy is fine. |
dfeec247 XL |
705 | } |
706 | }); | |
74b04a01 XL |
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 | } | |
1a4d82fc JJ |
751 | } |
752 | ||
94222f64 XL |
753 | #[derive(Clone, Copy, Debug)] |
754 | pub enum LetSource { | |
755 | GenericLet, | |
756 | IfLet, | |
757 | IfLetGuard, | |
758 | LetElse(Span), | |
759 | WhileLet, | |
760 | } | |
dfeec247 | 761 | |
94222f64 XL |
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 | } | |
dfeec247 | 777 | } |
94222f64 XL |
778 | _ => {} |
779 | } | |
780 | let parent_parent = hir.get_parent_node(parent); | |
781 | let parent_parent_node = hir.get(parent_parent); | |
3157f602 | 782 | |
94222f64 XL |
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); | |
dfeec247 | 786 | |
94222f64 XL |
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 | |
223e47cc LB |
799 | } |
800 | } |