Skip to main content

aver/ir/
matches.rs

1use crate::ast::{BinOp, Expr, Literal, MatchArm, Pattern, Spanned};
2
3use super::{CallLowerCtx, SemanticConstructor, WrapperKind, classify_constructor_name};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum DispatchLiteral {
7    Int(i64),
8    Float(String),
9    Bool(bool),
10    Str(String),
11    Unit,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum SemanticDispatchPattern {
16    Literal(DispatchLiteral),
17    EmptyList,
18    NoneValue,
19    WrapperTag(WrapperKind),
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct BoolMatchShape {
24    pub true_arm_index: usize,
25    pub false_arm_index: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum BoolCompareOp {
30    Eq,
31    Lt,
32    Gt,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum BoolSubjectPlan<'a> {
37    Expr(&'a Expr),
38    Compare {
39        lhs: &'a Spanned<Expr>,
40        rhs: &'a Spanned<Expr>,
41        op: BoolCompareOp,
42        invert: bool,
43    },
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct ListMatchShape {
48    pub empty_arm_index: usize,
49    pub cons_arm_index: usize,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum DispatchBindingPlan {
54    None,
55    WrapperPayload(String),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct DispatchArmPlan {
60    pub pattern: SemanticDispatchPattern,
61    pub arm_index: usize,
62    pub binding: DispatchBindingPlan,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct DispatchDefaultPlan {
67    pub arm_index: usize,
68    pub binding_name: Option<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct DispatchTableShape {
73    pub entries: Vec<DispatchArmPlan>,
74    pub default_arm: Option<DispatchDefaultPlan>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum MatchDispatchPlan {
79    Bool(BoolMatchShape),
80    List(ListMatchShape),
81    Table(DispatchTableShape),
82}
83
84pub fn classify_bool_match_shape_from_patterns(patterns: &[&Pattern]) -> Option<BoolMatchShape> {
85    if patterns.len() != 2 {
86        return None;
87    }
88
89    match (patterns[0], patterns[1]) {
90        (Pattern::Literal(Literal::Bool(true)), Pattern::Literal(Literal::Bool(false))) => {
91            Some(BoolMatchShape {
92                true_arm_index: 0,
93                false_arm_index: 1,
94            })
95        }
96        (Pattern::Literal(Literal::Bool(false)), Pattern::Literal(Literal::Bool(true))) => {
97            Some(BoolMatchShape {
98                true_arm_index: 1,
99                false_arm_index: 0,
100            })
101        }
102        (Pattern::Literal(Literal::Bool(true)), Pattern::Wildcard | Pattern::Ident(_)) => {
103            Some(BoolMatchShape {
104                true_arm_index: 0,
105                false_arm_index: 1,
106            })
107        }
108        _ => None,
109    }
110}
111
112pub fn classify_list_match_shape_from_patterns(patterns: &[&Pattern]) -> Option<ListMatchShape> {
113    if patterns.len() != 2 {
114        return None;
115    }
116
117    match (patterns[0], patterns[1]) {
118        (Pattern::EmptyList, Pattern::Cons(_, _)) => Some(ListMatchShape {
119            empty_arm_index: 0,
120            cons_arm_index: 1,
121        }),
122        (Pattern::Cons(_, _), Pattern::EmptyList) => Some(ListMatchShape {
123            empty_arm_index: 1,
124            cons_arm_index: 0,
125        }),
126        _ => None,
127    }
128}
129
130pub fn classify_dispatch_table_shape_from_patterns(
131    patterns: &[&Pattern],
132    ctx: &impl CallLowerCtx,
133) -> Option<DispatchTableShape> {
134    if patterns.len() < 2 {
135        return None;
136    }
137
138    let has_default = matches!(patterns.last(), Some(Pattern::Wildcard | Pattern::Ident(_)));
139    let dispatchable_end = if has_default {
140        patterns.len() - 1
141    } else {
142        patterns.len()
143    };
144
145    let mut entries = Vec::new();
146    for (arm_index, pattern) in patterns[..dispatchable_end].iter().enumerate() {
147        let semantic = classify_dispatch_pattern(pattern, ctx)?;
148        entries.push(DispatchArmPlan {
149            binding: classify_dispatch_binding(pattern, &semantic),
150            pattern: semantic,
151            arm_index,
152        });
153    }
154
155    if entries.len() < 2 {
156        return None;
157    }
158
159    Some(DispatchTableShape {
160        entries,
161        default_arm: has_default
162            .then(|| classify_default_arm_plan(patterns[patterns.len() - 1], patterns.len() - 1)),
163    })
164}
165
166pub fn classify_match_dispatch_plan_from_patterns(
167    patterns: &[&Pattern],
168    ctx: &impl CallLowerCtx,
169) -> Option<MatchDispatchPlan> {
170    if let Some(shape) = classify_bool_match_shape_from_patterns(patterns) {
171        return Some(MatchDispatchPlan::Bool(shape));
172    }
173
174    if let Some(shape) = classify_list_match_shape_from_patterns(patterns) {
175        return Some(MatchDispatchPlan::List(shape));
176    }
177
178    classify_dispatch_table_shape_from_patterns(patterns, ctx).map(MatchDispatchPlan::Table)
179}
180
181pub fn classify_dispatch_pattern(
182    pattern: &Pattern,
183    ctx: &impl CallLowerCtx,
184) -> Option<SemanticDispatchPattern> {
185    match pattern {
186        // A big-int literal pattern is never dispatch-tableable (see the resolved
187        // twin in `ir::hir::classify`): bail so the match uses the value-aware
188        // generic path that compares `AverInt`s.
189        Pattern::Literal(Literal::BigInt(_)) => None,
190        Pattern::Literal(lit) => Some(SemanticDispatchPattern::Literal(dispatch_literal_from_ast(
191            lit,
192        ))),
193        Pattern::EmptyList => Some(SemanticDispatchPattern::EmptyList),
194        Pattern::Constructor(name, bindings) => match classify_constructor_name(name, ctx) {
195            SemanticConstructor::NoneValue if bindings.is_empty() => {
196                Some(SemanticDispatchPattern::NoneValue)
197            }
198            SemanticConstructor::Wrapper(kind) if bindings.len() <= 1 => {
199                Some(SemanticDispatchPattern::WrapperTag(kind))
200            }
201            _ => None,
202        },
203        _ => None,
204    }
205}
206
207pub fn classify_bool_match_shape(arms: &[MatchArm]) -> Option<BoolMatchShape> {
208    let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
209    classify_bool_match_shape_from_patterns(&patterns)
210}
211
212pub fn classify_bool_subject_plan(subject: &Expr) -> BoolSubjectPlan<'_> {
213    let Expr::BinOp(op, lhs, rhs) = subject else {
214        return BoolSubjectPlan::Expr(subject);
215    };
216
217    match op {
218        BinOp::Eq => BoolSubjectPlan::Compare {
219            lhs,
220            rhs,
221            op: BoolCompareOp::Eq,
222            invert: false,
223        },
224        BinOp::Lt => BoolSubjectPlan::Compare {
225            lhs,
226            rhs,
227            op: BoolCompareOp::Lt,
228            invert: false,
229        },
230        BinOp::Gt => BoolSubjectPlan::Compare {
231            lhs,
232            rhs,
233            op: BoolCompareOp::Gt,
234            invert: false,
235        },
236        BinOp::Neq => BoolSubjectPlan::Compare {
237            lhs,
238            rhs,
239            op: BoolCompareOp::Eq,
240            invert: true,
241        },
242        BinOp::Gte => BoolSubjectPlan::Compare {
243            lhs,
244            rhs,
245            op: BoolCompareOp::Lt,
246            invert: true,
247        },
248        BinOp::Lte => BoolSubjectPlan::Compare {
249            lhs,
250            rhs,
251            op: BoolCompareOp::Gt,
252            invert: true,
253        },
254        BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => BoolSubjectPlan::Expr(subject),
255    }
256}
257
258pub fn classify_list_match_shape(arms: &[MatchArm]) -> Option<ListMatchShape> {
259    let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
260    classify_list_match_shape_from_patterns(&patterns)
261}
262
263pub fn classify_dispatch_table_shape(
264    arms: &[MatchArm],
265    ctx: &impl CallLowerCtx,
266) -> Option<DispatchTableShape> {
267    let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
268    classify_dispatch_table_shape_from_patterns(&patterns, ctx)
269}
270
271pub fn classify_match_dispatch_plan(
272    arms: &[MatchArm],
273    ctx: &impl CallLowerCtx,
274) -> Option<MatchDispatchPlan> {
275    let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
276    classify_match_dispatch_plan_from_patterns(&patterns, ctx)
277}
278
279fn dispatch_literal_from_ast(lit: &Literal) -> DispatchLiteral {
280    match lit {
281        Literal::Int(i) => DispatchLiteral::Int(*i),
282        Literal::Float(f) => DispatchLiteral::Float(f.to_string()),
283        Literal::Bool(b) => DispatchLiteral::Bool(*b),
284        Literal::Str(s) => DispatchLiteral::Str(s.clone()),
285        Literal::Unit => DispatchLiteral::Unit,
286        // Excluded upstream by `classify_dispatch_pattern` (returns `None` for a
287        // big-int literal pattern, so no dispatch table forms).
288        Literal::BigInt(_) => {
289            unreachable!("BigInt literal patterns are excluded from dispatch tables")
290        }
291    }
292}
293
294fn classify_dispatch_binding(
295    pattern: &Pattern,
296    semantic: &SemanticDispatchPattern,
297) -> DispatchBindingPlan {
298    match (pattern, semantic) {
299        (Pattern::Constructor(_, bindings), SemanticDispatchPattern::WrapperTag(_))
300            if !bindings.is_empty() && bindings[0] != "_" =>
301        {
302            DispatchBindingPlan::WrapperPayload(bindings[0].clone())
303        }
304        _ => DispatchBindingPlan::None,
305    }
306}
307
308fn classify_default_arm_plan(pattern: &Pattern, arm_index: usize) -> DispatchDefaultPlan {
309    let binding_name = match pattern {
310        Pattern::Ident(name) if name != "_" => Some(name.clone()),
311        _ => None,
312    };
313
314    DispatchDefaultPlan {
315        arm_index,
316        binding_name,
317    }
318}