Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / exhaustiveness / missing_patterns.rs
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}