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};
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(&param.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(&param.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
366/// Returns true if no redundancy found, false if an error was pushed to sink.
367fn 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        // Only unguarded arms count towards making later arms redundant.
441        // Guarded arms are treated as potentially non-matching.
442        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    // Suppress lints for patterns that already have or-pattern binding errors.
469    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
492/// Returns true if pattern is irrefutable, false if an error was pushed to sink.
493fn 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}