Fork of daniellemaywood.uk/gleam — Wasm codegen work
50 kB
1369 lines
1//! An implementation of the algorithm described at
2//! <https://julesjacobs.com/notes/patternmatching/patternmatching.pdf>.
3//!
4//! Adapted from Yorick Peterse's implementation at
5//! <https://github.com/yorickpeterse/pattern-matching-in-rust>. Thank you Yorick!
6//!
7//! > This module comment (and all the following doc comments) are a rough
8//! > explanation. It's great to set some expectations on what to expect from
9//! > the following code and why the data looks the way it does.
10//! > If you want a more detailed explanation, the original paper is a lot more
11//! > detailed!
12//!
13//! A case to be compiled looks a bit different from the case expressions we're
14//! used to in Gleam: instead of having a variable to match on and a series of
15//! branches, a `CaseToCompile` is made up of a series of branches that can each
16//! contain multiple pattern checks. With a psedo-Gleam syntax, this is what it
17//! would look like:
18//!
19//! ```
20//! case {
21//! a is Some, b is 1, c is _ -> todo
22//! a is wibble -> todo
23//! }
24//! ```
25//!
26//! > You may wonder, why are we writing branches like this? Usually a case
27//! > expression matches on a single variable and each branch refers to it. For
28//! > example in gleam you'd write:
29//! >
30//! > ```gleam
31//! > case a {
32//! > Some(_) -> todo
33//! > None -> todo
34//! > }
35//! > ```
36//! >
37//! > In out representation that would turn into:
38//! >
39//! > ```
40//! > case {
41//! > a is Some(_) -> todo
42//! > a is None -> todo
43//! > }
44//! > ```
45//! >
46//! > This change makes it way easier to compile the pattern matching into a
47//! > decision tree, because now we can add multiple checks on different
48//! > variables in each branch.
49//!
50//! Starting from this data structure, we'll be splitting all the branches into
51//! a decision tree that can be used to perform exhaustiveness checking and code
52//! generation.
53//!
54//! At the moment this tree is not suitable for use in code generation yet as it
55//! is incomplete. The tree is not correctly formed for:
56//! - Bit strings
57//! - String prefixes
58//!
59//! These were not implemented as they are more complex and I've not worked out
60//! a good way to do them yet. The tricky bit is that unlike the others they are
61//! not an exact match and they can overlap with other patterns. Take this
62//! example:
63//!
64//! ```text
65//! case x {
66//! "1" <> _ -> ...
67//! "12" <> _ -> ...
68//! "123" -> ...
69//! _ -> ...
70//! }
71//! ```
72//!
73//! The decision tree needs to take into account that the first pattern is a
74//! super-pattern of the second, and the second is a super-pattern of the third.
75//!
76
77mod missing_patterns;
78pub mod printer;
79
80use crate::{
81 ast::{AssignName, TypedClause, TypedPattern},
82 type_::{
83 Environment, Type, TypeValueConstructor, TypeValueConstructorField, TypeVar,
84 TypeVariantConstructors, collapse_links, error::UnreachableCaseClauseReason,
85 is_prelude_module,
86 },
87};
88use ecow::EcoString;
89use id_arena::{Arena, Id};
90use itertools::Itertools;
91use std::{
92 cell::RefCell,
93 collections::{HashMap, VecDeque},
94 hash::Hash,
95 sync::Arc,
96};
97
98/// A single branch composing a `case` expression to be compiled into a decision
99/// tree.
100///
101/// As shown in the module documentation, branches are a bit different from the
102/// usual branches we see in Gleam's case expressions. Each branch can perform
103/// multiple checks (each on a different variable, which appears in the check
104/// itself!):
105///
106/// ```
107/// a is Some, b is 1 if condition -> todo
108/// ─┬─────── ─┬──── ─┬────────── ─┬──
109/// │ │ │ ╰── body: an arbitrary expression
110/// │ │ ╰── guard: an additional boolean condition
111/// ╰──────────┴── checks: check that a variable matches with a pattern
112/// ─┬────────────────────────────────────
113/// ╰── branch: one of the branches making up a pattern matching expression
114/// ```
115///
116/// As shown here a branch can also optionally include a guard with a boolean
117/// condition and is followed by a body that is to be executed if all the checks
118/// match (and the guard evaluates to true).
119///
120#[derive(Clone, Eq, PartialEq, Debug)]
121struct Branch {
122 /// Each branch is identified by a numeric index, so we can nicely
123 /// report errors once we find something's wrong with a branch.
124 ///
125 branch_index: usize,
126
127 /// Each alternative pattern in an alternative pattern matching (e.g.
128 /// `one | two | three -> todo`) gets turned into its own branch in this
129 /// internal representation. So we also keep track of the index of the
130 /// alternative this comes from (0 being the first one and so on...)
131 ///
132 alternative_index: usize,
133 checks: Vec<PatternCheck>,
134 guard: Option<usize>,
135 body: Body,
136}
137
138impl Branch {
139 fn new(
140 branch_index: usize,
141 alternative_index: usize,
142 checks: Vec<PatternCheck>,
143 has_guard: bool,
144 ) -> Self {
145 Self {
146 branch_index,
147 alternative_index,
148 checks,
149 guard: if has_guard { Some(branch_index) } else { None },
150 body: Body::new(branch_index),
151 }
152 }
153
154 /// Removes and returns a `PatternCheck` on the given variable from this
155 /// branch.
156 ///
157 fn pop_check_on_var(&mut self, var: &Variable) -> Option<PatternCheck> {
158 let index = self.checks.iter().position(|check| check.var == *var)?;
159 Some(self.checks.remove(index))
160 }
161
162 fn add_check(&mut self, check: PatternCheck) {
163 self.checks.push(check);
164 }
165
166 /// To simplify compiling the pattern we can get rid of all catch-all
167 /// patterns that are guaranteed to match by turning those into assignments.
168 ///
169 /// What does this look like in practice? Let's go over an example.
170 /// Let's say we have this case to compile:
171 ///
172 /// ```gleam
173 /// case a {
174 /// Some(1) -> Some(2)
175 /// otherwise -> otherwise
176 /// }
177 /// ```
178 ///
179 /// In our internal representation this would become:
180 ///
181 /// ```
182 /// case {
183 /// a is Some(1) -> Some(2)
184 /// a is otherwise -> otherwise
185 /// ─┬────────────
186 /// ╰── `a` will always match with this "catch all" variable pattern
187 /// }
188 /// ```
189 ///
190 /// Focusing on the last branch, we can remove that check that always matches
191 /// by keeping track in its body of the correspondence. So it would end up
192 /// looking like this:
193 ///
194 /// ```
195 /// case {
196 /// a is Some(1) -> Some(2)
197 /// ∅ -> {
198 /// ┬
199 /// ╰── This represents the fact that there's no checks left for this branch!
200 /// So we can make another observation: if there's no checks left in a
201 /// branch we know it will always match and we can produce a leaf in the
202 /// decision tree (there's an exception when we have guards, but we'll
203 /// get to it later)!
204 ///
205 /// let otherwise = a
206 /// ─┬───────────────
207 /// ╰── And now we can understand what those `bindings` at the start of
208 /// a body are: as we remove variable patterns, we will rewrite those
209 /// as assignments at the top of the body of the corresponding branch.
210 ///
211 /// otherwise
212 /// }
213 /// }
214 /// ```
215 ///
216 fn move_unconditional_patterns(&mut self, compiler: &Compiler<'_>) {
217 self.checks.retain_mut(|check| {
218 loop {
219 match compiler.pattern(check.pattern) {
220 // Variable patterns always match, so we move those to the body
221 // and remove them from the branch's checks.
222 Pattern::Variable { name } => {
223 self.body.assign(name.clone(), check.var.clone());
224 return false;
225 }
226 // A discard pattern always matches, but since the value is not
227 // used we can just remove it without even adding an assignment
228 // to the body!
229 Pattern::Discard => return false,
230 // Assigns are kind of special: they get turned into assignments
231 // (shocking) but then we can't discard the pattern they wrap.
232 // So we replace the assignment pattern with the one it's wrapping
233 // and try again.
234 Pattern::Assign { name, pattern } => {
235 self.body.assign(name.clone(), check.var.clone());
236 check.pattern = *pattern;
237 continue;
238 }
239 // All other patterns are not unconditional, so we just keep them.
240 _ => return true,
241 }
242 }
243 });
244 }
245}
246
247/// The body of a branch. It always starts with a series of variable assignments
248/// in the form: `let a = b`. As explained in `move_unconditional_patterns`' doc,
249/// each body starts with a series of assignments we keep track of as we're
250/// compiling each branch.
251///
252#[derive(Clone, Eq, PartialEq, Debug)]
253pub struct Body {
254 /// Any variables to bind before running the code.
255 ///
256 /// The tuples are in the form `(name, value)`, so `(wibble, var)`
257 /// corresponds to `let wibble = var`.
258 ///
259 bindings: Vec<(EcoString, Variable)>,
260
261 /// The index of the clause in the case expression that should be run.
262 ///
263 branch_index: usize,
264}
265
266impl Body {
267 pub fn new(branch_index: usize) -> Self {
268 Self {
269 bindings: vec![],
270 branch_index,
271 }
272 }
273
274 /// Adds a new assignment to the body, binding `let var = value`
275 ///
276 pub fn assign(&mut self, var: EcoString, value: Variable) {
277 self.bindings.push((var, value));
278 }
279}
280
281/// A user defined pattern such as `Some((x, 10))`.
282/// This is a bit simpler than the full fledged `TypedPattern` used for code analysis
283/// and only focuses on the relevant bits needed to perform exhaustiveness checking
284/// and code generation.
285///
286/// Using this simplified version of a pattern for the case compiler makes it a
287/// whole lot simpler and more efficient (patterns will have to be cloned, so
288/// we use an arena to allocate those and only store ids to make this operation
289/// extra cheap).
290///
291#[derive(Clone, Eq, PartialEq, Debug)]
292pub enum Pattern {
293 Discard,
294 Int {
295 value: EcoString,
296 },
297 Float {
298 value: EcoString,
299 },
300 String {
301 value: EcoString,
302 },
303 StringPrefix {
304 prefix: EcoString,
305 rest: Id<Pattern>,
306 },
307 Assign {
308 name: EcoString,
309 pattern: Id<Pattern>,
310 },
311 Variable {
312 name: EcoString,
313 },
314 Tuple {
315 elements: Vec<Id<Pattern>>,
316 },
317 Constructor {
318 variant_index: usize,
319 arguments: Vec<Id<Pattern>>,
320 },
321 List {
322 first: Id<Pattern>,
323 rest: Id<Pattern>,
324 },
325 EmptyList,
326 // TODO: Compile the matching within the bit strings
327 BitArray {
328 value: EcoString,
329 },
330}
331
332impl Pattern {
333 fn inner_patterns(&self) -> Vec<Id<Pattern>> {
334 match self {
335 Pattern::Discard
336 | Pattern::Int { .. }
337 | Pattern::Float { .. }
338 | Pattern::String { .. }
339 | Pattern::Assign { .. }
340 | Pattern::Variable { .. }
341 | Pattern::BitArray { .. }
342 | Pattern::EmptyList => vec![],
343
344 Pattern::StringPrefix { rest, .. } => vec![*rest],
345
346 Pattern::Tuple { elements } => elements.clone(),
347 Pattern::Constructor { arguments, .. } => arguments.clone(),
348 Pattern::List { first, rest } => vec![*first, *rest],
349 }
350 }
351}
352
353/// A single check making up a branch, checking that a variable matches with a
354/// given pattern. For example, the following branch has 2 checks:
355///
356/// ```
357/// a is Some, b is 1 -> todo
358/// ┬ ─┬──
359/// │ ╰── This is the pattern being checked
360/// ╰── This is the variable being pattern matched on
361/// ─┬─────── ─┬────
362/// ╰─────────┴── Two `PatternCheck`s
363/// ```
364///
365#[derive(Clone, Eq, PartialEq, Debug)]
366struct PatternCheck {
367 var: Variable,
368 pattern: Id<Pattern>,
369}
370
371impl PatternCheck {
372 fn new(var: Variable, pattern: Id<Pattern>) -> Self {
373 Self { var, pattern }
374 }
375
376 /// Each pattern check (with a couple exceptions) can be turned into a
377 /// simpler `RuntimeCheck`: that is a check that can be performed at runtime
378 /// to make sure the `PatternCheck` can succeed on a specific value.
379 ///
380 fn to_runtime_check_kind(&self, compiler: &Compiler<'_>) -> Option<RuntimeCheckKind> {
381 let kind = match compiler.pattern(self.pattern) {
382 // These patterns are unconditional: they will always match and be moved
383 // out of a branch's checks. So there's no corresponding runtime check
384 // we can perform for them.
385 Pattern::Discard | Pattern::Variable { .. } | Pattern::Assign { .. } => return None,
386 Pattern::Int { value } => {
387 let value = value.clone();
388 RuntimeCheckKind::IsInt { value }
389 }
390 Pattern::Float { value } => {
391 let value = value.clone();
392 RuntimeCheckKind::IsFloat { value }
393 }
394 Pattern::String { value } => {
395 let value = value.clone();
396 RuntimeCheckKind::IsString { value }
397 }
398 Pattern::StringPrefix { prefix, .. } => {
399 let prefix = prefix.clone();
400 RuntimeCheckKind::IsStringPrefix { prefix }
401 }
402 Pattern::Tuple { elements } => {
403 let size = elements.len();
404 RuntimeCheckKind::IsTuple { size }
405 }
406 Pattern::Constructor { variant_index, .. } => {
407 let index = *variant_index;
408 RuntimeCheckKind::IsVariant { index }
409 }
410 Pattern::BitArray { value } => {
411 let value = value.clone();
412 RuntimeCheckKind::IsBitArray { value }
413 }
414 Pattern::List { .. } => RuntimeCheckKind::IsNonEmptyList,
415 Pattern::EmptyList => RuntimeCheckKind::IsEmptyList,
416 };
417
418 Some(kind)
419 }
420}
421
422/// This is one of the checks we can take at runtime to decide how to move
423/// forward in the decision tree.
424///
425/// After performing a successful check on a value we will discover something
426/// about its shape: it might be an int, an variant of a custom type, ...
427/// Some values (like variants and lists) might hold onto additional data we
428/// will have to pattern match on: in order to do that we need a name to refer
429/// to those new variables we've discovered after performing a check. That's
430/// what `args` is for.
431///
432/// Let's have a look at an example. Imagine we have a pattern like this one:
433/// `a is Wibble(1, _, [])`; after performing a runtime check to make sure `a`
434/// is indeed a `Wibble`, we'll need to perform additional checks on it's
435/// arguments: that pattern will be replaced by three new ones `a0 is 1`,
436/// `a1 is _` and `a2 is []`. Those new variables are the `args`.
437///
438#[derive(Clone, Debug)]
439pub struct RuntimeCheck {
440 kind: RuntimeCheckKind,
441 args: Vec<Variable>,
442}
443
444#[derive(Eq, PartialEq, Clone, Hash, Debug)]
445pub enum RuntimeCheckKind {
446 IsInt { value: EcoString },
447 IsFloat { value: EcoString },
448 IsString { value: EcoString },
449 IsStringPrefix { prefix: EcoString },
450 IsTuple { size: usize },
451 IsBitArray { value: EcoString },
452 IsVariant { index: usize },
453 IsEmptyList,
454 IsNonEmptyList,
455}
456
457impl RuntimeCheck {
458 fn is_empty_list() -> RuntimeCheck {
459 RuntimeCheck {
460 kind: RuntimeCheckKind::IsEmptyList,
461 args: vec![],
462 }
463 }
464}
465
466/// A variable that can be matched on in a branch.
467///
468#[derive(Eq, PartialEq, Clone, Debug)]
469pub struct Variable {
470 id: usize,
471 type_: Arc<Type>,
472}
473
474impl Variable {
475 fn new(id: usize, type_: Arc<Type>) -> Self {
476 Self { id, type_ }
477 }
478}
479
480#[derive(Debug)]
481/// Different types need to be handled differently when compiling a case expression
482/// into a decision tree. There's some types that have infinite matching patterns
483/// (like ints, strings, ...) and thus will always need a fallback option.
484///
485/// Other types, like custom types, only have a well defined and finite number
486/// of patterns that could match: when matching on a `Result` we know that we can
487/// only have an `Ok(_)` and an `Error(_)`, anything else would end up being a
488/// type error!
489///
490/// So this enum is used to pick the correct strategy to compile a case that's
491/// performing a `PatternCheck` on a variable with a specific type.
492///
493enum BranchMode {
494 /// This covers numbers, functions, variables, bitarrays and strings.
495 ///
496 /// TODO)) In the future it won't be the case: strings and bitarrays will be
497 /// special cased to improve on exhaustiveness checking and to be used for
498 /// code generation.
499 ///
500 Infinite,
501 Tuple {
502 elems: Vec<Arc<Type>>,
503 },
504 List {
505 inner_type: Arc<Type>,
506 },
507 NamedType {
508 constructors: Vec<TypeValueConstructor>,
509 inferred_variant: Option<usize>,
510 },
511}
512
513impl BranchMode {
514 fn is_infinite(&self) -> bool {
515 match self {
516 BranchMode::Infinite => true,
517 BranchMode::Tuple { .. } | BranchMode::List { .. } | BranchMode::NamedType { .. } => {
518 false
519 }
520 }
521 }
522}
523
524impl Variable {
525 fn branch_mode(&self, env: &Environment<'_>) -> BranchMode {
526 match collapse_links(self.type_.clone()).as_ref() {
527 Type::Fn { .. } | Type::Var { .. } => BranchMode::Infinite,
528 Type::Named { module, name, .. }
529 if is_prelude_module(module)
530 && (name == "Int"
531 || name == "Float"
532 || name == "BitArray"
533 || name == "String") =>
534 {
535 BranchMode::Infinite
536 }
537
538 Type::Named {
539 module, name, args, ..
540 } if is_prelude_module(module) && name == "List" => BranchMode::List {
541 inner_type: args.first().expect("list has a type argument").clone(),
542 },
543
544 Type::Tuple { elems } => BranchMode::Tuple {
545 elems: elems.clone(),
546 },
547
548 Type::Named {
549 module,
550 name,
551 args,
552 inferred_variant,
553 ..
554 } => {
555 let constructors = ConstructorSpecialiser::specialise_constructors(
556 env.get_constructors_for_type(module, name)
557 .expect("Custom type variants must exist"),
558 args.as_slice(),
559 );
560
561 let inferred_variant = inferred_variant.map(|i| i as usize);
562 BranchMode::NamedType {
563 constructors,
564 inferred_variant,
565 }
566 }
567 }
568 }
569}
570
571/// This is the decision tree that a pattern matching expression gets turned
572/// into: it's a tree-like structure where each path to a root node contains a
573/// series of checks to perform at runtime to understand if a value matches with
574/// a given pattern.
575///
576pub enum Decision {
577 /// This is the final node of the tree, once we get to this one we know we
578 /// have a body to run because a given pattern matched.
579 ///
580 // todo)) since the tree is not used for code generation, this field is unused.
581 // But it will be useful once we also use this for code gen purposes and not
582 // just for exhaustiveness checking
583 #[allow(dead_code)]
584 Run { body: Body },
585
586 /// We have to make this decision when we run into a branch that also has a
587 /// guard: if it is true we can finally run the body of the branch, stored in
588 /// `if_true`.
589 /// If it is false we might still have to take other decisions and so we might
590 /// have another `DecisionTree` to traverse, stored in `if_false`.
591 ///
592 // todo)) since the tree is not used for code generation, the `guard` and
593 // `if_true` fields are unused.
594 // But they will be useful once we also use this for code gen purposes and not
595 // just for exhaustiveness checking
596 #[allow(dead_code)]
597 Guard {
598 guard: usize,
599 if_true: Body,
600 if_false: Box<Decision>,
601 },
602
603 /// When reaching this node we'll have to see if any of the possible checks
604 /// in `choices` will succeed on `var`. If it does, we know that's the path
605 /// we have to go down to. If none of the checks matches, then we'll have to
606 /// go down the `fallback` branch.
607 ///
608 Switch {
609 var: Variable,
610 choices: Vec<(RuntimeCheck, Box<Decision>)>,
611 fallback: Box<Decision>,
612 },
613
614 /// This is similar to a `Switch` node: we're still picking a possible path
615 /// to follow based on a runtime check. The key difference is that we know
616 /// that one of those is always going to match and so there's no use for a
617 /// fallback branch.
618 ///
619 /// This is used when matching on custom types (and lists!) when we know
620 /// that there's a limited number of choices and exhaustiveness checking
621 /// ensures we'll always deal with all the possible cases.
622 ///
623 ExhaustiveSwitch {
624 var: Variable,
625 choices: Vec<(RuntimeCheck, Box<Decision>)>,
626 },
627
628 /// This is a special node: it represents a missing pattern. If a tree
629 /// contains such a node, then we know that the patterns it was compiled
630 /// from are not exhaustive and the path leading to this node will describe
631 /// what kind of pattern doesn't match!
632 ///
633 Fail,
634}
635
636impl Decision {
637 pub fn run(body: Body) -> Self {
638 Decision::Run { body }
639 }
640
641 pub fn guard(guard: usize, if_true: Body, if_false: Self) -> Self {
642 Decision::Guard {
643 guard,
644 if_true,
645 if_false: Box::new(if_false),
646 }
647 }
648
649 pub fn switch(
650 var: Variable,
651 choices: Vec<(RuntimeCheck, Box<Decision>)>,
652 fallback: Decision,
653 ) -> Self {
654 Self::Switch {
655 var,
656 choices,
657 fallback: Box::new(fallback),
658 }
659 }
660
661 fn exhaustive_switch(var: Variable, choices: Vec<(RuntimeCheck, Box<Decision>)>) -> Decision {
662 Self::ExhaustiveSwitch { var, choices }
663 }
664}
665
666/// The `case` compiler itself (shocking, I know).
667///
668#[derive(Debug)]
669struct Compiler<'a> {
670 environment: &'a Environment<'a>,
671 patterns: Arena<Pattern>,
672 variable_id: usize,
673 diagnostics: Diagnostics,
674}
675
676/// The result of compiling a pattern match expression.
677///
678pub struct Match {
679 pub tree: Decision,
680 pub diagnostics: Diagnostics,
681 pub subject_variables: Vec<Variable>,
682}
683
684/// Whether a clause is reachable, or why it is unreachable.
685///
686#[derive(Debug, Clone, Copy, PartialEq, Eq)]
687pub enum Reachability {
688 Reachable,
689 Unreachable(UnreachableCaseClauseReason),
690}
691
692impl Match {
693 pub fn is_reachable(&self, clause: usize) -> Reachability {
694 if self.diagnostics.reachable.contains(&clause) {
695 Reachability::Reachable
696 } else if self.diagnostics.match_impossible_variants.contains(&clause) {
697 Reachability::Unreachable(UnreachableCaseClauseReason::ImpossibleVariant)
698 } else {
699 Reachability::Unreachable(UnreachableCaseClauseReason::DuplicatePattern)
700 }
701 }
702
703 pub fn missing_patterns(&self, environment: &Environment<'_>) -> Vec<EcoString> {
704 missing_patterns::missing_patterns(self, environment)
705 }
706}
707
708/// A type for storing diagnostics produced by the decision tree compiler.
709///
710#[derive(Debug)]
711pub struct Diagnostics {
712 /// A flag indicating the match is missing one or more pattern.
713 pub missing: bool,
714
715 /// The right-hand sides that are reachable.
716 /// If a right-hand side isn't in this list it means its pattern is
717 /// redundant.
718 pub reachable: Vec<usize>,
719
720 /// Clauses which match on variants of a type which the compiler
721 /// can tell will never be present, due to variant inference.
722 pub match_impossible_variants: Vec<usize>,
723}
724
725impl<'a> Compiler<'a> {
726 fn new(environment: &'a Environment<'a>, variable_id: usize, patterns: Arena<Pattern>) -> Self {
727 Self {
728 environment,
729 patterns,
730 variable_id,
731 diagnostics: Diagnostics {
732 missing: false,
733 reachable: Vec::new(),
734 match_impossible_variants: Vec::new(),
735 },
736 }
737 }
738
739 fn pattern(&self, pattern_id: Id<Pattern>) -> &Pattern {
740 self.patterns.get(pattern_id).expect("unknown pattern id")
741 }
742
743 /// Returns a new fresh variable (i.e. guaranteed to have a unique `variable_id`)
744 /// with the given type.
745 ///
746 fn fresh_variable(&mut self, type_: Arc<Type>) -> Variable {
747 let var = Variable::new(self.variable_id, type_);
748 self.variable_id += 1;
749 var
750 }
751
752 fn mark_as_reached(&mut self, branch: &Branch) {
753 self.diagnostics.reachable.push(branch.branch_index)
754 }
755
756 fn compile(&mut self, mut branches: VecDeque<Branch>) -> Decision {
757 branches
758 .iter_mut()
759 .for_each(|branch| branch.move_unconditional_patterns(self));
760
761 let Some(first_branch) = branches.front() else {
762 // If there's no branches, that means we have a pattern that is not
763 // exhaustive as there's nothing that could match!
764 self.diagnostics.missing = true;
765 return Decision::Fail;
766 };
767
768 self.mark_as_reached(first_branch);
769
770 // In order to compile the branches, we need to pick a `PatternCheck` from
771 // the first branch, and use the variable it's pattern matching on to create
772 // a new node in the tree. All the branches will be split into different
773 // possible paths of this tree.
774 match find_pivot_check(first_branch, &branches) {
775 Some(PatternCheck { var, .. }) => self.split_and_compile_with_pivot_var(var, branches),
776
777 // If the branch has no remaining checks, it means that we've moved all
778 // its variable patterns as assignments into the body and there's no
779 // additional checks remaining. So the only thing left that could result
780 // in the match failing is the additional guard.
781 None => match first_branch.guard {
782 // If there's no guard we're in the following situation:
783 // `∅ -> body`. It means that this branch will always match no
784 // matter what, all the remaining branches are just discarded and
785 // we can produce a terminating node to run the body
786 // unconditionally.
787 None => Decision::run(first_branch.body.clone()),
788 // If we have a guard we're in this scenario:
789 // `∅ if condition -> body`. We can produce a `Guard` node:
790 // if the condition evaluates to `True` we can run its body.
791 // Otherwise, we'll have to keep looking at the remaining branches
792 // to know what to do if this branch doesn't match.
793 Some(guard) => {
794 let if_true = first_branch.body.clone();
795 // All the remaining branches will be compiled and end up
796 // in the path of the tree to choose if the guard is false.
797 let _ = branches.pop_front();
798 let if_false = self.compile(branches);
799 Decision::guard(guard, if_true, if_false)
800 }
801 },
802 }
803 }
804
805 fn split_and_compile_with_pivot_var(
806 &mut self,
807 pivot_var: Variable,
808 branches: VecDeque<Branch>,
809 ) -> Decision {
810 // We first try and come up with a list of all the runtime checks we might
811 // have to perform on the variable at runtime. In most cases it's a limited
812 // number of checks that we know before hand (for example, when matching
813 // on a list, or on a custom type).
814 let branch_mode = pivot_var.branch_mode(self.environment);
815 let known_checks = match &branch_mode {
816 // If the type being matched on is infinite there's no known runtime
817 // check we could come up with in advance. So we'll build them as
818 // we go.
819 BranchMode::Infinite => vec![],
820
821 // If the type is a tuple there's only one runtime check we could
822 // perform that actually makes sense.
823 BranchMode::Tuple { elems } => vec![self.is_tuple_check(elems)],
824
825 // If the type being matched on is a list we know the resulting
826 // decision tree node is only ever going to have two different paths:
827 // one to follow if the list is empty, and one to follow if it's not.
828 BranchMode::List { inner_type } => {
829 vec![
830 RuntimeCheck::is_empty_list(),
831 self.is_list_check(inner_type.clone()),
832 ]
833 }
834
835 // If we know that a specific variant is inferred we will require just
836 // that one and not all the other ones we know for sure are not going to
837 // be there.
838 BranchMode::NamedType {
839 constructors,
840 inferred_variant: Some(index),
841 } => {
842 let constructor = constructors
843 .get(*index)
844 .expect("wrong index for inferred variant");
845 vec![self.is_variant_check(*index, constructor)]
846 }
847
848 // Otherwise we know we'll need a check for each of its possible variants.
849 BranchMode::NamedType { constructors, .. } => constructors
850 .iter()
851 .enumerate()
852 .map(|(index, constructor)| self.is_variant_check(index, constructor))
853 .collect_vec(),
854 };
855
856 // We then split all the branches using these checks and compile the
857 // choices they've been split up into.
858 let mut splitter = BranchSplitter::from_checks(known_checks);
859 self.split_branches(&mut splitter, branches, pivot_var.clone());
860 let choices = splitter
861 .choices
862 .into_iter()
863 .map(|(check, branches)| (check, Box::new(self.compile(branches))))
864 .collect_vec();
865
866 if branch_mode.is_infinite() {
867 // If the branching is infinite, that means we always need to also have
868 // a fallback (imagine you're pattern matching on an `Int` and put no
869 // `_` at the end of the case expression).
870 let fallback = self.compile(splitter.fallback);
871 Decision::switch(pivot_var, choices, fallback)
872 } else {
873 // Otherwise we know that one of the possible runtime checks is always
874 // going to succeed and there's no need to also have a fallback branch.
875 Decision::exhaustive_switch(pivot_var, choices)
876 }
877 }
878
879 fn split_branches(
880 &mut self,
881 splitter: &mut BranchSplitter,
882 branches: VecDeque<Branch>,
883 pivot_var: Variable,
884 ) {
885 for mut branch in branches {
886 let Some(pattern_check) = branch.pop_check_on_var(&pivot_var) else {
887 // If the branch doesn't perform any check on the pivot variable, it means
888 // it could still match no matter what shape `pivot_var` has. So we must
889 // add it as a fallback branch, that is a branch that is still relevant
890 // for all possible paths in the decision tree.
891 splitter.add_fallback_branch(branch);
892 continue;
893 };
894
895 // If it does check the same variable, we know that this branch is only relevant
896 // for some specific shape of `pivot_var`, determined by the runtime check that
897 // it is performing.
898 let kind = pattern_check
899 .to_runtime_check_kind(self)
900 .expect("no var patterns left");
901
902 // We now replace the pattern check we've just removed with the new ones we might
903 // have discovered.
904 //
905 // This is a tricky part, so let's have a look at an example: let's say this is
906 // the pattern check `a is Wibble(1, _, [])`.
907 // After making the runtime check that `a` is indeed a `Wibble` we're still
908 // left with many things to consider! We'll need to also make sure that `a`'s
909 // fields match with the respective patterns `1`, `_` and `[]`.
910 // So we would have to add new pattern checks to this branch:
911 // `a_0 is 1`, `a_1 is _` and `a_2 is []` (where `a_n` are fresh names we use
912 // to refer to `a`'s fields).
913 let args_patterns = self.pattern(pattern_check.pattern).inner_patterns();
914 let args = match splitter.get_overlapping_runtime_check(&kind) {
915 Some(RuntimeCheck { args, .. }) => args,
916 None => vec![],
917 };
918
919 (args.clone().into_iter().zip(args_patterns))
920 .map(|(arg, pattern)| PatternCheck::new(arg, pattern))
921 .for_each(|check| branch.add_check(check));
922
923 splitter.add_checked_branch(RuntimeCheck { kind, args }, branch);
924 }
925 }
926
927 /// Builds an `IsVariant` runtime check, coming up with new fresh variable names
928 /// for its arguments.
929 ///
930 fn is_variant_check(
931 &mut self,
932 index: usize,
933 constructor: &TypeValueConstructor,
934 ) -> RuntimeCheck {
935 RuntimeCheck {
936 kind: RuntimeCheckKind::IsVariant { index },
937 args: constructor
938 .parameters
939 .iter()
940 .map(|p| p.type_.clone())
941 .map(|type_| self.fresh_variable(type_))
942 .collect_vec(),
943 }
944 }
945
946 /// Builds an `IsNonEmptyList` runtime check, coming up with fresh variable
947 /// names for its arguments.
948 ///
949 fn is_list_check(&mut self, inner_type: Arc<Type>) -> RuntimeCheck {
950 RuntimeCheck {
951 kind: RuntimeCheckKind::IsNonEmptyList,
952 args: vec![
953 self.fresh_variable(inner_type.clone()),
954 self.fresh_variable(Arc::new(Type::list(inner_type))),
955 ],
956 }
957 }
958
959 /// Builds an `IsTuple` runtime check, coming up with fresh variable
960 /// names for its arguments.
961 ///
962 fn is_tuple_check(&mut self, elems: &Vec<Arc<Type>>) -> RuntimeCheck {
963 RuntimeCheck {
964 kind: RuntimeCheckKind::IsTuple { size: elems.len() },
965 args: elems
966 .iter()
967 .map(|type_| self.fresh_variable(type_.clone()))
968 .collect_vec(),
969 }
970 }
971}
972
973/// Returns a pattern check from `first_branch` to be used as a pivot to split all
974/// the `branches`.
975///
976fn find_pivot_check(first_branch: &Branch, branches: &VecDeque<Branch>) -> Option<PatternCheck> {
977 // To try and minimise code duplication, we use the following heuristic: we
978 // choose the check matching on the variable that is referenced the most
979 // across all checks in all branches.
980 let mut var_references = HashMap::new();
981 for branch in branches {
982 for check in &branch.checks {
983 let _ = var_references
984 .entry(check.var.id)
985 .and_modify(|references| *references += 1)
986 .or_insert(0);
987 }
988 }
989
990 first_branch
991 .checks
992 .iter()
993 .max_by_key(|check| var_references.get(&check.var.id).cloned().unwrap_or(0))
994 .cloned()
995}
996
997/// A handy data structure we use to split branches in different possible paths
998/// based on a check.
999///
1000struct BranchSplitter {
1001 pub choices: Vec<(RuntimeCheck, VecDeque<Branch>)>,
1002 pub fallback: VecDeque<Branch>,
1003 /// This is used to allow quickly looking up a choice in the `choices`
1004 /// vector, without loosing track of the checks' order.
1005 indices: HashMap<RuntimeCheckKind, usize>,
1006}
1007
1008impl BranchSplitter {
1009 /// Creates a new splitter with the given starting checks.
1010 ///
1011 fn from_checks(checks: Vec<RuntimeCheck>) -> Self {
1012 let mut choices = Vec::with_capacity(checks.len());
1013 let mut indices = HashMap::new();
1014
1015 for (index, RuntimeCheck { kind, args }) in checks.into_iter().enumerate() {
1016 let _ = indices.insert(kind.clone(), index);
1017 choices.push((RuntimeCheck { kind, args }, VecDeque::new()));
1018 }
1019
1020 Self {
1021 fallback: VecDeque::new(),
1022 choices,
1023 indices,
1024 }
1025 }
1026
1027 /// Add a fallback branch: this is a branch that is relevant to all possible
1028 /// paths as it could still run, no matter the result of any of the `Check`s
1029 /// we've stored!
1030 ///
1031 fn add_fallback_branch(&mut self, branch: Branch) {
1032 self.choices
1033 .iter_mut()
1034 .for_each(|(_, branches)| branches.push_back(branch.clone()));
1035 self.fallback.push_back(branch);
1036 }
1037
1038 /// Add a branch that we know will only ever run if the `check` is true.
1039 ///
1040 fn add_checked_branch(&mut self, check: RuntimeCheck, branch: Branch) {
1041 match self.indices.get(&check.kind) {
1042 Some(index) => {
1043 let (_, branches) = self
1044 .choices
1045 .get_mut(*index)
1046 .expect("check to already be a choice");
1047 branches.push_back(branch);
1048 }
1049
1050 None => {
1051 let _ = self.indices.insert(check.kind.clone(), self.choices.len());
1052 let mut branches = self.fallback.clone();
1053 branches.push_back(branch);
1054 self.choices.push((check, branches));
1055 }
1056 }
1057 }
1058
1059 fn get_overlapping_runtime_check(&self, kind: &RuntimeCheckKind) -> Option<RuntimeCheck> {
1060 let index = self.indices.get(kind)?;
1061 let (runtime_check, _) = self.choices.get(*index)?;
1062 Some(runtime_check.clone())
1063 }
1064}
1065
1066pub struct ConstructorSpecialiser {
1067 specialised_types: HashMap<u64, Arc<Type>>,
1068}
1069
1070impl ConstructorSpecialiser {
1071 fn specialise_constructors(
1072 constructors: &TypeVariantConstructors,
1073 type_arguments: &[Arc<Type>],
1074 ) -> Vec<TypeValueConstructor> {
1075 let specialiser = Self::new(constructors.type_parameters_ids.as_slice(), type_arguments);
1076 constructors
1077 .variants
1078 .iter()
1079 .map(|v| specialiser.specialise_type_value_constructor(v))
1080 .collect_vec()
1081 }
1082
1083 fn new(parameters: &[u64], type_arguments: &[Arc<Type>]) -> Self {
1084 let specialised_types = parameters
1085 .iter()
1086 .copied()
1087 .zip(type_arguments.iter().cloned())
1088 .collect();
1089 Self { specialised_types }
1090 }
1091
1092 fn specialise_type_value_constructor(&self, v: &TypeValueConstructor) -> TypeValueConstructor {
1093 let TypeValueConstructor {
1094 name,
1095 parameters,
1096 documentation,
1097 } = v;
1098 let parameters = parameters
1099 .iter()
1100 .map(|p| TypeValueConstructorField {
1101 type_: self.specialise_type(p.type_.as_ref()),
1102 label: p.label.clone(),
1103 })
1104 .collect_vec();
1105 TypeValueConstructor {
1106 name: name.clone(),
1107 parameters,
1108 documentation: documentation.clone(),
1109 }
1110 }
1111
1112 fn specialise_type(&self, type_: &Type) -> Arc<Type> {
1113 Arc::new(match type_ {
1114 Type::Named {
1115 publicity,
1116 package,
1117 module,
1118 name,
1119 args,
1120 inferred_variant,
1121 } => Type::Named {
1122 publicity: *publicity,
1123 package: package.clone(),
1124 module: module.clone(),
1125 name: name.clone(),
1126 args: args.iter().map(|a| self.specialise_type(a)).collect(),
1127 inferred_variant: *inferred_variant,
1128 },
1129
1130 Type::Fn { args, retrn } => Type::Fn {
1131 args: args.iter().map(|a| self.specialise_type(a)).collect(),
1132 retrn: retrn.clone(),
1133 },
1134
1135 Type::Var { type_ } => Type::Var {
1136 type_: Arc::new(RefCell::new(self.specialise_var(type_))),
1137 },
1138
1139 Type::Tuple { elems } => Type::Tuple {
1140 elems: elems.iter().map(|e| self.specialise_type(e)).collect(),
1141 },
1142 })
1143 }
1144
1145 fn specialise_var(&self, type_: &RefCell<TypeVar>) -> TypeVar {
1146 match &*type_.borrow() {
1147 TypeVar::Unbound { id } => TypeVar::Unbound { id: *id },
1148
1149 TypeVar::Link { type_ } => TypeVar::Link {
1150 type_: self.specialise_type(type_.as_ref()),
1151 },
1152
1153 TypeVar::Generic { id } => match self.specialised_types.get(id) {
1154 Some(type_) => TypeVar::Link {
1155 type_: type_.clone(),
1156 },
1157 None => TypeVar::Generic { id: *id },
1158 },
1159 }
1160 }
1161}
1162
1163/// Intermiate data structure that's used to set up everything that's needed by
1164/// the pattern matching compiler and get a case expression ready to be compiled,
1165/// while hiding the intricacies of handling an arena to record different patterns.
1166///
1167pub struct CaseToCompile {
1168 patterns: Arena<Pattern>,
1169 branches: Vec<Branch>,
1170 subject_variables: Vec<Variable>,
1171 variable_id: usize,
1172}
1173
1174impl CaseToCompile {
1175 pub fn new(subject_types: &[Arc<Type>]) -> Self {
1176 let mut variable_id = 0;
1177 let subject_variables = subject_types
1178 .iter()
1179 .map(|type_| {
1180 let id = variable_id;
1181 variable_id += 1;
1182 Variable::new(id, type_.clone())
1183 })
1184 .collect_vec();
1185
1186 Self {
1187 patterns: Arena::new(),
1188 branches: vec![],
1189 subject_variables,
1190 variable_id,
1191 }
1192 }
1193
1194 /// Registers a `TypedClause` as one of the branches to be compiled.
1195 ///
1196 /// If you don't have a clause and just have a simple `TypedPattern` you want
1197 /// to generate a decision tree for you can use `add_pattern`.
1198 ///
1199 pub fn add_clause(&mut self, branch: &TypedClause) {
1200 let branch_index = self.branches.len();
1201 let all_patterns =
1202 std::iter::once(&branch.pattern).chain(branch.alternative_patterns.iter());
1203
1204 for (alternative_index, patterns) in all_patterns.enumerate() {
1205 let mut checks = Vec::with_capacity(patterns.len());
1206
1207 // We're doing the zipping ourselves instead of using iters.zip as the
1208 // borrow checker would complain and the only workaround would be to
1209 // allocate an entire new vector each time.
1210 for i in 0..patterns.len() {
1211 let pattern = self.register(patterns.get(i).expect("pattern index"));
1212 let var = self
1213 .subject_variables
1214 .get(i)
1215 .expect("wrong number of subjects")
1216 .clone();
1217
1218 checks.push(PatternCheck::new(var, pattern))
1219 }
1220
1221 let has_guard = branch.guard.is_some();
1222 let branch = Branch::new(branch_index, alternative_index, checks, has_guard);
1223 self.branches.push(branch);
1224 }
1225 }
1226
1227 /// Add a single pattern as a branch to be compiled.
1228 ///
1229 /// This is useful in case one wants to check exhaustiveness of a single
1230 /// pattern without having a fully fledged `TypedClause` to pass to the `add_clause`
1231 /// method. For example, in `let` destructurings.
1232 ///
1233 pub fn add_pattern(&mut self, pattern: &TypedPattern) {
1234 let branch_index = self.branches.len();
1235 let pattern = self.register(pattern);
1236 let var = self
1237 .subject_variables
1238 .first()
1239 .expect("wrong number of subject variables for pattern")
1240 .clone();
1241 let checks = vec![PatternCheck::new(var, pattern)];
1242 let branch = Branch::new(branch_index, 0, checks, false);
1243 self.branches.push(branch);
1244 }
1245
1246 pub fn compile(self, env: &Environment<'_>) -> Match {
1247 let mut compiler = Compiler::new(env, self.variable_id, self.patterns);
1248
1249 let decision = if self.branches.is_empty() {
1250 let var = self
1251 .subject_variables
1252 .first()
1253 .expect("case with no subjects")
1254 .clone();
1255
1256 compiler.split_and_compile_with_pivot_var(var, VecDeque::new())
1257 } else {
1258 compiler.compile(self.branches.into())
1259 };
1260
1261 Match {
1262 tree: decision,
1263 diagnostics: compiler.diagnostics,
1264 subject_variables: self.subject_variables,
1265 }
1266 }
1267
1268 /// Registers a typed pattern (and all its sub-patterns) into this
1269 /// `CaseToCompile`'s pattern arena, returning an id to get the pattern back.
1270 ///
1271 fn register(&mut self, pattern: &TypedPattern) -> Id<Pattern> {
1272 match pattern {
1273 TypedPattern::Invalid { .. } => self.insert(Pattern::Discard),
1274 TypedPattern::Discard { .. } => self.insert(Pattern::Discard),
1275
1276 TypedPattern::Int { value, .. } => {
1277 let value = value.clone();
1278 self.insert(Pattern::Int { value })
1279 }
1280
1281 TypedPattern::Float { value, .. } => {
1282 let value = value.clone();
1283 self.insert(Pattern::Float { value })
1284 }
1285
1286 TypedPattern::String { value, .. } => {
1287 let value = value.clone();
1288 self.insert(Pattern::String { value })
1289 }
1290
1291 TypedPattern::Variable { name, .. } => {
1292 let name = name.clone();
1293 self.insert(Pattern::Variable { name })
1294 }
1295
1296 TypedPattern::Assign { name, pattern, .. } => {
1297 let name = name.clone();
1298 let pattern = self.register(pattern);
1299 self.insert(Pattern::Assign { name, pattern })
1300 }
1301
1302 TypedPattern::Tuple { elems, .. } => {
1303 let elements = elems.iter().map(|elem| self.register(elem)).collect_vec();
1304 self.insert(Pattern::Tuple { elements })
1305 }
1306
1307 TypedPattern::List { elements, tail, .. } => {
1308 let mut list = match tail {
1309 Some(tail) => self.register(tail),
1310 None => self.insert(Pattern::EmptyList),
1311 };
1312 for element in elements.iter().rev() {
1313 let first = self.register(element);
1314 list = self.insert(Pattern::List { first, rest: list });
1315 }
1316 list
1317 }
1318
1319 TypedPattern::Constructor {
1320 arguments,
1321 constructor,
1322 ..
1323 } => {
1324 let variant_index =
1325 constructor.expect_ref("must be inferred").constructor_index as usize;
1326 let arguments = arguments
1327 .iter()
1328 .map(|argument| self.register(&argument.value))
1329 .collect_vec();
1330 self.insert(Pattern::Constructor {
1331 variant_index,
1332 arguments,
1333 })
1334 }
1335
1336 TypedPattern::BitArray { location, .. } => {
1337 // TODO: in future support bit strings fully and check the
1338 // exhaustiveness of their segment patterns.
1339 // For now we use the location to give each bit string a pattern
1340 // a unique value.
1341 self.insert(Pattern::BitArray {
1342 value: format!("{}:{}", location.start, location.end).into(),
1343 })
1344 }
1345
1346 TypedPattern::StringPrefix {
1347 left_side_string,
1348 right_side_assignment,
1349 ..
1350 } => {
1351 let prefix = left_side_string.clone();
1352 let rest_pattern = match right_side_assignment {
1353 AssignName::Variable(name) => Pattern::Variable { name: name.clone() },
1354 AssignName::Discard(_) => Pattern::Discard,
1355 };
1356 let rest = self.insert(rest_pattern);
1357 self.insert(Pattern::StringPrefix { prefix, rest })
1358 }
1359
1360 TypedPattern::VarUsage { .. } => {
1361 unreachable!("Cannot convert VarUsage to exhaustiveness pattern")
1362 }
1363 }
1364 }
1365
1366 fn insert(&mut self, pattern: Pattern) -> Id<Pattern> {
1367 self.patterns.alloc(pattern)
1368 }
1369}