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