Fork of daniellemaywood.uk/gleam — Wasm codegen work
16 kB
514 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2024 The Gleam contributors
3
4use std::collections::HashMap;
5
6use ecow::EcoString;
7
8use crate::{
9 ast::Publicity,
10 type_::printer::{NameContextInformation, Names},
11};
12
13use super::{Variable, missing_patterns::Term};
14
15#[derive(Debug)]
16pub struct Printer<'a> {
17 names: &'a Names,
18 /// This is the module that is being currently analysed.
19 current_module: EcoString,
20}
21
22impl<'a> Printer<'a> {
23 pub fn new(current_module: EcoString, names: &'a Names) -> Self {
24 Printer {
25 current_module,
26 names,
27 }
28 }
29
30 pub fn print_terms(
31 &self,
32 subjects: &[Variable],
33 terms: &[Term],
34 mapping: &HashMap<usize, usize>,
35 ) -> EcoString {
36 let mut buffer = EcoString::new();
37 for (i, subject) in subjects.iter().enumerate() {
38 if i != 0 {
39 buffer.push_str(", ");
40 }
41
42 match mapping.get(&subject.id) {
43 Some(&index) => {
44 let term = terms.get(index).expect("Term must exist");
45 self.print(term, terms, mapping, &mut buffer);
46 }
47 None => buffer.push('_'),
48 }
49 }
50 buffer
51 }
52
53 fn print(
54 &self,
55 term: &Term,
56 terms: &[Term],
57 mapping: &HashMap<usize, usize>,
58 buffer: &mut EcoString,
59 ) {
60 match term {
61 Term::Variant {
62 name,
63 module,
64 fields,
65 variable,
66 } => {
67 let is_defined_in_current_module = *module == self.current_module;
68 let is_internal = variable
69 .type_
70 .named_type_publicity()
71 .unwrap_or(Publicity::Public)
72 .is_internal();
73
74 // We don't want to expose information about an internal type,
75 // making it easy to rely on its internal structure.
76 // So what we do is we just show a catch all pattern `_` for
77 // those.
78 // We do this only if the internal type is defined in a
79 // different module from the one being analysed, otherwise it's
80 // totally fair to want to match on such type.
81 if is_internal && !is_defined_in_current_module {
82 buffer.push('_');
83 return;
84 }
85
86 let (module, name) = match self.names.named_constructor(module, name) {
87 NameContextInformation::Qualified(module, name) => (Some(module), name),
88 NameContextInformation::Unqualified(name) => (None, name),
89 NameContextInformation::Unimported(module, name) => {
90 (module.split('/').next_back(), name)
91 }
92 };
93
94 if let Some(module) = module {
95 buffer.push_str(module);
96 buffer.push('.');
97 }
98 buffer.push_str(name);
99
100 if fields.is_empty() {
101 return;
102 }
103 buffer.push('(');
104 for (i, field) in fields.iter().enumerate() {
105 if i != 0 {
106 buffer.push_str(", ");
107 }
108
109 let mut has_label = false;
110
111 if let Some(label) = &field.label {
112 buffer.push_str(label);
113 buffer.push(':');
114 has_label = true;
115 }
116
117 if let Some(&idx) = mapping.get(&field.variable.id) {
118 let term = terms.get(idx).expect("Term must exist");
119
120 match term {
121 // If it is an infinite term and this field is labelled, it is generally
122 // more useful to print just the label using label shorthand syntax.
123 // For example, printing `Person(name:, age:)` instead of
124 // `Person(name: _, age: _)`.
125 Term::Infinite { .. } if has_label => {}
126 Term::Infinite { .. }
127 | Term::Variant { .. }
128 | Term::Tuple { .. }
129 | Term::EmptyList { .. }
130 | Term::List { .. } => {
131 // If this field has a label, the current buffer looks like `label:`,
132 // so we want to print a space before printing the pattern for it.
133 // If there is no label, we don't need to print the space.
134 if has_label {
135 buffer.push(' ');
136 }
137 self.print(term, terms, mapping, buffer);
138 }
139 }
140 } else if !has_label {
141 buffer.push('_');
142 }
143 }
144 buffer.push(')');
145 }
146 Term::Tuple { elements, .. } => {
147 buffer.push_str("#(");
148 for (i, variable) in elements.iter().enumerate() {
149 if i != 0 {
150 buffer.push_str(", ");
151 }
152
153 if let Some(&idx) = mapping.get(&variable.id) {
154 self.print(
155 terms.get(idx).expect("Term must exist"),
156 terms,
157 mapping,
158 buffer,
159 );
160 } else {
161 buffer.push('_');
162 }
163 }
164 buffer.push(')');
165 }
166 Term::Infinite { .. } => buffer.push('_'),
167 Term::EmptyList { .. } => buffer.push_str("[]"),
168 Term::List { .. } => {
169 buffer.push('[');
170 self.print_list(term, terms, mapping, buffer);
171 buffer.push(']');
172 }
173 }
174 }
175
176 fn print_list(
177 &self,
178 term: &Term,
179 terms: &[Term],
180 mapping: &HashMap<usize, usize>,
181 buffer: &mut EcoString,
182 ) {
183 match term {
184 Term::Infinite { .. } | Term::Variant { .. } | Term::Tuple { .. } => buffer.push('_'),
185
186 Term::EmptyList { .. } => {}
187
188 Term::List { first, rest, .. } => {
189 if let Some(&idx) = mapping.get(&first.id) {
190 self.print(
191 terms.get(idx).expect("Term must exist"),
192 terms,
193 mapping,
194 buffer,
195 );
196 } else {
197 buffer.push('_');
198 }
199
200 if let Some(&idx) = mapping.get(&rest.id) {
201 let term = terms.get(idx).expect("Term must exist");
202
203 match term {
204 Term::EmptyList { .. } => {}
205 Term::Variant { .. }
206 | Term::Tuple { .. }
207 | Term::Infinite { .. }
208 | Term::List { .. } => {
209 buffer.push_str(", ");
210 self.print_list(term, terms, mapping, buffer);
211 }
212 }
213 } else {
214 buffer.push_str(", ..");
215 }
216 }
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use ecow::EcoString;
224
225 use super::Printer;
226 use std::{collections::HashMap, sync::Arc};
227
228 use crate::{
229 ast::SrcSpan,
230 exhaustiveness::{
231 Variable,
232 missing_patterns::{Term, VariantField},
233 },
234 type_::{Type, printer::Names},
235 };
236
237 /// Create a variable with a dummy type, for ease of writing tests
238 fn make_variable(id: usize) -> Variable {
239 Variable {
240 id,
241 type_: Arc::new(Type::Tuple {
242 elements: Vec::new(),
243 }),
244 }
245 }
246
247 fn field(variable: Variable, label: Option<&str>) -> VariantField {
248 VariantField {
249 variable,
250 label: label.map(EcoString::from),
251 }
252 }
253
254 fn get_mapping(terms: &[Term]) -> HashMap<usize, usize> {
255 let mut mapping: HashMap<usize, usize> = HashMap::new();
256
257 for (index, term) in terms.iter().enumerate() {
258 _ = mapping.insert(term.variable().id, index);
259 }
260 mapping
261 }
262
263 #[test]
264 fn test_value_in_current_module() {
265 let current_module = EcoString::from("module");
266 let mut names = Names::new();
267 names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into());
268
269 let printer = Printer::new(current_module.clone(), &names);
270
271 let subjects = &[make_variable(0)];
272 let term = Term::Variant {
273 variable: subjects[0].clone(),
274 name: "Wibble".into(),
275 module: current_module,
276 fields: Vec::new(),
277 };
278
279 let terms = &[term];
280 let mapping = get_mapping(terms);
281
282 assert_eq!(printer.print_terms(subjects, terms, &mapping), "Wibble");
283 }
284
285 #[test]
286 fn test_value_in_current_module_with_arguments() {
287 let current_module = EcoString::from("module");
288 let mut names = Names::new();
289 names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into());
290
291 let printer = Printer::new(current_module.clone(), &names);
292
293 let var1 = make_variable(1);
294
295 let var2 = make_variable(2);
296
297 let subjects = &[make_variable(0)];
298 let term = Term::Variant {
299 variable: subjects[0].clone(),
300 name: "Wibble".into(),
301 module: current_module,
302 fields: vec![field(var1.clone(), None), field(var2.clone(), None)],
303 };
304
305 let terms = &[
306 term,
307 Term::EmptyList { variable: var1 },
308 Term::Infinite { variable: var2 },
309 ];
310 let mapping = get_mapping(terms);
311
312 assert_eq!(
313 printer.print_terms(subjects, terms, &mapping),
314 "Wibble([], _)"
315 );
316 }
317
318 #[test]
319 fn test_value_in_current_module_with_labelled_arguments() {
320 let current_module = EcoString::from("module");
321 let mut names = Names::new();
322 names.named_constructor_in_scope(current_module.clone(), "Wibble".into(), "Wibble".into());
323
324 let printer = Printer::new(current_module.clone(), &names);
325
326 let var1 = make_variable(1);
327
328 let var2 = make_variable(2);
329
330 let subjects = &[make_variable(0)];
331 let term = Term::Variant {
332 variable: subjects[0].clone(),
333 name: "Wibble".into(),
334 module: current_module,
335 fields: vec![
336 field(var1.clone(), Some("list")),
337 field(var2.clone(), Some("other")),
338 ],
339 };
340
341 let terms = &[
342 term,
343 Term::EmptyList { variable: var1 },
344 Term::Infinite { variable: var2 },
345 ];
346 let mapping = get_mapping(terms);
347
348 assert_eq!(
349 printer.print_terms(subjects, terms, &mapping),
350 "Wibble(list: [], other:)"
351 );
352 }
353
354 #[test]
355 fn test_module_alias() {
356 let mut names = Names::new();
357
358 assert!(
359 names
360 .imported_module("mod".into(), "shapes".into(), SrcSpan::new(50, 60))
361 .is_none()
362 );
363
364 let printer = Printer::new("module".into(), &names);
365
366 let subjects = &[make_variable(0)];
367 let term = Term::Variant {
368 variable: subjects[0].clone(),
369 name: "Rectangle".into(),
370 module: "mod".into(),
371 fields: Vec::new(),
372 };
373
374 let terms = &[term];
375 let mapping = get_mapping(terms);
376
377 assert_eq!(
378 printer.print_terms(subjects, terms, &mapping),
379 "shapes.Rectangle"
380 );
381 }
382
383 #[test]
384 fn test_unqualified_value() {
385 let mut names = Names::new();
386
387 names.named_constructor_in_scope("regex".into(), "Regex".into(), "Regex".into());
388
389 let printer = Printer::new("module".into(), &names);
390
391 let arg = make_variable(1);
392
393 let subjects = &[make_variable(0)];
394 let term = Term::Variant {
395 variable: subjects[0].clone(),
396 name: "Regex".into(),
397 module: "regex".into(),
398 fields: vec![field(arg.clone(), None)],
399 };
400
401 let terms = &[term, Term::Infinite { variable: arg }];
402 let mapping = get_mapping(terms);
403
404 assert_eq!(printer.print_terms(subjects, terms, &mapping), "Regex(_)");
405 }
406
407 #[test]
408 fn test_unqualified_value_with_alias() {
409 let mut names = Names::new();
410
411 names.named_constructor_in_scope("regex".into(), "Regex".into(), "Reg".into());
412 names.named_constructor_in_scope("gleam".into(), "None".into(), "None".into());
413
414 let printer = Printer::new("current_module".into(), &names);
415
416 let arg = make_variable(1);
417
418 let subjects = &[make_variable(0)];
419 let term = Term::Variant {
420 variable: subjects[0].clone(),
421 name: "Regex".into(),
422 module: "regex".into(),
423 fields: vec![field(arg.clone(), None)],
424 };
425
426 let terms = &[
427 term,
428 Term::Variant {
429 variable: arg,
430 name: "None".into(),
431 module: "gleam".into(),
432 fields: vec![],
433 },
434 ];
435 let mapping = get_mapping(terms);
436
437 assert_eq!(printer.print_terms(subjects, terms, &mapping), "Reg(None)");
438 }
439
440 #[test]
441 fn test_list_pattern() {
442 let mut names = Names::new();
443
444 names.named_constructor_in_scope("module".into(), "Type".into(), "Type".into());
445
446 let printer = Printer::new("module".into(), &names);
447
448 let var1 = make_variable(1);
449 let var2 = make_variable(2);
450 let var3 = make_variable(3);
451
452 let subjects = &[make_variable(0)];
453 let term = Term::List {
454 variable: subjects[0].clone(),
455 first: var1.clone(),
456 rest: var2.clone(),
457 };
458
459 let terms = &[
460 term,
461 Term::Variant {
462 variable: var1,
463 name: "Type".into(),
464 module: "module".into(),
465 fields: Vec::new(),
466 },
467 Term::List {
468 variable: var2,
469 first: var3.clone(),
470 rest: make_variable(4),
471 },
472 Term::Infinite { variable: var3 },
473 ];
474 let mapping = get_mapping(terms);
475
476 assert_eq!(
477 printer.print_terms(subjects, terms, &mapping),
478 "[Type, _, ..]"
479 );
480 }
481
482 #[test]
483 fn test_multi_pattern() {
484 let mut names = Names::new();
485
486 names.named_constructor_in_scope("gleam".into(), "Ok".into(), "Ok".into());
487 names.named_constructor_in_scope("gleam".into(), "False".into(), "False".into());
488
489 let printer = Printer::new("module".into(), &names);
490
491 let subjects = &[make_variable(0), make_variable(1), make_variable(2)];
492
493 let terms = &[
494 Term::Variant {
495 variable: subjects[0].clone(),
496 name: "Ok".into(),
497 module: "gleam".into(),
498 fields: vec![field(make_variable(3), None)],
499 },
500 Term::Variant {
501 variable: subjects[2].clone(),
502 name: "False".into(),
503 module: "gleam".into(),
504 fields: Vec::new(),
505 },
506 ];
507 let mapping = get_mapping(terms);
508
509 assert_eq!(
510 printer.print_terms(subjects, terms, &mapping),
511 "Ok(_), _, False"
512 );
513 }
514}