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