Fork of daniellemaywood.uk/gleam — Wasm codegen work
114 kB
3041 lines
1//! An implementation of the algorithm described in:
2//!
3//! - How to compile pattern matching, Jules Jacobs.
4//! <https://julesjacobs.com/notes/patternmatching/patternmatching.pdf>
5//!
6//! - Efficient manipulation of binary data using pattern matching,
7//! Per Gustafsson and Konstantinos Sagonas.
8//! <https://user.it.uu.se/~kostis/Papers/JFP_06.pdf>
9//!
10//! Adapted from Yorick Peterse's implementation at
11//! <https://github.com/yorickpeterse/pattern-matching-in-rust>.
12//! Thank you Yorick!
13//!
14//! > This module comment (and all the following doc comments) are a rough
15//! > explanation. It's great to set some expectations on what to expect from
16//! > the following code and why the data looks the way it does.
17//! > If you want a more detailed explanation, the original paper is a lot more
18//! > detailed!
19//!
20//! A case to be compiled looks a bit different from the case expressions we're
21//! used to in Gleam: instead of having a variable to match on and a series of
22//! branches, a `CaseToCompile` is made up of a series of branches that can each
23//! contain multiple pattern checks. With a psedo-Gleam syntax, this is what it
24//! would look like:
25//!
26//! ```text
27//! case {
28//! a is Some, b is 1, c is _ -> todo
29//! a is wibble -> todo
30//! }
31//! ```
32//!
33//! > You may wonder, why are we writing branches like this? Usually a case
34//! > expression matches on a single variable and each branch refers to it. For
35//! > example in gleam you'd write:
36//! >
37//! > ```gleam
38//! > case a {
39//! > Some(_) -> todo
40//! > None -> todo
41//! > }
42//! > ```
43//! >
44//! > In out representation that would turn into:
45//! >
46//! > ```text
47//! > case {
48//! > a is Some(_) -> todo
49//! > a is None -> todo
50//! > }
51//! > ```
52//! >
53//! > This change makes it way easier to compile the pattern matching into a
54//! > decision tree, because now we can add multiple checks on different
55//! > variables in each branch.
56//!
57//! Starting from this data structure, we'll be splitting all the branches into
58//! a decision tree that can be used to perform exhaustiveness checking and code
59//! generation.
60//!
61
62mod missing_patterns;
63pub mod printer;
64
65use crate::{
66 ast::{
67 self, AssignName, BitArraySize, Endianness, TypedBitArraySize, TypedClause, TypedPattern,
68 TypedPatternBitArraySegment,
69 },
70 strings::{convert_string_escape_chars, length_utf16, length_utf32},
71 type_::{
72 Environment, Opaque, Type, TypeValueConstructor, TypeValueConstructorField, TypeVar,
73 TypeVariantConstructors, collapse_links, error::UnreachablePatternReason,
74 is_prelude_module, string,
75 },
76};
77use ecow::EcoString;
78use id_arena::{Arena, Id};
79use itertools::Itertools;
80use num_bigint::BigInt;
81use radix_trie::{Trie, TrieCommon};
82use std::{
83 cell::RefCell,
84 collections::{HashMap, HashSet, VecDeque},
85 hash::Hash,
86 sync::Arc,
87};
88
89/// A single branch composing a `case` expression to be compiled into a decision
90/// tree.
91///
92/// As shown in the module documentation, branches are a bit different from the
93/// usual branches we see in Gleam's case expressions. Each branch can perform
94/// multiple checks (each on a different variable, which appears in the check
95/// itself!):
96///
97/// ```text
98/// a is Some, b is 1 if condition -> todo
99/// ─┬─────── ─┬──── ─┬────────── ─┬──
100/// │ │ │ ╰── body: an arbitrary expression
101/// │ │ ╰── guard: an additional boolean condition
102/// ╰──────────┴── checks: check that a variable matches with a pattern
103/// ─┬────────────────────────────────────
104/// ╰── branch: one of the branches making up a pattern matching expression
105/// ```
106///
107/// As shown here a branch can also optionally include a guard with a boolean
108/// condition and is followed by a body that is to be executed if all the checks
109/// match (and the guard evaluates to true).
110///
111#[derive(Clone, Eq, PartialEq, Debug)]
112struct Branch {
113 /// Each branch is identified by a numeric index, so we can nicely
114 /// report errors once we find something's wrong with a branch.
115 ///
116 clause_index: usize,
117
118 /// Each alternative pattern in an alternative pattern matching (e.g.
119 /// `one | two | three -> todo`) gets turned into its own branch in this
120 /// internal representation. So we also keep track of the index of the
121 /// alternative this comes from (0 being the first one and so on...)
122 ///
123 alternative_index: usize,
124 checks: Vec<PatternCheck>,
125 guard: Option<usize>,
126 body: Body,
127}
128
129impl Branch {
130 fn new(
131 clause_index: usize,
132 alternative_index: usize,
133 checks: Vec<PatternCheck>,
134 has_guard: bool,
135 ) -> Self {
136 Self {
137 clause_index,
138 alternative_index,
139 checks,
140 guard: if has_guard { Some(clause_index) } else { None },
141 body: Body::new(clause_index),
142 }
143 }
144
145 /// Removes and returns a `PatternCheck` on the given variable from this
146 /// branch.
147 ///
148 fn pop_check_on_var(&mut self, var: &Variable) -> Option<PatternCheck> {
149 let index = self.checks.iter().position(|check| check.var == *var)?;
150 Some(self.checks.remove(index))
151 }
152
153 fn add_check(&mut self, check: PatternCheck) {
154 self.checks.push(check);
155 }
156
157 /// To simplify compiling the pattern we can get rid of all catch-all
158 /// patterns that are guaranteed to match by turning those into assignments.
159 ///
160 /// What does this look like in practice? Let's go over an example.
161 /// Let's say we have this case to compile:
162 ///
163 /// ```gleam
164 /// case a {
165 /// Some(1) -> Some(2)
166 /// otherwise -> otherwise
167 /// }
168 /// ```
169 ///
170 /// In our internal representation this would become:
171 ///
172 /// ```text
173 /// case {
174 /// a is Some(1) -> Some(2)
175 /// a is otherwise -> otherwise
176 /// ─┬────────────
177 /// ╰── `a` will always match with this "catch all" variable pattern
178 /// }
179 /// ```
180 ///
181 /// Focusing on the last branch, we can remove that check that always matches
182 /// by keeping track in its body of the correspondence. So it would end up
183 /// looking like this:
184 ///
185 /// ```text
186 /// case {
187 /// a is Some(1) -> Some(2)
188 /// ∅ -> {
189 /// ┬
190 /// ╰── This represents the fact that there's no checks left for this branch!
191 /// So we can make another observation: if there's no checks left in a
192 /// branch we know it will always match and we can produce a leaf in the
193 /// decision tree (there's an exception when we have guards, but we'll
194 /// get to it later)!
195 ///
196 /// let otherwise = a
197 /// ─┬───────────────
198 /// ╰── And now we can understand what those `bindings` at the start of
199 /// a body are: as we remove variable patterns, we will rewrite those
200 /// as assignments at the top of the body of the corresponding branch.
201 ///
202 /// otherwise
203 /// }
204 /// }
205 /// ```
206 ///
207 fn move_unconditional_patterns(&mut self, compiler: &mut Compiler<'_>) {
208 self.checks.retain_mut(|check| {
209 loop {
210 match compiler.pattern(check.pattern) {
211 // Variable patterns always match, so we move those to the body
212 // and remove them from the branch's checks.
213 Pattern::Variable { name } => {
214 self.body.assign(name.clone(), check.var.clone());
215 return false;
216 }
217 // A discard pattern always matches, but since the value is not
218 // used we can just remove it without even adding an assignment
219 // to the body!
220 Pattern::Discard => return false,
221 // Assigns are kind of special: they get turned into assignments
222 // (shocking) but then we can't discard the pattern they wrap.
223 // So we replace the assignment pattern with the one it's wrapping
224 // and try again.
225 Pattern::Assign { name, pattern } => {
226 self.body.assign(name.clone(), check.var.clone());
227 check.pattern = *pattern;
228 }
229 // There's a special case of assignments when it comes to string
230 // prefix patterns. We can give a name to a literal prefix like this:
231 // `"0" as digit <> rest`.
232 // We also want to move this special case of an assignment to the
233 // branch body!
234 Pattern::StringPrefix {
235 prefix,
236 prefix_name,
237 rest: _,
238 } => {
239 if let Some(variable) = std::mem::take(prefix_name) {
240 self.body
241 .assign_literal_string(variable.clone(), prefix.clone());
242 }
243 return true;
244 }
245 // There's a special case of assignments when it comes to bit
246 // array patterns. We can give a name to one slice of the array and
247 // bind it to a variable to be used by later steps of the pattern
248 // like this: `<<len, payload:size(len)>>` (here we're binding
249 // two variables! `len` and `payload`).
250 //
251 // This kind of slicing will always match if it's not guarded by
252 // any size test, so if we find a `ReadAction` that is the first
253 // test to perform in a bit array pattern we know it's always
254 // going to match and can be safely moved into the branch's body.
255 Pattern::BitArray { tests } => match tests.front_mut() {
256 Some(BitArrayTest::Match(MatchTest {
257 value: BitArrayMatchedValue::Variable(name),
258 read_action,
259 })) => {
260 let bit_array = check.var.clone();
261 self.body.assign_bit_array_slice(
262 name.clone(),
263 bit_array,
264 read_action.clone(),
265 );
266 let _ = tests.pop_front();
267 }
268
269 Some(test) => match test {
270 // If we have `_ as a` we treat that as a regular variable
271 // assignment.
272 BitArrayTest::Match(MatchTest {
273 value: BitArrayMatchedValue::Assign { name, value },
274 read_action,
275 }) if value.is_discard() => {
276 *test = BitArrayTest::Match(MatchTest {
277 value: BitArrayMatchedValue::Variable(name.clone()),
278 read_action: read_action.clone(),
279 });
280 }
281
282 // Just like regular assigns, those patterns are unrefutable
283 // and will become assignments in the branch's body.
284 BitArrayTest::Match(MatchTest {
285 value: BitArrayMatchedValue::Assign { name, value },
286 read_action,
287 }) => {
288 self.body
289 .assign_segment_constant_value(name.clone(), value.as_ref());
290
291 // We will still need to check the aliased value!
292 *test = BitArrayTest::Match(MatchTest {
293 value: value.as_ref().clone(),
294 read_action: read_action.clone(),
295 });
296 }
297
298 // Discards are removed directly without even binding them
299 // in the branch's body.
300 _ if test.is_discard() => {
301 let _ = tests.pop_front();
302 }
303
304 // Otherwise there's no unconditional test to pop, we
305 // keep the pattern without changing it.
306 _ => return true,
307 },
308
309 // If a bit array pattern has no tests then it's always
310 // going to match, no matter what. We just remove it.
311 None => return false,
312 },
313
314 // All other patterns are not unconditional, so we just keep them.
315 _ => return true,
316 }
317 }
318 });
319 }
320}
321
322/// The body of a branch. It always starts with a series of variable assignments
323/// in the form: `let a = b`. As explained in `move_unconditional_patterns`' doc,
324/// each body starts with a series of assignments we keep track of as we're
325/// compiling each branch.
326///
327#[derive(Clone, Eq, PartialEq, Debug)]
328pub struct Body {
329 /// Any variables to bind before running the code.
330 ///
331 /// The tuples are in the form `(name, value)`, so `(wibble, var)`
332 /// corresponds to `let wibble = var`.
333 ///
334 pub bindings: Vec<(EcoString, BoundValue)>,
335
336 /// The index of the clause in the case expression that should be run.
337 ///
338 pub clause_index: usize,
339}
340
341/// A value that can appear on the right hand side of one of the assignments we
342/// find at the top of a body.
343///
344#[derive(Clone, Eq, PartialEq, Debug)]
345pub enum BoundValue {
346 /// `let a = variable`
347 ///
348 Variable(Variable),
349
350 /// `let a = "a literal string"`
351 ///
352 LiteralString(EcoString),
353
354 /// `let a = 123`
355 ///
356 LiteralInt(BigInt),
357
358 /// `let a = 12.2`
359 ///
360 LiteralFloat(EcoString),
361
362 /// `let a = sliceAsInt(bit_array, 0, 16, ...)`
363 ///
364 BitArraySlice {
365 bit_array: Variable,
366 read_action: ReadAction,
367 },
368}
369
370impl Body {
371 pub fn new(clause_index: usize) -> Self {
372 Self {
373 bindings: vec![],
374 clause_index,
375 }
376 }
377
378 /// Adds a new assignment to the body, binding `let variable = value`
379 ///
380 pub fn assign(&mut self, variable: EcoString, value: Variable) {
381 self.bindings.push((variable, BoundValue::Variable(value)));
382 }
383
384 fn assign_literal_string(&mut self, variable: EcoString, value: EcoString) {
385 self.bindings
386 .push((variable, BoundValue::LiteralString(value)));
387 }
388
389 fn assign_bit_array_slice(
390 &mut self,
391 segment_name: EcoString,
392 bit_array: Variable,
393 value: ReadAction,
394 ) {
395 self.bindings.push((
396 segment_name,
397 BoundValue::BitArraySlice {
398 bit_array,
399 read_action: value,
400 },
401 ))
402 }
403
404 fn assign_segment_constant_value(&mut self, name: EcoString, value: &BitArrayMatchedValue) {
405 let value = match value {
406 BitArrayMatchedValue::LiteralFloat(value) => BoundValue::LiteralFloat(value.clone()),
407 BitArrayMatchedValue::LiteralInt(value) => BoundValue::LiteralInt(value.clone()),
408 BitArrayMatchedValue::LiteralString { value, .. } => {
409 BoundValue::LiteralString(value.clone())
410 }
411 BitArrayMatchedValue::Variable(_)
412 | BitArrayMatchedValue::Discard(_)
413 | BitArrayMatchedValue::Assign { .. } => {
414 panic!("aliased non constant value: {value:#?}")
415 }
416 };
417
418 self.bindings.push((name, value))
419 }
420}
421
422/// A user defined pattern such as `Some((x, 10))`.
423/// This is a bit simpler than the full fledged `TypedPattern` used for code analysis
424/// and only focuses on the relevant bits needed to perform exhaustiveness checking
425/// and code generation.
426///
427/// Using this simplified version of a pattern for the case compiler makes it a
428/// whole lot simpler and more efficient (patterns will have to be cloned, so
429/// we use an arena to allocate those and only store ids to make this operation
430/// extra cheap).
431///
432#[derive(Clone, Eq, PartialEq, Debug)]
433pub enum Pattern {
434 Discard,
435 Int {
436 value: EcoString,
437 },
438 Float {
439 value: EcoString,
440 },
441 String {
442 value: EcoString,
443 },
444 StringPrefix {
445 prefix: EcoString,
446 prefix_name: Option<EcoString>,
447 rest: Id<Pattern>,
448 },
449 Assign {
450 name: EcoString,
451 pattern: Id<Pattern>,
452 },
453 Variable {
454 name: EcoString,
455 },
456 Tuple {
457 elements: Vec<Id<Pattern>>,
458 },
459 Variant {
460 index: usize,
461 name: EcoString,
462 module: Option<EcoString>,
463 fields: Vec<Id<Pattern>>,
464 },
465 NonEmptyList {
466 first: Id<Pattern>,
467 rest: Id<Pattern>,
468 },
469 EmptyList,
470 BitArray {
471 tests: VecDeque<BitArrayTest>,
472 },
473}
474
475impl Pattern {
476 /// Each pattern (with a couple exceptions) can be turned into a
477 /// simpler `RuntimeCheck`: that is a check that can be performed at runtime
478 /// to make sure a `PatternCheck` can succeed on a specific value.
479 ///
480 fn to_runtime_check_kind(&self) -> Option<RuntimeCheckKind> {
481 let kind = match self {
482 // These patterns are unconditional: they will always match and be moved
483 // out of a branch's checks. So there's no corresponding runtime check
484 // we can perform for them.
485 Pattern::Discard | Pattern::Variable { .. } | Pattern::Assign { .. } => return None,
486 Pattern::Int { value } => RuntimeCheckKind::Int {
487 value: value.clone(),
488 },
489 Pattern::Float { value } => RuntimeCheckKind::Float {
490 value: value.clone(),
491 },
492 Pattern::String { value } => RuntimeCheckKind::String {
493 value: value.clone(),
494 },
495 Pattern::StringPrefix { prefix, .. } => RuntimeCheckKind::StringPrefix {
496 prefix: prefix.clone(),
497 },
498 Pattern::Tuple { elements } => RuntimeCheckKind::Tuple {
499 size: elements.len(),
500 },
501 Pattern::Variant { index, .. } => RuntimeCheckKind::Variant { index: *index },
502 Pattern::NonEmptyList { .. } => RuntimeCheckKind::NonEmptyList,
503 Pattern::EmptyList => RuntimeCheckKind::EmptyList,
504 // Bit arrays have no corresponding kind as they're dealt with in a
505 // completely different way.
506 Pattern::BitArray { .. } => return None,
507 };
508
509 Some(kind)
510 }
511
512 fn is_matching_on_unreachable_variant(&self, branch_mode: &BranchMode) -> bool {
513 match (self, branch_mode) {
514 (
515 Self::Variant { index, .. },
516 BranchMode::NamedType {
517 inferred_variant: Some(variant),
518 ..
519 },
520 ) if index != variant => true,
521 _ => false,
522 }
523 }
524}
525
526/// A single check making up a branch, checking that a variable matches with a
527/// given pattern. For example, the following branch has 2 checks:
528///
529/// ```text
530/// a is Some, b is 1 -> todo
531/// ┬ ─┬──
532/// │ ╰── This is the pattern being checked
533/// ╰── This is the variable being pattern matched on
534/// ─┬─────── ─┬────
535/// ╰─────────┴── Two `PatternCheck`s
536/// ```
537///
538#[derive(Clone, Eq, PartialEq, Debug)]
539struct PatternCheck {
540 var: Variable,
541 pattern: Id<Pattern>,
542}
543
544/// This is one of the checks we can take at runtime to decide how to move
545/// forward in the decision tree.
546///
547/// After performing a successful check on a value we will discover something
548/// about its shape: it might be an int, an variant of a custom type, ...
549/// Some values (like variants and lists) might hold onto additional data we
550/// will have to pattern match on: in order to do that we need a name to refer
551/// to those new variables we've discovered after performing a check. That's
552/// what `args` is for.
553///
554/// Let's have a look at an example. Imagine we have a pattern like this one:
555/// `a is Wibble(1, _, [])`; after performing a runtime check to make sure `a`
556/// is indeed a `Wibble`, we'll need to perform additional checks on it's
557/// arguments: that pattern will be replaced by three new ones `a0 is 1`,
558/// `a1 is _` and `a2 is []`. Those new variables are the `args`.
559///
560#[derive(Clone, Debug, PartialEq, Eq)]
561pub enum RuntimeCheck {
562 Int {
563 value: EcoString,
564 },
565 Float {
566 value: EcoString,
567 },
568 String {
569 value: EcoString,
570 },
571 StringPrefix {
572 prefix: EcoString,
573 rest: Variable,
574 },
575 Tuple {
576 size: usize,
577 elements: Vec<Variable>,
578 },
579 BitArray {
580 test: BitArrayTest,
581 },
582 Variant {
583 match_: VariantMatch,
584 index: usize,
585 labels: HashMap<usize, EcoString>,
586 fields: Vec<Variable>,
587 },
588 NonEmptyList {
589 first: Variable,
590 rest: Variable,
591 },
592 EmptyList,
593}
594
595impl RuntimeCheck {
596 fn kind(&self) -> Option<RuntimeCheckKind> {
597 let kind = match self {
598 RuntimeCheck::Int { value } => RuntimeCheckKind::Int {
599 value: value.clone(),
600 },
601 RuntimeCheck::Float { value } => RuntimeCheckKind::Float {
602 value: value.clone(),
603 },
604 RuntimeCheck::String { value } => RuntimeCheckKind::String {
605 value: value.clone(),
606 },
607 RuntimeCheck::StringPrefix { prefix, rest: _ } => RuntimeCheckKind::StringPrefix {
608 prefix: prefix.clone(),
609 },
610 RuntimeCheck::Tuple { size, elements: _ } => RuntimeCheckKind::Tuple { size: *size },
611 RuntimeCheck::Variant { index, .. } => RuntimeCheckKind::Variant { index: *index },
612 RuntimeCheck::EmptyList => RuntimeCheckKind::EmptyList,
613 RuntimeCheck::NonEmptyList { first: _, rest: _ } => RuntimeCheckKind::NonEmptyList,
614 RuntimeCheck::BitArray { .. } => return None,
615 };
616 Some(kind)
617 }
618
619 pub(crate) fn is_ignored(&self) -> bool {
620 match self {
621 RuntimeCheck::Variant {
622 match_: VariantMatch::NeverExplicitlyMatchedOn { .. },
623 ..
624 } => true,
625 _ => false,
626 }
627 }
628
629 /// Returns all the bit array segments referenced in this check.
630 /// For each segment it returns its name and the read action used to access
631 /// such segment.
632 ///
633 pub(crate) fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> {
634 match self {
635 RuntimeCheck::BitArray { test } => test.referenced_segment_patterns(),
636 RuntimeCheck::Int { .. }
637 | RuntimeCheck::Float { .. }
638 | RuntimeCheck::String { .. }
639 | RuntimeCheck::StringPrefix { .. }
640 | RuntimeCheck::Tuple { .. }
641 | RuntimeCheck::Variant { .. }
642 | RuntimeCheck::NonEmptyList { .. }
643 | RuntimeCheck::EmptyList => vec![],
644 }
645 }
646}
647
648#[derive(Eq, PartialEq, Clone, Hash, Debug)]
649pub enum RuntimeCheckKind {
650 Int { value: EcoString },
651 Float { value: EcoString },
652 String { value: EcoString },
653 StringPrefix { prefix: EcoString },
654 Tuple { size: usize },
655 Variant { index: usize },
656 EmptyList,
657 NonEmptyList,
658}
659
660/// All possible variant checks are automatically generated beforehand once we
661/// know we are matching on a value with a custom type.
662/// Then if the compiled case is explicitly matching on one of those, we update
663/// it to store additional information: for example how the variant is used
664/// (if qualified or unqualified and if it is aliased).
665///
666/// This way when we get to code generation we can clump all variants that were
667/// never explicitly matched on in a single `else` block without blowing up code
668/// size!
669///
670#[derive(Clone, Debug, PartialEq, Eq)]
671pub enum VariantMatch {
672 ExplicitlyMatchedOn {
673 name: EcoString,
674 module: Option<EcoString>,
675 },
676 NeverExplicitlyMatchedOn {
677 name: EcoString,
678 },
679}
680
681impl VariantMatch {
682 pub(crate) fn name(&self) -> EcoString {
683 match self {
684 VariantMatch::ExplicitlyMatchedOn { name, module: _ } => name.clone(),
685 VariantMatch::NeverExplicitlyMatchedOn { name } => name.clone(),
686 }
687 }
688
689 pub(crate) fn module(&self) -> Option<EcoString> {
690 match self {
691 VariantMatch::ExplicitlyMatchedOn { name: _, module } => module.clone(),
692 VariantMatch::NeverExplicitlyMatchedOn { name: _ } => None,
693 }
694 }
695}
696
697/// A variable that can be matched on in a branch.
698///
699#[derive(Eq, PartialEq, Clone, Debug)]
700pub struct Variable {
701 pub id: usize,
702 pub type_: Arc<Type>,
703}
704
705impl Variable {
706 fn new(id: usize, type_: Arc<Type>) -> Self {
707 Self { id, type_ }
708 }
709
710 /// Builds a `PatternCheck` that checks this variable matches the given pattern.
711 /// So we can build pattern checks the same way we informally describe them:
712 /// ```text
713 /// var is pattern
714 /// ```
715 /// With this builder method would become:
716 /// ```rs
717 /// var.is(pattern)
718 /// ```
719 ///
720 fn is(&self, pattern: Id<Pattern>) -> PatternCheck {
721 PatternCheck {
722 var: self.clone(),
723 pattern,
724 }
725 }
726}
727
728#[derive(Debug)]
729/// Different types need to be handled differently when compiling a case expression
730/// into a decision tree. There's some types that have infinite matching patterns
731/// (like ints, strings, ...) and thus will always need a fallback option.
732///
733/// Other types, like custom types, only have a well defined and finite number
734/// of patterns that could match: when matching on a `Result` we know that we can
735/// only have an `Ok(_)` and an `Error(_)`, anything else would end up being a
736/// type error!
737///
738/// So this enum is used to pick the correct strategy to compile a case that's
739/// performing a `PatternCheck` on a variable with a specific type.
740///
741enum BranchMode {
742 /// This covers numbers, functions, variables, strings, and bitarrays.
743 Infinite,
744 Tuple {
745 elements: Vec<Arc<Type>>,
746 },
747 List {
748 inner_type: Arc<Type>,
749 },
750 NamedType {
751 constructors: Vec<TypeValueConstructor>,
752 inferred_variant: Option<usize>,
753 },
754}
755
756impl BranchMode {
757 fn is_infinite(&self) -> bool {
758 match self {
759 BranchMode::Infinite => true,
760 BranchMode::Tuple { .. } | BranchMode::List { .. } | BranchMode::NamedType { .. } => {
761 false
762 }
763 }
764 }
765}
766
767impl Variable {
768 fn branch_mode(&self, env: &Environment<'_>) -> BranchMode {
769 match collapse_links(self.type_.clone()).as_ref() {
770 Type::Fn { .. } | Type::Var { .. } => BranchMode::Infinite,
771 Type::Named { module, name, .. }
772 if is_prelude_module(module)
773 && (name == "Int"
774 || name == "Float"
775 || name == "BitArray"
776 || name == "String") =>
777 {
778 BranchMode::Infinite
779 }
780
781 Type::Named {
782 module, name, args, ..
783 } if is_prelude_module(module) && name == "List" => BranchMode::List {
784 inner_type: args.first().expect("list has a type argument").clone(),
785 },
786
787 Type::Tuple { elements } => BranchMode::Tuple {
788 elements: elements.clone(),
789 },
790
791 Type::Named {
792 module,
793 name,
794 args,
795 inferred_variant,
796 ..
797 } => {
798 let constructors = ConstructorSpecialiser::specialise_constructors(
799 env.get_constructors_for_type(module, name)
800 .expect("Custom type variants must exist"),
801 args.as_slice(),
802 &env.current_module,
803 module,
804 );
805
806 let inferred_variant = inferred_variant.map(|i| i as usize);
807 BranchMode::NamedType {
808 constructors,
809 inferred_variant,
810 }
811 }
812 }
813 }
814}
815
816/// When compiling a bit array pattern each segment is turned into a series of
817/// tests that all need to match in order for the pattern to succeed.
818/// Each segment might add some requirements on the total size of a bit array
819/// and/or on the value of some of its specific parts.
820///
821/// Let's look at a simple example to get started:
822///
823/// ```txt
824/// <<0:size(12), 1, rest:bits>>
825/// ─┬────────
826/// ╰── This first segment requires that the bit array must have at least
827/// 12 bits and their value must be the Int `0`. So this first
828/// segment would be turned into the following series of tests:
829/// `Size(>=, 12)` and `Match(0, ReadAction(0, 12, int))`
830/// ```
831///
832/// However, the various sizes and offsets of the different segments might not
833/// always be known at compile time and depend on previous sections of the
834/// pattern. This is no big deal: as you'll discover in more detail in the
835/// `SizeExpression`'s doc we can also represent those variable sizes:
836///
837/// ```txt
838/// <<len, payload:size(len), rest:bits>>
839/// ─┬─ ─┬───────────────
840/// │ ╰── For this segment to match the bit array must have enough bits
841/// │ for the previous segment (8 bits) and for this one (`len` bits),
842/// │ so it will turn into the following series of tests:
843/// │ `Size(>=, 8 + len)` and `Match(len, ReadAction(8, len, int))`
844/// │
845/// ╰── This first segment is 8 bits and an int (it's the default Gleam picks
846/// if no options are supplied)
847/// ```
848///
849#[derive(Clone, Eq, PartialEq, Debug)]
850pub enum BitArrayTest {
851 Size(SizeTest),
852 Match(MatchTest),
853
854 /// This is a special test to check that the remaining part of a bit array
855 /// has a whole number of bytes when using the `:bytes` option.
856 ///
857 CatchAllIsBytes {
858 size_so_far: Offset,
859 },
860
861 /// This test is made to ensure a given variable is positive: a segment
862 /// pattern where the size is a variable with a negative value will never
863 /// match. So we check this to make sure the test will fail.
864 ///
865 VariableIsNotNegative {
866 variable: VariableUsage,
867 },
868
869 /// This test checks that the segment read by the given read action is a
870 /// finite Float and not a `NaN` or `Infinity`.
871 ///
872 /// We need this check as `NaN` and `Infinity` will not match with float
873 /// segments (like `<<_:32-float>>`) on the Erlang target and we must
874 /// replicate the same behavious on the JavaScript target as well.
875 ///
876 SegmentIsFiniteFloat {
877 read_action: ReadAction,
878 },
879}
880
881impl BitArrayTest {
882 fn is_discard(&self) -> bool {
883 match self {
884 BitArrayTest::Match(MatchTest {
885 value: BitArrayMatchedValue::Discard(_),
886 ..
887 }) => true,
888 _ => false,
889 }
890 }
891
892 pub(crate) fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> {
893 match self {
894 BitArrayTest::VariableIsNotNegative { variable } => match variable {
895 VariableUsage::PatternSegment(segment_value, read_action) => {
896 vec![(segment_value, read_action)]
897 }
898 VariableUsage::OutsideVariable(..) => vec![],
899 },
900
901 BitArrayTest::Size(SizeTest { operator: _, size })
902 | BitArrayTest::CatchAllIsBytes { size_so_far: size } => {
903 size.referenced_segment_patterns()
904 }
905
906 BitArrayTest::SegmentIsFiniteFloat {
907 read_action: ReadAction { from, size, .. },
908 }
909 | BitArrayTest::Match(MatchTest {
910 read_action: ReadAction { from, size, .. },
911 ..
912 }) => {
913 let mut references = vec![];
914 references.append(&mut from.referenced_segment_patterns());
915 match size {
916 ReadSize::VariableBits { variable, unit: _ } => match variable.as_ref() {
917 VariableUsage::PatternSegment(segment_value, read_action) => {
918 references.push((segment_value, read_action))
919 }
920 VariableUsage::OutsideVariable(..) => (),
921 },
922
923 ReadSize::ConstantBits(..)
924 | ReadSize::RemainingBits
925 | ReadSize::RemainingBytes => (),
926 };
927
928 references
929 }
930 }
931 }
932}
933
934/// Test to make sure the bit array has a specific number of bits.
935///
936#[derive(Clone, Eq, PartialEq, Debug)]
937pub struct SizeTest {
938 pub operator: SizeOperator,
939 pub size: Offset,
940}
941
942impl SizeTest {
943 /// Tells us if this test is guaranteed to succeed given another test that
944 /// we know has already succeeded.
945 ///
946 /// For example, ">= 5" is certainly going to succeed if ">= 6" has already
947 /// succeeded.
948 ///
949 fn succeeds_if_succeeding(&self, succeeding: &SizeTest) -> Confidence {
950 match (succeeding.operator, self.operator) {
951 (SizeOperator::Equal, SizeOperator::Equal) if succeeding.size == self.size => {
952 Confidence::Certain
953 }
954 (_, SizeOperator::GreaterEqual) => succeeding.size.greater_equal(&self.size),
955 _ => Confidence::Uncertain,
956 }
957 }
958
959 /// Tells us if this test is guaranteed to fail given another test that
960 /// we know has already failed.
961 ///
962 /// For example, ">= 5" is certainly going to fail if ">= 4" has already
963 /// failed.
964 ///
965 fn fails_if_failing(&self, failing: &SizeTest) -> Confidence {
966 match (failing.operator, self.operator) {
967 (SizeOperator::GreaterEqual, _) => self.size.greater_equal(&failing.size),
968 (_, _) if self == failing => Confidence::Certain,
969 _ => Confidence::Uncertain,
970 }
971 }
972
973 /// Tells us if this test is guaranteed to fail given another test that
974 /// we know has already succeeded.
975 ///
976 /// For example, "= 1" is certainly going to fail if "= 2" has already
977 /// succeeded.
978 ///
979 fn fails_if_succeeding(&self, succeeding: &SizeTest) -> Confidence {
980 match (succeeding.operator, self.operator) {
981 (SizeOperator::GreaterEqual, SizeOperator::Equal) => {
982 succeeding.size.greater(&self.size)
983 }
984 (SizeOperator::Equal, SizeOperator::GreaterEqual) => {
985 self.size.greater(&succeeding.size)
986 }
987 (SizeOperator::Equal, SizeOperator::Equal) => succeeding.size.different(&self.size),
988 _ => Confidence::Uncertain,
989 }
990 }
991}
992
993/// Test to make sure the segment read by the specified `read_action` matches
994/// a given value.
995///
996#[derive(Clone, Eq, PartialEq, Debug)]
997pub struct MatchTest {
998 pub value: BitArrayMatchedValue,
999 pub read_action: ReadAction,
1000}
1001
1002/// A value that can be matched in a bit array pattern's segment. We do not use
1003/// a `Pattern` directly since the allowed values are actually a subset of all
1004/// the possible patterns: it can only contain literal floats, ints, strings,
1005/// a variable name, and a discard.
1006///
1007#[derive(Clone, Eq, PartialEq, Debug)]
1008pub enum BitArrayMatchedValue {
1009 LiteralFloat(EcoString),
1010 LiteralInt(BigInt),
1011 LiteralString {
1012 value: EcoString,
1013 encoding: StringEncoding,
1014 },
1015 Variable(EcoString),
1016 Discard(EcoString),
1017 Assign {
1018 name: EcoString,
1019 value: Box<BitArrayMatchedValue>,
1020 },
1021}
1022
1023impl BitArrayMatchedValue {
1024 pub(crate) fn is_literal(&self) -> bool {
1025 match self {
1026 BitArrayMatchedValue::LiteralFloat(_)
1027 | BitArrayMatchedValue::LiteralInt(_)
1028 | BitArrayMatchedValue::LiteralString { .. } => true,
1029 BitArrayMatchedValue::Variable(..) | BitArrayMatchedValue::Discard(..) => false,
1030 BitArrayMatchedValue::Assign { value, .. } => value.is_literal(),
1031 }
1032 }
1033}
1034
1035#[derive(Clone, Copy, Eq, PartialEq, Debug)]
1036pub enum StringEncoding {
1037 Utf8,
1038 Utf16,
1039 Utf32,
1040}
1041
1042impl BitArrayMatchedValue {
1043 pub fn is_discard(&self) -> bool {
1044 match self {
1045 BitArrayMatchedValue::Discard(_) => true,
1046 BitArrayMatchedValue::LiteralFloat(_)
1047 | BitArrayMatchedValue::LiteralInt(_)
1048 | BitArrayMatchedValue::LiteralString { .. }
1049 | BitArrayMatchedValue::Variable(_)
1050 | BitArrayMatchedValue::Assign { .. } => false,
1051 }
1052 }
1053}
1054
1055impl BitArrayTest {
1056 // TODO: for these tests we could also implement a more sophisticated
1057 // approach for `Match` tests. This is described in the linked paper as
1058 // read action interference and can help making the tree smaller in some
1059 // specific cases.
1060 // This could be an interesting optimisation once we start using the tree
1061 // for code generation as well.
1062
1063 /// Tells us if this test is guaranteed to succeed given another test that
1064 /// we know has already succeeded.
1065 ///
1066 /// For example, "size >= 5" is certainly going to succeed if "size >= 6"
1067 /// has already succeeded.
1068 ///
1069 #[must_use]
1070 fn succeeds_if_succeeding(&self, succeeding: &BitArrayTest) -> Confidence {
1071 match (succeeding, self) {
1072 (one, other) if one == other => Confidence::Certain,
1073 (BitArrayTest::Size(succeeding), BitArrayTest::Size(test)) => {
1074 test.succeeds_if_succeeding(succeeding)
1075 }
1076 // The tests are not comparable, we can't deduce any new information.
1077 _ => Confidence::Uncertain,
1078 }
1079 }
1080
1081 /// Tells us if this test is guaranteed to fail given another test that
1082 /// we know has already failed.
1083 ///
1084 /// For example, "size >= 5" is certainly going to fail if "size >= 4"
1085 /// has already failed.
1086 ///
1087 #[must_use]
1088 fn fails_if_failing(&self, failing: &BitArrayTest) -> Confidence {
1089 match (failing, self) {
1090 (one, other) if one == other => Confidence::Certain,
1091 (BitArrayTest::Size(failing), BitArrayTest::Size(test)) => {
1092 test.fails_if_failing(failing)
1093 }
1094 // The tests are not comparable, we can't deduce any new information.
1095 _ => Confidence::Uncertain,
1096 }
1097 }
1098
1099 /// Tells us if this test is guaranteed to fail given another test that
1100 /// we know has already succeeded.
1101 ///
1102 /// For example, "size = 1" is certainly going to fail if "size = 2" has already
1103 /// succeeded.
1104 ///
1105 #[must_use]
1106 fn fails_if_succeeding(&self, succeeding: &BitArrayTest) -> Confidence {
1107 match (succeeding, self) {
1108 // This is an implementation of Table 6 (a) of the bit array pattern
1109 // matching paper linked at the top of this module's documentation!
1110 (BitArrayTest::Size(succeeding), BitArrayTest::Size(test)) => {
1111 test.fails_if_succeeding(succeeding)
1112 }
1113 // The tests are not comparable, we can't deduce any new information.
1114 _ => Confidence::Uncertain,
1115 }
1116 }
1117}
1118
1119/// When performing a size test it could require that a size is exactly some
1120/// specific number of bits (for example if we're matching the last segment
1121/// of a bit array) or that it has at least some number of bits. For example:
1122///
1123/// ```text
1124/// <<0:size(12), 1:size(8)>>
1125/// ─┬──────── ─┬───────
1126/// │ ╰── For this segment to successfully match the bit array must
1127/// │ have exactly 20 bits: because it will need to read 8 bits
1128/// │ from bit 13 where the previous one ends: `Size(=, 20)`
1129/// │
1130/// ╰── For this segment to successfully match, the bit array must have at
1131/// least 12 bits: `Size(>, 12)`
1132/// ```
1133///
1134#[derive(Clone, Eq, PartialEq, Debug, Copy)]
1135pub enum SizeOperator {
1136 GreaterEqual,
1137 Equal,
1138}
1139
1140/// Represents the action of reading a certain number of bits from a bit array
1141/// at a given position, returning a value with the given type.
1142///
1143/// Notice how the starting position and number of bits to read might not be
1144/// known at compile time but also include variables! For example here:
1145///
1146/// ```txt
1147// <<len, payload:size(len), rest:bits>>
1148/// ─┬─ ─┬───────────────
1149/// │ ╰── Here payload has a variable size, given by the `len` variable
1150/// │
1151/// ╰── While this segment has a constant size of 8 bits
1152/// ```
1153///
1154#[derive(Clone, Eq, PartialEq, Debug, Hash)]
1155pub struct ReadAction {
1156 /// The offset to start reading the bits from.
1157 ///
1158 pub from: Offset,
1159 /// Number of _bits_ to read.
1160 ///
1161 pub size: ReadSize,
1162 /// The type of the read value.
1163 ///
1164 pub type_: ReadType,
1165 /// Endianness of the read value.
1166 ///
1167 pub endianness: Endianness,
1168 /// Signedness of the read value.
1169 ///
1170 pub signed: bool,
1171}
1172
1173/// Only a subset of all the possible Gleam types can be used for a pattern
1174/// segment. We enumerate those out explicitly here.
1175///
1176#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1177pub enum ReadType {
1178 Float,
1179 Int,
1180 String,
1181 BitArray,
1182 UtfCodepoint,
1183}
1184
1185impl ReadType {
1186 pub(crate) fn is_int(&self) -> bool {
1187 match self {
1188 ReadType::Int => true,
1189 ReadType::Float | ReadType::String | ReadType::BitArray | ReadType::UtfCodepoint => {
1190 false
1191 }
1192 }
1193 }
1194
1195 pub(crate) fn is_float(&self) -> bool {
1196 match self {
1197 ReadType::Float => true,
1198 ReadType::Int | ReadType::String | ReadType::BitArray | ReadType::UtfCodepoint => false,
1199 }
1200 }
1201}
1202
1203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1204enum Confidence {
1205 Certain,
1206 Uncertain,
1207}
1208
1209impl Confidence {
1210 fn is_certain(&self) -> bool {
1211 match self {
1212 Confidence::Certain => true,
1213 Confidence::Uncertain => false,
1214 }
1215 }
1216
1217 fn or_else(&self, fun: impl Fn() -> Confidence) -> Confidence {
1218 match self {
1219 Confidence::Certain => Confidence::Certain,
1220 Confidence::Uncertain => fun(),
1221 }
1222 }
1223}
1224
1225#[derive(Clone, Eq, PartialEq, Debug, Hash)]
1226pub struct Offset {
1227 pub constant: BigInt,
1228 pub variables: im::HashMap<VariableUsage, usize>,
1229}
1230
1231impl Offset {
1232 pub fn constant(value: impl Into<BigInt>) -> Self {
1233 Self {
1234 constant: value.into(),
1235 variables: im::HashMap::new(),
1236 }
1237 }
1238
1239 fn add_size(self, size: &ReadSize) -> Offset {
1240 match size {
1241 ReadSize::ConstantBits(value) => Self {
1242 constant: self.constant + value,
1243 variables: self.variables,
1244 },
1245 ReadSize::VariableBits { variable, unit } => Self {
1246 constant: self.constant,
1247 variables: self.variables.alter(
1248 |value| match value {
1249 Some(value) => Some(value + (*unit as usize)),
1250 None => Some(*unit as usize),
1251 },
1252 variable.as_ref().clone(),
1253 ),
1254 },
1255 ReadSize::RemainingBits | ReadSize::RemainingBytes => self,
1256 }
1257 }
1258
1259 pub fn add_constant(&self, constant: impl Into<BigInt>) -> Offset {
1260 Offset {
1261 constant: self.constant.clone() + constant.into(),
1262 variables: self.variables.clone(),
1263 }
1264 }
1265
1266 /// Returns `true` if we can tell for certain this offset is greater than
1267 /// another one.
1268 ///
1269 fn greater(&self, other: &Offset) -> Confidence {
1270 if self.constant > other.constant && superset(&self.variables, &other.variables) {
1271 Confidence::Certain
1272 } else {
1273 Confidence::Uncertain
1274 }
1275 }
1276
1277 /// Returns `true` if we can tell for certain this offset is greater or equal
1278 /// to another one.
1279 ///
1280 fn greater_equal(&self, other: &Offset) -> Confidence {
1281 if self == other
1282 || (self.constant >= other.constant && superset(&self.variables, &other.variables))
1283 {
1284 Confidence::Certain
1285 } else {
1286 Confidence::Uncertain
1287 }
1288 }
1289
1290 /// Returns `true` if we can tell for certain this offset is different from
1291 /// another one.
1292 ///
1293 fn different(&self, other: &Offset) -> Confidence {
1294 self.greater(other).or_else(|| other.greater(self))
1295 }
1296
1297 pub(crate) fn is_zero(&self) -> bool {
1298 self.constant == BigInt::ZERO && self.variables.is_empty()
1299 }
1300
1301 /// If this offset has a constant size, returns its size in bits.
1302 ///
1303 pub(crate) fn constant_bits(&self) -> Option<BigInt> {
1304 if self.variables.is_empty() {
1305 Some(self.constant.clone())
1306 } else {
1307 None
1308 }
1309 }
1310
1311 /// If this offset has a constant size that's a whole number of bytes,
1312 /// returns its size in bytes.
1313 ///
1314 pub(crate) fn constant_bytes(&self) -> Option<BigInt> {
1315 let bits = self.constant_bits()?;
1316 if bits.clone() % 8 == BigInt::ZERO {
1317 Some(bits / 8)
1318 } else {
1319 None
1320 }
1321 }
1322
1323 fn referenced_segment_patterns(&self) -> Vec<(&EcoString, &ReadAction)> {
1324 self.variables
1325 .keys()
1326 .filter_map(|variable_usage| match variable_usage {
1327 VariableUsage::PatternSegment(segment_name, read_action) => {
1328 Some((segment_name, read_action))
1329 }
1330 VariableUsage::OutsideVariable(..) => None,
1331 })
1332 .collect_vec()
1333 }
1334}
1335
1336/// The number of bits to read in a read action when reading a segment.
1337///
1338#[derive(Clone, Eq, PartialEq, Debug, Hash)]
1339pub enum ReadSize {
1340 /// Read a constant, compile-time-known number of bits.
1341 ///
1342 /// ```txt
1343 /// <<tag:size(8)>>
1344 /// ─┬─────────
1345 /// ╰─ Here we know that we have to read exactly 8 bits
1346 /// ```
1347 ///
1348 ConstantBits(BigInt),
1349 /// Read a variable number of bits.
1350 ///
1351 /// ```txt
1352 /// <<len, payload:size(len)>>
1353 /// ─┬───────────────
1354 /// ╰─ Here we will know how many bits to read only at runtime since
1355 /// the length is a variable
1356 /// ```
1357 ///
1358 VariableBits {
1359 variable: Box<VariableUsage>,
1360 unit: u8,
1361 },
1362 /// Read all the remaining bits in the bit array when using a catch all
1363 /// pattern.
1364 ///
1365 /// ```txt
1366 /// <<_, rest:bits>>
1367 /// ─┬───────
1368 /// ╰─ We take all the remaining bits from the bit array
1369 /// ```
1370 ///
1371 RemainingBits,
1372 RemainingBytes,
1373}
1374
1375impl ReadSize {
1376 /// If this is a constant number of bits returns it wrapped in `Some`.
1377 pub(crate) fn constant_bits(&self) -> Option<BigInt> {
1378 match self {
1379 ReadSize::ConstantBits(value) => Some(value.clone()),
1380 ReadSize::VariableBits { .. } | ReadSize::RemainingBits | ReadSize::RemainingBytes => {
1381 None
1382 }
1383 }
1384 }
1385
1386 /// If this is a constant number of bytes returns it wrapped in `Some`.
1387 pub(crate) fn constant_bytes(&self) -> Option<BigInt> {
1388 let bits = self.constant_bits()?;
1389 if bits.clone() % 8 == BigInt::ZERO {
1390 Some(bits / 8)
1391 } else {
1392 None
1393 }
1394 }
1395}
1396
1397#[derive(Clone, Eq, PartialEq, Debug, Hash)]
1398pub enum VariableUsage {
1399 /// A bit array named segment that was brought into scope in the bit array
1400 /// pattern itself and might be referenced by later segments.
1401 PatternSegment(EcoString, ReadAction),
1402 /// A variable defined somewhere else
1403 OutsideVariable(EcoString),
1404}
1405
1406impl VariableUsage {
1407 pub fn name(&self) -> &EcoString {
1408 match self {
1409 VariableUsage::PatternSegment(name, _) | VariableUsage::OutsideVariable(name) => name,
1410 }
1411 }
1412}
1413
1414/// This is the decision tree that a pattern matching expression gets turned
1415/// into: it's a tree-like structure where each path to a root node contains a
1416/// series of checks to perform at runtime to understand if a value matches with
1417/// a given pattern.
1418///
1419#[derive(Debug, Clone, PartialEq, Eq)]
1420pub enum Decision {
1421 /// This is the final node of the tree, once we get to this one we know we
1422 /// have a body to run because a given pattern matched.
1423 ///
1424 Run { body: Body },
1425
1426 /// We have to make this decision when we run into a branch that also has a
1427 /// guard: if it is true we can finally run the body of the branch, stored in
1428 /// `if_true`.
1429 /// If it is false we might still have to take other decisions and so we might
1430 /// have another `DecisionTree` to traverse, stored in `if_false`.
1431 ///
1432 Guard {
1433 guard: usize,
1434 if_true: Body,
1435 if_false: Box<Decision>,
1436 },
1437
1438 /// When reaching this node we'll have to see if any of the possible checks
1439 /// in `choices` will succeed on `var`. If it does, we know that's the path
1440 /// we have to go down to. If none of the checks matches, then we'll have to
1441 /// go down the `fallback` branch.
1442 ///
1443 /// The type system guarantees that all switches will always have at least
1444 /// one last choice that acts as a fallback to pick if none of the others
1445 /// matches; no matter the value we're matching on.
1446 ///
1447 /// In case we're dealing with exhaustive cases (for example on lists/custom
1448 /// types) we also keep track of the final check associated with the final
1449 /// branch: keep in mind, we don't actually have to run that check because
1450 /// we know that the type system guarantees it will always match; but it
1451 /// can hold useful informations for type generation so we keep it around.
1452 /// You can read the doc for `FallbackCheck` to find out a more in depth
1453 /// explanation and some examples!
1454 ///
1455 Switch {
1456 var: Variable,
1457 choices: Vec<(RuntimeCheck, Box<Decision>)>,
1458 fallback: Box<Decision>,
1459 fallback_check: FallbackCheck,
1460 },
1461
1462 /// This is a special node: it represents a missing pattern. If a tree
1463 /// contains such a node, then we know that the patterns it was compiled
1464 /// from are not exhaustive and the path leading to this node will describe
1465 /// what kind of pattern doesn't match!
1466 ///
1467 Fail,
1468}
1469
1470/// When we have a swith in the decision tree we know there's always going to be
1471/// at least one choice that comes last and we know is going to match no matter
1472/// what. This might fall under three different categories.
1473///
1474#[derive(Debug, Eq, PartialEq, Clone)]
1475pub enum FallbackCheck {
1476 /// This corresponds to the catch all added at the end of a case expression
1477 /// matching on an infinite type.
1478 ///
1479 /// ```txt
1480 /// case todo {
1481 /// 1 -> todo
1482 /// _ -> todo
1483 /// ─┬───────
1484 // ╰── when matching on an infinite type there's going to be a catch all
1485 /// }
1486 ///
1487 InfiniteCatchAll,
1488
1489 /// This happens when we're matching on a variant whose checks are all
1490 /// explicitly written down:
1491 ///
1492 /// ```txt
1493 /// case todo {
1494 /// Ok(_) -> todo
1495 /// Error(Nil) -> todo
1496 /// ─┬────────────────
1497 // ╰── when matching on a custom type (or list!) we'll have to write all
1498 // variants down, so there's always going to be a last one.
1499 /// }
1500 /// ```
1501 ///
1502 /// The type system will make sure that this last check will always match,
1503 /// no matter what, if none of the other checks matches. We still keep the
1504 /// corresponding runtime check around because it's useful for code generation!
1505 ///
1506 RuntimeCheck { check: RuntimeCheck },
1507
1508 /// This is a special case for a catch all! It happens when we're matching
1509 /// on a variant and use a catch all pattern:
1510 ///
1511 /// ```txt
1512 /// case todo {
1513 /// Ok(_) -> todo
1514 /// _ -> todo
1515 /// ─┬───────
1516 /// ╰── here we know we're just skipping over the `Error` variant with
1517 /// this catch all.
1518 /// }
1519 /// ```
1520 ///
1521 /// We know exactly which variants we're skipping, so we keep track of
1522 /// those skipped checks (they will end up being useful for reporting
1523 /// missing patterns!)
1524 ///
1525 CatchAll { ignored_checks: Vec<RuntimeCheck> },
1526}
1527
1528impl Decision {
1529 pub fn run(body: Body) -> Self {
1530 Decision::Run { body }
1531 }
1532
1533 pub fn guard(guard: usize, if_true: Body, if_false: Self) -> Self {
1534 Decision::Guard {
1535 guard,
1536 if_true,
1537 if_false: Box::new(if_false),
1538 }
1539 }
1540}
1541
1542/// The `case` compiler itself (shocking, I know).
1543///
1544#[derive(Debug)]
1545struct Compiler<'a> {
1546 environment: &'a Environment<'a>,
1547 patterns: Arena<Pattern>,
1548 variable_id: usize,
1549 diagnostics: Diagnostics,
1550}
1551
1552/// The result of compiling a pattern match expression.
1553///
1554pub struct CompileCaseResult {
1555 pub compiled_case: CompiledCase,
1556 pub diagnostics: Diagnostics,
1557}
1558
1559impl CompileCaseResult {
1560 pub fn is_reachable(&self, clause: usize, pattern_index: usize) -> Reachability {
1561 if self
1562 .diagnostics
1563 .reachable
1564 .contains(&(clause, pattern_index))
1565 {
1566 Reachability::Reachable
1567 } else if self
1568 .diagnostics
1569 .match_impossible_variants
1570 .contains(&(clause, pattern_index))
1571 {
1572 Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant)
1573 } else {
1574 Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern)
1575 }
1576 }
1577
1578 pub fn missing_patterns(&self, environment: &Environment<'_>) -> Vec<EcoString> {
1579 missing_patterns::missing_patterns(self, environment)
1580 }
1581}
1582
1583#[derive(Debug, PartialEq, Eq, Clone)]
1584pub struct CompiledCase {
1585 pub tree: Decision,
1586 pub subject_variables: Vec<Variable>,
1587}
1588
1589impl CompiledCase {
1590 pub fn failure() -> Self {
1591 Self {
1592 tree: Decision::Fail,
1593 subject_variables: vec![],
1594 }
1595 }
1596}
1597
1598/// Whether a pattern is reachable, or why it is unreachable.
1599///
1600#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1601pub enum Reachability {
1602 Reachable,
1603 Unreachable(UnreachablePatternReason),
1604}
1605
1606/// A type for storing diagnostics produced by the decision tree compiler.
1607///
1608#[derive(Debug)]
1609pub struct Diagnostics {
1610 /// A flag indicating the match is missing one or more pattern.
1611 pub missing: bool,
1612
1613 /// The patterns that are reachable. If a pattern isn't in this list it
1614 /// means it is redundant.
1615 /// Each entry is a pair of indices: The index of the clause the pattern
1616 /// belongs to, followed by the index of the pattern itself within the
1617 /// clause. For example, in this case expression:
1618 /// ```gleam
1619 /// case x {
1620 /// 1 -> todo
1621 /// 2 | 3 -> todo
1622 /// _ -> todo
1623 /// }
1624 /// ```
1625 ///
1626 /// The `3` pattern would be `(1, 1)`, because it is the second pattern of
1627 /// the second clause, and both are zero-indexed.
1628 ///
1629 pub reachable: HashSet<(usize, usize)>,
1630
1631 /// Patterns which match on variants of a type which the compiler
1632 /// can tell will never be present, due to variant inference.
1633 ///
1634 /// See `reachable` for an explanation of its structure.
1635 ///
1636 pub match_impossible_variants: HashSet<(usize, usize)>,
1637}
1638
1639impl<'a> Compiler<'a> {
1640 fn new(environment: &'a Environment<'a>, variable_id: usize, patterns: Arena<Pattern>) -> Self {
1641 Self {
1642 environment,
1643 patterns,
1644 variable_id,
1645 diagnostics: Diagnostics {
1646 missing: false,
1647 reachable: HashSet::new(),
1648 match_impossible_variants: HashSet::new(),
1649 },
1650 }
1651 }
1652
1653 fn pattern(&mut self, pattern_id: Id<Pattern>) -> &mut Pattern {
1654 self.patterns
1655 .get_mut(pattern_id)
1656 .expect("unknown pattern id")
1657 }
1658
1659 /// Returns a new fresh variable (i.e. guaranteed to have a unique `variable_id`)
1660 /// with the given type.
1661 ///
1662 fn fresh_variable(&mut self, type_: Arc<Type>) -> Variable {
1663 let var = Variable::new(self.variable_id, type_);
1664 self.variable_id += 1;
1665 var
1666 }
1667
1668 fn mark_as_reached(&mut self, branch: &Branch) {
1669 let _ = self
1670 .diagnostics
1671 .reachable
1672 .insert((branch.clause_index, branch.alternative_index));
1673 }
1674
1675 fn mark_as_matching_impossible_variant(&mut self, branch: &Branch) {
1676 let _ = self
1677 .diagnostics
1678 .reachable
1679 .remove(&(branch.clause_index, branch.alternative_index));
1680 let _ = self
1681 .diagnostics
1682 .match_impossible_variants
1683 .insert((branch.clause_index, branch.alternative_index));
1684 }
1685
1686 fn compile(&mut self, mut branches: VecDeque<Branch>) -> Decision {
1687 branches
1688 .iter_mut()
1689 .for_each(|branch| branch.move_unconditional_patterns(self));
1690
1691 let Some(first_branch) = branches.front() else {
1692 // If there's no branches, that means we have a pattern that is not
1693 // exhaustive as there's nothing that could match!
1694 self.diagnostics.missing = true;
1695 return Decision::Fail;
1696 };
1697
1698 self.mark_as_reached(first_branch);
1699
1700 // In order to compile the branches, we need to pick a `PatternCheck` from
1701 // the first branch, and use the variable it's pattern matching on to create
1702 // a new node in the tree. All the branches will be split into different
1703 // possible paths of this tree.
1704 match find_pivot_check(first_branch, &branches) {
1705 Some(PatternCheck { var, .. }) => self.split_and_compile_with_pivot_var(var, branches),
1706
1707 // If the branch has no remaining checks, it means that we've moved all
1708 // its variable patterns as assignments into the body and there's no
1709 // additional checks remaining. So the only thing left that could result
1710 // in the match failing is the additional guard.
1711 None => match first_branch.guard {
1712 // If there's no guard we're in the following situation:
1713 // `∅ -> body`. It means that this branch will always match no
1714 // matter what, all the remaining branches are just discarded and
1715 // we can produce a terminating node to run the body
1716 // unconditionally.
1717 None => Decision::run(first_branch.body.clone()),
1718 // If we have a guard we're in this scenario:
1719 // `∅ if condition -> body`. We can produce a `Guard` node:
1720 // if the condition evaluates to `True` we can run its body.
1721 // Otherwise, we'll have to keep looking at the remaining branches
1722 // to know what to do if this branch doesn't match.
1723 Some(guard) => {
1724 let if_true = first_branch.body.clone();
1725 // All the remaining branches will be compiled and end up
1726 // in the path of the tree to choose if the guard is false.
1727 let _ = branches.pop_front();
1728 let if_false = self.compile(branches);
1729 Decision::guard(guard, if_true, if_false)
1730 }
1731 },
1732 }
1733 }
1734
1735 fn split_and_compile_with_pivot_var(
1736 &mut self,
1737 pivot_var: Variable,
1738 branches: VecDeque<Branch>,
1739 ) -> Decision {
1740 // We first try and come up with a list of all the runtime checks we might
1741 // have to perform on the variable at runtime. In most cases it's a limited
1742 // number of checks that we know before hand (for example, when matching
1743 // on a list, or on a custom type).
1744 let branch_mode = pivot_var.branch_mode(self.environment);
1745 let known_checks = match &branch_mode {
1746 // If the type being matched on is infinite there's no known runtime
1747 // check we could come up with in advance. So we'll build them as
1748 // we go.
1749 BranchMode::Infinite => vec![],
1750
1751 // If the type is a tuple there's only one runtime check we could
1752 // perform that actually makes sense.
1753 BranchMode::Tuple { elements } => vec![self.is_tuple_check(elements)],
1754
1755 // If the type being matched on is a list we know the resulting
1756 // decision tree node is only ever going to have two different paths:
1757 // one to follow if the list is empty, and one to follow if it's not.
1758 BranchMode::List { inner_type } => {
1759 vec![
1760 RuntimeCheck::EmptyList,
1761 self.is_list_check(inner_type.clone()),
1762 ]
1763 }
1764
1765 // If we know that a specific variant is inferred we will require just
1766 // that one and not all the other ones we know for sure are not going to
1767 // be there.
1768 BranchMode::NamedType {
1769 constructors,
1770 inferred_variant: Some(index),
1771 } => {
1772 let constructor = constructors
1773 .get(*index)
1774 .expect("wrong index for inferred variant");
1775 vec![self.is_variant_check(*index, constructor)]
1776 }
1777
1778 // Otherwise we know we'll need a check for each of its possible variants.
1779 BranchMode::NamedType { constructors, .. } => constructors
1780 .iter()
1781 .enumerate()
1782 .map(|(index, constructor)| self.is_variant_check(index, constructor))
1783 .collect_vec(),
1784 };
1785
1786 // We then split all the branches using these checks and compile the
1787 // choices they've been split up into.
1788 let mut splitter = BranchSplitter::from_checks(known_checks);
1789 self.split_branches(&mut splitter, branches, pivot_var.clone(), &branch_mode);
1790 if branch_mode.is_infinite() {
1791 // If the branching is infinite, that means we always need to also have
1792 // a fallback (imagine you're pattern matching on an `Int` and put no
1793 // `_` at the end of the case expression).
1794 self.splitter_to_switch(pivot_var, splitter)
1795 } else if splitter.choices.is_empty() {
1796 // If the branching doesn't need any fallback but we ended up with no
1797 // checks it means we're trying to pattern match on an external type
1798 // but haven't provided a catch-all case.
1799 // That's never going to match, so we produce a failure node.
1800 self.diagnostics.missing = true;
1801 Decision::Fail
1802 } else {
1803 // Otherwise we know that one of the possible runtime checks is always
1804 // going to succeed and there's no need to also have a fallback branch.
1805 self.splitter_to_exhaustive_switch(pivot_var, splitter)
1806 }
1807 }
1808
1809 fn split_branches(
1810 &mut self,
1811 splitter: &mut BranchSplitter,
1812 branches: VecDeque<Branch>,
1813 pivot_var: Variable,
1814 branch_mode: &BranchMode,
1815 ) {
1816 for mut branch in branches {
1817 let Some(pattern_check) = branch.pop_check_on_var(&pivot_var) else {
1818 // If the branch doesn't perform any check on the pivot variable, it means
1819 // it could still match no matter what shape `pivot_var` has. So we must
1820 // add it as a fallback branch, that is a branch that is still relevant
1821 // for all possible paths in the decision tree.
1822 splitter.add_fallback_branch(branch);
1823 continue;
1824 };
1825
1826 let checked_pattern = self.pattern(pattern_check.pattern);
1827 if checked_pattern.is_matching_on_unreachable_variant(branch_mode) {
1828 self.mark_as_matching_impossible_variant(&branch);
1829 continue;
1830 }
1831
1832 splitter.add_checked_branch(pattern_check, branch, branch_mode, self);
1833 }
1834 }
1835
1836 /// Compiles the branches in a splitter down to a switch matching on a type
1837 /// with infinite variants (like ints, floats and strings). With a fallaback
1838 /// branch.
1839 ///
1840 fn splitter_to_switch(&mut self, var: Variable, splitter: BranchSplitter) -> Decision {
1841 let choices = self.compile_all_choices(splitter.choices);
1842 let last_choice = self.compile(splitter.fallback);
1843 Decision::Switch {
1844 var,
1845 choices,
1846 fallback: Box::new(last_choice),
1847 fallback_check: FallbackCheck::InfiniteCatchAll,
1848 }
1849 }
1850
1851 /// Compiles the branches in a splitter down to a switch matching on a type
1852 /// with a finite known number of variants.
1853 ///
1854 fn splitter_to_exhaustive_switch(
1855 &mut self,
1856 var: Variable,
1857 splitter: BranchSplitter,
1858 ) -> Decision {
1859 let mut choices = splitter.choices;
1860 let (choices, fallback, fallback_check) =
1861 if choices.iter().any(|(check, _)| check.is_ignored()) {
1862 // If there's any check that is ignored and not explicitly being
1863 // matched on then we want to ignore all of those and clump them
1864 // into a single "fallback" branch to avoid bloating the generated
1865 // tree.
1866 let mut ignored_checks = vec![];
1867 let mut remaining_choices = vec![];
1868 for choice in choices.into_iter() {
1869 if choice.0.is_ignored() {
1870 ignored_checks.push(choice.0)
1871 } else {
1872 remaining_choices.push(choice)
1873 }
1874 }
1875
1876 let fallback_check = FallbackCheck::CatchAll { ignored_checks };
1877 (remaining_choices, splitter.fallback, fallback_check)
1878 } else {
1879 // Otherwise we just use the last check as the final one that
1880 // can be outright skipped.
1881 let (last_check, last_choice) = choices.pop().expect("at least one choice");
1882 let fallback_check = FallbackCheck::RuntimeCheck { check: last_check };
1883 (choices, last_choice, fallback_check)
1884 };
1885
1886 let choices = self.compile_all_choices(choices);
1887 let fallback = Box::new(self.compile(fallback));
1888 Decision::Switch {
1889 var,
1890 choices,
1891 fallback,
1892 fallback_check,
1893 }
1894 }
1895
1896 fn compile_all_choices(
1897 &mut self,
1898 choices: Vec<(RuntimeCheck, VecDeque<Branch>)>,
1899 ) -> Vec<(RuntimeCheck, Box<Decision>)> {
1900 choices
1901 .into_iter()
1902 .map(|(check, branches)| (check, Box::new(self.compile(branches))))
1903 .collect_vec()
1904 }
1905
1906 /// Turns a `RuntimeCheckKind` into a new `RuntimeCheck` by coming up with
1907 /// the needed new fresh variables.
1908 /// All the type information needed to create these variables is in the
1909 /// `branch_mode` arg.
1910 ///
1911 fn fresh_runtime_check(
1912 &mut self,
1913 kind: RuntimeCheckKind,
1914 branch_mode: &BranchMode,
1915 ) -> RuntimeCheck {
1916 match (kind, branch_mode) {
1917 (RuntimeCheckKind::Int { value }, _) => RuntimeCheck::Int {
1918 value: value.clone(),
1919 },
1920 (RuntimeCheckKind::Float { value }, _) => RuntimeCheck::Float {
1921 value: value.clone(),
1922 },
1923 (RuntimeCheckKind::String { value }, _) => RuntimeCheck::String {
1924 value: value.clone(),
1925 },
1926 (RuntimeCheckKind::StringPrefix { prefix }, _) => RuntimeCheck::StringPrefix {
1927 prefix: prefix.clone(),
1928 rest: self.fresh_variable(string()),
1929 },
1930 (RuntimeCheckKind::Tuple { .. }, BranchMode::Tuple { elements }) => {
1931 self.is_tuple_check(elements)
1932 }
1933 (RuntimeCheckKind::Variant { index }, BranchMode::NamedType { constructors, .. }) => {
1934 self.is_variant_check(
1935 index,
1936 constructors.get(index).expect("unknown variant index"),
1937 )
1938 }
1939 (RuntimeCheckKind::EmptyList, _) => RuntimeCheck::EmptyList,
1940 (RuntimeCheckKind::NonEmptyList, BranchMode::List { inner_type }) => {
1941 self.is_list_check(inner_type.clone())
1942 }
1943 (_, _) => unreachable!("type checking should make this impossible"),
1944 }
1945 }
1946
1947 /// Comes up with new pattern cecks that have to match in case a given
1948 /// runtime check succeeds for the given pattern.
1949 ///
1950 /// Let's make an example: when we have a pattern - say `a is Wibble(1, [])` -
1951 /// we come up with a runtime check to perform on it. For our example the
1952 /// runtime check is to make sure that `a` is indeed a `Wibble` variant.
1953 /// However, after successfully performing that check we're left with much to
1954 /// do! We know that `a` is `Wibble` but now we'll have to make sure that its
1955 /// inner arguments also match the given patterns. So the new additional checks
1956 /// we have to add are `a0 is 1, a1 is []` (where `a0` and `a1` are the fresh
1957 /// variable names we use to refer to the constructor's arguments).
1958 ///
1959 fn new_checks(
1960 &mut self,
1961 for_pattern: &Pattern,
1962 after_succeding_check: &RuntimeCheck,
1963 ) -> Vec<PatternCheck> {
1964 match (for_pattern, after_succeding_check) {
1965 // These patterns never result in adding new checks. After a runtime
1966 // check matches on them there's nothing else to discover.
1967 (
1968 Pattern::Discard
1969 | Pattern::Assign { .. }
1970 | Pattern::Variable { .. }
1971 | Pattern::Int { .. }
1972 | Pattern::Float { .. }
1973 | Pattern::BitArray { .. }
1974 | Pattern::EmptyList,
1975 _,
1976 )
1977 | (Pattern::String { .. }, RuntimeCheck::String { .. }) => vec![],
1978
1979 // After making sure a value is not an empty list we'll have to perform
1980 // additional checks on its first item and on the tail.
1981 (
1982 Pattern::NonEmptyList {
1983 first: first_pattern,
1984 rest: rest_pattern,
1985 },
1986 RuntimeCheck::NonEmptyList {
1987 first: first_variable,
1988 rest: rest_variable,
1989 },
1990 ) => vec![
1991 first_variable.is(*first_pattern),
1992 rest_variable.is(*rest_pattern),
1993 ],
1994
1995 // After making sure a value is a specific variant we'll have to check each
1996 // of its arguments respects the given patterns (as shown in the doc example for
1997 // this function!)
1998 (
1999 Pattern::Variant {
2000 fields: patterns, ..
2001 },
2002 RuntimeCheck::Variant {
2003 fields: variables, ..
2004 },
2005 ) => (variables.iter().zip(patterns))
2006 .map(|(field, pattern)| field.is(*pattern))
2007 .collect_vec(),
2008
2009 // Tuples are exactly the same as variants: after making sure we're dealing with
2010 // a tuple, we will have to check that each of its elements matches the given
2011 // pattern: `a is #(1, _)` will result in the following checks
2012 // `a0 is 1, a1 is _` (where `a0` and `a1` are fresh variable names we use to
2013 // refer to each of the tuple's elements).
2014 (
2015 Pattern::Tuple { elements: patterns },
2016 RuntimeCheck::Tuple {
2017 elements: variables,
2018 ..
2019 },
2020 ) => (variables.iter().zip(patterns))
2021 .map(|(element, pattern)| element.is(*pattern))
2022 .collect_vec(),
2023
2024 // Strings are quite fun: if we've checked at runtime a string starts with a given
2025 // prefix and we want to check that it's some overlapping literal value we'll still
2026 // have some amount of work to perform.
2027 //
2028 // Let's have a look at an example: the pattern we care about is `a is "wibble"`
2029 // and we've just successfully ran the runtime check for `a is "wib" <> rest`.
2030 // So we know the string already starts with `"wib"` what we have to check now
2031 // is that the remaining part `rest` is `"ble"`.
2032 (Pattern::String { value }, RuntimeCheck::StringPrefix { prefix, rest, .. }) => {
2033 let remaining = value.strip_prefix(prefix.as_str()).unwrap_or(value);
2034 vec![rest.is(self.string_pattern(remaining))]
2035 }
2036
2037 // String prefixes are similar to strings, but a bit more involved. Let's say we're
2038 // checking the pattern:
2039 //
2040 // ```text
2041 // "wibblest" <> rest1
2042 // ─┬────────
2043 // ╰── We will refer to this as `prefix1`
2044 // ```
2045 //
2046 // And we know that the following overlapping runtime check has already succeeded:
2047 //
2048 // ```text
2049 // "wibble" <> rest0
2050 // ─┬──────
2051 // ╰── We will refer to this as `prefix0`
2052 // ```
2053 //
2054 // We're lucky because we now know quite a bit about the shape of the string. Since
2055 // we know it already starts with `"wibble"` we can just check that the remaining
2056 // part after that starts with the missing part of the prefix:
2057 // `prefix0 is "st" <> rest1`.
2058 (
2059 Pattern::StringPrefix {
2060 prefix: prefix1,
2061 prefix_name: _,
2062 rest: rest1,
2063 },
2064 RuntimeCheck::StringPrefix {
2065 prefix: prefix0,
2066 rest: rest0,
2067 },
2068 ) => {
2069 let remaining = prefix1.strip_prefix(prefix0.as_str()).unwrap_or(prefix1);
2070 // If the prefixes are exactly the same then the only remaining check
2071 // is for the two remaining bits to be the same.
2072 if remaining.is_empty() {
2073 vec![rest0.is(*rest1)]
2074 } else {
2075 vec![rest0.is(self.string_prefix_pattern(remaining, *rest1))]
2076 }
2077 }
2078
2079 (_, _) => unreachable!("invalid pattern overlapping"),
2080 }
2081 }
2082
2083 /// Builds an `IsVariant` runtime check, coming up with new fresh variable names
2084 /// for its arguments.
2085 ///
2086 fn is_variant_check(
2087 &mut self,
2088 index: usize,
2089 constructor: &TypeValueConstructor,
2090 ) -> RuntimeCheck {
2091 RuntimeCheck::Variant {
2092 index,
2093 match_: VariantMatch::NeverExplicitlyMatchedOn {
2094 name: constructor.name.clone(),
2095 },
2096 labels: constructor
2097 .parameters
2098 .iter()
2099 .enumerate()
2100 .filter_map(|(i, parameter)| Some((i, parameter.label.clone()?)))
2101 .collect(),
2102 fields: constructor
2103 .parameters
2104 .iter()
2105 .map(|parameter| parameter.type_.clone())
2106 .map(|type_| self.fresh_variable(type_))
2107 .collect_vec(),
2108 }
2109 }
2110
2111 /// Builds an `IsNonEmptyList` runtime check, coming up with fresh variable
2112 /// names for its arguments.
2113 ///
2114 fn is_list_check(&mut self, inner_type: Arc<Type>) -> RuntimeCheck {
2115 RuntimeCheck::NonEmptyList {
2116 first: self.fresh_variable(inner_type.clone()),
2117 rest: self.fresh_variable(Arc::new(Type::list(inner_type))),
2118 }
2119 }
2120
2121 /// Builds an `IsTuple` runtime check, coming up with fresh variable
2122 /// names for its arguments.
2123 ///
2124 fn is_tuple_check(&mut self, elements: &[Arc<Type>]) -> RuntimeCheck {
2125 RuntimeCheck::Tuple {
2126 size: elements.len(),
2127 elements: elements
2128 .iter()
2129 .map(|type_| self.fresh_variable(type_.clone()))
2130 .collect_vec(),
2131 }
2132 }
2133
2134 /// Allocates a new `StringPattern` with the given value.
2135 ///
2136 fn string_pattern(&mut self, value: &str) -> Id<Pattern> {
2137 self.patterns.alloc(Pattern::String {
2138 value: EcoString::from(value),
2139 })
2140 }
2141
2142 fn bit_array_pattern(&mut self, tests: VecDeque<BitArrayTest>) -> Id<Pattern> {
2143 self.patterns.alloc(Pattern::BitArray { tests })
2144 }
2145
2146 /// Allocates a new `StringPrefix` pattern with the given prefix and pattern
2147 /// for the rest of the string.
2148 ///
2149 fn string_prefix_pattern(&mut self, prefix: &str, rest: Id<Pattern>) -> Id<Pattern> {
2150 self.patterns.alloc(Pattern::StringPrefix {
2151 prefix: EcoString::from(prefix),
2152 prefix_name: None,
2153 rest,
2154 })
2155 }
2156}
2157
2158/// Returns a pattern check from `first_branch` to be used as a pivot to split all
2159/// the `branches`.
2160///
2161fn find_pivot_check(first_branch: &Branch, branches: &VecDeque<Branch>) -> Option<PatternCheck> {
2162 // To try and minimise code duplication, we use the following heuristic: we
2163 // choose the check matching on the variable that is referenced the most
2164 // across all checks in all branches.
2165 let mut var_references = HashMap::new();
2166 for branch in branches {
2167 for check in &branch.checks {
2168 let _ = var_references
2169 .entry(check.var.id)
2170 .and_modify(|references| *references += 1)
2171 .or_insert(0);
2172 }
2173 }
2174
2175 first_branch
2176 .checks
2177 .iter()
2178 .max_by_key(|check| var_references.get(&check.var.id).cloned().unwrap_or(0))
2179 .cloned()
2180}
2181
2182/// A handy data structure we use to split branches in different possible paths
2183/// based on a check.
2184///
2185struct BranchSplitter {
2186 pub choices: Vec<(RuntimeCheck, VecDeque<Branch>)>,
2187 pub fallback: VecDeque<Branch>,
2188
2189 /// This is used to allow quickly looking up a choice in the `choices`
2190 /// vector, without loosing track of the checks' order.
2191 indices: HashMap<RuntimeCheckKind, usize>,
2192
2193 /// This is used to store the indices of just the prefix checks as they have
2194 /// different rules from all the other `RuntimeCheckKinds` whose indices are
2195 /// instead stored in the `indices` field.
2196 ///
2197 /// We discuss this in more detail in the `index_of_overlapping_runtime_check`
2198 /// function!
2199 prefix_indices: Trie<String, usize>,
2200}
2201
2202impl BranchSplitter {
2203 /// Creates a new splitter with the given starting checks.
2204 ///
2205 fn from_checks(checks: Vec<RuntimeCheck>) -> Self {
2206 let mut choices = Vec::with_capacity(checks.len());
2207 let mut indices = HashMap::new();
2208
2209 for (index, runtime_check) in checks.into_iter().enumerate() {
2210 let Some(kind) = runtime_check.kind() else {
2211 continue;
2212 };
2213 let _ = indices.insert(kind, index);
2214 choices.push((runtime_check, VecDeque::new()));
2215 }
2216
2217 Self {
2218 fallback: VecDeque::new(),
2219 choices,
2220 indices,
2221 prefix_indices: Trie::new(),
2222 }
2223 }
2224
2225 /// Add a fallback branch: this is a branch that is relevant to all possible
2226 /// paths as it could still run, no matter the result of any of the `Check`s
2227 /// we've stored!
2228 ///
2229 fn add_fallback_branch(&mut self, branch: Branch) {
2230 self.choices
2231 .iter_mut()
2232 .for_each(|(_, branches)| branches.push_back(branch.clone()));
2233 self.fallback.push_back(branch);
2234 }
2235
2236 /// Given a branch and the pattern its using to check on the pivot variable,
2237 /// adds it to the paths where it's relevant, that is where we know from
2238 /// previous checks that this pattern has a chance of matching.
2239 ///
2240 fn add_checked_branch(
2241 &mut self,
2242 pattern_check: PatternCheck,
2243 mut branch: Branch,
2244 branch_mode: &BranchMode,
2245 compiler: &mut Compiler<'_>,
2246 ) {
2247 let pattern = compiler.pattern(pattern_check.pattern).clone();
2248
2249 // Bit array patterns are split in a different way that requires special
2250 // handling. Instead of reasoning on overlapping checks what we do it we
2251 // always split the decision tree in two distinct paths based on one of
2252 // the bit array pattern's tests.
2253 if let Pattern::BitArray { tests } = pattern {
2254 self.add_checked_bit_array_branch(pattern_check, tests, branch, compiler);
2255 return;
2256 };
2257
2258 let kind = pattern
2259 .to_runtime_check_kind()
2260 .expect("no unconditional patterns left");
2261
2262 let indices_of_overlapping_checks = self.indices_of_overlapping_checks(&kind);
2263 if indices_of_overlapping_checks.is_empty() {
2264 // This is a new choice we haven't yet discovered as it is not overlapping
2265 // with any of the existing ones. So we add it as a possible new path
2266 // we might have to go down to in the decision tree.
2267 self.save_index_of_new_choice(kind.clone());
2268
2269 let check = compiler.fresh_runtime_check(kind, branch_mode);
2270 for new_check in compiler.new_checks(&pattern, &check) {
2271 branch.add_check(new_check);
2272 }
2273 let mut branches = self.fallback.clone();
2274 branches.push_back(branch);
2275
2276 self.choices.push((check, branches));
2277 } else {
2278 // Otherwise, we know that the check for this branch overlaps with
2279 // (possibly more than one) existing checks and so is relevant only
2280 // as part of those existing paths.
2281 // We'll add the branch with its newly discovered checks only to those
2282 // paths.
2283 for index in indices_of_overlapping_checks.iter() {
2284 let (overlapping_check, branches) = self
2285 .choices
2286 .get_mut(*index)
2287 .expect("check to already be a choice");
2288
2289 let mut branch = branch.clone();
2290 for new_check in compiler.new_checks(&pattern, overlapping_check) {
2291 branch.add_check(new_check);
2292 }
2293 branches.push_back(branch);
2294 }
2295 }
2296
2297 // Then we have to update all variant checks with any new name/module
2298 // we might have discovered from the pattern to make sure we're using
2299 // the correct qualification to refer to each constructor.
2300 if let Pattern::Variant { name, module, .. } = pattern {
2301 for index in indices_of_overlapping_checks {
2302 let (check, _) = self
2303 .choices
2304 .get_mut(index)
2305 .expect("check to already be a choice");
2306 if let RuntimeCheck::Variant { match_, .. } = check {
2307 *match_ = VariantMatch::ExplicitlyMatchedOn {
2308 module: module.clone(),
2309 name: name.clone(),
2310 }
2311 }
2312 }
2313 }
2314 }
2315
2316 /// When we work with bit array patterns the splitter follows a different
2317 /// strategy to split branches: instead of trying to create a multiway
2318 /// decision tree with possibly many branches, we create only two branches
2319 /// based on the first segment test we run into.
2320 /// One path is going to be for the branches that can match if the test is
2321 /// successful, and the other one is going to be the usual fallback to go
2322 /// down to if it's not successful.
2323 ///
2324 /// > "And now for the tricky bit..."
2325 /// > <https://youtu.be/lKXe3HUG2l4?si=i1thYB-kjfMU8NSe&t=645>
2326 ///
2327 fn add_checked_bit_array_branch(
2328 &mut self,
2329 pattern_check: PatternCheck,
2330 tests: VecDeque<BitArrayTest>,
2331 mut branch: Branch,
2332 compiler: &mut Compiler<'_>,
2333 ) {
2334 // If we haven't found a test yet we just use the first one we find
2335 // as the pivot to split all the branches.
2336 let pivot_test = match self.choices.as_slice() {
2337 [] => {
2338 let test = tests.front().expect("empty bit array test").clone();
2339 self.choices.push((
2340 RuntimeCheck::BitArray { test: test.clone() },
2341 VecDeque::new(),
2342 ));
2343 test
2344 }
2345 [(RuntimeCheck::BitArray { test }, _)] => test.clone(),
2346 _ => unreachable!("non bit array check when splitting bit array patterns"),
2347 };
2348
2349 let pattern_fails_if_test_succeeds = tests
2350 .iter()
2351 .any(|test| test.fails_if_succeeding(&pivot_test).is_certain());
2352
2353 let pattern_fails_if_test_fails = tests
2354 .iter()
2355 .any(|test| test.fails_if_failing(&pivot_test).is_certain());
2356
2357 // The branch is still relevant for the if_true path if it still has a
2358 // chance of matching knowing that the pivot test has matched.
2359 // So if we know for certain that the pattern would fail, we don't even
2360 // bother adding it to the if_true path.
2361 if !pattern_fails_if_test_succeeds {
2362 // If the branch is relevant we can further simplify it by removing
2363 // all those tests that are guaranteed to succeed. For example,
2364 // if the succeeding pivot test is `size >= 20` there's no point
2365 // in checking that `size >= 10`, we know that's always true in this
2366 // path of the decision tree!
2367 let tests = tests
2368 .into_iter()
2369 .filter(|test| test.succeeds_if_succeeding(&pivot_test) == Confidence::Uncertain)
2370 .collect::<VecDeque<_>>();
2371
2372 let mut branch = branch.clone();
2373 let variable = &pattern_check.var;
2374 branch.add_check(variable.is(compiler.bit_array_pattern(tests)));
2375
2376 // We know that there's always going to be a single choice for the
2377 // successful check, so we get that and add the branch to it.
2378 let (_, if_true_branches) = self
2379 .choices
2380 .get_mut(0)
2381 .expect("bit array compilation with no choice");
2382 if_true_branches.push_back(branch);
2383 }
2384
2385 // Same goes for the if_false branch: knowing the pivot test has failed
2386 // we only want to keep those branches with a pattern that still have a
2387 // chance to match.
2388 if !pattern_fails_if_test_fails {
2389 // The main difference with the if true case is that we have no way
2390 // of pruning the number of needed tests, so we add this check back
2391 // exactly as it is.
2392 branch.add_check(pattern_check);
2393 self.fallback.push_back(branch);
2394 }
2395 }
2396
2397 fn save_index_of_new_choice(&mut self, kind: RuntimeCheckKind) {
2398 let _ = match kind {
2399 RuntimeCheckKind::Int { .. }
2400 | RuntimeCheckKind::Float { .. }
2401 | RuntimeCheckKind::String { .. }
2402 | RuntimeCheckKind::Tuple { .. }
2403 | RuntimeCheckKind::Variant { .. }
2404 | RuntimeCheckKind::EmptyList
2405 | RuntimeCheckKind::NonEmptyList => self.indices.insert(kind, self.choices.len()),
2406
2407 RuntimeCheckKind::StringPrefix { prefix } => self
2408 .prefix_indices
2409 .insert(prefix.to_string(), self.choices.len()),
2410 };
2411 }
2412
2413 fn indices_of_overlapping_checks(&self, kind: &RuntimeCheckKind) -> Vec<usize> {
2414 match kind {
2415 // All these checks will only overlap with a check that is exactly the
2416 // same, so we just look up their index in the `indices` map using the
2417 // kind as the lookup.
2418 RuntimeCheckKind::Int { .. }
2419 | RuntimeCheckKind::Float { .. }
2420 | RuntimeCheckKind::Tuple { .. }
2421 | RuntimeCheckKind::Variant { .. }
2422 | RuntimeCheckKind::EmptyList
2423 | RuntimeCheckKind::NonEmptyList => {
2424 self.indices.get(kind).cloned().into_iter().collect_vec()
2425 }
2426
2427 // String patterns are a bit more tricky as they might end up overlapping
2428 // even if they're not exactly the same kind of check! Let's have a look
2429 // at an example. Say we're compiling these branches:
2430 //
2431 // ```
2432 // a is "wibble" <> rest -> todo
2433 // a is "wibbler" <> rest -> todo
2434 // ```
2435 //
2436 // We use the first (and only) check in the first branch as the pivot and
2437 // now we have to decide where to put the next branch. Is it matching with
2438 // the first one or completely unrelated?
2439 // Since `"wibbler"` starts with `"wibble"` we know it's overlapping and
2440 // it cannot possibly match if the previous one doesn't!
2441 //
2442 // So when we find a `String`/`StringPrefix` pattern we look for a prefix
2443 // among the ones we have discovered so far that could match with it.
2444 // That is, we look for a prefix of the pattern we're checking in the prefix
2445 // trie.
2446 RuntimeCheckKind::StringPrefix { prefix: value } => {
2447 ancestors_values(&self.prefix_indices, value).collect_vec()
2448 }
2449
2450 // Strings are almost exactly the same, except they could also have an exact
2451 // match with other string patterns. So a string pattern could overlap with
2452 // another string pattern (if they're matching on the same value), or with
2453 // one or more string prefix patterns with a matching prefix.
2454 RuntimeCheckKind::String { value } => {
2455 let first_index = self.indices.get(kind).cloned();
2456 first_index
2457 .into_iter()
2458 .chain(ancestors_values(&self.prefix_indices, value))
2459 .collect_vec()
2460 }
2461 }
2462 }
2463}
2464
2465fn ancestors_values(trie: &Trie<String, usize>, key: &str) -> impl Iterator<Item = usize> {
2466 trie.get_ancestor(key)
2467 .into_iter()
2468 .flat_map(|ancestor| ancestor.values().copied())
2469}
2470
2471pub struct ConstructorSpecialiser {
2472 specialised_types: HashMap<u64, Arc<Type>>,
2473}
2474
2475impl ConstructorSpecialiser {
2476 fn specialise_constructors(
2477 constructors: &TypeVariantConstructors,
2478 type_arguments: &[Arc<Type>],
2479 current_module: &EcoString,
2480 type_module: &EcoString,
2481 ) -> Vec<TypeValueConstructor> {
2482 match constructors.opaque {
2483 // If the type is opaque and we are not in the definition module of
2484 // that type, we don't have access to any constructors so we treat
2485 // it the same as an external type.
2486 Opaque::Opaque if current_module != type_module => return Vec::new(),
2487 Opaque::Opaque | Opaque::NotOpaque => {}
2488 };
2489
2490 let specialiser = Self::new(constructors.type_parameters_ids.as_slice(), type_arguments);
2491 constructors
2492 .variants
2493 .iter()
2494 .map(|v| specialiser.specialise_type_value_constructor(v))
2495 .collect_vec()
2496 }
2497
2498 fn new(parameters: &[u64], type_arguments: &[Arc<Type>]) -> Self {
2499 let specialised_types = parameters
2500 .iter()
2501 .copied()
2502 .zip(type_arguments.iter().cloned())
2503 .collect();
2504 Self { specialised_types }
2505 }
2506
2507 fn specialise_type_value_constructor(&self, v: &TypeValueConstructor) -> TypeValueConstructor {
2508 let TypeValueConstructor {
2509 name,
2510 parameters,
2511 documentation,
2512 } = v;
2513 let parameters = parameters
2514 .iter()
2515 .map(|p| TypeValueConstructorField {
2516 type_: self.specialise_type(p.type_.as_ref()),
2517 label: p.label.clone(),
2518 })
2519 .collect_vec();
2520 TypeValueConstructor {
2521 name: name.clone(),
2522 parameters,
2523 documentation: documentation.clone(),
2524 }
2525 }
2526
2527 fn specialise_type(&self, type_: &Type) -> Arc<Type> {
2528 Arc::new(match type_ {
2529 Type::Named {
2530 publicity,
2531 package,
2532 module,
2533 name,
2534 args,
2535 inferred_variant,
2536 } => Type::Named {
2537 publicity: *publicity,
2538 package: package.clone(),
2539 module: module.clone(),
2540 name: name.clone(),
2541 args: args.iter().map(|a| self.specialise_type(a)).collect(),
2542 inferred_variant: *inferred_variant,
2543 },
2544
2545 Type::Fn { args, return_ } => Type::Fn {
2546 args: args.iter().map(|a| self.specialise_type(a)).collect(),
2547 return_: return_.clone(),
2548 },
2549
2550 Type::Var { type_ } => Type::Var {
2551 type_: Arc::new(RefCell::new(self.specialise_var(type_))),
2552 },
2553
2554 Type::Tuple { elements } => Type::Tuple {
2555 elements: elements
2556 .iter()
2557 .map(|element| self.specialise_type(element))
2558 .collect(),
2559 },
2560 })
2561 }
2562
2563 fn specialise_var(&self, type_: &RefCell<TypeVar>) -> TypeVar {
2564 match &*type_.borrow() {
2565 TypeVar::Unbound { id } => TypeVar::Unbound { id: *id },
2566
2567 TypeVar::Link { type_ } => TypeVar::Link {
2568 type_: self.specialise_type(type_.as_ref()),
2569 },
2570
2571 TypeVar::Generic { id } => match self.specialised_types.get(id) {
2572 Some(type_) => TypeVar::Link {
2573 type_: type_.clone(),
2574 },
2575 None => TypeVar::Generic { id: *id },
2576 },
2577 }
2578 }
2579}
2580
2581/// Intermiate data structure that's used to set up everything that's needed by
2582/// the pattern matching compiler and get a case expression ready to be compiled,
2583/// while hiding the intricacies of handling an arena to record different patterns.
2584///
2585pub struct CaseToCompile {
2586 patterns: Arena<Pattern>,
2587 branches: Vec<Branch>,
2588 subject_variables: Vec<Variable>,
2589 /// The number of clauses in this case to compile.
2590 number_of_clauses: usize,
2591 variable_id: usize,
2592}
2593
2594impl CaseToCompile {
2595 pub fn new(subject_types: &[Arc<Type>]) -> Self {
2596 let mut variable_id = 0;
2597 let subject_variables = subject_types
2598 .iter()
2599 .map(|type_| {
2600 let id = variable_id;
2601 variable_id += 1;
2602 Variable::new(id, type_.clone())
2603 })
2604 .collect_vec();
2605
2606 Self {
2607 patterns: Arena::new(),
2608 branches: vec![],
2609 number_of_clauses: 0,
2610 subject_variables,
2611 variable_id,
2612 }
2613 }
2614
2615 /// Registers a `TypedClause` as one of the branches to be compiled.
2616 ///
2617 /// If you don't have a clause and just have a simple `TypedPattern` you want
2618 /// to generate a decision tree for you can use `add_pattern`.
2619 ///
2620 pub fn add_clause(&mut self, branch: &TypedClause) {
2621 let all_patterns =
2622 std::iter::once(&branch.pattern).chain(branch.alternative_patterns.iter());
2623
2624 for (alternative_index, patterns) in all_patterns.enumerate() {
2625 let mut checks = Vec::with_capacity(patterns.len());
2626
2627 // We're doing the zipping ourselves instead of using iters.zip as the
2628 // borrow checker would complain and the only workaround would be to
2629 // allocate an entire new vector each time.
2630 for i in 0..patterns.len() {
2631 let pattern = self.register(patterns.get(i).expect("pattern index"));
2632 let var = self
2633 .subject_variables
2634 .get(i)
2635 .expect("wrong number of subjects");
2636 checks.push(var.is(pattern))
2637 }
2638
2639 let has_guard = branch.guard.is_some();
2640 let branch = Branch::new(self.number_of_clauses, alternative_index, checks, has_guard);
2641 self.branches.push(branch);
2642 }
2643
2644 self.number_of_clauses += 1;
2645 }
2646
2647 /// Add a single pattern as a branch to be compiled.
2648 ///
2649 /// This is useful in case one wants to check exhaustiveness of a single
2650 /// pattern without having a fully fledged `TypedClause` to pass to the `add_clause`
2651 /// method. For example, in `let` destructurings.
2652 ///
2653 pub fn add_pattern(&mut self, pattern: &TypedPattern) {
2654 let pattern = self.register(pattern);
2655 let var = self
2656 .subject_variables
2657 .first()
2658 .expect("wrong number of subject variables for pattern");
2659 let branch = Branch::new(self.number_of_clauses, 0, vec![var.is(pattern)], false);
2660 self.number_of_clauses += 1;
2661 self.branches.push(branch);
2662 }
2663
2664 pub fn compile(self, env: &Environment<'_>) -> CompileCaseResult {
2665 let mut compiler = Compiler::new(env, self.variable_id, self.patterns);
2666
2667 let decision = if self.branches.is_empty() {
2668 let var = self
2669 .subject_variables
2670 .first()
2671 .expect("case with no subjects")
2672 .clone();
2673
2674 compiler.split_and_compile_with_pivot_var(var, VecDeque::new())
2675 } else {
2676 compiler.compile(self.branches.into())
2677 };
2678
2679 CompileCaseResult {
2680 diagnostics: compiler.diagnostics,
2681 compiled_case: CompiledCase {
2682 tree: decision,
2683 subject_variables: self.subject_variables,
2684 },
2685 }
2686 }
2687
2688 /// Registers a typed pattern (and all its sub-patterns) into this
2689 /// `CaseToCompile`'s pattern arena, returning an id to get the pattern back.
2690 ///
2691 fn register(&mut self, pattern: &TypedPattern) -> Id<Pattern> {
2692 match pattern {
2693 TypedPattern::Invalid { .. } => self.insert(Pattern::Discard),
2694 TypedPattern::Discard { .. } => self.insert(Pattern::Discard),
2695
2696 TypedPattern::Int { value, .. } => {
2697 let value = value.clone();
2698 self.insert(Pattern::Int { value })
2699 }
2700
2701 TypedPattern::Float { value, .. } => {
2702 let value = value.clone();
2703 self.insert(Pattern::Float { value })
2704 }
2705
2706 TypedPattern::String { value, .. } => {
2707 let value = value.clone();
2708 self.insert(Pattern::String { value })
2709 }
2710
2711 TypedPattern::Variable { name, .. } => {
2712 let name = name.clone();
2713 self.insert(Pattern::Variable { name })
2714 }
2715
2716 TypedPattern::Assign { name, pattern, .. } => {
2717 let name = name.clone();
2718 let pattern = self.register(pattern);
2719 self.insert(Pattern::Assign { name, pattern })
2720 }
2721
2722 TypedPattern::Tuple { elements, .. } => {
2723 let elements = elements
2724 .iter()
2725 .map(|element| self.register(element))
2726 .collect_vec();
2727 self.insert(Pattern::Tuple { elements })
2728 }
2729
2730 TypedPattern::List { elements, tail, .. } => {
2731 let mut list = match tail {
2732 Some(tail) => self.register(tail),
2733 None => self.insert(Pattern::EmptyList),
2734 };
2735 for element in elements.iter().rev() {
2736 let first = self.register(element);
2737 list = self.insert(Pattern::NonEmptyList { first, rest: list });
2738 }
2739 list
2740 }
2741
2742 TypedPattern::Constructor {
2743 arguments,
2744 constructor,
2745 name,
2746 module,
2747 ..
2748 } => {
2749 let index = constructor.expect_ref("must be inferred").constructor_index as usize;
2750 let module = module.as_ref().map(|(module, _)| module.clone());
2751 let fields = arguments
2752 .iter()
2753 .map(|argument| self.register(&argument.value))
2754 .collect_vec();
2755 self.insert(Pattern::Variant {
2756 name: name.clone(),
2757 module,
2758 index,
2759 fields,
2760 })
2761 }
2762
2763 TypedPattern::BitArray { segments, .. } => {
2764 let tests = self.bit_array_to_tests(segments);
2765 self.insert(Pattern::BitArray { tests })
2766 }
2767
2768 TypedPattern::StringPrefix {
2769 left_side_string,
2770 left_side_assignment,
2771 right_side_assignment,
2772 ..
2773 } => {
2774 let prefix = left_side_string.clone();
2775 let prefix_name = left_side_assignment
2776 .as_ref()
2777 .map(|(label, _)| label.clone());
2778 let rest_pattern = match right_side_assignment {
2779 AssignName::Variable(name) => Pattern::Variable { name: name.clone() },
2780 AssignName::Discard(_) => Pattern::Discard,
2781 };
2782 let rest = self.insert(rest_pattern);
2783 self.insert(Pattern::StringPrefix {
2784 prefix,
2785 prefix_name,
2786 rest,
2787 })
2788 }
2789
2790 TypedPattern::BitArraySize { .. } => {
2791 unreachable!("Cannot convert BitArraySize to exhaustiveness pattern")
2792 }
2793 }
2794 }
2795
2796 fn insert(&mut self, pattern: Pattern) -> Id<Pattern> {
2797 self.patterns.alloc(pattern)
2798 }
2799
2800 fn bit_array_to_tests(
2801 &mut self,
2802 segments: &[TypedPatternBitArraySegment],
2803 ) -> VecDeque<BitArrayTest> {
2804 // If there's no segments then we just add a single check to make sure
2805 // the bit array is empty.
2806 if segments.is_empty() {
2807 let mut test = VecDeque::new();
2808 test.push_front(BitArrayTest::Size(SizeTest {
2809 operator: SizeOperator::Equal,
2810 size: Offset::constant(0),
2811 }));
2812 return test;
2813 }
2814
2815 let mut previous_end = Offset::constant(0);
2816 let mut tests = VecDeque::with_capacity(segments.len() * 2);
2817 let mut pattern_variables = HashMap::new();
2818
2819 let segments_count = segments.len();
2820 for (i, segment) in segments.iter().enumerate() {
2821 let segment_size = segment_size(segment, &pattern_variables, None);
2822
2823 // If we're reading a variable number of bits we need to make sure
2824 // that that variable is not negative!
2825 if let ReadSize::VariableBits { variable, .. } = &segment_size {
2826 match variable.as_ref() {
2827 // If the size variable comes from reading an unsigned
2828 // number we know that it can't be negative! So we can skip
2829 // checking it.
2830 VariableUsage::PatternSegment(_, read_action) if !read_action.signed => (),
2831
2832 // Otherwise we must make sure that the read size is not
2833 // negative.
2834 VariableUsage::PatternSegment(..) | VariableUsage::OutsideVariable(_) => tests
2835 .push_back(BitArrayTest::VariableIsNotNegative {
2836 variable: variable.as_ref().clone(),
2837 }),
2838 }
2839 }
2840
2841 // All segments but the last will require the original bit array to
2842 // have a minimum number of bits for the pattern to succeed. The
2843 // final segment is special as it could require a specific size, or
2844 // be a catch all that matches with any number of remaining bits.
2845 let is_last_segment = i + 1 == segments_count;
2846 match &segment_size {
2847 ReadSize::RemainingBits => (),
2848 ReadSize::RemainingBytes => tests.push_back(BitArrayTest::CatchAllIsBytes {
2849 size_so_far: previous_end.clone(),
2850 }),
2851 segment_size => {
2852 let size = previous_end.clone().add_size(segment_size);
2853 let operator = if is_last_segment {
2854 SizeOperator::Equal
2855 } else {
2856 SizeOperator::GreaterEqual
2857 };
2858 tests.push_back(BitArrayTest::Size(SizeTest { operator, size }));
2859 }
2860 };
2861
2862 // Each segment is also turned into a match test, checking the
2863 // selected bits match with the pattern's value.
2864 let value = segment_matched_value(segment, None);
2865
2866 let type_ = match &segment.type_ {
2867 type_ if type_.is_int() => ReadType::Int,
2868 type_ if type_.is_float() => ReadType::Float,
2869 type_ if type_.is_string() => ReadType::String,
2870 type_ if type_.is_bit_array() => ReadType::BitArray,
2871 type_ if type_.is_utf_codepoint() => ReadType::UtfCodepoint,
2872 x => panic!("invalid segment type in exhaustiveness {x:?}"),
2873 };
2874
2875 let read_action = ReadAction {
2876 size: segment_size.clone(),
2877 from: previous_end.clone(),
2878 type_,
2879 endianness: segment.endianness(),
2880 signed: segment.signed(),
2881 };
2882
2883 // Then if the matched value is a variable that is in scope for the
2884 // rest of the pattern we keep track of it, so it can be used in the
2885 // following read actions as a valid size.
2886 match &value {
2887 BitArrayMatchedValue::LiteralFloat(_)
2888 | BitArrayMatchedValue::LiteralInt(_)
2889 | BitArrayMatchedValue::LiteralString { .. }
2890 | BitArrayMatchedValue::Discard(_) => {}
2891 BitArrayMatchedValue::Variable(name)
2892 | BitArrayMatchedValue::Assign { name, .. } => {
2893 let _ = pattern_variables.insert(name.clone(), read_action.clone());
2894 }
2895 }
2896
2897 // If we are matching on a float segment that is not a literal we want
2898 // to add an additional check to make sure that we won't match with
2899 // `NaN` and `Infinity`!
2900 if type_.is_float() && !value.is_literal() {
2901 tests.push_back(BitArrayTest::SegmentIsFiniteFloat {
2902 read_action: read_action.clone(),
2903 });
2904 }
2905 tests.push_back(BitArrayTest::Match(MatchTest { value, read_action }));
2906
2907 previous_end = previous_end.add_size(&segment_size);
2908 }
2909 tests
2910 }
2911}
2912
2913fn segment_matched_value(
2914 segment: &TypedPatternBitArraySegment,
2915 // If we are compiling an assignment pattern, we still need access to the
2916 // `type_` and `options` fields of the `segment`, so we must still pass that
2917 // in above. However, we need to check the correct sub-pattern of the original
2918 // pattern, so if they are different we set this argument to `Some`.
2919 pattern: Option<&TypedPattern>,
2920) -> BitArrayMatchedValue {
2921 let pattern = pattern.unwrap_or(&segment.value);
2922 match pattern {
2923 ast::Pattern::Int { int_value, .. } => BitArrayMatchedValue::LiteralInt(int_value.clone()),
2924 ast::Pattern::Float { value, .. } => BitArrayMatchedValue::LiteralFloat(value.clone()),
2925 ast::Pattern::String { value, .. } if segment.has_utf16_option() => {
2926 BitArrayMatchedValue::LiteralString {
2927 value: value.clone(),
2928 encoding: StringEncoding::Utf16,
2929 }
2930 }
2931 ast::Pattern::String { value, .. } if segment.has_utf32_option() => {
2932 BitArrayMatchedValue::LiteralString {
2933 value: value.clone(),
2934 encoding: StringEncoding::Utf32,
2935 }
2936 }
2937 ast::Pattern::String { value, .. } => BitArrayMatchedValue::LiteralString {
2938 value: value.clone(),
2939 encoding: StringEncoding::Utf8,
2940 },
2941 ast::Pattern::Variable { name, .. } => BitArrayMatchedValue::Variable(name.clone()),
2942 ast::Pattern::Discard { name, .. } => BitArrayMatchedValue::Discard(name.clone()),
2943 ast::Pattern::Assign { name, pattern, .. } => BitArrayMatchedValue::Assign {
2944 name: name.clone(),
2945 value: Box::new(segment_matched_value(segment, Some(pattern))),
2946 },
2947 x => panic!("unexpected segment value pattern {x:?}"),
2948 }
2949}
2950
2951fn segment_size(
2952 segment: &TypedPatternBitArraySegment,
2953 pattern_variables: &HashMap<EcoString, ReadAction>,
2954 // If we are compiling an assignment pattern, we still need access to the
2955 // `type_` and `options` fields of the `segment`, so we must still pass that
2956 // in above. However, we need to check the correct sub-pattern of the original
2957 // pattern, so if they are different we set this argument to `Some`.
2958 pattern: Option<&TypedPattern>,
2959) -> ReadSize {
2960 let pattern = pattern.unwrap_or(&segment.value);
2961
2962 match segment.size() {
2963 // The size of a segment must be a `BitArraySize` pattern.
2964 Some(ast::Pattern::BitArraySize(size)) => bit_array_size(segment, pattern_variables, size),
2965 Some(x) => panic!("invalid pattern size made it to code generation {x:?}"),
2966
2967 // If a segment has the `bits`/`bytes` option and has no size, that
2968 // means it's the final catch all segment: we'll have to read any number
2969 // of bits.
2970 _ if segment.has_bits_option() => ReadSize::RemainingBits,
2971 _ if segment.has_bytes_option() => ReadSize::RemainingBytes,
2972
2973 // If there's no size option we go for a default: 8 bits for int
2974 // segments, and 64 for anything else.
2975 None if segment.type_.is_int() => ReadSize::ConstantBits(8.into()),
2976 None => match pattern {
2977 ast::Pattern::Assign { pattern, .. } => {
2978 segment_size(segment, pattern_variables, Some(pattern))
2979 }
2980
2981 ast::Pattern::String { value, .. } if segment.has_utf16_option() => {
2982 ReadSize::ConstantBits(
2983 // Each utf16 code unit is 16 bits
2984 length_utf16(&convert_string_escape_chars(value)) * BigInt::from(16),
2985 )
2986 }
2987 ast::Pattern::String { value, .. } if segment.has_utf32_option() => {
2988 // Each utf32 code unit is 32 bits
2989 ReadSize::ConstantBits(
2990 length_utf32(&convert_string_escape_chars(value)) * BigInt::from(32),
2991 )
2992 }
2993 // If the segment is a literal string then it has an automatic size
2994 // given by its number of bytes.
2995 ast::Pattern::String { value, .. } => {
2996 ReadSize::ConstantBits(convert_string_escape_chars(value).len() * BigInt::from(8))
2997 }
2998 // In all other cases the segment is considered to be 64 bits.
2999 _ => ReadSize::ConstantBits(64.into()),
3000 },
3001 }
3002}
3003
3004fn bit_array_size(
3005 segment: &TypedPatternBitArraySegment,
3006 pattern_variables: &HashMap<EcoString, ReadAction>,
3007 size: &TypedBitArraySize,
3008) -> ReadSize {
3009 match size {
3010 BitArraySize::Int { int_value, .. } => ReadSize::ConstantBits(int_value * segment.unit()),
3011 BitArraySize::Variable { name, .. } => {
3012 let variable = match pattern_variables.get(name) {
3013 Some(read_action) => {
3014 VariableUsage::PatternSegment(name.clone(), read_action.clone())
3015 }
3016 None => VariableUsage::OutsideVariable(name.clone()),
3017 };
3018 ReadSize::VariableBits {
3019 variable: Box::new(variable),
3020 unit: segment.unit(),
3021 }
3022 }
3023 _ => todo!(),
3024 }
3025}
3026
3027/// Returns `true` if one bag is a superset of the other: that is it contains
3028/// all the keys of `other` in a quantity that's greater or equal.
3029///
3030#[must_use]
3031fn superset(
3032 one: &im::HashMap<VariableUsage, usize>,
3033 other: &im::HashMap<VariableUsage, usize>,
3034) -> bool {
3035 other
3036 .iter()
3037 .all(|(key, other_occurrences)| match one.get(key) {
3038 Some(occurrences) => occurrences >= other_occurrences,
3039 None => false,
3040 })
3041}