1mod inhabitance;
2mod maranget;
3mod normalize;
4mod pattern_matrix;
5mod types;
6mod witness;
7
8use crate::is_trivial_expression;
9
10pub use inhabitance::InhabitanceCache;
11pub use inhabitance::is_inhabited;
12pub use maranget::check_exhaustiveness;
13pub use normalize::{NormalizationContext, normalize_typed_pattern};
14pub use types::*;
15pub use witness::{format_pattern, format_witness};
16
17pub use self::PatternAnalysisContext as Context;
18
19use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
20use std::cell::RefCell;
21
22use crate::store::Store;
23use diagnostics::{DiagnosticSink, IssueKind, PatternIssue};
24use syntax::ast::{
25 Expression, Literal, MatchOrigin, Pattern, SelectArmPattern, Span, TypedPattern,
26};
27use syntax::types::Type;
28
29use maranget::is_useful;
30use normalize::normalize_arm;
31
32pub struct PatternAnalysisContext<'a> {
33 pub store: &'a Store,
34 cache: InhabitanceCache,
35 issues: RefCell<Vec<PatternIssue>>,
36 or_pattern_error_spans: &'a HashSet<Span>,
37}
38
39impl<'a> PatternAnalysisContext<'a> {
40 pub fn new(store: &'a Store, or_pattern_error_spans: &'a HashSet<Span>) -> Self {
41 Self {
42 store,
43 cache: InhabitanceCache::new(),
44 issues: RefCell::new(Vec::new()),
45 or_pattern_error_spans,
46 }
47 }
48
49 fn normalize_context(&self) -> NormalizationContext<'_> {
50 NormalizationContext {
51 store: self.store,
52 cache: &self.cache,
53 scrutinee_type: None,
54 }
55 }
56
57 fn normalize_context_for_match(&self, scrutinee_type: Type) -> NormalizationContext<'_> {
58 NormalizationContext {
59 store: self.store,
60 cache: &self.cache,
61 scrutinee_type: Some(scrutinee_type),
62 }
63 }
64
65 fn add_issue(&self, span: Span, kind: IssueKind) {
66 self.issues.borrow_mut().push(PatternIssue { span, kind });
67 }
68
69 pub fn take_issues(self) -> Vec<PatternIssue> {
70 self.issues.into_inner()
71 }
72}
73
74pub fn check(expression: &Expression, ctx: &PatternAnalysisContext, sink: &DiagnosticSink) {
75 match expression {
76 Expression::Literal { literal, .. } => {
77 if let Literal::Slice(expressions) = literal {
78 for e in expressions {
79 check(e, ctx, sink);
80 }
81 }
82 }
83
84 Expression::Function { params, body, .. } => {
85 for param in params {
86 if !check_refutability(¶m.pattern, param.typed_pattern.as_ref(), ctx, sink) {
87 return;
88 }
89 }
90 check(body, ctx, sink);
91 }
92 Expression::Lambda { params, body, .. } => {
93 for param in params {
94 if !check_refutability(¶m.pattern, param.typed_pattern.as_ref(), ctx, sink) {
95 return;
96 }
97 }
98 check(body, ctx, sink);
99 }
100
101 Expression::Block { items, .. } => {
102 for e in items {
103 check(e, ctx, sink);
104 }
105 }
106
107 Expression::TryBlock { items, .. } | Expression::RecoverBlock { items, .. } => {
108 for e in items {
109 check(e, ctx, sink);
110 }
111 }
112
113 Expression::Let {
114 binding,
115 value,
116 else_block,
117 typed_pattern,
118 ..
119 } => {
120 check(value, ctx, sink);
121
122 if let Some(else_expression) = else_block {
123 check(else_expression, ctx, sink);
124
125 if let Some(tp) = typed_pattern
126 && is_pattern_irrefutable(tp, ctx.store)
127 {
128 ctx.add_issue(binding.pattern.get_span(), IssueKind::RedundantLetElse);
129 }
130 } else if !check_refutability(&binding.pattern, typed_pattern.as_ref(), ctx, sink) {
131 }
132 }
133
134 Expression::Identifier { .. } => {}
135
136 Expression::Call {
137 expression,
138 args,
139 spread,
140 ..
141 } => {
142 check(expression, ctx, sink);
143 for e in args {
144 check(e, ctx, sink);
145 }
146 if let Some(spread_expr) = spread.as_ref() {
147 check(spread_expr, ctx, sink);
148 }
149 }
150
151 Expression::If {
152 condition,
153 consequence,
154 alternative,
155 ..
156 } => {
157 check(condition, ctx, sink);
158 check(consequence, ctx, sink);
159 check(alternative, ctx, sink);
160 }
161
162 Expression::IfLet { .. } => {
163 unreachable!("IfLet should be desugared to Match before pattern analysis")
164 }
165
166 Expression::Match {
167 subject,
168 arms,
169 origin,
170 span,
171 ..
172 } => {
173 check(subject, ctx, sink);
174
175 if !is_inhabited(&subject.get_type(), ctx.store, &ctx.cache) {
176 return;
177 }
178
179 let mut unions = HashMap::default();
180 let norm_ctx = ctx.normalize_context_for_match(subject.get_type());
181
182 let unguarded_rows: Vec<Row> = arms
183 .iter()
184 .filter(|arm| !arm.has_guard())
185 .flat_map(|arm| normalize_arm(arm, &mut unions, &norm_ctx))
186 .collect();
187
188 if let Err(witnesses) = check_exhaustiveness(&unguarded_rows, &unions) {
189 let first_witness = witnesses.first().expect("witnesses should not be empty");
190 let case = witness::format_witness(first_witness);
191
192 let subject_span = subject.get_span();
193 let match_span = Span::new(
194 span.file_id,
195 span.byte_offset,
196 (subject_span.byte_offset + subject_span.byte_length) - span.byte_offset,
197 );
198
199 sink.push(diagnostics::pattern::non_exhaustive(match_span, &case));
200 return;
201 }
202
203 if let MatchOrigin::IfLet { else_span } = origin {
204 check_desugared_if_let(arms, *else_span, ctx);
205 } else if !check_redundancy_with_guards(arms, &mut unions, &norm_ctx, sink) {
206 return;
207 }
208
209 for a in arms {
210 check(&a.expression, ctx, sink);
211 if let Some(guard) = &a.guard {
212 check(guard, ctx, sink);
213 }
214 }
215 }
216
217 Expression::Tuple { elements, .. } => {
218 for e in elements {
219 check(e, ctx, sink);
220 }
221 }
222
223 Expression::Enum { .. } => {}
224 Expression::Struct { .. } => {}
225 Expression::StructCall { spread, .. } => {
226 if let Some(expression) = &**spread {
227 check(expression, ctx, sink);
228 }
229 }
230 Expression::DotAccess { expression, .. } => check(expression, ctx, sink),
231 Expression::Assignment { .. } => {}
232
233 Expression::Return { expression, .. } => check(expression, ctx, sink),
234 Expression::Propagate { expression, .. } => check(expression, ctx, sink),
235
236 Expression::Interface { .. } => {}
237 Expression::ImplBlock { methods, .. } => {
238 for e in methods {
239 check(e, ctx, sink);
240 }
241 }
242
243 Expression::Binary { left, right, .. } => {
244 check(left, ctx, sink);
245 check(right, ctx, sink);
246 }
247
248 Expression::Paren { expression, .. } => check(expression, ctx, sink),
249 Expression::Unary { expression, .. } => check(expression, ctx, sink),
250 Expression::Const { expression, .. } => check(expression, ctx, sink),
251 Expression::Reference { expression, .. } => check(expression, ctx, sink),
252 Expression::IndexedAccess {
253 expression, index, ..
254 } => {
255 check(expression, ctx, sink);
256 check(index, ctx, sink);
257 }
258
259 Expression::Loop { body, .. } => check(body, ctx, sink),
260
261 Expression::While {
262 condition, body, ..
263 } => {
264 check(condition, ctx, sink);
265 check(body, ctx, sink);
266 }
267
268 Expression::WhileLet {
269 pattern,
270 scrutinee,
271 body,
272 typed_pattern,
273 ..
274 } => {
275 check(scrutinee, ctx, sink);
276 check(body, ctx, sink);
277
278 if let Some(tp) = typed_pattern
279 && is_pattern_irrefutable(tp, ctx.store)
280 {
281 sink.push(diagnostics::pattern::irrefutable_while_let(
282 pattern.get_span(),
283 ));
284 }
285 }
286
287 Expression::For {
288 binding,
289 iterable,
290 body,
291 ..
292 } => {
293 if !check_refutability(&binding.pattern, binding.typed_pattern.as_ref(), ctx, sink) {
294 return;
295 }
296 check(iterable, ctx, sink);
297 check(body, ctx, sink);
298 }
299
300 Expression::Task { expression, .. } => check(expression, ctx, sink),
301
302 Expression::Defer { expression, .. } => check(expression, ctx, sink),
303
304 Expression::Select { arms, .. } => {
305 for arm in arms {
306 match &arm.pattern {
307 SelectArmPattern::Receive {
308 receive_expression,
309 body,
310 ..
311 } => {
312 check(receive_expression.as_ref(), ctx, sink);
313 check(body.as_ref(), ctx, sink);
314 }
315 SelectArmPattern::Send {
316 send_expression,
317 body,
318 } => {
319 check(send_expression.as_ref(), ctx, sink);
320 check(body.as_ref(), ctx, sink);
321 }
322 SelectArmPattern::MatchReceive {
323 receive_expression,
324 arms: match_arms,
325 } => {
326 check(receive_expression.as_ref(), ctx, sink);
327 for match_arm in match_arms {
328 check(&match_arm.expression, ctx, sink);
329 }
330 }
331 SelectArmPattern::WildCard { body } => {
332 check(body.as_ref(), ctx, sink);
333 }
334 }
335 }
336 }
337 Expression::Range { start, end, .. } => {
338 if let Some(start_expression) = start {
339 check(start_expression, ctx, sink);
340 }
341 if let Some(end_expression) = end {
342 check(end_expression, ctx, sink);
343 }
344 }
345
346 Expression::Cast { expression, .. } => {
347 check(expression, ctx, sink);
348 }
349
350 Expression::TypeAlias { .. } => {}
351 Expression::VariableDeclaration { .. } => {}
352 Expression::ModuleImport { .. } => {}
353 Expression::Unit { .. } => {}
354 Expression::RawGo { .. } => {}
355 Expression::Break { value, .. } => {
356 if let Some(v) = value {
357 check(v, ctx, sink);
358 }
359 }
360 Expression::Continue { .. } => {}
361 Expression::NoOp => {}
362 Expression::ValueEnum { .. } => {}
363 }
364}
365
366fn check_redundancy_with_guards(
368 arms: &[syntax::ast::MatchArm],
369 unions: &mut UnionTable,
370 norm_ctx: &NormalizationContext,
371 sink: &DiagnosticSink,
372) -> bool {
373 let mut unguarded_previous: Vec<(usize, Row)> = vec![];
374
375 for (index, arm) in arms.iter().enumerate() {
376 let current_rows = normalize_arm(arm, unions, norm_ctx);
377
378 let mut current_arm_rows: Vec<Row> = vec![];
379
380 for (alt_index, current_row) in current_rows.iter().enumerate() {
381 let mut previous_rows: Vec<Row> =
382 unguarded_previous.iter().map(|(_, r)| r.clone()).collect();
383 previous_rows.extend(current_arm_rows.iter().cloned());
384
385 if !is_useful(&previous_rows, current_row, unions) {
386 let span = if let Pattern::Or { patterns, .. } = &arm.pattern {
387 patterns
388 .get(alt_index)
389 .map(|p| p.get_span())
390 .unwrap_or_else(|| arm.pattern.get_span())
391 } else {
392 arm.pattern.get_span()
393 };
394
395 let covered_by_same_arm = current_arm_rows
396 .iter()
397 .any(|prev| !is_useful(std::slice::from_ref(prev), current_row, unions));
398
399 let help = if covered_by_same_arm {
400 "This alternative is unreachable because it is already covered by an earlier alternative in the same arm"
401 .to_string()
402 } else {
403 let covering = unguarded_previous.iter().find_map(|(orig_idx, prev)| {
404 if !is_useful(std::slice::from_ref(prev), current_row, unions) {
405 Some((*orig_idx, prev))
406 } else {
407 None
408 }
409 });
410
411 if let Some((covering_index, covering_row)) = covering {
412 let covering_pattern = covering_row
413 .first()
414 .map(witness::format_pattern)
415 .unwrap_or_default();
416 format!(
417 "This pattern is unreachable because it is already covered by arm #{}: `{}`",
418 covering_index + 1,
419 covering_pattern
420 )
421 } else {
422 "This pattern is covered by earlier match arms and will never be reached"
423 .to_string()
424 }
425 };
426
427 let label = if covered_by_same_arm {
428 "this alternative is unreachable".to_string()
429 } else {
430 format!("arm #{} is unreachable", index + 1)
431 };
432
433 sink.push(diagnostics::pattern::redundant_arm(span, label, help));
434 return false;
435 }
436
437 current_arm_rows.push(current_row.clone());
438 }
439
440 if !arm.has_guard() {
443 for current_row in current_rows {
444 unguarded_previous.push((index, current_row));
445 }
446 }
447 }
448
449 true
450}
451
452fn check_desugared_if_let(
453 arms: &[syntax::ast::MatchArm],
454 else_span: Option<Span>,
455 ctx: &PatternAnalysisContext,
456) {
457 if arms.len() != 2 {
458 return;
459 }
460
461 let first_arm = &arms[0];
462 let second_arm = &arms[1];
463
464 let Some(tp) = &first_arm.typed_pattern else {
465 return;
466 };
467
468 if ctx
470 .or_pattern_error_spans
471 .contains(&first_arm.pattern.get_span())
472 {
473 return;
474 }
475
476 if is_pattern_irrefutable(tp, ctx.store) {
477 ctx.add_issue(first_arm.pattern.get_span(), IssueKind::RedundantIfLet);
478
479 if let Some(else_span) = else_span
480 && !is_trivial_expression(&second_arm.expression)
481 {
482 ctx.add_issue(else_span, IssueKind::UnreachableIfLetElse);
483 }
484 } else if let Some(else_span) = else_span
485 && is_trivial_expression(&second_arm.expression)
486 && !second_arm.expression.is_conditional()
487 {
488 ctx.add_issue(else_span, IssueKind::RedundantIfLetElse);
489 }
490}
491
492fn check_refutability(
494 pattern: &Pattern,
495 typed_pattern: Option<&TypedPattern>,
496 ctx: &PatternAnalysisContext,
497 sink: &DiagnosticSink,
498) -> bool {
499 let Some(typed_pattern) = typed_pattern else {
500 return true;
501 };
502
503 if matches!(typed_pattern, TypedPattern::Or { .. }) {
504 return true;
505 }
506
507 let mut unions = HashMap::default();
508 let norm_ctx = ctx.normalize_context();
509 let row = vec![normalize_typed_pattern(
510 typed_pattern,
511 &mut unions,
512 &norm_ctx,
513 )];
514
515 if let Err(witnesses) = check_exhaustiveness(&[row], &unions) {
516 let witness = witnesses.first().expect("witnesses not empty");
517 let witness_string = format_witness(witness);
518
519 let slice_info = if let Pattern::Slice { prefix, rest, .. } = pattern {
520 Some((prefix.len(), rest.is_present()))
521 } else {
522 None
523 };
524
525 sink.push(diagnostics::pattern::refutable_pattern(
526 pattern.get_span(),
527 &witness_string,
528 slice_info,
529 ));
530 return false;
531 }
532
533 true
534}
535
536pub fn is_pattern_irrefutable(typed_pattern: &TypedPattern, store: &Store) -> bool {
537 let cache = InhabitanceCache::new();
538 let norm_ctx = NormalizationContext {
539 store,
540 cache: &cache,
541 scrutinee_type: None,
542 };
543
544 let mut unions = HashMap::default();
545
546 let rows: Vec<Row> = if let TypedPattern::Or { alternatives } = typed_pattern {
547 alternatives
548 .iter()
549 .map(|alt| vec![normalize_typed_pattern(alt, &mut unions, &norm_ctx)])
550 .collect()
551 } else {
552 vec![vec![normalize_typed_pattern(
553 typed_pattern,
554 &mut unions,
555 &norm_ctx,
556 )]]
557 };
558
559 check_exhaustiveness(&rows, &unions).is_ok()
560}