Skip to main content

lisette_semantics/pattern_analysis/
mod.rs

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(&param.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(&param.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
350/// Returns true if no redundancy found, false if an error was pushed to sink.
351fn 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        // Only unguarded arms count towards making later arms redundant.
425        // Guarded arms are treated as potentially non-matching.
426        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    // Suppress lints for patterns that already have or-pattern binding errors.
453    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
476/// Returns true if pattern is irrefutable, false if an error was pushed to sink.
477fn 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}