Fork of daniellemaywood.uk/gleam — Wasm codegen work
7.2 kB
219 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use super::{CompileCaseResult, Decision, FallbackCheck, RuntimeCheck, Variable, printer::Printer};
5use crate::type_::environment::Environment;
6use ecow::EcoString;
7use indexmap::IndexSet;
8use std::collections::HashMap;
9
10/// Returns a list of patterns not covered by the match expression.
11pub fn missing_patterns(
12 result: &CompileCaseResult,
13 environment: &Environment<'_>,
14) -> Vec<EcoString> {
15 let subjects = &result.compiled_case.subject_variables;
16 let mut generator = MissingPatternsGenerator::new(subjects, environment);
17 generator.add_missing_patterns(&result.compiled_case.tree);
18
19 generator.missing.into_iter().collect()
20}
21
22#[derive(Debug, Clone)]
23pub struct VariantField {
24 pub label: Option<EcoString>,
25 pub variable: Variable,
26}
27
28/// Information about a single constructor/value (aka term) being tested, used
29/// to build a list of names of missing patterns.
30#[derive(Debug)]
31pub enum Term {
32 Variant {
33 variable: Variable,
34 name: EcoString,
35 module: EcoString,
36 fields: Vec<VariantField>,
37 },
38 Tuple {
39 variable: Variable,
40 elements: Vec<Variable>,
41 },
42 Infinite {
43 variable: Variable,
44 },
45 EmptyList {
46 variable: Variable,
47 },
48 List {
49 variable: Variable,
50 first: Variable,
51 rest: Variable,
52 },
53}
54
55impl Term {
56 pub fn variable(&self) -> &Variable {
57 match self {
58 Term::Variant { variable, .. } => variable,
59 Term::Tuple { variable, .. } => variable,
60 Term::Infinite { variable } => variable,
61 Term::EmptyList { variable } => variable,
62 Term::List { variable, .. } => variable,
63 }
64 }
65}
66
67struct MissingPatternsGenerator<'a, 'env> {
68 subjects: &'a Vec<Variable>,
69 terms: Vec<Term>,
70 missing: IndexSet<EcoString>,
71 environment: &'a Environment<'env>,
72 printer: Printer<'a>,
73}
74
75impl<'a, 'env> MissingPatternsGenerator<'a, 'env> {
76 fn new(subjects: &'a Vec<Variable>, environment: &'a Environment<'env>) -> Self {
77 MissingPatternsGenerator {
78 subjects,
79 terms: vec![],
80 missing: IndexSet::new(),
81 environment,
82 printer: Printer::new(environment.current_module.clone(), &environment.names),
83 }
84 }
85
86 fn print_terms(&self, mapping: HashMap<usize, usize>) -> EcoString {
87 self.printer
88 .print_terms(self.subjects, &self.terms, &mapping)
89 }
90
91 fn add_missing_patterns(&mut self, node: &Decision) {
92 match node {
93 Decision::Run { .. } => {}
94
95 Decision::Guard { if_false, .. } => self.add_missing_patterns(if_false),
96
97 Decision::Fail => {
98 // At this point the terms stack looks something like this:
99 // `[term, term + arguments, term, ...]`. To construct a pattern
100 // name from this stack, we first map all variables to their
101 // term indexes. This is needed because when a term defines
102 // arguments, the terms for those arguments don't necessarily
103 // appear in order in the term stack.
104 //
105 // This mapping is then used when (recursively) generating a
106 // pattern name.
107 //
108 // This approach could probably be done more efficiently, so if
109 // you're reading this and happen to know of a way, please
110 // submit a merge request :)
111 let mut mapping = HashMap::new();
112 for (index, step) in self.terms.iter().enumerate() {
113 _ = mapping.insert(step.variable().id, index);
114 }
115 let pattern = self.print_terms(mapping);
116
117 _ = self.missing.insert(pattern);
118 }
119
120 Decision::Switch {
121 var,
122 choices,
123 fallback,
124 fallback_check,
125 } => {
126 for (check, body) in choices {
127 self.add_missing_patterns_after_check(var, check, body);
128 }
129
130 match fallback_check.as_ref() {
131 FallbackCheck::InfiniteCatchAll => {
132 self.add_missing_patterns(fallback);
133 }
134 FallbackCheck::RuntimeCheck { check } => {
135 self.add_missing_patterns_after_check(var, check, fallback);
136 }
137 FallbackCheck::CatchAll { ignored_checks } => {
138 for check in ignored_checks {
139 self.add_missing_patterns_after_check(var, check, fallback);
140 }
141 }
142 }
143 }
144 }
145 }
146
147 fn add_missing_patterns_after_check(
148 &mut self,
149 var: &Variable,
150 check: &RuntimeCheck,
151 body: &Decision,
152 ) {
153 let term = self.check_to_term(var.clone(), check);
154 self.terms.push(term);
155 self.add_missing_patterns(body);
156 _ = self.terms.pop();
157 }
158
159 fn check_to_term(&self, variable: Variable, check: &RuntimeCheck) -> Term {
160 match check {
161 RuntimeCheck::Int { .. }
162 | RuntimeCheck::Float { .. }
163 | RuntimeCheck::String { .. }
164 | RuntimeCheck::BitArray { .. }
165 | RuntimeCheck::StringPrefix { .. } => Term::Infinite { variable },
166
167 RuntimeCheck::Tuple { elements, .. } => Term::Tuple {
168 variable,
169 elements: elements.clone(),
170 },
171
172 RuntimeCheck::Variant {
173 index,
174 fields,
175 labels,
176 ..
177 } => {
178 let (module, name) = variable
179 .type_
180 .named_type_name()
181 .expect("Should be a named type");
182
183 let name = self
184 .environment
185 .get_constructors_for_type(&module, &name)
186 .expect("Custom type constructor must have custom type kind")
187 .variants
188 .get(*index)
189 .expect("Custom type constructor exist for type")
190 .name
191 .clone();
192
193 let fields = fields
194 .iter()
195 .enumerate()
196 .map(|(index, variable)| VariantField {
197 label: labels.get(&index).cloned(),
198 variable: variable.clone(),
199 })
200 .collect();
201
202 Term::Variant {
203 variable,
204 name,
205 module,
206 fields,
207 }
208 }
209
210 RuntimeCheck::NonEmptyList { first, rest } => Term::List {
211 variable,
212 first: first.clone(),
213 rest: rest.clone(),
214 },
215
216 RuntimeCheck::EmptyList => Term::EmptyList { variable },
217 }
218 }
219}