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

Configure Feed

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

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}