]>
Commit | Line | Data |
---|---|---|
1 | use crate::errors::*; | |
2 | ||
3 | use rustc_arena::{DroplessArena, TypedArena}; | |
4 | use rustc_ast::Mutability; | |
5 | use rustc_data_structures::fx::FxIndexSet; | |
6 | use rustc_data_structures::stack::ensure_sufficient_stack; | |
7 | use rustc_errors::{codes::*, struct_span_code_err, Applicability, ErrorGuaranteed, MultiSpan}; | |
8 | use rustc_hir::def::*; | |
9 | use rustc_hir::def_id::LocalDefId; | |
10 | use rustc_hir::{self as hir, BindingMode, ByRef, HirId}; | |
11 | use rustc_middle::bug; | |
12 | use rustc_middle::middle::limits::get_limit_size; | |
13 | use rustc_middle::thir::visit::Visitor; | |
14 | use rustc_middle::thir::*; | |
15 | use rustc_middle::ty::print::with_no_trimmed_paths; | |
16 | use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; | |
17 | use rustc_pattern_analysis::errors::Uncovered; | |
18 | use rustc_pattern_analysis::rustc::{ | |
19 | Constructor, DeconstructedPat, MatchArm, RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport, | |
20 | WitnessPat, | |
21 | }; | |
22 | use rustc_session::lint::builtin::{ | |
23 | BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, | |
24 | }; | |
25 | use rustc_span::hygiene::DesugaringKind; | |
26 | use rustc_span::{sym, Span}; | |
27 | use tracing::instrument; | |
28 | ||
29 | pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> { | |
30 | let typeck_results = tcx.typeck(def_id); | |
31 | let (thir, expr) = tcx.thir_body(def_id)?; | |
32 | let thir = thir.borrow(); | |
33 | let pattern_arena = TypedArena::default(); | |
34 | let dropless_arena = DroplessArena::default(); | |
35 | let mut visitor = MatchVisitor { | |
36 | tcx, | |
37 | thir: &*thir, | |
38 | typeck_results, | |
39 | param_env: tcx.param_env(def_id), | |
40 | lint_level: tcx.local_def_id_to_hir_id(def_id), | |
41 | let_source: LetSource::None, | |
42 | pattern_arena: &pattern_arena, | |
43 | dropless_arena: &dropless_arena, | |
44 | error: Ok(()), | |
45 | }; | |
46 | visitor.visit_expr(&thir[expr]); | |
47 | ||
48 | let origin = match tcx.def_kind(def_id) { | |
49 | DefKind::AssocFn | DefKind::Fn => "function argument", | |
50 | DefKind::Closure => "closure argument", | |
51 | // other types of MIR don't have function parameters, and we don't need to | |
52 | // categorize those for the irrefutable check. | |
53 | _ if thir.params.is_empty() => "", | |
54 | kind => bug!("unexpected function parameters in THIR: {kind:?} {def_id:?}"), | |
55 | }; | |
56 | ||
57 | for param in thir.params.iter() { | |
58 | if let Some(box ref pattern) = param.pat { | |
59 | visitor.check_binding_is_irrefutable(pattern, origin, None, None); | |
60 | } | |
61 | } | |
62 | visitor.error | |
63 | } | |
64 | ||
65 | #[derive(Debug, Copy, Clone, PartialEq)] | |
66 | enum RefutableFlag { | |
67 | Irrefutable, | |
68 | Refutable, | |
69 | } | |
70 | use RefutableFlag::*; | |
71 | ||
72 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
73 | enum LetSource { | |
74 | None, | |
75 | PlainLet, | |
76 | IfLet, | |
77 | IfLetGuard, | |
78 | LetElse, | |
79 | WhileLet, | |
80 | } | |
81 | ||
82 | struct MatchVisitor<'p, 'tcx> { | |
83 | tcx: TyCtxt<'tcx>, | |
84 | param_env: ty::ParamEnv<'tcx>, | |
85 | typeck_results: &'tcx ty::TypeckResults<'tcx>, | |
86 | thir: &'p Thir<'tcx>, | |
87 | lint_level: HirId, | |
88 | let_source: LetSource, | |
89 | pattern_arena: &'p TypedArena<DeconstructedPat<'p, 'tcx>>, | |
90 | dropless_arena: &'p DroplessArena, | |
91 | /// Tracks if we encountered an error while checking this body. That the first function to | |
92 | /// report it stores it here. Some functions return `Result` to allow callers to short-circuit | |
93 | /// on error, but callers don't need to store it here again. | |
94 | error: Result<(), ErrorGuaranteed>, | |
95 | } | |
96 | ||
97 | // Visitor for a thir body. This calls `check_match`, `check_let` and `check_let_chain` as | |
98 | // appropriate. | |
99 | impl<'p, 'tcx> Visitor<'p, 'tcx> for MatchVisitor<'p, 'tcx> { | |
100 | fn thir(&self) -> &'p Thir<'tcx> { | |
101 | self.thir | |
102 | } | |
103 | ||
104 | #[instrument(level = "trace", skip(self))] | |
105 | fn visit_arm(&mut self, arm: &'p Arm<'tcx>) { | |
106 | self.with_lint_level(arm.lint_level, |this| { | |
107 | if let Some(expr) = arm.guard { | |
108 | this.with_let_source(LetSource::IfLetGuard, |this| { | |
109 | this.visit_expr(&this.thir[expr]) | |
110 | }); | |
111 | } | |
112 | this.visit_pat(&arm.pattern); | |
113 | this.visit_expr(&self.thir[arm.body]); | |
114 | }); | |
115 | } | |
116 | ||
117 | #[instrument(level = "trace", skip(self))] | |
118 | fn visit_expr(&mut self, ex: &'p Expr<'tcx>) { | |
119 | match ex.kind { | |
120 | ExprKind::Scope { value, lint_level, .. } => { | |
121 | self.with_lint_level(lint_level, |this| { | |
122 | this.visit_expr(&this.thir[value]); | |
123 | }); | |
124 | return; | |
125 | } | |
126 | ExprKind::If { cond, then, else_opt, if_then_scope: _ } => { | |
127 | // Give a specific `let_source` for the condition. | |
128 | let let_source = match ex.span.desugaring_kind() { | |
129 | Some(DesugaringKind::WhileLoop) => LetSource::WhileLet, | |
130 | _ => LetSource::IfLet, | |
131 | }; | |
132 | self.with_let_source(let_source, |this| this.visit_expr(&self.thir[cond])); | |
133 | self.with_let_source(LetSource::None, |this| { | |
134 | this.visit_expr(&this.thir[then]); | |
135 | if let Some(else_) = else_opt { | |
136 | this.visit_expr(&this.thir[else_]); | |
137 | } | |
138 | }); | |
139 | return; | |
140 | } | |
141 | ExprKind::Match { scrutinee, scrutinee_hir_id: _, box ref arms, match_source } => { | |
142 | self.check_match(scrutinee, arms, match_source, ex.span); | |
143 | } | |
144 | ExprKind::Let { box ref pat, expr } => { | |
145 | self.check_let(pat, Some(expr), ex.span); | |
146 | } | |
147 | ExprKind::LogicalOp { op: LogicalOp::And, .. } | |
148 | if !matches!(self.let_source, LetSource::None) => | |
149 | { | |
150 | let mut chain_refutabilities = Vec::new(); | |
151 | let Ok(()) = self.visit_land(ex, &mut chain_refutabilities) else { return }; | |
152 | // If at least one of the operands is a `let ... = ...`. | |
153 | if chain_refutabilities.iter().any(|x| x.is_some()) { | |
154 | self.check_let_chain(chain_refutabilities, ex.span); | |
155 | } | |
156 | return; | |
157 | } | |
158 | _ => {} | |
159 | }; | |
160 | self.with_let_source(LetSource::None, |this| visit::walk_expr(this, ex)); | |
161 | } | |
162 | ||
163 | fn visit_stmt(&mut self, stmt: &'p Stmt<'tcx>) { | |
164 | match stmt.kind { | |
165 | StmtKind::Let { | |
166 | box ref pattern, initializer, else_block, lint_level, span, .. | |
167 | } => { | |
168 | self.with_lint_level(lint_level, |this| { | |
169 | let let_source = | |
170 | if else_block.is_some() { LetSource::LetElse } else { LetSource::PlainLet }; | |
171 | this.with_let_source(let_source, |this| { | |
172 | this.check_let(pattern, initializer, span) | |
173 | }); | |
174 | visit::walk_stmt(this, stmt); | |
175 | }); | |
176 | } | |
177 | StmtKind::Expr { .. } => { | |
178 | visit::walk_stmt(self, stmt); | |
179 | } | |
180 | } | |
181 | } | |
182 | } | |
183 | ||
184 | impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | |
185 | #[instrument(level = "trace", skip(self, f))] | |
186 | fn with_let_source(&mut self, let_source: LetSource, f: impl FnOnce(&mut Self)) { | |
187 | let old_let_source = self.let_source; | |
188 | self.let_source = let_source; | |
189 | ensure_sufficient_stack(|| f(self)); | |
190 | self.let_source = old_let_source; | |
191 | } | |
192 | ||
193 | fn with_lint_level<T>( | |
194 | &mut self, | |
195 | new_lint_level: LintLevel, | |
196 | f: impl FnOnce(&mut Self) -> T, | |
197 | ) -> T { | |
198 | if let LintLevel::Explicit(hir_id) = new_lint_level { | |
199 | let old_lint_level = self.lint_level; | |
200 | self.lint_level = hir_id; | |
201 | let ret = f(self); | |
202 | self.lint_level = old_lint_level; | |
203 | ret | |
204 | } else { | |
205 | f(self) | |
206 | } | |
207 | } | |
208 | ||
209 | /// Visit a nested chain of `&&`. Used for if-let chains. This must call `visit_expr` on the | |
210 | /// subexpressions we are not handling ourselves. | |
211 | fn visit_land( | |
212 | &mut self, | |
213 | ex: &'p Expr<'tcx>, | |
214 | accumulator: &mut Vec<Option<(Span, RefutableFlag)>>, | |
215 | ) -> Result<(), ErrorGuaranteed> { | |
216 | match ex.kind { | |
217 | ExprKind::Scope { value, lint_level, .. } => self.with_lint_level(lint_level, |this| { | |
218 | this.visit_land(&this.thir[value], accumulator) | |
219 | }), | |
220 | ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => { | |
221 | // We recurse into the lhs only, because `&&` chains associate to the left. | |
222 | let res_lhs = self.visit_land(&self.thir[lhs], accumulator); | |
223 | let res_rhs = self.visit_land_rhs(&self.thir[rhs])?; | |
224 | accumulator.push(res_rhs); | |
225 | res_lhs | |
226 | } | |
227 | _ => { | |
228 | let res = self.visit_land_rhs(ex)?; | |
229 | accumulator.push(res); | |
230 | Ok(()) | |
231 | } | |
232 | } | |
233 | } | |
234 | ||
235 | /// Visit the right-hand-side of a `&&`. Used for if-let chains. Returns `Some` if the | |
236 | /// expression was ultimately a `let ... = ...`, and `None` if it was a normal boolean | |
237 | /// expression. This must call `visit_expr` on the subexpressions we are not handling ourselves. | |
238 | fn visit_land_rhs( | |
239 | &mut self, | |
240 | ex: &'p Expr<'tcx>, | |
241 | ) -> Result<Option<(Span, RefutableFlag)>, ErrorGuaranteed> { | |
242 | match ex.kind { | |
243 | ExprKind::Scope { value, lint_level, .. } => { | |
244 | self.with_lint_level(lint_level, |this| this.visit_land_rhs(&this.thir[value])) | |
245 | } | |
246 | ExprKind::Let { box ref pat, expr } => { | |
247 | let expr = &self.thir()[expr]; | |
248 | self.with_let_source(LetSource::None, |this| { | |
249 | this.visit_expr(expr); | |
250 | }); | |
251 | Ok(Some((ex.span, self.is_let_irrefutable(pat, Some(expr))?))) | |
252 | } | |
253 | _ => { | |
254 | self.with_let_source(LetSource::None, |this| { | |
255 | this.visit_expr(ex); | |
256 | }); | |
257 | Ok(None) | |
258 | } | |
259 | } | |
260 | } | |
261 | ||
262 | fn lower_pattern( | |
263 | &mut self, | |
264 | cx: &PatCtxt<'p, 'tcx>, | |
265 | pat: &'p Pat<'tcx>, | |
266 | ) -> Result<&'p DeconstructedPat<'p, 'tcx>, ErrorGuaranteed> { | |
267 | if let Err(err) = pat.pat_error_reported() { | |
268 | self.error = Err(err); | |
269 | Err(err) | |
270 | } else { | |
271 | // Check the pattern for some things unrelated to exhaustiveness. | |
272 | let refutable = if cx.refutable { Refutable } else { Irrefutable }; | |
273 | let mut err = Ok(()); | |
274 | pat.walk_always(|pat| { | |
275 | check_borrow_conflicts_in_at_patterns(self, pat); | |
276 | check_for_bindings_named_same_as_variants(self, pat, refutable); | |
277 | err = err.and(check_never_pattern(cx, pat)); | |
278 | }); | |
279 | err?; | |
280 | Ok(self.pattern_arena.alloc(cx.lower_pat(pat))) | |
281 | } | |
282 | } | |
283 | ||
284 | /// Inspects the match scrutinee expression to determine whether the place it evaluates to may | |
285 | /// hold invalid data. | |
286 | fn is_known_valid_scrutinee(&self, scrutinee: &Expr<'tcx>) -> bool { | |
287 | use ExprKind::*; | |
288 | match &scrutinee.kind { | |
289 | // Pointers can validly point to a place with invalid data. It is undecided whether | |
290 | // references can too, so we conservatively assume they can. | |
291 | Deref { .. } => false, | |
292 | // Inherit validity of the parent place, unless the parent is an union. | |
293 | Field { lhs, .. } => { | |
294 | let lhs = &self.thir()[*lhs]; | |
295 | match lhs.ty.kind() { | |
296 | ty::Adt(def, _) if def.is_union() => false, | |
297 | _ => self.is_known_valid_scrutinee(lhs), | |
298 | } | |
299 | } | |
300 | // Essentially a field access. | |
301 | Index { lhs, .. } => { | |
302 | let lhs = &self.thir()[*lhs]; | |
303 | self.is_known_valid_scrutinee(lhs) | |
304 | } | |
305 | ||
306 | // No-op. | |
307 | Scope { value, .. } => self.is_known_valid_scrutinee(&self.thir()[*value]), | |
308 | ||
309 | // Casts don't cause a load. | |
310 | NeverToAny { source } | |
311 | | Cast { source } | |
312 | | Use { source } | |
313 | | PointerCoercion { source, .. } | |
314 | | PlaceTypeAscription { source, .. } | |
315 | | ValueTypeAscription { source, .. } => { | |
316 | self.is_known_valid_scrutinee(&self.thir()[*source]) | |
317 | } | |
318 | ||
319 | // These diverge. | |
320 | Become { .. } | Break { .. } | Continue { .. } | Return { .. } => true, | |
321 | ||
322 | // These are statements that evaluate to `()`. | |
323 | Assign { .. } | AssignOp { .. } | InlineAsm { .. } | Let { .. } => true, | |
324 | ||
325 | // These evaluate to a value. | |
326 | AddressOf { .. } | |
327 | | Adt { .. } | |
328 | | Array { .. } | |
329 | | Binary { .. } | |
330 | | Block { .. } | |
331 | | Borrow { .. } | |
332 | | Box { .. } | |
333 | | Call { .. } | |
334 | | Closure { .. } | |
335 | | ConstBlock { .. } | |
336 | | ConstParam { .. } | |
337 | | If { .. } | |
338 | | Literal { .. } | |
339 | | LogicalOp { .. } | |
340 | | Loop { .. } | |
341 | | Match { .. } | |
342 | | NamedConst { .. } | |
343 | | NonHirLiteral { .. } | |
344 | | OffsetOf { .. } | |
345 | | Repeat { .. } | |
346 | | StaticRef { .. } | |
347 | | ThreadLocalRef { .. } | |
348 | | Tuple { .. } | |
349 | | Unary { .. } | |
350 | | UpvarRef { .. } | |
351 | | VarRef { .. } | |
352 | | ZstLiteral { .. } | |
353 | | Yield { .. } => true, | |
354 | } | |
355 | } | |
356 | ||
357 | fn new_cx( | |
358 | &self, | |
359 | refutability: RefutableFlag, | |
360 | whole_match_span: Option<Span>, | |
361 | scrutinee: Option<&Expr<'tcx>>, | |
362 | scrut_span: Span, | |
363 | ) -> PatCtxt<'p, 'tcx> { | |
364 | let refutable = match refutability { | |
365 | Irrefutable => false, | |
366 | Refutable => true, | |
367 | }; | |
368 | // If we don't have a scrutinee we're either a function parameter or a `let x;`. Both cases | |
369 | // require validity. | |
370 | let known_valid_scrutinee = | |
371 | scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true); | |
372 | PatCtxt { | |
373 | tcx: self.tcx, | |
374 | typeck_results: self.typeck_results, | |
375 | param_env: self.param_env, | |
376 | module: self.tcx.parent_module(self.lint_level).to_def_id(), | |
377 | dropless_arena: self.dropless_arena, | |
378 | match_lint_level: self.lint_level, | |
379 | whole_match_span, | |
380 | scrut_span, | |
381 | refutable, | |
382 | known_valid_scrutinee, | |
383 | } | |
384 | } | |
385 | ||
386 | fn analyze_patterns( | |
387 | &mut self, | |
388 | cx: &PatCtxt<'p, 'tcx>, | |
389 | arms: &[MatchArm<'p, 'tcx>], | |
390 | scrut_ty: Ty<'tcx>, | |
391 | ) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> { | |
392 | let pattern_complexity_limit = | |
393 | get_limit_size(cx.tcx.hir().krate_attrs(), cx.tcx.sess, sym::pattern_complexity); | |
394 | let report = | |
395 | rustc_pattern_analysis::analyze_match(&cx, &arms, scrut_ty, pattern_complexity_limit) | |
396 | .map_err(|err| { | |
397 | self.error = Err(err); | |
398 | err | |
399 | })?; | |
400 | ||
401 | // Warn unreachable subpatterns. | |
402 | for (arm, is_useful) in report.arm_usefulness.iter() { | |
403 | if let Usefulness::Useful(redundant_subpats) = is_useful | |
404 | && !redundant_subpats.is_empty() | |
405 | { | |
406 | let mut redundant_subpats = redundant_subpats.clone(); | |
407 | // Emit lints in the order in which they occur in the file. | |
408 | redundant_subpats.sort_unstable_by_key(|pat| pat.data().span); | |
409 | for pat in redundant_subpats { | |
410 | report_unreachable_pattern(cx, arm.arm_data, pat.data().span, None) | |
411 | } | |
412 | } | |
413 | } | |
414 | Ok(report) | |
415 | } | |
416 | ||
417 | #[instrument(level = "trace", skip(self))] | |
418 | fn check_let(&mut self, pat: &'p Pat<'tcx>, scrutinee: Option<ExprId>, span: Span) { | |
419 | assert!(self.let_source != LetSource::None); | |
420 | let scrut = scrutinee.map(|id| &self.thir[id]); | |
421 | if let LetSource::PlainLet = self.let_source { | |
422 | self.check_binding_is_irrefutable(pat, "local binding", scrut, Some(span)) | |
423 | } else { | |
424 | let Ok(refutability) = self.is_let_irrefutable(pat, scrut) else { return }; | |
425 | if matches!(refutability, Irrefutable) { | |
426 | report_irrefutable_let_patterns( | |
427 | self.tcx, | |
428 | self.lint_level, | |
429 | self.let_source, | |
430 | 1, | |
431 | span, | |
432 | ); | |
433 | } | |
434 | } | |
435 | } | |
436 | ||
437 | fn check_match( | |
438 | &mut self, | |
439 | scrut: ExprId, | |
440 | arms: &[ArmId], | |
441 | source: hir::MatchSource, | |
442 | expr_span: Span, | |
443 | ) { | |
444 | let scrut = &self.thir[scrut]; | |
445 | let cx = self.new_cx(Refutable, Some(expr_span), Some(scrut), scrut.span); | |
446 | ||
447 | let mut tarms = Vec::with_capacity(arms.len()); | |
448 | for &arm in arms { | |
449 | let arm = &self.thir.arms[arm]; | |
450 | let got_error = self.with_lint_level(arm.lint_level, |this| { | |
451 | let Ok(pat) = this.lower_pattern(&cx, &arm.pattern) else { return true }; | |
452 | let arm = | |
453 | MatchArm { pat, arm_data: this.lint_level, has_guard: arm.guard.is_some() }; | |
454 | tarms.push(arm); | |
455 | false | |
456 | }); | |
457 | if got_error { | |
458 | return; | |
459 | } | |
460 | } | |
461 | ||
462 | let Ok(report) = self.analyze_patterns(&cx, &tarms, scrut.ty) else { return }; | |
463 | ||
464 | match source { | |
465 | // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` | |
466 | // when the iterator is an uninhabited type. unreachable_code will trigger instead. | |
467 | hir::MatchSource::ForLoopDesugar if arms.len() == 1 => {} | |
468 | hir::MatchSource::ForLoopDesugar | |
469 | | hir::MatchSource::Postfix | |
470 | | hir::MatchSource::Normal | |
471 | | hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report), | |
472 | // Unreachable patterns in try and await expressions occur when one of | |
473 | // the arms are an uninhabited type. Which is OK. | |
474 | hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar(_) => {} | |
475 | } | |
476 | ||
477 | // Check if the match is exhaustive. | |
478 | let witnesses = report.non_exhaustiveness_witnesses; | |
479 | if !witnesses.is_empty() { | |
480 | if source == hir::MatchSource::ForLoopDesugar && arms.len() == 2 { | |
481 | // the for loop pattern is not irrefutable | |
482 | let pat = &self.thir[arms[1]].pattern; | |
483 | // `pat` should be `Some(<pat_field>)` from a desugared for loop. | |
484 | debug_assert_eq!(pat.span.desugaring_kind(), Some(DesugaringKind::ForLoop)); | |
485 | let PatKind::Variant { ref subpatterns, .. } = pat.kind else { bug!() }; | |
486 | let [pat_field] = &subpatterns[..] else { bug!() }; | |
487 | self.check_binding_is_irrefutable( | |
488 | &pat_field.pattern, | |
489 | "`for` loop binding", | |
490 | None, | |
491 | None, | |
492 | ); | |
493 | } else { | |
494 | // span after scrutinee, or after `.match`. That is, the braces, arms, | |
495 | // and any whitespace preceding the braces. | |
496 | let braces_span = match source { | |
497 | hir::MatchSource::Normal => scrut | |
498 | .span | |
499 | .find_ancestor_in_same_ctxt(expr_span) | |
500 | .map(|scrut_span| scrut_span.shrink_to_hi().with_hi(expr_span.hi())), | |
501 | hir::MatchSource::Postfix => { | |
502 | // This is horrendous, and we should deal with it by just | |
503 | // stashing the span of the braces somewhere (like in the match source). | |
504 | scrut.span.find_ancestor_in_same_ctxt(expr_span).and_then(|scrut_span| { | |
505 | let sm = self.tcx.sess.source_map(); | |
506 | let brace_span = sm.span_extend_to_next_char(scrut_span, '{', true); | |
507 | if sm.span_to_snippet(sm.next_point(brace_span)).as_deref() == Ok("{") { | |
508 | let sp = brace_span.shrink_to_hi().with_hi(expr_span.hi()); | |
509 | // We also need to extend backwards for whitespace | |
510 | sm.span_extend_prev_while(sp, |c| c.is_whitespace()).ok() | |
511 | } else { | |
512 | None | |
513 | } | |
514 | }) | |
515 | } | |
516 | hir::MatchSource::ForLoopDesugar | |
517 | | hir::MatchSource::TryDesugar(_) | |
518 | | hir::MatchSource::AwaitDesugar | |
519 | | hir::MatchSource::FormatArgs => None, | |
520 | }; | |
521 | self.error = Err(report_non_exhaustive_match( | |
522 | &cx, | |
523 | self.thir, | |
524 | scrut.ty, | |
525 | scrut.span, | |
526 | witnesses, | |
527 | arms, | |
528 | braces_span, | |
529 | )); | |
530 | } | |
531 | } | |
532 | } | |
533 | ||
534 | #[instrument(level = "trace", skip(self))] | |
535 | fn check_let_chain( | |
536 | &mut self, | |
537 | chain_refutabilities: Vec<Option<(Span, RefutableFlag)>>, | |
538 | whole_chain_span: Span, | |
539 | ) { | |
540 | assert!(self.let_source != LetSource::None); | |
541 | ||
542 | if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, Irrefutable)))) { | |
543 | // The entire chain is made up of irrefutable `let` statements | |
544 | report_irrefutable_let_patterns( | |
545 | self.tcx, | |
546 | self.lint_level, | |
547 | self.let_source, | |
548 | chain_refutabilities.len(), | |
549 | whole_chain_span, | |
550 | ); | |
551 | return; | |
552 | } | |
553 | ||
554 | if let Some(until) = | |
555 | chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, Irrefutable)))) | |
556 | && until > 0 | |
557 | { | |
558 | // The chain has a non-zero prefix of irrefutable `let` statements. | |
559 | ||
560 | // Check if the let source is while, for there is no alternative place to put a prefix, | |
561 | // and we shouldn't lint. | |
562 | // For let guards inside a match, prefixes might use bindings of the match pattern, | |
563 | // so can't always be moved out. | |
564 | // FIXME: Add checking whether the bindings are actually used in the prefix, | |
565 | // and lint if they are not. | |
566 | if !matches!(self.let_source, LetSource::WhileLet | LetSource::IfLetGuard) { | |
567 | // Emit the lint | |
568 | let prefix = &chain_refutabilities[..until]; | |
569 | let span_start = prefix[0].unwrap().0; | |
570 | let span_end = prefix.last().unwrap().unwrap().0; | |
571 | let span = span_start.to(span_end); | |
572 | let count = prefix.len(); | |
573 | self.tcx.emit_node_span_lint( | |
574 | IRREFUTABLE_LET_PATTERNS, | |
575 | self.lint_level, | |
576 | span, | |
577 | LeadingIrrefutableLetPatterns { count }, | |
578 | ); | |
579 | } | |
580 | } | |
581 | ||
582 | if let Some(from) = | |
583 | chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, Irrefutable)))) | |
584 | && from != (chain_refutabilities.len() - 1) | |
585 | { | |
586 | // The chain has a non-empty suffix of irrefutable `let` statements | |
587 | let suffix = &chain_refutabilities[from + 1..]; | |
588 | let span_start = suffix[0].unwrap().0; | |
589 | let span_end = suffix.last().unwrap().unwrap().0; | |
590 | let span = span_start.to(span_end); | |
591 | let count = suffix.len(); | |
592 | self.tcx.emit_node_span_lint( | |
593 | IRREFUTABLE_LET_PATTERNS, | |
594 | self.lint_level, | |
595 | span, | |
596 | TrailingIrrefutableLetPatterns { count }, | |
597 | ); | |
598 | } | |
599 | } | |
600 | ||
601 | fn analyze_binding( | |
602 | &mut self, | |
603 | pat: &'p Pat<'tcx>, | |
604 | refutability: RefutableFlag, | |
605 | scrut: Option<&Expr<'tcx>>, | |
606 | ) -> Result<(PatCtxt<'p, 'tcx>, UsefulnessReport<'p, 'tcx>), ErrorGuaranteed> { | |
607 | let cx = self.new_cx(refutability, None, scrut, pat.span); | |
608 | let pat = self.lower_pattern(&cx, pat)?; | |
609 | let arms = [MatchArm { pat, arm_data: self.lint_level, has_guard: false }]; | |
610 | let report = self.analyze_patterns(&cx, &arms, pat.ty().inner())?; | |
611 | Ok((cx, report)) | |
612 | } | |
613 | ||
614 | fn is_let_irrefutable( | |
615 | &mut self, | |
616 | pat: &'p Pat<'tcx>, | |
617 | scrut: Option<&Expr<'tcx>>, | |
618 | ) -> Result<RefutableFlag, ErrorGuaranteed> { | |
619 | let (cx, report) = self.analyze_binding(pat, Refutable, scrut)?; | |
620 | // Report if the pattern is unreachable, which can only occur when the type is uninhabited. | |
621 | report_arm_reachability(&cx, &report); | |
622 | // If the list of witnesses is empty, the match is exhaustive, i.e. the `if let` pattern is | |
623 | // irrefutable. | |
624 | Ok(if report.non_exhaustiveness_witnesses.is_empty() { Irrefutable } else { Refutable }) | |
625 | } | |
626 | ||
627 | #[instrument(level = "trace", skip(self))] | |
628 | fn check_binding_is_irrefutable( | |
629 | &mut self, | |
630 | pat: &'p Pat<'tcx>, | |
631 | origin: &str, | |
632 | scrut: Option<&Expr<'tcx>>, | |
633 | sp: Option<Span>, | |
634 | ) { | |
635 | let pattern_ty = pat.ty; | |
636 | ||
637 | let Ok((cx, report)) = self.analyze_binding(pat, Irrefutable, scrut) else { return }; | |
638 | let witnesses = report.non_exhaustiveness_witnesses; | |
639 | if witnesses.is_empty() { | |
640 | // The pattern is irrefutable. | |
641 | return; | |
642 | } | |
643 | ||
644 | let inform = sp.is_some().then_some(Inform); | |
645 | let mut let_suggestion = None; | |
646 | let mut misc_suggestion = None; | |
647 | let mut interpreted_as_const = None; | |
648 | ||
649 | if let PatKind::Constant { .. } | |
650 | | PatKind::AscribeUserType { | |
651 | subpattern: box Pat { kind: PatKind::Constant { .. }, .. }, | |
652 | .. | |
653 | } = pat.kind | |
654 | && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span) | |
655 | { | |
656 | // If the pattern to match is an integer literal: | |
657 | if snippet.chars().all(|c| c.is_digit(10)) { | |
658 | // Then give a suggestion, the user might've meant to create a binding instead. | |
659 | misc_suggestion = Some(MiscPatternSuggestion::AttemptedIntegerLiteral { | |
660 | start_span: pat.span.shrink_to_lo(), | |
661 | }); | |
662 | } else if snippet.chars().all(|c| c.is_alphanumeric() || c == '_') { | |
663 | interpreted_as_const = | |
664 | Some(InterpretedAsConst { span: pat.span, variable: snippet }); | |
665 | } | |
666 | } | |
667 | ||
668 | if let Some(span) = sp | |
669 | && self.tcx.sess.source_map().is_span_accessible(span) | |
670 | && interpreted_as_const.is_none() | |
671 | && scrut.is_some() | |
672 | { | |
673 | let mut bindings = vec![]; | |
674 | pat.each_binding(|name, _, _, _| bindings.push(name)); | |
675 | ||
676 | let semi_span = span.shrink_to_hi(); | |
677 | let start_span = span.shrink_to_lo(); | |
678 | let end_span = semi_span.shrink_to_lo(); | |
679 | let count = witnesses.len(); | |
680 | ||
681 | let_suggestion = Some(if bindings.is_empty() { | |
682 | SuggestLet::If { start_span, semi_span, count } | |
683 | } else { | |
684 | SuggestLet::Else { end_span, count } | |
685 | }); | |
686 | }; | |
687 | ||
688 | let adt_defined_here = report_adt_defined_here(self.tcx, pattern_ty, &witnesses, false); | |
689 | ||
690 | // Emit an extra note if the first uncovered witness would be uninhabited | |
691 | // if we disregard visibility. | |
692 | let witness_1_is_privately_uninhabited = if (self.tcx.features().exhaustive_patterns | |
693 | || self.tcx.features().min_exhaustive_patterns) | |
694 | && let Some(witness_1) = witnesses.get(0) | |
695 | && let ty::Adt(adt, args) = witness_1.ty().kind() | |
696 | && adt.is_enum() | |
697 | && let Constructor::Variant(variant_index) = witness_1.ctor() | |
698 | { | |
699 | let variant = adt.variant(*variant_index); | |
700 | let inhabited = variant.inhabited_predicate(self.tcx, *adt).instantiate(self.tcx, args); | |
701 | assert!(inhabited.apply(self.tcx, cx.param_env, cx.module)); | |
702 | !inhabited.apply_ignore_module(self.tcx, cx.param_env) | |
703 | } else { | |
704 | false | |
705 | }; | |
706 | ||
707 | self.error = Err(self.tcx.dcx().emit_err(PatternNotCovered { | |
708 | span: pat.span, | |
709 | origin, | |
710 | uncovered: Uncovered::new(pat.span, &cx, witnesses), | |
711 | inform, | |
712 | interpreted_as_const, | |
713 | witness_1_is_privately_uninhabited: witness_1_is_privately_uninhabited.then_some(()), | |
714 | _p: (), | |
715 | pattern_ty, | |
716 | let_suggestion, | |
717 | misc_suggestion, | |
718 | adt_defined_here, | |
719 | })); | |
720 | } | |
721 | } | |
722 | ||
723 | /// Check if a by-value binding is by-value. That is, check if the binding's type is not `Copy`. | |
724 | /// Check that there are no borrow or move conflicts in `binding @ subpat` patterns. | |
725 | /// | |
726 | /// For example, this would reject: | |
727 | /// - `ref x @ Some(ref mut y)`, | |
728 | /// - `ref mut x @ Some(ref y)`, | |
729 | /// - `ref mut x @ Some(ref mut y)`, | |
730 | /// - `ref mut? x @ Some(y)`, and | |
731 | /// - `x @ Some(ref mut? y)`. | |
732 | /// | |
733 | /// This analysis is *not* subsumed by NLL. | |
734 | fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat: &Pat<'tcx>) { | |
735 | // Extract `sub` in `binding @ sub`. | |
736 | let PatKind::Binding { name, mode, ty, subpattern: Some(box ref sub), .. } = pat.kind else { | |
737 | return; | |
738 | }; | |
739 | ||
740 | let is_binding_by_move = |ty: Ty<'tcx>| !ty.is_copy_modulo_regions(cx.tcx, cx.param_env); | |
741 | ||
742 | let sess = cx.tcx.sess; | |
743 | ||
744 | // Get the binding move, extract the mutability if by-ref. | |
745 | let mut_outer = match mode.0 { | |
746 | ByRef::No if is_binding_by_move(ty) => { | |
747 | // We have `x @ pat` where `x` is by-move. Reject all borrows in `pat`. | |
748 | let mut conflicts_ref = Vec::new(); | |
749 | sub.each_binding(|_, mode, _, span| { | |
750 | if matches!(mode, ByRef::Yes(_)) { | |
751 | conflicts_ref.push(span) | |
752 | } | |
753 | }); | |
754 | if !conflicts_ref.is_empty() { | |
755 | sess.dcx().emit_err(BorrowOfMovedValue { | |
756 | binding_span: pat.span, | |
757 | conflicts_ref, | |
758 | name, | |
759 | ty, | |
760 | suggest_borrowing: Some(pat.span.shrink_to_lo()), | |
761 | }); | |
762 | } | |
763 | return; | |
764 | } | |
765 | ByRef::No => return, | |
766 | ByRef::Yes(m) => m, | |
767 | }; | |
768 | ||
769 | // We now have `ref $mut_outer binding @ sub` (semantically). | |
770 | // Recurse into each binding in `sub` and find mutability or move conflicts. | |
771 | let mut conflicts_move = Vec::new(); | |
772 | let mut conflicts_mut_mut = Vec::new(); | |
773 | let mut conflicts_mut_ref = Vec::new(); | |
774 | sub.each_binding(|name, mode, ty, span| { | |
775 | match mode { | |
776 | ByRef::Yes(mut_inner) => match (mut_outer, mut_inner) { | |
777 | // Both sides are `ref`. | |
778 | (Mutability::Not, Mutability::Not) => {} | |
779 | // 2x `ref mut`. | |
780 | (Mutability::Mut, Mutability::Mut) => { | |
781 | conflicts_mut_mut.push(Conflict::Mut { span, name }) | |
782 | } | |
783 | (Mutability::Not, Mutability::Mut) => { | |
784 | conflicts_mut_ref.push(Conflict::Mut { span, name }) | |
785 | } | |
786 | (Mutability::Mut, Mutability::Not) => { | |
787 | conflicts_mut_ref.push(Conflict::Ref { span, name }) | |
788 | } | |
789 | }, | |
790 | ByRef::No if is_binding_by_move(ty) => { | |
791 | conflicts_move.push(Conflict::Moved { span, name }) // `ref mut?` + by-move conflict. | |
792 | } | |
793 | ByRef::No => {} // `ref mut?` + by-copy is fine. | |
794 | } | |
795 | }); | |
796 | ||
797 | let report_mut_mut = !conflicts_mut_mut.is_empty(); | |
798 | let report_mut_ref = !conflicts_mut_ref.is_empty(); | |
799 | let report_move_conflict = !conflicts_move.is_empty(); | |
800 | ||
801 | let mut occurrences = match mut_outer { | |
802 | Mutability::Mut => vec![Conflict::Mut { span: pat.span, name }], | |
803 | Mutability::Not => vec![Conflict::Ref { span: pat.span, name }], | |
804 | }; | |
805 | occurrences.extend(conflicts_mut_mut); | |
806 | occurrences.extend(conflicts_mut_ref); | |
807 | occurrences.extend(conflicts_move); | |
808 | ||
809 | // Report errors if any. | |
810 | if report_mut_mut { | |
811 | // Report mutability conflicts for e.g. `ref mut x @ Some(ref mut y)`. | |
812 | sess.dcx().emit_err(MultipleMutBorrows { span: pat.span, occurrences }); | |
813 | } else if report_mut_ref { | |
814 | // Report mutability conflicts for e.g. `ref x @ Some(ref mut y)` or the converse. | |
815 | match mut_outer { | |
816 | Mutability::Mut => { | |
817 | sess.dcx().emit_err(AlreadyMutBorrowed { span: pat.span, occurrences }); | |
818 | } | |
819 | Mutability::Not => { | |
820 | sess.dcx().emit_err(AlreadyBorrowed { span: pat.span, occurrences }); | |
821 | } | |
822 | }; | |
823 | } else if report_move_conflict { | |
824 | // Report by-ref and by-move conflicts, e.g. `ref x @ y`. | |
825 | sess.dcx().emit_err(MovedWhileBorrowed { span: pat.span, occurrences }); | |
826 | } | |
827 | } | |
828 | ||
829 | fn check_for_bindings_named_same_as_variants( | |
830 | cx: &MatchVisitor<'_, '_>, | |
831 | pat: &Pat<'_>, | |
832 | rf: RefutableFlag, | |
833 | ) { | |
834 | if let PatKind::Binding { | |
835 | name, | |
836 | mode: BindingMode(ByRef::No, Mutability::Not), | |
837 | subpattern: None, | |
838 | ty, | |
839 | .. | |
840 | } = pat.kind | |
841 | && let ty::Adt(edef, _) = ty.peel_refs().kind() | |
842 | && edef.is_enum() | |
843 | && edef | |
844 | .variants() | |
845 | .iter() | |
846 | .any(|variant| variant.name == name && variant.ctor_kind() == Some(CtorKind::Const)) | |
847 | { | |
848 | let variant_count = edef.variants().len(); | |
849 | let ty_path = with_no_trimmed_paths!(cx.tcx.def_path_str(edef.did())); | |
850 | cx.tcx.emit_node_span_lint( | |
851 | BINDINGS_WITH_VARIANT_NAME, | |
852 | cx.lint_level, | |
853 | pat.span, | |
854 | BindingsWithVariantName { | |
855 | // If this is an irrefutable pattern, and there's > 1 variant, | |
856 | // then we can't actually match on this. Applying the below | |
857 | // suggestion would produce code that breaks on `check_binding_is_irrefutable`. | |
858 | suggestion: if rf == Refutable || variant_count == 1 { | |
859 | Some(pat.span) | |
860 | } else { | |
861 | None | |
862 | }, | |
863 | ty_path, | |
864 | name, | |
865 | }, | |
866 | ) | |
867 | } | |
868 | } | |
869 | ||
870 | /// Check that never patterns are only used on inhabited types. | |
871 | fn check_never_pattern<'tcx>( | |
872 | cx: &PatCtxt<'_, 'tcx>, | |
873 | pat: &Pat<'tcx>, | |
874 | ) -> Result<(), ErrorGuaranteed> { | |
875 | if let PatKind::Never = pat.kind { | |
876 | if !cx.is_uninhabited(pat.ty) { | |
877 | return Err(cx.tcx.dcx().emit_err(NonEmptyNeverPattern { span: pat.span, ty: pat.ty })); | |
878 | } | |
879 | } | |
880 | Ok(()) | |
881 | } | |
882 | ||
883 | fn report_irrefutable_let_patterns( | |
884 | tcx: TyCtxt<'_>, | |
885 | id: HirId, | |
886 | source: LetSource, | |
887 | count: usize, | |
888 | span: Span, | |
889 | ) { | |
890 | macro_rules! emit_diag { | |
891 | ($lint:tt) => {{ | |
892 | tcx.emit_node_span_lint(IRREFUTABLE_LET_PATTERNS, id, span, $lint { count }); | |
893 | }}; | |
894 | } | |
895 | ||
896 | match source { | |
897 | LetSource::None | LetSource::PlainLet => bug!(), | |
898 | LetSource::IfLet => emit_diag!(IrrefutableLetPatternsIfLet), | |
899 | LetSource::IfLetGuard => emit_diag!(IrrefutableLetPatternsIfLetGuard), | |
900 | LetSource::LetElse => emit_diag!(IrrefutableLetPatternsLetElse), | |
901 | LetSource::WhileLet => emit_diag!(IrrefutableLetPatternsWhileLet), | |
902 | } | |
903 | } | |
904 | ||
905 | /// Report unreachable arms, if any. | |
906 | fn report_unreachable_pattern<'p, 'tcx>( | |
907 | cx: &PatCtxt<'p, 'tcx>, | |
908 | hir_id: HirId, | |
909 | span: Span, | |
910 | catchall: Option<Span>, | |
911 | ) { | |
912 | cx.tcx.emit_node_span_lint( | |
913 | UNREACHABLE_PATTERNS, | |
914 | hir_id, | |
915 | span, | |
916 | UnreachablePattern { span: if catchall.is_some() { Some(span) } else { None }, catchall }, | |
917 | ); | |
918 | } | |
919 | ||
920 | /// Report unreachable arms, if any. | |
921 | fn report_arm_reachability<'p, 'tcx>(cx: &PatCtxt<'p, 'tcx>, report: &UsefulnessReport<'p, 'tcx>) { | |
922 | let mut catchall = None; | |
923 | for (arm, is_useful) in report.arm_usefulness.iter() { | |
924 | if matches!(is_useful, Usefulness::Redundant) { | |
925 | report_unreachable_pattern(cx, arm.arm_data, arm.pat.data().span, catchall) | |
926 | } | |
927 | if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) { | |
928 | catchall = Some(arm.pat.data().span); | |
929 | } | |
930 | } | |
931 | } | |
932 | ||
933 | /// Checks for common cases of "catchall" patterns that may not be intended as such. | |
934 | fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool { | |
935 | match pat.ctor() { | |
936 | Constructor::Wildcard => true, | |
937 | Constructor::Struct | Constructor::Ref => { | |
938 | pat.iter_fields().all(|ipat| pat_is_catchall(&ipat.pat)) | |
939 | } | |
940 | _ => false, | |
941 | } | |
942 | } | |
943 | ||
944 | /// Report that a match is not exhaustive. | |
945 | fn report_non_exhaustive_match<'p, 'tcx>( | |
946 | cx: &PatCtxt<'p, 'tcx>, | |
947 | thir: &Thir<'tcx>, | |
948 | scrut_ty: Ty<'tcx>, | |
949 | sp: Span, | |
950 | witnesses: Vec<WitnessPat<'p, 'tcx>>, | |
951 | arms: &[ArmId], | |
952 | braces_span: Option<Span>, | |
953 | ) -> ErrorGuaranteed { | |
954 | let is_empty_match = arms.is_empty(); | |
955 | let non_empty_enum = match scrut_ty.kind() { | |
956 | ty::Adt(def, _) => def.is_enum() && !def.variants().is_empty(), | |
957 | _ => false, | |
958 | }; | |
959 | // In the case of an empty match, replace the '`_` not covered' diagnostic with something more | |
960 | // informative. | |
961 | if is_empty_match && !non_empty_enum { | |
962 | return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty { | |
963 | cx, | |
964 | scrut_span: sp, | |
965 | braces_span, | |
966 | ty: scrut_ty, | |
967 | }); | |
968 | } | |
969 | ||
970 | // FIXME: migration of this diagnostic will require list support | |
971 | let joined_patterns = joined_uncovered_patterns(cx, &witnesses); | |
972 | let mut err = struct_span_code_err!( | |
973 | cx.tcx.dcx(), | |
974 | sp, | |
975 | E0004, | |
976 | "non-exhaustive patterns: {joined_patterns} not covered" | |
977 | ); | |
978 | err.span_label( | |
979 | sp, | |
980 | format!( | |
981 | "pattern{} {} not covered", | |
982 | rustc_errors::pluralize!(witnesses.len()), | |
983 | joined_patterns | |
984 | ), | |
985 | ); | |
986 | ||
987 | // Point at the definition of non-covered `enum` variants. | |
988 | if let Some(AdtDefinedHere { adt_def_span, ty, variants }) = | |
989 | report_adt_defined_here(cx.tcx, scrut_ty, &witnesses, true) | |
990 | { | |
991 | let mut multi_span = MultiSpan::from_span(adt_def_span); | |
992 | multi_span.push_span_label(adt_def_span, ""); | |
993 | for Variant { span } in variants { | |
994 | multi_span.push_span_label(span, "not covered"); | |
995 | } | |
996 | err.span_note(multi_span, format!("`{ty}` defined here")); | |
997 | } | |
998 | err.note(format!("the matched value is of type `{}`", scrut_ty)); | |
999 | ||
1000 | if !is_empty_match { | |
1001 | let mut non_exhaustive_tys = FxIndexSet::default(); | |
1002 | // Look at the first witness. | |
1003 | collect_non_exhaustive_tys(cx, &witnesses[0], &mut non_exhaustive_tys); | |
1004 | ||
1005 | for ty in non_exhaustive_tys { | |
1006 | if ty.is_ptr_sized_integral() { | |
1007 | if ty == cx.tcx.types.usize { | |
1008 | err.note(format!( | |
1009 | "`{ty}` does not have a fixed maximum value, so half-open ranges are necessary to match \ | |
1010 | exhaustively", | |
1011 | )); | |
1012 | } else if ty == cx.tcx.types.isize { | |
1013 | err.note(format!( | |
1014 | "`{ty}` does not have fixed minimum and maximum values, so half-open ranges are necessary to match \ | |
1015 | exhaustively", | |
1016 | )); | |
1017 | } | |
1018 | } else if ty == cx.tcx.types.str_ { | |
1019 | err.note("`&str` cannot be matched exhaustively, so a wildcard `_` is necessary"); | |
1020 | } else if cx.is_foreign_non_exhaustive_enum(cx.reveal_opaque_ty(ty)) { | |
1021 | err.note(format!("`{ty}` is marked as non-exhaustive, so a wildcard `_` is necessary to match exhaustively")); | |
1022 | } | |
1023 | } | |
1024 | } | |
1025 | ||
1026 | if let ty::Ref(_, sub_ty, _) = scrut_ty.kind() { | |
1027 | if !sub_ty.is_inhabited_from(cx.tcx, cx.module, cx.param_env) { | |
1028 | err.note("references are always considered inhabited"); | |
1029 | } | |
1030 | } | |
1031 | ||
1032 | // Whether we suggest the actual missing patterns or `_`. | |
1033 | let suggest_the_witnesses = witnesses.len() < 4; | |
1034 | let suggested_arm = if suggest_the_witnesses { | |
1035 | let pattern = witnesses | |
1036 | .iter() | |
1037 | .map(|witness| cx.hoist_witness_pat(witness).to_string()) | |
1038 | .collect::<Vec<String>>() | |
1039 | .join(" | "); | |
1040 | if witnesses.iter().all(|p| p.is_never_pattern()) && cx.tcx.features().never_patterns { | |
1041 | // Arms with a never pattern don't take a body. | |
1042 | pattern | |
1043 | } else { | |
1044 | format!("{pattern} => todo!()") | |
1045 | } | |
1046 | } else { | |
1047 | format!("_ => todo!()") | |
1048 | }; | |
1049 | let mut suggestion = None; | |
1050 | let sm = cx.tcx.sess.source_map(); | |
1051 | match arms { | |
1052 | [] if let Some(braces_span) = braces_span => { | |
1053 | // Get the span for the empty match body `{}`. | |
1054 | let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) { | |
1055 | (format!("\n{snippet}"), " ") | |
1056 | } else { | |
1057 | (" ".to_string(), "") | |
1058 | }; | |
1059 | suggestion = Some(( | |
1060 | braces_span, | |
1061 | format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",), | |
1062 | )); | |
1063 | } | |
1064 | [only] => { | |
1065 | let only = &thir[*only]; | |
1066 | let (pre_indentation, is_multiline) = if let Some(snippet) = | |
1067 | sm.indentation_before(only.span) | |
1068 | && let Ok(with_trailing) = | |
1069 | sm.span_extend_while(only.span, |c| c.is_whitespace() || c == ',') | |
1070 | && sm.is_multiline(with_trailing) | |
1071 | { | |
1072 | (format!("\n{snippet}"), true) | |
1073 | } else { | |
1074 | (" ".to_string(), false) | |
1075 | }; | |
1076 | let only_body = &thir[only.body]; | |
1077 | let comma = if matches!(only_body.kind, ExprKind::Block { .. }) | |
1078 | && only.span.eq_ctxt(only_body.span) | |
1079 | && is_multiline | |
1080 | { | |
1081 | "" | |
1082 | } else { | |
1083 | "," | |
1084 | }; | |
1085 | suggestion = Some(( | |
1086 | only.span.shrink_to_hi(), | |
1087 | format!("{comma}{pre_indentation}{suggested_arm}"), | |
1088 | )); | |
1089 | } | |
1090 | [.., prev, last] => { | |
1091 | let prev = &thir[*prev]; | |
1092 | let last = &thir[*last]; | |
1093 | if prev.span.eq_ctxt(last.span) { | |
1094 | let last_body = &thir[last.body]; | |
1095 | let comma = if matches!(last_body.kind, ExprKind::Block { .. }) | |
1096 | && last.span.eq_ctxt(last_body.span) | |
1097 | { | |
1098 | "" | |
1099 | } else { | |
1100 | "," | |
1101 | }; | |
1102 | let spacing = if sm.is_multiline(prev.span.between(last.span)) { | |
1103 | sm.indentation_before(last.span).map(|indent| format!("\n{indent}")) | |
1104 | } else { | |
1105 | Some(" ".to_string()) | |
1106 | }; | |
1107 | if let Some(spacing) = spacing { | |
1108 | suggestion = Some(( | |
1109 | last.span.shrink_to_hi(), | |
1110 | format!("{comma}{spacing}{suggested_arm}"), | |
1111 | )); | |
1112 | } | |
1113 | } | |
1114 | } | |
1115 | _ => {} | |
1116 | } | |
1117 | ||
1118 | let msg = format!( | |
1119 | "ensure that all possible cases are being handled by adding a match arm with a wildcard \ | |
1120 | pattern{}{}", | |
1121 | if witnesses.len() > 1 && suggest_the_witnesses && suggestion.is_some() { | |
1122 | ", a match arm with multiple or-patterns" | |
1123 | } else { | |
1124 | // we are either not suggesting anything, or suggesting `_` | |
1125 | "" | |
1126 | }, | |
1127 | match witnesses.len() { | |
1128 | // non-exhaustive enum case | |
1129 | 0 if suggestion.is_some() => " as shown", | |
1130 | 0 => "", | |
1131 | 1 if suggestion.is_some() => " or an explicit pattern as shown", | |
1132 | 1 => " or an explicit pattern", | |
1133 | _ if suggestion.is_some() => " as shown, or multiple match arms", | |
1134 | _ => " or multiple match arms", | |
1135 | }, | |
1136 | ); | |
1137 | ||
1138 | let all_arms_have_guards = arms.iter().all(|arm_id| thir[*arm_id].guard.is_some()); | |
1139 | if !is_empty_match && all_arms_have_guards { | |
1140 | err.subdiagnostic(NonExhaustiveMatchAllArmsGuarded); | |
1141 | } | |
1142 | if let Some((span, sugg)) = suggestion { | |
1143 | err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders); | |
1144 | } else { | |
1145 | err.help(msg); | |
1146 | } | |
1147 | err.emit() | |
1148 | } | |
1149 | ||
1150 | fn joined_uncovered_patterns<'p, 'tcx>( | |
1151 | cx: &PatCtxt<'p, 'tcx>, | |
1152 | witnesses: &[WitnessPat<'p, 'tcx>], | |
1153 | ) -> String { | |
1154 | const LIMIT: usize = 3; | |
1155 | let pat_to_str = |pat: &WitnessPat<'p, 'tcx>| cx.hoist_witness_pat(pat).to_string(); | |
1156 | match witnesses { | |
1157 | [] => bug!(), | |
1158 | [witness] => format!("`{}`", cx.hoist_witness_pat(witness)), | |
1159 | [head @ .., tail] if head.len() < LIMIT => { | |
1160 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); | |
1161 | format!("`{}` and `{}`", head.join("`, `"), cx.hoist_witness_pat(tail)) | |
1162 | } | |
1163 | _ => { | |
1164 | let (head, tail) = witnesses.split_at(LIMIT); | |
1165 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); | |
1166 | format!("`{}` and {} more", head.join("`, `"), tail.len()) | |
1167 | } | |
1168 | } | |
1169 | } | |
1170 | ||
1171 | fn collect_non_exhaustive_tys<'tcx>( | |
1172 | cx: &PatCtxt<'_, 'tcx>, | |
1173 | pat: &WitnessPat<'_, 'tcx>, | |
1174 | non_exhaustive_tys: &mut FxIndexSet<Ty<'tcx>>, | |
1175 | ) { | |
1176 | if matches!(pat.ctor(), Constructor::NonExhaustive) { | |
1177 | non_exhaustive_tys.insert(pat.ty().inner()); | |
1178 | } | |
1179 | if let Constructor::IntRange(range) = pat.ctor() { | |
1180 | if cx.is_range_beyond_boundaries(range, *pat.ty()) { | |
1181 | // The range denotes the values before `isize::MIN` or the values after `usize::MAX`/`isize::MAX`. | |
1182 | non_exhaustive_tys.insert(pat.ty().inner()); | |
1183 | } | |
1184 | } | |
1185 | pat.iter_fields() | |
1186 | .for_each(|field_pat| collect_non_exhaustive_tys(cx, field_pat, non_exhaustive_tys)) | |
1187 | } | |
1188 | ||
1189 | fn report_adt_defined_here<'tcx>( | |
1190 | tcx: TyCtxt<'tcx>, | |
1191 | ty: Ty<'tcx>, | |
1192 | witnesses: &[WitnessPat<'_, 'tcx>], | |
1193 | point_at_non_local_ty: bool, | |
1194 | ) -> Option<AdtDefinedHere<'tcx>> { | |
1195 | let ty = ty.peel_refs(); | |
1196 | let ty::Adt(def, _) = ty.kind() else { | |
1197 | return None; | |
1198 | }; | |
1199 | let adt_def_span = | |
1200 | tcx.hir().get_if_local(def.did()).and_then(|node| node.ident()).map(|ident| ident.span); | |
1201 | let adt_def_span = if point_at_non_local_ty { | |
1202 | adt_def_span.unwrap_or_else(|| tcx.def_span(def.did())) | |
1203 | } else { | |
1204 | adt_def_span? | |
1205 | }; | |
1206 | ||
1207 | let mut variants = vec![]; | |
1208 | for span in maybe_point_at_variant(tcx, *def, witnesses.iter().take(5)) { | |
1209 | variants.push(Variant { span }); | |
1210 | } | |
1211 | Some(AdtDefinedHere { adt_def_span, ty, variants }) | |
1212 | } | |
1213 | ||
1214 | fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'p>( | |
1215 | tcx: TyCtxt<'tcx>, | |
1216 | def: AdtDef<'tcx>, | |
1217 | patterns: impl Iterator<Item = &'a WitnessPat<'p, 'tcx>>, | |
1218 | ) -> Vec<Span> { | |
1219 | let mut covered = vec![]; | |
1220 | for pattern in patterns { | |
1221 | if let Constructor::Variant(variant_index) = pattern.ctor() { | |
1222 | if let ty::Adt(this_def, _) = pattern.ty().kind() | |
1223 | && this_def.did() != def.did() | |
1224 | { | |
1225 | continue; | |
1226 | } | |
1227 | let sp = def.variant(*variant_index).ident(tcx).span; | |
1228 | if covered.contains(&sp) { | |
1229 | // Don't point at variants that have already been covered due to other patterns to avoid | |
1230 | // visual clutter. | |
1231 | continue; | |
1232 | } | |
1233 | covered.push(sp); | |
1234 | } | |
1235 | covered.extend(maybe_point_at_variant(tcx, def, pattern.iter_fields())); | |
1236 | } | |
1237 | covered | |
1238 | } |