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