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 / erlang.rs
100 kB 3172 lines
1// TODO: Refactor this module to be methods on structs rather than free 2// functions with a load of arguments. See the JavaScript code generator and the 3// formatter for examples. 4 5mod pattern; 6#[cfg(test)] 7mod tests; 8 9use crate::build::{Target, module_erlang_name}; 10use crate::strings::{convert_string_escape_chars, to_snake_case}; 11use crate::type_::is_prelude_module; 12use crate::{ 13 Result, 14 ast::{CustomType, Function, Import, ModuleConstant, TypeAlias, *}, 15 docvec, 16 line_numbers::LineNumbers, 17 pretty::*, 18 type_::{ 19 ModuleValueConstructor, PatternConstructor, Type, TypeVar, TypedCallArg, ValueConstructor, 20 ValueConstructorVariant, 21 }, 22}; 23use camino::Utf8Path; 24use ecow::{EcoString, eco_format}; 25use itertools::Itertools; 26use pattern::pattern; 27use regex::{Captures, Regex}; 28use std::collections::HashSet; 29use std::sync::OnceLock; 30use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc}; 31use vec1::Vec1; 32 33const INDENT: isize = 4; 34const MAX_COLUMNS: isize = 80; 35 36fn module_name_atom(module: &str) -> Document<'static> { 37 atom_string(module.replace('/', "@").into()) 38} 39 40#[derive(Debug, Clone)] 41struct Env<'a> { 42 module: &'a str, 43 function: &'a str, 44 line_numbers: &'a LineNumbers, 45 needs_function_docs: bool, 46 echo_used: bool, 47 current_scope_vars: im::HashMap<String, usize>, 48 erl_function_scope_vars: im::HashMap<String, usize>, 49} 50 51impl<'env> Env<'env> { 52 pub fn new(module: &'env str, function: &'env str, line_numbers: &'env LineNumbers) -> Self { 53 let vars: im::HashMap<_, _> = std::iter::once(("_".into(), 0)).collect(); 54 Self { 55 current_scope_vars: vars.clone(), 56 erl_function_scope_vars: vars, 57 needs_function_docs: false, 58 echo_used: false, 59 line_numbers, 60 function, 61 module, 62 } 63 } 64 65 pub fn local_var_name<'a>(&mut self, name: &str) -> Document<'a> { 66 match self.current_scope_vars.get(name) { 67 None => { 68 let _ = self.current_scope_vars.insert(name.to_string(), 0); 69 let _ = self.erl_function_scope_vars.insert(name.to_string(), 0); 70 variable_name(name).to_doc() 71 } 72 Some(0) => variable_name(name).to_doc(), 73 Some(n) => { 74 use std::fmt::Write; 75 let mut name = variable_name(name); 76 write!(name, "@{n}").expect("pushing number suffix to name"); 77 name.to_doc() 78 } 79 } 80 } 81 82 pub fn next_local_var_name<'a>(&mut self, name: &str) -> Document<'a> { 83 let next = self.erl_function_scope_vars.get(name).map_or(0, |i| i + 1); 84 let _ = self.erl_function_scope_vars.insert(name.to_string(), next); 85 let _ = self.current_scope_vars.insert(name.to_string(), next); 86 self.local_var_name(name) 87 } 88} 89 90pub fn records(module: &TypedModule) -> Vec<(&str, String)> { 91 module 92 .definitions 93 .iter() 94 .filter_map(|definition| match definition { 95 Definition::CustomType(CustomType { 96 publicity: Publicity::Public, 97 constructors, 98 location, 99 .. 100 }) if !module.unused_definition_positions.contains(&location.start) => { 101 Some(constructors) 102 } 103 _ => None, 104 }) 105 .flatten() 106 .filter(|constructor| !constructor.arguments.is_empty()) 107 .filter_map(|constructor| { 108 constructor 109 .arguments 110 .iter() 111 .map( 112 |RecordConstructorArg { 113 label, 114 ast: _, 115 location: _, 116 type_, 117 .. 118 }| { 119 label 120 .as_ref() 121 .map(|(_, label)| (label.as_str(), type_.clone())) 122 }, 123 ) 124 .collect::<Option<Vec<_>>>() 125 .map(|fields| (constructor.name.as_str(), fields)) 126 }) 127 .map(|(name, fields)| (name, record_definition(name, &fields))) 128 .collect() 129} 130 131pub fn record_definition(name: &str, fields: &[(&str, Arc<Type>)]) -> String { 132 let name = to_snake_case(name); 133 let type_printer = TypePrinter::new("").var_as_any(); 134 let fields = fields.iter().map(move |(name, type_)| { 135 let type_ = type_printer.print(type_); 136 docvec![atom_string((*name).into()), " :: ", type_.group()] 137 }); 138 let fields = break_("", "") 139 .append(join(fields, break_(",", ", "))) 140 .nest(INDENT) 141 .append(break_("", "")) 142 .group(); 143 docvec!["-record(", atom_string(name), ", {", fields, "}).", line()] 144 .to_pretty_string(MAX_COLUMNS) 145} 146 147pub fn module<'a>( 148 module: &'a TypedModule, 149 line_numbers: &'a LineNumbers, 150 root: &'a Utf8Path, 151) -> Result<String> { 152 Ok(module_document(module, line_numbers, root)?.to_pretty_string(MAX_COLUMNS)) 153} 154 155fn module_document<'a>( 156 module: &'a TypedModule, 157 line_numbers: &'a LineNumbers, 158 root: &'a Utf8Path, 159) -> Result<Document<'a>> { 160 let mut exports = vec![]; 161 let mut type_defs = vec![]; 162 let mut type_exports = vec![]; 163 164 let header = "-module(" 165 .to_doc() 166 .append(module.erlang_name()) 167 .append(").") 168 .append(line()); 169 170 // We need to know which private functions are referenced in importable 171 // constants so that we can export them anyway in the generated Erlang. 172 // This is because otherwise when the constant is used in another module it 173 // would result in an error as it tries to reference this private function. 174 let overridden_publicity = find_private_functions_referenced_in_importable_constants(module); 175 176 for definition in &module.definitions { 177 register_imports_and_exports( 178 definition, 179 &mut exports, 180 &mut type_exports, 181 &mut type_defs, 182 &module.name, 183 &overridden_publicity, 184 &module.unused_definition_positions, 185 ); 186 } 187 188 let exports = match (!exports.is_empty(), !type_exports.is_empty()) { 189 (false, false) => return Ok(header), 190 (true, false) => "-export([" 191 .to_doc() 192 .append(join(exports, ", ".to_doc())) 193 .append("]).") 194 .append(lines(2)), 195 196 (true, true) => "-export([" 197 .to_doc() 198 .append(join(exports, ", ".to_doc())) 199 .append("]).") 200 .append(line()) 201 .append("-export_type([") 202 .to_doc() 203 .append(join(type_exports, ", ".to_doc())) 204 .append("]).") 205 .append(lines(2)), 206 207 (false, true) => "-export_type([" 208 .to_doc() 209 .append(join(type_exports, ", ".to_doc())) 210 .append("]).") 211 .append(lines(2)), 212 }; 213 214 let type_defs = if type_defs.is_empty() { 215 nil() 216 } else { 217 join(type_defs, lines(2)).append(lines(2)) 218 }; 219 220 let src_path_full = &module.type_info.src_path; 221 let src_path_relative = EcoString::from( 222 src_path_full 223 .strip_prefix(root) 224 .unwrap_or(src_path_full) 225 .as_str(), 226 ) 227 .replace("\\", "\\\\"); 228 229 let mut needs_function_docs = false; 230 let mut echo_used = false; 231 let mut statements = Vec::with_capacity(module.definitions.len()); 232 for definition in module.definitions.iter() { 233 if let Some((statement_document, env)) = module_statement( 234 definition, 235 &module.name, 236 module.type_info.is_internal, 237 line_numbers, 238 src_path_relative.clone(), 239 &module.unused_definition_positions, 240 ) { 241 needs_function_docs = needs_function_docs || env.needs_function_docs; 242 echo_used = echo_used || env.echo_used; 243 statements.push(statement_document); 244 } 245 } 246 247 let module_doc = if module.type_info.is_internal { 248 Some(hidden_module_doc().append(lines(2))) 249 } else if module.documentation.is_empty() { 250 None 251 } else { 252 Some(module_doc(&module.documentation).append(lines(2))) 253 }; 254 255 // We're going to need the documentation directives if any of the module's 256 // functions need it, or if the module has a module comment that we want to 257 // include in the generated Erlang source, or if the module is internal. 258 let needs_doc_directive = needs_function_docs || module_doc.is_some(); 259 let documentation_directive = if needs_doc_directive { 260 "-if(?OTP_RELEASE >= 27). 261-define(MODULEDOC(Str), -moduledoc(Str)). 262-define(DOC(Str), -doc(Str)). 263-else. 264-define(MODULEDOC(Str), -compile([])). 265-define(DOC(Str), -compile([])). 266-endif." 267 .to_doc() 268 .append(lines(2)) 269 } else { 270 nil() 271 }; 272 273 let module = docvec![ 274 header, 275 "-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).", 276 line(), 277 "-define(FILEPATH, \"", 278 src_path_relative, 279 "\").", 280 line(), 281 exports, 282 documentation_directive, 283 module_doc, 284 type_defs, 285 join(statements, lines(2)), 286 ]; 287 288 let module = if echo_used { 289 module 290 .append(lines(2)) 291 .append(std::include_str!("../templates/echo.erl").to_doc()) 292 } else { 293 module 294 }; 295 296 Ok(module.append(line())) 297} 298 299fn register_imports_and_exports( 300 definition: &TypedDefinition, 301 exports: &mut Vec<Document<'_>>, 302 type_exports: &mut Vec<Document<'_>>, 303 type_defs: &mut Vec<Document<'_>>, 304 module_name: &str, 305 overridden_publicity: &im::HashSet<EcoString>, 306 unused_definition_positions: &HashSet<u32>, 307) { 308 // Do not generate any code for unused items 309 if unused_definition_positions.contains(&definition.location().start) { 310 return; 311 } 312 313 match definition { 314 Definition::Function(Function { 315 publicity, 316 name: Some((_, name)), 317 arguments: args, 318 implementations, 319 .. 320 }) if publicity.is_importable() || overridden_publicity.contains(name) => { 321 // If the function isn't for this target then don't attempt to export it 322 if implementations.supports(Target::Erlang) { 323 let function_name = escape_erlang_existing_name(name); 324 exports.push( 325 atom_string(function_name.into()) 326 .append("/") 327 .append(args.len()), 328 ) 329 } 330 } 331 332 Definition::CustomType(CustomType { 333 name, 334 constructors, 335 typed_parameters, 336 opaque, 337 .. 338 }) => { 339 // Erlang doesn't allow phantom type variables in type definitions but gleam does 340 // so we check the type declaratinon against its constroctors and generate a phantom 341 // value that uses the unused type variables. 342 let type_var_usages = collect_type_var_usages(HashMap::new(), typed_parameters); 343 let mut constructor_var_usages = HashMap::new(); 344 for c in constructors { 345 constructor_var_usages = collect_type_var_usages( 346 constructor_var_usages, 347 c.arguments.iter().map(|a| &a.type_), 348 ); 349 } 350 let phantom_vars: Vec<_> = type_var_usages 351 .keys() 352 .filter(|&id| !constructor_var_usages.contains_key(id)) 353 .sorted() 354 .map(|&id| Type::Var { 355 type_: Arc::new(std::cell::RefCell::new(TypeVar::Generic { id })), 356 }) 357 .collect(); 358 let phantom_vars_constructor = if !phantom_vars.is_empty() { 359 let type_printer = TypePrinter::new(module_name); 360 Some(tuple( 361 std::iter::once("gleam_phantom".to_doc()) 362 .chain(phantom_vars.iter().map(|pv| type_printer.print(pv))), 363 )) 364 } else { 365 None 366 }; 367 // Type Exports 368 type_exports.push( 369 erl_safe_type_name(to_snake_case(name)) 370 .to_doc() 371 .append("/") 372 .append(typed_parameters.len()), 373 ); 374 // Type definitions 375 let definition = if constructors.is_empty() { 376 let constructors = 377 std::iter::once("any()".to_doc()).chain(phantom_vars_constructor); 378 join(constructors, break_(" |", " | ")) 379 } else { 380 let constructors = constructors 381 .iter() 382 .map(|constructor| { 383 let name = atom_string(to_snake_case(&constructor.name)); 384 if constructor.arguments.is_empty() { 385 name 386 } else { 387 let type_printer = TypePrinter::new(module_name); 388 let args = constructor 389 .arguments 390 .iter() 391 .map(|argument| type_printer.print(&argument.type_)); 392 tuple(std::iter::once(name).chain(args)) 393 } 394 }) 395 .chain(phantom_vars_constructor); 396 join(constructors, break_(" |", " | ")) 397 } 398 .nest(INDENT); 399 let type_printer = TypePrinter::new(module_name); 400 let params = join( 401 typed_parameters 402 .iter() 403 .map(|type_| type_printer.print(type_)), 404 ", ".to_doc(), 405 ); 406 let doc = if *opaque { "-opaque " } else { "-type " } 407 .to_doc() 408 .append(erl_safe_type_name(to_snake_case(name))) 409 .append("(") 410 .append(params) 411 .append(") :: ") 412 .append(definition) 413 .group() 414 .append("."); 415 type_defs.push(doc); 416 } 417 418 Definition::Function(Function { .. }) 419 | Definition::Import(Import { .. }) 420 | Definition::TypeAlias(TypeAlias { .. }) 421 | Definition::ModuleConstant(ModuleConstant { .. }) => (), 422 } 423} 424 425fn module_statement<'a>( 426 statement: &'a TypedDefinition, 427 module: &'a str, 428 is_internal_module: bool, 429 line_numbers: &'a LineNumbers, 430 src_path: EcoString, 431 unused_definition_positions: &HashSet<u32>, 432) -> Option<(Document<'a>, Env<'a>)> { 433 match statement { 434 // Do not generate any code for unused items 435 Definition::Function(function) 436 if unused_definition_positions.contains(&function.location.start) => 437 { 438 None 439 } 440 441 Definition::Function(function) => { 442 module_function(function, module, is_internal_module, line_numbers, src_path) 443 } 444 445 Definition::TypeAlias(TypeAlias { .. }) 446 | Definition::CustomType(CustomType { .. }) 447 | Definition::Import(Import { .. }) 448 | Definition::ModuleConstant(ModuleConstant { .. }) => None, 449 } 450} 451 452fn module_function<'a>( 453 function: &'a TypedFunction, 454 module: &'a str, 455 is_internal_module: bool, 456 line_numbers: &'a LineNumbers, 457 src_path: EcoString, 458) -> Option<(Document<'a>, Env<'a>)> { 459 // Private external functions don't need to render anything, the underlying 460 // Erlang implementation is used directly at the call site. 461 if function.external_erlang.is_some() && function.publicity.is_private() { 462 return None; 463 } 464 465 // If the function has no suitable Erlang implementation then there is nothing 466 // to generate for it. 467 if !function.implementations.supports(Target::Erlang) { 468 return None; 469 } 470 471 let (_, function_name) = function 472 .name 473 .as_ref() 474 .expect("A module's function must be named"); 475 let function_name = escape_erlang_existing_name(function_name); 476 let file_attribute = file_attribute(src_path, function, line_numbers); 477 478 let mut env = Env::new(module, function_name, line_numbers); 479 let var_usages = collect_type_var_usages( 480 HashMap::new(), 481 std::iter::once(&function.return_type).chain(function.arguments.iter().map(|a| &a.type_)), 482 ); 483 let type_printer = TypePrinter::new(module).with_var_usages(&var_usages); 484 let args_spec = function 485 .arguments 486 .iter() 487 .map(|a| type_printer.print(&a.type_)); 488 let return_spec = type_printer.print(&function.return_type); 489 490 let spec = fun_spec(function_name, args_spec, return_spec); 491 let arguments = if function.external_erlang.is_some() { 492 external_fun_args(&function.arguments, &mut env) 493 } else { 494 fun_args(&function.arguments, &mut env) 495 }; 496 497 let body = function 498 .external_erlang 499 .as_ref() 500 .map(|(module, function, _location)| { 501 docvec![ 502 atom(module), 503 ":", 504 atom(escape_erlang_existing_name(function)), 505 arguments.clone() 506 ] 507 }) 508 .unwrap_or_else(|| statement_sequence(&function.body, &mut env)); 509 510 let attributes = file_attribute; 511 let attributes = if is_internal_module || function.publicity.is_internal() { 512 // If a function is marked as internal or comes from an internal module 513 // we want to hide its documentation in the Erlang shell! 514 // So the doc directive will look like this: `-doc(false).` 515 env.needs_function_docs = true; 516 docvec![attributes, line(), hidden_function_doc()] 517 } else { 518 match &function.documentation { 519 Some((_, documentation)) => { 520 env.needs_function_docs = true; 521 let doc_lines = documentation 522 .trim_end() 523 .split('\n') 524 .map(EcoString::from) 525 .collect_vec(); 526 docvec![attributes, line(), function_doc(&doc_lines)] 527 } 528 _ => attributes, 529 } 530 }; 531 532 Some(( 533 docvec![ 534 attributes, 535 line(), 536 spec, 537 atom_string(escape_erlang_existing_name(function_name).into()), 538 arguments, 539 " ->", 540 line().append(body).nest(INDENT).group(), 541 ".", 542 ], 543 env, 544 )) 545} 546 547fn file_attribute<'a>( 548 path: EcoString, 549 function: &'a TypedFunction, 550 line_numbers: &'a LineNumbers, 551) -> Document<'a> { 552 let line = line_numbers.line_number(function.location.start); 553 docvec!["-file(\"", path, "\", ", line, ")."] 554} 555 556enum DocCommentKind { 557 Module, 558 Function, 559} 560 561enum DocCommentContent<'a> { 562 String(&'a Vec<EcoString>), 563 False, 564} 565 566fn hidden_module_doc<'a>() -> Document<'a> { 567 doc_attribute(DocCommentKind::Module, DocCommentContent::False) 568} 569 570fn module_doc<'a>(content: &Vec<EcoString>) -> Document<'a> { 571 doc_attribute(DocCommentKind::Module, DocCommentContent::String(content)) 572} 573 574fn hidden_function_doc<'a>() -> Document<'a> { 575 doc_attribute(DocCommentKind::Function, DocCommentContent::False) 576} 577 578fn function_doc<'a>(content: &Vec<EcoString>) -> Document<'a> { 579 doc_attribute(DocCommentKind::Function, DocCommentContent::String(content)) 580} 581 582fn doc_attribute<'a>(kind: DocCommentKind, content: DocCommentContent<'_>) -> Document<'a> { 583 let prefix = match kind { 584 DocCommentKind::Module => "?MODULEDOC", 585 DocCommentKind::Function => "?DOC", 586 }; 587 588 match content { 589 DocCommentContent::False => prefix.to_doc().append("(false)."), 590 DocCommentContent::String(doc_lines) => { 591 let is_multiline_doc_comment = doc_lines.len() > 1; 592 let doc_lines = join( 593 doc_lines.iter().map(|line| { 594 let line = line.replace("\\", "\\\\").replace("\"", "\\\""); 595 docvec!["\"", line, "\\n\""] 596 }), 597 line(), 598 ); 599 if is_multiline_doc_comment { 600 let nested_documentation = docvec![line(), doc_lines].nest(INDENT); 601 docvec![prefix, "(", nested_documentation, line(), ")."] 602 } else { 603 docvec![prefix, "(", doc_lines, ")."] 604 } 605 } 606 } 607} 608 609fn external_fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { 610 wrap_args(args.iter().map(|a| { 611 let name = match &a.names { 612 ArgNames::Discard { name, .. } 613 | ArgNames::LabelledDiscard { name, .. } 614 | ArgNames::Named { name, .. } 615 | ArgNames::NamedLabelled { name, .. } => name, 616 }; 617 if name.chars().all(|c| c == '_') { 618 env.next_local_var_name("argument") 619 } else { 620 env.next_local_var_name(name) 621 } 622 })) 623} 624 625fn fun_args<'a>(args: &'a [TypedArg], env: &mut Env<'a>) -> Document<'a> { 626 wrap_args(args.iter().map(|a| match &a.names { 627 ArgNames::Discard { .. } | ArgNames::LabelledDiscard { .. } => "_".to_doc(), 628 ArgNames::Named { name, .. } | ArgNames::NamedLabelled { name, .. } => { 629 env.next_local_var_name(name) 630 } 631 })) 632} 633 634fn wrap_args<'a, I>(args: I) -> Document<'a> 635where 636 I: IntoIterator<Item = Document<'a>>, 637{ 638 break_("", "") 639 .append(join(args, break_(",", ", "))) 640 .nest(INDENT) 641 .append(break_("", "")) 642 .surround("(", ")") 643 .group() 644} 645 646fn fun_spec<'a>( 647 name: &'a str, 648 args: impl IntoIterator<Item = Document<'a>>, 649 return_: Document<'a>, 650) -> Document<'a> { 651 "-spec " 652 .to_doc() 653 .append(atom(name)) 654 .append(wrap_args(args)) 655 .append(" -> ") 656 .append(return_) 657 .append(".") 658 .append(line()) 659 .group() 660} 661 662fn atom_string(value: EcoString) -> Document<'static> { 663 escape_atom_string(value).to_doc() 664} 665 666fn atom_pattern() -> &'static Regex { 667 static ATOM_PATTERN: OnceLock<Regex> = OnceLock::new(); 668 ATOM_PATTERN.get_or_init(|| Regex::new(r"^[a-z][a-z0-9_@]*$").expect("atom RE regex")) 669} 670 671fn atom(value: &str) -> Document<'_> { 672 if is_erlang_reserved_word(value) { 673 // Escape because of keyword collision 674 eco_format!("'{value}'").to_doc() 675 } else if atom_pattern().is_match(value) { 676 // No need to escape 677 EcoString::from(value).to_doc() 678 } else { 679 // Escape because of characters contained 680 eco_format!("'{value}'").to_doc() 681 } 682} 683 684pub fn escape_atom_string(value: EcoString) -> EcoString { 685 if is_erlang_reserved_word(&value) { 686 // Escape because of keyword collision 687 eco_format!("'{value}'") 688 } else if atom_pattern().is_match(&value) { 689 value 690 } else { 691 // Escape because of characters contained 692 eco_format!("'{value}'") 693 } 694} 695 696fn unicode_escape_sequence_pattern() -> &'static Regex { 697 static PATTERN: OnceLock<Regex> = OnceLock::new(); 698 PATTERN.get_or_init(|| { 699 Regex::new(r#"(\\+)(u)"#).expect("Unicode escape sequence regex cannot be constructed") 700 }) 701} 702 703fn string_inner(value: &str) -> Document<'_> { 704 let content = unicode_escape_sequence_pattern() 705 // `\\u`-s should not be affected, so that "\\u..." is not converted to 706 // "\\x...". That's why capturing groups is used to exclude cases that 707 // shouldn't be replaced. 708 .replace_all(value, |caps: &Captures<'_>| { 709 let slashes = caps.get(1).map_or("", |m| m.as_str()); 710 711 if slashes.len() % 2 == 0 { 712 format!("{slashes}u") 713 } else { 714 format!("{slashes}x") 715 } 716 }); 717 EcoString::from(content).to_doc() 718} 719 720fn string(value: &str) -> Document<'_> { 721 string_inner(value).surround("<<\"", "\"/utf8>>") 722} 723 724fn string_length_utf8_bytes(str: &EcoString) -> usize { 725 convert_string_escape_chars(str).len() 726} 727 728fn tuple<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> { 729 join(elements, break_(",", ", ")) 730 .nest(INDENT) 731 .surround("{", "}") 732 .group() 733} 734 735fn const_string_concatenate_bit_array<'a>( 736 elements: impl IntoIterator<Item = Document<'a>>, 737) -> Document<'a> { 738 join(elements, break_(",", ", ")) 739 .nest(INDENT) 740 .surround("<<", ">>") 741 .group() 742} 743 744fn const_string_concatenate<'a>( 745 left: &'a TypedConstant, 746 right: &'a TypedConstant, 747 env: &mut Env<'a>, 748) -> Document<'a> { 749 let left = const_string_concatenate_argument(left, env); 750 let right = const_string_concatenate_argument(right, env); 751 const_string_concatenate_bit_array([left, right]) 752} 753 754fn const_string_concatenate_inner<'a>( 755 left: &'a TypedConstant, 756 right: &'a TypedConstant, 757 env: &mut Env<'a>, 758) -> Document<'a> { 759 let left = const_string_concatenate_argument(left, env); 760 let right = const_string_concatenate_argument(right, env); 761 join([left, right], break_(",", ", ")) 762} 763 764fn const_string_concatenate_argument<'a>( 765 value: &'a TypedConstant, 766 env: &mut Env<'a>, 767) -> Document<'a> { 768 match value { 769 Constant::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], 770 771 Constant::Var { 772 constructor: Some(constructor), 773 .. 774 } => match &constructor.variant { 775 ValueConstructorVariant::ModuleConstant { 776 literal: Constant::String { value, .. }, 777 .. 778 } => docvec!['"', string_inner(value), "\"/utf8"], 779 ValueConstructorVariant::ModuleConstant { 780 literal: Constant::StringConcatenation { left, right, .. }, 781 .. 782 } => const_string_concatenate_inner(left, right, env), 783 _ => const_inline(value, env), 784 }, 785 786 Constant::StringConcatenation { left, right, .. } => { 787 const_string_concatenate_inner(left, right, env) 788 } 789 790 _ => const_inline(value, env), 791 } 792} 793 794fn string_concatenate<'a>( 795 left: &'a TypedExpr, 796 right: &'a TypedExpr, 797 env: &mut Env<'a>, 798) -> Document<'a> { 799 let left = string_concatenate_argument(left, env); 800 let right = string_concatenate_argument(right, env); 801 bit_array([left, right]) 802} 803 804fn string_concatenate_argument<'a>(value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 805 match value { 806 TypedExpr::Var { 807 constructor: 808 ValueConstructor { 809 variant: 810 ValueConstructorVariant::ModuleConstant { 811 literal: Constant::String { value, .. }, 812 .. 813 }, 814 .. 815 }, 816 .. 817 } 818 | TypedExpr::String { value, .. } => docvec!['"', string_inner(value), "\"/utf8"], 819 820 TypedExpr::Var { 821 name, 822 constructor: 823 ValueConstructor { 824 variant: ValueConstructorVariant::LocalVariable { .. }, 825 .. 826 }, 827 .. 828 } => docvec![env.local_var_name(name), "/binary"], 829 830 TypedExpr::BinOp { 831 name: BinOp::Concatenate, 832 .. 833 } => docvec![expr(value, env), "/binary"], 834 835 _ => docvec!["(", maybe_block_expr(value, env), ")/binary"], 836 } 837} 838 839fn bit_array<'a>(elements: impl IntoIterator<Item = Document<'a>>) -> Document<'a> { 840 join(elements, break_(",", ", ")) 841 .nest(INDENT) 842 .surround("<<", ">>") 843 .group() 844} 845 846fn const_segment<'a>( 847 value: &'a TypedConstant, 848 options: &'a [TypedConstantBitArraySegmentOption], 849 env: &mut Env<'a>, 850) -> Document<'a> { 851 let value_is_a_string_literal = matches!(value, Constant::String { .. }); 852 853 let create_document = |env: &mut Env<'a>| { 854 match value { 855 // Skip the normal <<value/utf8>> surrounds 856 Constant::String { value, .. } => value.to_doc().surround("\"", "\""), 857 858 // As normal 859 Constant::Int { .. } | Constant::Float { .. } | Constant::BitArray { .. } => { 860 const_inline(value, env) 861 } 862 863 // Wrap anything else in parentheses 864 value => const_inline(value, env).surround("(", ")"), 865 } 866 }; 867 868 let size = |value: &'a TypedConstant, env: &mut Env<'a>| match value { 869 Constant::Int { .. } => Some(":".to_doc().append(const_inline(value, env))), 870 _ => Some( 871 ":".to_doc() 872 .append(const_inline(value, env).surround("(", ")")), 873 ), 874 }; 875 876 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); 877 878 bit_array_segment( 879 create_document, 880 options, 881 size, 882 unit, 883 value_is_a_string_literal, 884 false, 885 env, 886 ) 887} 888 889enum Position { 890 Tail, 891 NotTail, 892} 893 894fn statement<'a>( 895 statement: &'a TypedStatement, 896 env: &mut Env<'a>, 897 position: Position, 898) -> Document<'a> { 899 match statement { 900 Statement::Expression(e) => expr(e, env), 901 Statement::Assignment(a) => assignment(a, env, position), 902 Statement::Use(use_) => expr(&use_.call, env), 903 Statement::Assert(a) => assert(a, env), 904 } 905} 906 907fn expr_segment<'a>( 908 value: &'a TypedExpr, 909 options: &'a [BitArrayOption<TypedExpr>], 910 env: &mut Env<'a>, 911) -> Document<'a> { 912 let value_is_a_string_literal = matches!(value, TypedExpr::String { .. }); 913 914 let create_document = |env: &mut Env<'a>| { 915 match value { 916 // Skip the normal <<value/utf8>> surrounds and set the string literal flag 917 TypedExpr::String { value, .. } => string_inner(value).surround("\"", "\""), 918 919 // As normal 920 TypedExpr::Int { .. } 921 | TypedExpr::Float { .. } 922 | TypedExpr::Var { .. } 923 | TypedExpr::BitArray { .. } => expr(value, env), 924 925 // Wrap anything else in parentheses 926 value => expr(value, env).surround("(", ")"), 927 } 928 }; 929 930 let size = |expression: &'a TypedExpr, env: &mut Env<'a>| match expression { 931 TypedExpr::Int { value, .. } => { 932 let v = value.replace("_", ""); 933 let v = u64::from_str(&v).unwrap_or(0); 934 Some(eco_format!(":{v}").to_doc()) 935 } 936 937 _ => { 938 let inner_expr = maybe_block_expr(expression, env).surround("(", ")"); 939 // The value of size must be a non-negative integer, we use lists:max here to ensure 940 // it is at least 0; 941 let value_guard = ":(lists:max([" 942 .to_doc() 943 .append(inner_expr) 944 .append(", 0]))") 945 .group(); 946 Some(value_guard) 947 } 948 }; 949 950 let unit = |value: &'a u8| Some(eco_format!("unit:{value}").to_doc()); 951 952 bit_array_segment( 953 create_document, 954 options, 955 size, 956 unit, 957 value_is_a_string_literal, 958 false, 959 env, 960 ) 961} 962 963fn bit_array_segment<'a, Value: 'a, CreateDoc, SizeToDoc, UnitToDoc>( 964 mut create_document: CreateDoc, 965 options: &'a [BitArrayOption<Value>], 966 mut size_to_doc: SizeToDoc, 967 mut unit_to_doc: UnitToDoc, 968 value_is_a_string_literal: bool, 969 value_is_a_discard: bool, 970 env: &mut Env<'a>, 971) -> Document<'a> 972where 973 CreateDoc: FnMut(&mut Env<'a>) -> Document<'a>, 974 SizeToDoc: FnMut(&'a Value, &mut Env<'a>) -> Option<Document<'a>>, 975 UnitToDoc: FnMut(&'a u8) -> Option<Document<'a>>, 976{ 977 let mut size: Option<Document<'a>> = None; 978 let mut unit: Option<Document<'a>> = None; 979 let mut others = Vec::new(); 980 981 // Erlang only allows valid codepoint integers to be used as values for utf segments 982 // We want to support <<string_var:utf8>> for all string variables, but <<StringVar/utf8>> is invalid 983 // To work around this we use the binary type specifier for these segments instead 984 let override_type = if !value_is_a_string_literal && !value_is_a_discard { 985 Some("binary") 986 } else { 987 None 988 }; 989 990 for option in options { 991 use BitArrayOption as Opt; 992 if !others.is_empty() && !matches!(option, Opt::Size { .. } | Opt::Unit { .. }) { 993 others.push("-".to_doc()); 994 } 995 match option { 996 Opt::Utf8 { .. } => others.push(override_type.unwrap_or("utf8").to_doc()), 997 Opt::Utf16 { .. } => others.push(override_type.unwrap_or("utf16").to_doc()), 998 Opt::Utf32 { .. } => others.push(override_type.unwrap_or("utf32").to_doc()), 999 Opt::Int { .. } => others.push("integer".to_doc()), 1000 Opt::Float { .. } => others.push("float".to_doc()), 1001 Opt::Bytes { .. } => others.push("binary".to_doc()), 1002 Opt::Bits { .. } => others.push("bitstring".to_doc()), 1003 Opt::Utf8Codepoint { .. } => others.push("utf8".to_doc()), 1004 Opt::Utf16Codepoint { .. } => others.push("utf16".to_doc()), 1005 Opt::Utf32Codepoint { .. } => others.push("utf32".to_doc()), 1006 Opt::Signed { .. } => others.push("signed".to_doc()), 1007 Opt::Unsigned { .. } => others.push("unsigned".to_doc()), 1008 Opt::Big { .. } => others.push("big".to_doc()), 1009 Opt::Little { .. } => others.push("little".to_doc()), 1010 Opt::Native { .. } => others.push("native".to_doc()), 1011 Opt::Size { value, .. } => size = size_to_doc(value, env), 1012 Opt::Unit { value, .. } => unit = unit_to_doc(value), 1013 } 1014 } 1015 1016 let mut document = create_document(env); 1017 1018 document = document.append(size); 1019 let others_is_empty = others.is_empty(); 1020 1021 if !others_is_empty { 1022 document = document.append("/").append(others); 1023 } 1024 1025 if unit.is_some() { 1026 if !others_is_empty { 1027 document = document.append("-").append(unit) 1028 } else { 1029 document = document.append("/").append(unit) 1030 } 1031 } 1032 1033 document 1034} 1035 1036fn block<'a>(statements: &'a Vec1<TypedStatement>, env: &mut Env<'a>) -> Document<'a> { 1037 if statements.len() == 1 { 1038 if let Statement::Expression(expression) = statements.first() { 1039 if !needs_begin_end_wrapping(expression) { 1040 return docvec!['(', expr(expression, env), ')']; 1041 } 1042 } 1043 } 1044 1045 let vars = env.current_scope_vars.clone(); 1046 let document = statement_sequence(statements, env); 1047 env.current_scope_vars = vars; 1048 1049 begin_end(document) 1050} 1051 1052fn statement_sequence<'a>(statements: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> { 1053 let count = statements.len(); 1054 let mut documents = Vec::with_capacity(count * 3); 1055 for (i, expression) in statements.iter().enumerate() { 1056 let position = if i + 1 == count { 1057 Position::Tail 1058 } else { 1059 Position::NotTail 1060 }; 1061 documents.push(statement(expression, env, position).group()); 1062 1063 if i + 1 < count { 1064 // This isn't the final expression so add the delimeters 1065 documents.push(",".to_doc()); 1066 documents.push(line()); 1067 } 1068 } 1069 if count == 1 { 1070 documents.to_doc() 1071 } else { 1072 documents.to_doc().force_break() 1073 } 1074} 1075 1076fn float_div<'a>(left: &'a TypedExpr, right: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 1077 if right.non_zero_compile_time_number() { 1078 return binop_exprs(left, "/", right, env); 1079 } 1080 1081 let left = expr(left, env); 1082 let right = expr(right, env); 1083 let denominator = env.next_local_var_name("gleam@denominator"); 1084 let clauses = docvec![ 1085 line(), 1086 "+0.0 -> +0.0;", 1087 line(), 1088 "-0.0 -> -0.0;", 1089 line(), 1090 denominator.clone(), 1091 " -> ", 1092 binop_documents(left, "/", denominator) 1093 ]; 1094 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] 1095} 1096 1097fn int_div<'a>( 1098 left: &'a TypedExpr, 1099 right: &'a TypedExpr, 1100 op: &'static str, 1101 env: &mut Env<'a>, 1102) -> Document<'a> { 1103 if right.non_zero_compile_time_number() { 1104 return binop_exprs(left, op, right, env); 1105 } 1106 1107 let left = expr(left, env); 1108 let right = expr(right, env); 1109 let denominator = env.next_local_var_name("gleam@denominator"); 1110 let clauses = docvec![ 1111 line(), 1112 "0 -> 0;", 1113 line(), 1114 denominator.clone(), 1115 " -> ", 1116 binop_documents(left, op, denominator) 1117 ]; 1118 docvec!["case ", right, " of", clauses.nest(INDENT), line(), "end"] 1119} 1120 1121fn bin_op<'a>( 1122 name: &'a BinOp, 1123 left: &'a TypedExpr, 1124 right: &'a TypedExpr, 1125 env: &mut Env<'a>, 1126) -> Document<'a> { 1127 let op = match name { 1128 BinOp::And => "andalso", 1129 BinOp::Or => "orelse", 1130 BinOp::LtInt | BinOp::LtFloat => "<", 1131 BinOp::LtEqInt | BinOp::LtEqFloat => "=<", 1132 BinOp::Eq => "=:=", 1133 BinOp::NotEq => "/=", 1134 BinOp::GtInt | BinOp::GtFloat => ">", 1135 BinOp::GtEqInt | BinOp::GtEqFloat => ">=", 1136 BinOp::AddInt => "+", 1137 BinOp::AddFloat => "+", 1138 BinOp::SubInt => "-", 1139 BinOp::SubFloat => "-", 1140 BinOp::MultInt => "*", 1141 BinOp::MultFloat => "*", 1142 BinOp::DivFloat => return float_div(left, right, env), 1143 BinOp::DivInt => return int_div(left, right, "div", env), 1144 BinOp::RemainderInt => return int_div(left, right, "rem", env), 1145 BinOp::Concatenate => return string_concatenate(left, right, env), 1146 }; 1147 1148 binop_exprs(left, op, right, env) 1149} 1150 1151fn binop_exprs<'a>( 1152 left: &'a TypedExpr, 1153 op: &'static str, 1154 right: &'a TypedExpr, 1155 env: &mut Env<'a>, 1156) -> Document<'a> { 1157 let left = match left { 1158 TypedExpr::BinOp { .. } => expr(left, env).surround("(", ")"), 1159 _ => maybe_block_expr(left, env), 1160 }; 1161 let right = match right { 1162 TypedExpr::BinOp { .. } => expr(right, env).surround("(", ")"), 1163 _ => maybe_block_expr(right, env), 1164 }; 1165 binop_documents(left, op, right) 1166} 1167 1168fn binop_documents<'a>(left: Document<'a>, op: &'static str, right: Document<'a>) -> Document<'a> { 1169 left.append(break_("", " ")) 1170 .append(op) 1171 .group() 1172 .append(" ") 1173 .append(right) 1174} 1175 1176fn let_assert<'a>( 1177 value: &'a TypedExpr, 1178 pattern: &'a TypedPattern, 1179 env: &mut Env<'a>, 1180 message: Option<&'a TypedExpr>, 1181 position: Position, 1182 location: SrcSpan, 1183) -> Document<'a> { 1184 // If the pattern will never fail, like a tuple or a simple variable, we 1185 // simply treat it as if it were a `let` assignment. 1186 if pattern.always_matches() { 1187 return let_(value, pattern, env); 1188 } 1189 1190 let mut vars: Vec<&str> = vec![]; 1191 let subject = maybe_block_expr(value, env); 1192 1193 // The code we generated for a `let assert` assignment looks something like 1194 // this. For this Gleam code: 1195 // 1196 // ```gleam 1197 // let assert [a, b, c] = [1, 2, 3] 1198 // ``` 1199 // 1200 // We generate (roughly) the following Erlang: 1201 // 1202 // ```erlang 1203 // {A, B, C} = case [1, 2, 3] of 1204 // [A, B, C] -> {A, B, C}; 1205 // _ -> erlang:error(...) 1206 // end. 1207 // ``` 1208 // This is the most efficient way to properly extract all the required 1209 // variables from the pattern. However, if the `let assert` assignment is 1210 // the last in a block, like this: 1211 // 1212 // ```gleam 1213 // let x = { 1214 // let assert [a, b, c] = [1, 2, 3] 1215 // } 1216 // ``` 1217 // 1218 // The generated Erlang code will end up assigning the value `#(1, 2, 3)` 1219 // to the variable `x`, instead of `[1, 2, 3]`. In this case, we must 1220 // generate slightly different code. Since we know we won't be using the 1221 // bound variables anywhere (there is nothing else in this scope to 1222 // reference them), we can safely remove the assignment from the generated 1223 // code, and generate the following: 1224 // 1225 // ```erlang 1226 // X = begin 1227 // _assert_subject = [1, 2, 3] 1228 // case _assert_subject of 1229 // [A, B, C] -> _assert_subject; 1230 // _ -> erlang:error(...) 1231 // end 1232 // end. 1233 // ``` 1234 // 1235 // That correctly assigns `[1, 2, 3]` to the `x` variable. 1236 // 1237 let is_tail = match position { 1238 Position::Tail => true, 1239 Position::NotTail => false, 1240 }; 1241 1242 let (subject_assignment, subject) = if is_tail && !value.is_var() { 1243 let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE); 1244 let assignment = docvec![variable.clone(), " = ", subject, ",", line()]; 1245 (assignment, variable) 1246 } else { 1247 (nil(), subject) 1248 }; 1249 1250 let mut guards = vec![]; 1251 let pattern_document = pattern::to_doc(pattern, &mut vars, env, &mut guards); 1252 let clause_guard = optional_clause_guard(None, guards, env); 1253 1254 let message = match message { 1255 Some(message) => expr(message, env), 1256 None => string("Pattern match failed, no pattern matched the value."), 1257 }; 1258 1259 let value_document = match vars.as_slice() { 1260 _ if is_tail => subject.clone(), 1261 [] => "nil".to_doc(), 1262 [variable] => env.local_var_name(variable), 1263 variables => { 1264 let variables = variables 1265 .iter() 1266 .map(|variable| env.local_var_name(variable)); 1267 docvec![ 1268 break_("{", "{"), 1269 join(variables, break_(",", ", ")).nest(INDENT), 1270 "}" 1271 ] 1272 .group() 1273 } 1274 }; 1275 1276 let assignment = match vars.as_slice() { 1277 _ if is_tail => nil(), 1278 [] => nil(), 1279 [variable] => env.next_local_var_name(variable).append(" = "), 1280 variables => { 1281 let variables = variables 1282 .iter() 1283 .map(|variable| env.next_local_var_name(variable)); 1284 docvec![ 1285 break_("{", "{"), 1286 join(variables, break_(",", ", ")).nest(INDENT), 1287 "} = " 1288 ] 1289 .group() 1290 } 1291 }; 1292 1293 let clauses = docvec![ 1294 pattern_document, 1295 clause_guard, 1296 " -> ", 1297 value_document, 1298 ";", 1299 line(), 1300 env.next_local_var_name(ASSERT_FAIL_VARIABLE), 1301 " ->", 1302 docvec![ 1303 line(), 1304 erlang_error( 1305 "let_assert", 1306 &message, 1307 location, 1308 vec![ 1309 ("value", env.local_var_name(ASSERT_FAIL_VARIABLE)), 1310 ("start", location.start.to_doc()), 1311 ("'end'", value.location().end.to_doc()), 1312 ("pattern_start", pattern.location().start.to_doc()), 1313 ("pattern_end", pattern.location().end.to_doc()), 1314 ], 1315 env, 1316 ) 1317 .nest(INDENT) 1318 ] 1319 .nest(INDENT) 1320 ]; 1321 docvec![ 1322 subject_assignment, 1323 assignment, 1324 "case ", 1325 subject, 1326 " of", 1327 docvec![line(), clauses].nest(INDENT), 1328 line(), 1329 "end", 1330 ] 1331} 1332 1333fn let_<'a>(value: &'a TypedExpr, pat: &'a TypedPattern, env: &mut Env<'a>) -> Document<'a> { 1334 let body = maybe_block_expr(value, env).group(); 1335 let mut guards = vec![]; 1336 pattern(pat, env, &mut guards).append(" = ").append(body) 1337} 1338 1339fn float<'a>(value: &str) -> Document<'a> { 1340 let mut value = value.replace('_', ""); 1341 if value.ends_with('.') { 1342 value.push('0') 1343 } 1344 1345 match value.split('.').collect_vec().as_slice() { 1346 ["0", "0"] => "+0.0".to_doc(), 1347 [before_dot, after_dot] if after_dot.starts_with('e') => { 1348 eco_format!("{before_dot}.0{after_dot}").to_doc() 1349 } 1350 _ => EcoString::from(value).to_doc(), 1351 } 1352} 1353 1354fn expr_list<'a>( 1355 elements: &'a [TypedExpr], 1356 tail: &'a Option<Box<TypedExpr>>, 1357 env: &mut Env<'a>, 1358) -> Document<'a> { 1359 let elements = join( 1360 elements 1361 .iter() 1362 .map(|element| maybe_block_expr(element, env)), 1363 break_(",", ", "), 1364 ); 1365 list( 1366 elements, 1367 tail.as_ref().map(|element| maybe_block_expr(element, env)), 1368 ) 1369} 1370 1371fn list<'a>(elements: Document<'a>, tail: Option<Document<'a>>) -> Document<'a> { 1372 let elements = match tail { 1373 Some(tail) if elements.is_empty() => return tail.to_doc(), 1374 1375 Some(tail) => elements.append(break_(" |", " | ")).append(tail), 1376 1377 None => elements, 1378 }; 1379 1380 elements.to_doc().nest(INDENT).surround("[", "]").group() 1381} 1382 1383fn var<'a>(name: &'a str, constructor: &'a ValueConstructor, env: &mut Env<'a>) -> Document<'a> { 1384 match &constructor.variant { 1385 ValueConstructorVariant::Record { 1386 name: record_name, .. 1387 } => match constructor.type_.deref() { 1388 Type::Fn { args, .. } => { 1389 let chars = incrementing_args_list(args.len()); 1390 "fun(" 1391 .to_doc() 1392 .append(chars.clone()) 1393 .append(") -> {") 1394 .append(atom_string(to_snake_case(record_name))) 1395 .append(", ") 1396 .append(chars) 1397 .append("} end") 1398 } 1399 _ => atom_string(to_snake_case(record_name)), 1400 }, 1401 1402 ValueConstructorVariant::LocalVariable { .. } => env.local_var_name(name), 1403 1404 ValueConstructorVariant::ModuleConstant { literal, .. } 1405 | ValueConstructorVariant::LocalConstant { literal } => const_inline(literal, env), 1406 1407 ValueConstructorVariant::ModuleFn { 1408 arity, 1409 external_erlang: Some((module, name)), 1410 .. 1411 } if module == env.module => function_reference(None, name, *arity), 1412 1413 ValueConstructorVariant::ModuleFn { 1414 arity, 1415 external_erlang: Some((module, name)), 1416 .. 1417 } => function_reference(Some(module), name, *arity), 1418 1419 ValueConstructorVariant::ModuleFn { arity, module, .. } if module == env.module => { 1420 function_reference(None, name, *arity) 1421 } 1422 1423 ValueConstructorVariant::ModuleFn { 1424 arity, 1425 module, 1426 name, 1427 .. 1428 } => function_reference(Some(module), name, *arity), 1429 } 1430} 1431 1432fn function_reference<'a>(module: Option<&'a str>, name: &'a str, arity: usize) -> Document<'a> { 1433 match module { 1434 None => "fun ".to_doc(), 1435 Some(module) => "fun ".to_doc().append(module_name_atom(module)).append(":"), 1436 } 1437 .append(atom(escape_erlang_existing_name(name))) 1438 .append("/") 1439 .append(arity) 1440} 1441 1442fn int<'a>(value: &str) -> Document<'a> { 1443 let mut value = value.replace('_', ""); 1444 if value.starts_with("0x") { 1445 value.replace_range(..2, "16#"); 1446 } else if value.starts_with("0o") { 1447 value.replace_range(..2, "8#"); 1448 } else if value.starts_with("0b") { 1449 value.replace_range(..2, "2#"); 1450 } 1451 1452 EcoString::from(value).to_doc() 1453} 1454 1455fn const_inline<'a>(literal: &'a TypedConstant, env: &mut Env<'a>) -> Document<'a> { 1456 match literal { 1457 Constant::Int { value, .. } => int(value), 1458 Constant::Float { value, .. } => float(value), 1459 Constant::String { value, .. } => string(value), 1460 Constant::Tuple { elements, .. } => { 1461 tuple(elements.iter().map(|element| const_inline(element, env))) 1462 } 1463 1464 Constant::List { elements, .. } => join( 1465 elements.iter().map(|element| const_inline(element, env)), 1466 break_(",", ", "), 1467 ) 1468 .nest(INDENT) 1469 .surround("[", "]") 1470 .group(), 1471 1472 Constant::BitArray { segments, .. } => bit_array( 1473 segments 1474 .iter() 1475 .map(|s| const_segment(&s.value, &s.options, env)), 1476 ), 1477 1478 Constant::Record { 1479 tag, type_, args, .. 1480 } if args.is_empty() => match type_.deref() { 1481 Type::Fn { args, .. } => record_constructor_function(tag, args.len()), 1482 _ => atom_string(to_snake_case(tag)), 1483 }, 1484 1485 Constant::Record { tag, args, .. } => { 1486 let args = args.iter().map(|a| const_inline(&a.value, env)); 1487 let tag = atom_string(to_snake_case(tag)); 1488 tuple(std::iter::once(tag).chain(args)) 1489 } 1490 1491 Constant::Var { 1492 name, constructor, .. 1493 } => var( 1494 name, 1495 constructor 1496 .as_ref() 1497 .expect("This is guaranteed to hold a value."), 1498 env, 1499 ), 1500 1501 Constant::StringConcatenation { left, right, .. } => { 1502 const_string_concatenate(left, right, env) 1503 } 1504 1505 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 1506 } 1507} 1508 1509fn record_constructor_function(tag: &EcoString, arity: usize) -> Document<'_> { 1510 let chars = incrementing_args_list(arity); 1511 "fun(" 1512 .to_doc() 1513 .append(chars.clone()) 1514 .append(") -> {") 1515 .append(atom_string(to_snake_case(tag))) 1516 .append(", ") 1517 .append(chars) 1518 .append("} end") 1519} 1520 1521fn clause<'a>(clause: &'a TypedClause, env: &mut Env<'a>) -> Document<'a> { 1522 let Clause { 1523 guard, 1524 pattern: pat, 1525 alternative_patterns, 1526 then, 1527 .. 1528 } = clause; 1529 1530 // These are required to get the alternative patterns working properly. 1531 // Simply rendering the duplicate erlang clauses breaks the variable 1532 // rewriting because each pattern would define different (rewritten) 1533 // variables names. 1534 let mut then_doc = None; 1535 let initial_erlang_vars = env.erl_function_scope_vars.clone(); 1536 let mut end_erlang_vars = im::HashMap::new(); 1537 1538 let doc = join( 1539 std::iter::once(pat) 1540 .chain(alternative_patterns) 1541 .map(|patterns| { 1542 let mut additional_guards = vec![]; 1543 env.erl_function_scope_vars = initial_erlang_vars.clone(); 1544 1545 let patterns_doc = if patterns.len() == 1 { 1546 let p = patterns.first().expect("Single pattern clause printing"); 1547 pattern(p, env, &mut additional_guards) 1548 } else { 1549 tuple( 1550 patterns 1551 .iter() 1552 .map(|p| pattern(p, env, &mut additional_guards)), 1553 ) 1554 }; 1555 1556 let guard = optional_clause_guard(guard.as_ref(), additional_guards, env); 1557 if then_doc.is_none() { 1558 then_doc = Some(clause_consequence(then, env)); 1559 end_erlang_vars = env.erl_function_scope_vars.clone(); 1560 } 1561 1562 patterns_doc.append( 1563 guard 1564 .append(" ->") 1565 .append(line().append(then_doc.clone()).nest(INDENT).group()), 1566 ) 1567 }), 1568 ";".to_doc().append(lines(2)), 1569 ); 1570 1571 env.erl_function_scope_vars = end_erlang_vars; 1572 doc 1573} 1574 1575fn clause_consequence<'a>(consequence: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 1576 match consequence { 1577 TypedExpr::Block { statements, .. } => statement_sequence(statements, env), 1578 _ => expr(consequence, env), 1579 } 1580} 1581 1582fn optional_clause_guard<'a>( 1583 guard: Option<&'a TypedClauseGuard>, 1584 additional_guards: Vec<Document<'a>>, 1585 env: &mut Env<'a>, 1586) -> Document<'a> { 1587 let guard_doc = guard.map(|guard| bare_clause_guard(guard, env)); 1588 1589 let guards_count = guard_doc.iter().len() + additional_guards.len(); 1590 let guards_docs = additional_guards.into_iter().chain(guard_doc).map(|guard| { 1591 if guards_count > 1 { 1592 guard.surround("(", ")") 1593 } else { 1594 guard 1595 } 1596 }); 1597 let doc = join(guards_docs, " andalso ".to_doc()); 1598 if doc.is_empty() { 1599 doc 1600 } else { 1601 " when ".to_doc().append(doc) 1602 } 1603} 1604 1605fn bare_clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> { 1606 match guard { 1607 ClauseGuard::Not { expression, .. } => docvec!["not ", bare_clause_guard(expression, env)], 1608 1609 ClauseGuard::Or { left, right, .. } => clause_guard(left, env) 1610 .append(" orelse ") 1611 .append(clause_guard(right, env)), 1612 1613 ClauseGuard::And { left, right, .. } => clause_guard(left, env) 1614 .append(" andalso ") 1615 .append(clause_guard(right, env)), 1616 1617 ClauseGuard::Equals { left, right, .. } => clause_guard(left, env) 1618 .append(" =:= ") 1619 .append(clause_guard(right, env)), 1620 1621 ClauseGuard::NotEquals { left, right, .. } => clause_guard(left, env) 1622 .append(" =/= ") 1623 .append(clause_guard(right, env)), 1624 1625 ClauseGuard::GtInt { left, right, .. } => clause_guard(left, env) 1626 .append(" > ") 1627 .append(clause_guard(right, env)), 1628 1629 ClauseGuard::GtEqInt { left, right, .. } => clause_guard(left, env) 1630 .append(" >= ") 1631 .append(clause_guard(right, env)), 1632 1633 ClauseGuard::LtInt { left, right, .. } => clause_guard(left, env) 1634 .append(" < ") 1635 .append(clause_guard(right, env)), 1636 1637 ClauseGuard::LtEqInt { left, right, .. } => clause_guard(left, env) 1638 .append(" =< ") 1639 .append(clause_guard(right, env)), 1640 1641 ClauseGuard::GtFloat { left, right, .. } => clause_guard(left, env) 1642 .append(" > ") 1643 .append(clause_guard(right, env)), 1644 1645 ClauseGuard::GtEqFloat { left, right, .. } => clause_guard(left, env) 1646 .append(" >= ") 1647 .append(clause_guard(right, env)), 1648 1649 ClauseGuard::LtFloat { left, right, .. } => clause_guard(left, env) 1650 .append(" < ") 1651 .append(clause_guard(right, env)), 1652 1653 ClauseGuard::LtEqFloat { left, right, .. } => clause_guard(left, env) 1654 .append(" =< ") 1655 .append(clause_guard(right, env)), 1656 1657 ClauseGuard::AddInt { left, right, .. } => clause_guard(left, env) 1658 .append(" + ") 1659 .append(clause_guard(right, env)), 1660 1661 ClauseGuard::AddFloat { left, right, .. } => clause_guard(left, env) 1662 .append(" + ") 1663 .append(clause_guard(right, env)), 1664 1665 ClauseGuard::SubInt { left, right, .. } => clause_guard(left, env) 1666 .append(" - ") 1667 .append(clause_guard(right, env)), 1668 1669 ClauseGuard::SubFloat { left, right, .. } => clause_guard(left, env) 1670 .append(" - ") 1671 .append(clause_guard(right, env)), 1672 1673 ClauseGuard::MultInt { left, right, .. } => clause_guard(left, env) 1674 .append(" * ") 1675 .append(clause_guard(right, env)), 1676 1677 ClauseGuard::MultFloat { left, right, .. } => clause_guard(left, env) 1678 .append(" * ") 1679 .append(clause_guard(right, env)), 1680 1681 ClauseGuard::DivInt { left, right, .. } => clause_guard(left, env) 1682 .append(" div ") 1683 .append(clause_guard(right, env)), 1684 1685 ClauseGuard::DivFloat { left, right, .. } => clause_guard(left, env) 1686 .append(" / ") 1687 .append(clause_guard(right, env)), 1688 1689 ClauseGuard::RemainderInt { left, right, .. } => clause_guard(left, env) 1690 .append(" rem ") 1691 .append(clause_guard(right, env)), 1692 1693 // Only local variables are supported and the typer ensures that all 1694 // ClauseGuard::Vars are local variables 1695 ClauseGuard::Var { name, .. } => env.local_var_name(name), 1696 1697 ClauseGuard::TupleIndex { tuple, index, .. } => tuple_index_inline(tuple, *index, env), 1698 1699 ClauseGuard::FieldAccess { 1700 container, index, .. 1701 } => tuple_index_inline(container, index.expect("Unable to find index") + 1, env), 1702 1703 ClauseGuard::ModuleSelect { literal, .. } => const_inline(literal, env), 1704 1705 ClauseGuard::Constant(constant) => const_inline(constant, env), 1706 } 1707} 1708 1709fn tuple_index_inline<'a>( 1710 tuple: &'a TypedClauseGuard, 1711 index: u64, 1712 env: &mut Env<'a>, 1713) -> Document<'a> { 1714 let index_doc = eco_format!("{}", (index + 1)).to_doc(); 1715 let tuple_doc = bare_clause_guard(tuple, env); 1716 "erlang:element" 1717 .to_doc() 1718 .append(wrap_args([index_doc, tuple_doc])) 1719} 1720 1721fn clause_guard<'a>(guard: &'a TypedClauseGuard, env: &mut Env<'a>) -> Document<'a> { 1722 match guard { 1723 // Binary operators are wrapped in parens 1724 ClauseGuard::Or { .. } 1725 | ClauseGuard::And { .. } 1726 | ClauseGuard::Equals { .. } 1727 | ClauseGuard::NotEquals { .. } 1728 | ClauseGuard::GtInt { .. } 1729 | ClauseGuard::GtEqInt { .. } 1730 | ClauseGuard::LtInt { .. } 1731 | ClauseGuard::LtEqInt { .. } 1732 | ClauseGuard::GtFloat { .. } 1733 | ClauseGuard::GtEqFloat { .. } 1734 | ClauseGuard::LtFloat { .. } 1735 | ClauseGuard::LtEqFloat { .. } 1736 | ClauseGuard::AddInt { .. } 1737 | ClauseGuard::AddFloat { .. } 1738 | ClauseGuard::SubInt { .. } 1739 | ClauseGuard::SubFloat { .. } 1740 | ClauseGuard::MultInt { .. } 1741 | ClauseGuard::MultFloat { .. } 1742 | ClauseGuard::DivInt { .. } 1743 | ClauseGuard::DivFloat { .. } 1744 | ClauseGuard::RemainderInt { .. } => "(" 1745 .to_doc() 1746 .append(bare_clause_guard(guard, env)) 1747 .append(")"), 1748 1749 // Other expressions are not 1750 ClauseGuard::Constant(_) 1751 | ClauseGuard::Not { .. } 1752 | ClauseGuard::Var { .. } 1753 | ClauseGuard::TupleIndex { .. } 1754 | ClauseGuard::FieldAccess { .. } 1755 | ClauseGuard::ModuleSelect { .. } => bare_clause_guard(guard, env), 1756 } 1757} 1758 1759fn clauses<'a>(cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { 1760 join( 1761 cs.iter().map(|c| { 1762 let vars = env.current_scope_vars.clone(); 1763 let erl = clause(c, env); 1764 env.current_scope_vars = vars; // Reset the known variables now the clauses' scope has ended 1765 erl 1766 }), 1767 ";".to_doc().append(lines(2)), 1768 ) 1769} 1770 1771fn case<'a>(subjects: &'a [TypedExpr], cs: &'a [TypedClause], env: &mut Env<'a>) -> Document<'a> { 1772 let subjects_doc = if subjects.len() == 1 { 1773 let subject = subjects 1774 .first() 1775 .expect("erl case printing of single subject"); 1776 maybe_block_expr(subject, env).group() 1777 } else { 1778 tuple( 1779 subjects 1780 .iter() 1781 .map(|element| maybe_block_expr(element, env)), 1782 ) 1783 }; 1784 "case " 1785 .to_doc() 1786 .append(subjects_doc) 1787 .append(" of") 1788 .append(line().append(clauses(cs, env)).nest(INDENT)) 1789 .append(line()) 1790 .append("end") 1791 .group() 1792} 1793 1794fn call<'a>(fun: &'a TypedExpr, args: &'a [TypedCallArg], env: &mut Env<'a>) -> Document<'a> { 1795 docs_args_call( 1796 fun, 1797 args.iter() 1798 .map(|arg| maybe_block_expr(&arg.value, env)) 1799 .collect(), 1800 env, 1801 ) 1802} 1803 1804fn module_fn_with_args<'a>( 1805 module: &'a str, 1806 name: &'a str, 1807 args: Vec<Document<'a>>, 1808 env: &Env<'a>, 1809) -> Document<'a> { 1810 let name = escape_erlang_existing_name(name); 1811 let args = wrap_args(args); 1812 if module == env.module { 1813 atom(name).append(args) 1814 } else { 1815 atom_string(module.replace('/', "@").into()) 1816 .append(":") 1817 .append(atom(name)) 1818 .append(args) 1819 } 1820} 1821 1822fn docs_args_call<'a>( 1823 fun: &'a TypedExpr, 1824 mut args: Vec<Document<'a>>, 1825 env: &mut Env<'a>, 1826) -> Document<'a> { 1827 match fun { 1828 TypedExpr::ModuleSelect { 1829 constructor: ModuleValueConstructor::Record { name, .. }, 1830 .. 1831 } 1832 | TypedExpr::Var { 1833 constructor: 1834 ValueConstructor { 1835 variant: ValueConstructorVariant::Record { name, .. }, 1836 .. 1837 }, 1838 .. 1839 } => tuple(std::iter::once(atom_string(to_snake_case(name))).chain(args)), 1840 1841 TypedExpr::Var { 1842 constructor: 1843 ValueConstructor { 1844 variant: 1845 ValueConstructorVariant::ModuleFn { 1846 external_erlang: Some((module, name)), 1847 .. 1848 } 1849 | ValueConstructorVariant::ModuleFn { module, name, .. }, 1850 .. 1851 }, 1852 .. 1853 } => module_fn_with_args(module, name, args, env), 1854 1855 // Match against a Constant::Var that contains a function. 1856 // We want this to be emitted like a normal function call, not a function variable 1857 // substitution. 1858 TypedExpr::Var { 1859 constructor: 1860 ValueConstructor { 1861 variant: 1862 ValueConstructorVariant::ModuleConstant { 1863 literal: 1864 Constant::Var { 1865 constructor: Some(constructor), 1866 .. 1867 }, 1868 .. 1869 }, 1870 .. 1871 }, 1872 .. 1873 } if constructor.variant.is_module_fn() => match &constructor.variant { 1874 ValueConstructorVariant::ModuleFn { 1875 external_erlang: Some((module, name)), 1876 .. 1877 } 1878 | ValueConstructorVariant::ModuleFn { module, name, .. } => { 1879 module_fn_with_args(module, name, args, env) 1880 } 1881 _ => { 1882 unreachable!("The above clause guard ensures that this is a module fn") 1883 } 1884 }, 1885 1886 TypedExpr::ModuleSelect { 1887 constructor: 1888 ModuleValueConstructor::Fn { 1889 external_erlang: Some((module, name)), 1890 .. 1891 } 1892 | ModuleValueConstructor::Fn { module, name, .. }, 1893 .. 1894 } => { 1895 let args = wrap_args(args); 1896 let name = escape_erlang_existing_name(name); 1897 // We use the constructor Fn variant's `module` and function `name`. 1898 // It would also be valid to use the module and label as in the 1899 // Gleam code, but using the variant can result in an optimisation 1900 // in which the target function is used for `external fn`s, removing 1901 // one layer of wrapping. 1902 // This also enables an optimisation in the Erlang compiler in which 1903 // some Erlang BIFs can be replaced with literals if their arguments 1904 // are literals, such as `binary_to_atom`. 1905 atom_string(module_erlang_name(module)) 1906 .append(":") 1907 .append(atom_string(name.into())) 1908 .append(args) 1909 } 1910 1911 TypedExpr::Fn { kind, body, .. } if kind.is_capture() => { 1912 if let Statement::Expression(TypedExpr::Call { 1913 fun, 1914 args: inner_args, 1915 .. 1916 }) = body.first() 1917 { 1918 let mut merged_args = Vec::with_capacity(inner_args.len()); 1919 for arg in inner_args { 1920 match &arg.value { 1921 TypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => { 1922 merged_args.push(args.swap_remove(0)) 1923 } 1924 e => merged_args.push(maybe_block_expr(e, env)), 1925 } 1926 } 1927 docs_args_call(fun, merged_args, env) 1928 } else { 1929 panic!("Erl printing: Capture was not a call") 1930 } 1931 } 1932 1933 TypedExpr::Fn { .. } 1934 | TypedExpr::Call { .. } 1935 | TypedExpr::Todo { .. } 1936 | TypedExpr::Panic { .. } 1937 | TypedExpr::RecordAccess { .. } 1938 | TypedExpr::TupleIndex { .. } => { 1939 let args = wrap_args(args); 1940 expr(fun, env).surround("(", ")").append(args) 1941 } 1942 1943 other => { 1944 let args = wrap_args(args); 1945 maybe_block_expr(other, env).append(args) 1946 } 1947 } 1948} 1949 1950fn record_update<'a>( 1951 record: &'a Option<Box<TypedAssignment>>, 1952 constructor: &'a TypedExpr, 1953 args: &'a [TypedCallArg], 1954 env: &mut Env<'a>, 1955) -> Document<'a> { 1956 let vars = env.current_scope_vars.clone(); 1957 1958 let document = match record.as_ref() { 1959 Some(record) => docvec![ 1960 assignment(record, env, Position::NotTail), 1961 ",", 1962 line(), 1963 call(constructor, args, env) 1964 ], 1965 None => call(constructor, args, env), 1966 }; 1967 1968 env.current_scope_vars = vars; 1969 1970 document 1971} 1972 1973/// Wrap a document in begin end 1974/// 1975fn begin_end(document: Document<'_>) -> Document<'_> { 1976 docvec!["begin", line().append(document).nest(INDENT), line(), "end"].force_break() 1977} 1978 1979fn maybe_block_expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 1980 if needs_begin_end_wrapping(expression) { 1981 begin_end(expr(expression, env)) 1982 } else { 1983 expr(expression, env) 1984 } 1985} 1986 1987fn needs_begin_end_wrapping(expression: &TypedExpr) -> bool { 1988 match expression { 1989 // Record updates are 1 expression if there's no assignment, multiple otherwise. 1990 TypedExpr::RecordUpdate { 1991 record_assignment, .. 1992 } => record_assignment.is_some(), 1993 1994 TypedExpr::Pipeline { .. } => true, 1995 1996 TypedExpr::Int { .. } 1997 | TypedExpr::Float { .. } 1998 | TypedExpr::String { .. } 1999 | TypedExpr::Var { .. } 2000 | TypedExpr::Fn { .. } 2001 | TypedExpr::List { .. } 2002 | TypedExpr::Call { .. } 2003 | TypedExpr::BinOp { .. } 2004 | TypedExpr::Case { .. } 2005 | TypedExpr::RecordAccess { .. } 2006 | TypedExpr::Block { .. } 2007 | TypedExpr::ModuleSelect { .. } 2008 | TypedExpr::Tuple { .. } 2009 | TypedExpr::TupleIndex { .. } 2010 | TypedExpr::Todo { .. } 2011 | TypedExpr::Echo { .. } 2012 | TypedExpr::Panic { .. } 2013 | TypedExpr::BitArray { .. } 2014 | TypedExpr::NegateBool { .. } 2015 | TypedExpr::NegateInt { .. } 2016 | TypedExpr::Invalid { .. } => false, 2017 } 2018} 2019 2020fn todo<'a>(message: Option<&'a TypedExpr>, location: SrcSpan, env: &mut Env<'a>) -> Document<'a> { 2021 let message = match message { 2022 Some(m) => expr(m, env), 2023 None => string("`todo` expression evaluated. This code has not yet been implemented."), 2024 }; 2025 erlang_error("todo", &message, location, vec![], env) 2026} 2027 2028fn panic<'a>(location: SrcSpan, message: Option<&'a TypedExpr>, env: &mut Env<'a>) -> Document<'a> { 2029 let message = match message { 2030 Some(m) => expr(m, env), 2031 None => string("`panic` expression evaluated."), 2032 }; 2033 erlang_error("panic", &message, location, vec![], env) 2034} 2035 2036fn echo<'a>( 2037 body: Document<'a>, 2038 message: Option<&'a TypedExpr>, 2039 location: &SrcSpan, 2040 env: &mut Env<'a>, 2041) -> Document<'a> { 2042 env.echo_used = true; 2043 2044 let message = message 2045 .as_ref() 2046 .map(|message| maybe_block_expr(message, env)) 2047 .unwrap_or("nil".to_doc()); 2048 2049 "echo".to_doc().append(wrap_args(vec![ 2050 body, 2051 message, 2052 env.line_numbers.line_number(location.start).to_doc(), 2053 ])) 2054} 2055 2056fn erlang_error<'a>( 2057 name: &'a str, 2058 message: &Document<'a>, 2059 location: SrcSpan, 2060 fields: Vec<(&'a str, Document<'a>)>, 2061 env: &Env<'a>, 2062) -> Document<'a> { 2063 let mut fields_doc = docvec![ 2064 "gleam_error => ", 2065 name, 2066 ",", 2067 line(), 2068 "message => ", 2069 message.clone(), 2070 ",", 2071 line(), 2072 "file => <<?FILEPATH/utf8>>,", 2073 line(), 2074 "module => ", 2075 env.module.to_doc().surround("<<\"", "\"/utf8>>"), 2076 ",", 2077 line(), 2078 "function => ", 2079 string(env.function), 2080 ",", 2081 line(), 2082 "line => ", 2083 env.line_numbers.line_number(location.start), 2084 ]; 2085 for (key, value) in fields { 2086 fields_doc = fields_doc 2087 .append(",") 2088 .append(line()) 2089 .append(key) 2090 .append(" => ") 2091 .append(value); 2092 } 2093 let error = docvec!["#{", fields_doc.group().nest(INDENT), "}"]; 2094 docvec!["erlang:error", wrap_args([error.group()])] 2095} 2096 2097fn expr<'a>(expression: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 2098 match expression { 2099 TypedExpr::Todo { 2100 message: label, 2101 location, 2102 .. 2103 } => todo(label.as_deref(), *location, env), 2104 2105 TypedExpr::Panic { 2106 location, message, .. 2107 } => panic(*location, message.as_deref(), env), 2108 2109 TypedExpr::Echo { 2110 expression, 2111 location, 2112 message, 2113 .. 2114 } => { 2115 let expression = expression 2116 .as_ref() 2117 .expect("echo with no expression outside of pipe"); 2118 let expression = maybe_block_expr(expression, env); 2119 echo(expression, message.as_deref(), location, env) 2120 } 2121 2122 TypedExpr::Int { value, .. } => int(value), 2123 TypedExpr::Float { value, .. } => float(value), 2124 TypedExpr::String { value, .. } => string(value), 2125 2126 TypedExpr::Pipeline { 2127 first_value, 2128 assignments, 2129 finally, 2130 .. 2131 } => pipeline(first_value, assignments, finally, env), 2132 2133 TypedExpr::Block { statements, .. } => block(statements, env), 2134 2135 TypedExpr::TupleIndex { tuple, index, .. } => tuple_index(tuple, *index, env), 2136 2137 TypedExpr::Var { 2138 name, constructor, .. 2139 } => var(name, constructor, env), 2140 2141 TypedExpr::Fn { args, body, .. } => fun(args, body, env), 2142 2143 TypedExpr::NegateBool { value, .. } => negate_with("not ", value, env), 2144 2145 TypedExpr::NegateInt { value, .. } => negate_with("- ", value, env), 2146 2147 TypedExpr::List { elements, tail, .. } => expr_list(elements, tail, env), 2148 2149 TypedExpr::Call { fun, args, .. } => call(fun, args, env), 2150 2151 TypedExpr::ModuleSelect { 2152 constructor: ModuleValueConstructor::Record { name, arity: 0, .. }, 2153 .. 2154 } => atom_string(to_snake_case(name)), 2155 2156 TypedExpr::ModuleSelect { 2157 constructor: ModuleValueConstructor::Constant { literal, .. }, 2158 .. 2159 } => const_inline(literal, env), 2160 2161 TypedExpr::ModuleSelect { 2162 constructor: ModuleValueConstructor::Record { name, arity, .. }, 2163 .. 2164 } => record_constructor_function(name, *arity as usize), 2165 2166 TypedExpr::ModuleSelect { 2167 type_, 2168 constructor: 2169 ModuleValueConstructor::Fn { 2170 external_erlang: Some((module, name)), 2171 .. 2172 } 2173 | ModuleValueConstructor::Fn { module, name, .. }, 2174 .. 2175 } => module_select_fn(type_.clone(), module, name), 2176 2177 TypedExpr::RecordAccess { record, index, .. } => tuple_index(record, index + 1, env), 2178 2179 TypedExpr::RecordUpdate { 2180 record_assignment, 2181 constructor, 2182 args, 2183 .. 2184 } => record_update(record_assignment, constructor, args, env), 2185 2186 TypedExpr::Case { 2187 subjects, clauses, .. 2188 } => case(subjects, clauses, env), 2189 2190 TypedExpr::BinOp { 2191 name, left, right, .. 2192 } => bin_op(name, left, right, env), 2193 2194 TypedExpr::Tuple { elements, .. } => tuple( 2195 elements 2196 .iter() 2197 .map(|element| maybe_block_expr(element, env)), 2198 ), 2199 2200 TypedExpr::BitArray { segments, .. } => bit_array( 2201 segments 2202 .iter() 2203 .map(|s| expr_segment(&s.value, &s.options, env)), 2204 ), 2205 2206 TypedExpr::Invalid { .. } => panic!("invalid expressions should not reach code generation"), 2207 } 2208} 2209 2210fn pipeline<'a>( 2211 first_value: &'a TypedPipelineAssignment, 2212 assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)], 2213 finally: &'a TypedExpr, 2214 env: &mut Env<'a>, 2215) -> Document<'a> { 2216 let mut documents = Vec::with_capacity((assignments.len() + 1) * 3); 2217 2218 let all_assignments = std::iter::once(first_value) 2219 .chain(assignments.iter().map(|(assignment, _kind)| assignment)); 2220 2221 let echo_doc = |var_name: &Option<Document<'a>>, 2222 message: Option<&'a TypedExpr>, 2223 location: &SrcSpan, 2224 env: &mut Env<'a>| { 2225 let name = var_name 2226 .to_owned() 2227 .expect("echo with no previous step in a pipe"); 2228 echo(name, message, location, env) 2229 }; 2230 2231 let mut prev_local_var_name = None; 2232 for a in all_assignments { 2233 match a.value.as_ref() { 2234 // An echo in a pipeline won't result in an assignment, instead it 2235 // just prints the previous variable assigned in the pipeline. 2236 TypedExpr::Echo { 2237 expression: None, 2238 message, 2239 location, 2240 .. 2241 } => documents.push(echo_doc( 2242 &prev_local_var_name, 2243 message.as_deref(), 2244 location, 2245 env, 2246 )), 2247 2248 // Otherwise we assign the intermediate pipe value to a variable. 2249 _ => { 2250 let body = maybe_block_expr(&a.value, env).group(); 2251 let name = env.next_local_var_name(&a.name); 2252 prev_local_var_name = Some(name.clone()); 2253 documents.push(docvec![name, " = ", body]); 2254 } 2255 }; 2256 documents.push(",".to_doc()); 2257 documents.push(line()); 2258 } 2259 2260 match finally { 2261 TypedExpr::Echo { 2262 expression: None, 2263 message, 2264 location, 2265 .. 2266 } => documents.push(echo_doc( 2267 &prev_local_var_name, 2268 message.as_deref(), 2269 location, 2270 env, 2271 )), 2272 _ => documents.push(expr(finally, env)), 2273 } 2274 2275 documents.to_doc() 2276} 2277 2278fn assignment<'a>( 2279 assignment: &'a TypedAssignment, 2280 env: &mut Env<'a>, 2281 position: Position, 2282) -> Document<'a> { 2283 match &assignment.kind { 2284 AssignmentKind::Let | AssignmentKind::Generated => { 2285 let_(&assignment.value, &assignment.pattern, env) 2286 } 2287 AssignmentKind::Assert { 2288 message, location, .. 2289 } => let_assert( 2290 &assignment.value, 2291 &assignment.pattern, 2292 env, 2293 message.as_ref(), 2294 position, 2295 *location, 2296 ), 2297 } 2298} 2299 2300fn assert<'a>(assert: &'a TypedAssert, env: &mut Env<'a>) -> Document<'a> { 2301 let Assert { 2302 value, 2303 location, 2304 message, 2305 } = assert; 2306 2307 let message = match message { 2308 Some(message) => expr(message, env), 2309 None => string("Assertion failed."), 2310 }; 2311 2312 let mut assignments = Vec::new(); 2313 2314 let (subject, mut fields) = match value { 2315 TypedExpr::Call { fun, args, .. } => assert_call(fun, args, &mut assignments, env), 2316 TypedExpr::BinOp { 2317 name, left, right, .. 2318 } => { 2319 let operator = match name { 2320 BinOp::And => { 2321 return assert_and(left, right, message, *location, env); 2322 } 2323 BinOp::Or => { 2324 return assert_or(left, right, message, *location, env); 2325 } 2326 BinOp::Eq => "=:=", 2327 BinOp::NotEq => "/=", 2328 BinOp::LtInt | BinOp::LtFloat => "<", 2329 BinOp::LtEqInt | BinOp::LtEqFloat => "=<", 2330 BinOp::GtInt | BinOp::GtFloat => ">", 2331 BinOp::GtEqInt | BinOp::GtEqFloat => ">=", 2332 BinOp::AddInt 2333 | BinOp::AddFloat 2334 | BinOp::SubInt 2335 | BinOp::SubFloat 2336 | BinOp::MultInt 2337 | BinOp::MultFloat 2338 | BinOp::DivInt 2339 | BinOp::DivFloat 2340 | BinOp::RemainderInt 2341 | BinOp::Concatenate => { 2342 panic!("Non-boolean operators cannot appear here in well-typed code") 2343 } 2344 }; 2345 2346 let left_document = assign_to_variable(left, &mut assignments, env); 2347 let right_document = assign_to_variable(right, &mut assignments, env); 2348 ( 2349 binop_documents(left_document.clone(), operator, right_document.clone()), 2350 vec![ 2351 ("kind", atom("binary_operator")), 2352 ("operator", atom(name.name())), 2353 ( 2354 "left", 2355 asserted_expression( 2356 AssertExpression::from_expression(left), 2357 Some(left_document), 2358 left.location(), 2359 ), 2360 ), 2361 ( 2362 "right", 2363 asserted_expression( 2364 AssertExpression::from_expression(right), 2365 Some(right_document), 2366 right.location(), 2367 ), 2368 ), 2369 ], 2370 ) 2371 } 2372 2373 _ => ( 2374 maybe_block_expr(value, env), 2375 vec![ 2376 ("kind", atom("expression")), 2377 ( 2378 "expression", 2379 asserted_expression( 2380 AssertExpression::from_expression(value), 2381 Some("false".to_doc()), 2382 value.location(), 2383 ), 2384 ), 2385 ], 2386 ), 2387 }; 2388 2389 fields.push(("start", location.start.to_doc())); 2390 fields.push(("'end'", value.location().end.to_doc())); 2391 fields.push(("expression_start", value.location().start.to_doc())); 2392 2393 let clauses = docvec![ 2394 line(), 2395 "true -> nil;", 2396 line(), 2397 "false -> ", 2398 erlang_error("assert", &message, *location, fields, env), 2399 ]; 2400 2401 docvec![ 2402 assignments, 2403 "case ", 2404 subject, 2405 " of", 2406 clauses.nest(INDENT), 2407 line(), 2408 "end" 2409 ] 2410} 2411 2412fn assert_call<'a>( 2413 function: &'a TypedExpr, 2414 arguments: &'a Vec<CallArg<TypedExpr>>, 2415 assignments: &mut Vec<Document<'a>>, 2416 env: &mut Env<'a>, 2417) -> (Document<'a>, Vec<(&'static str, Document<'a>)>) { 2418 let argument_variables = arguments 2419 .iter() 2420 .map(|argument| assign_to_variable(&argument.value, assignments, env)) 2421 .collect_vec(); 2422 2423 let arguments = join( 2424 argument_variables 2425 .iter() 2426 .zip(arguments) 2427 .map(|(variable, argument)| { 2428 asserted_expression( 2429 AssertExpression::from_expression(&argument.value), 2430 Some(variable.clone()), 2431 argument.location(), 2432 ) 2433 }), 2434 break_(",", ", "), 2435 ) 2436 .nest(INDENT) 2437 .surround("[", "]"); 2438 2439 ( 2440 docs_args_call(function, argument_variables, env), 2441 vec![("kind", atom("function_call")), ("arguments", arguments)], 2442 ) 2443} 2444 2445/// In Gleam, the `&&` operator is short-circuiting, meaning that we can't 2446/// pre-evaluate both sides of it, and use them in the exception that is 2447/// thrown. 2448/// Instead, we need to implement this short-circuiting logic ourself. 2449/// 2450/// If we short-circuit, we must leave the second expression unevaluated, 2451/// and signal that using the `unevaluated` variant, as detailed in the 2452/// exception format. For the first expression, we know it must be `false`, 2453/// otherwise we would have continued by evaluating the second expression. 2454/// 2455/// Similarly, if we do evaluate the second expression and fail, we know 2456/// that the first expression must have evaluated to `true`, and the second 2457/// to `false`. This way, we avoid needing to evaluate either expression 2458/// twice. 2459/// 2460/// The generated code then looks something like this: 2461/// ```erlang 2462/// case expr1 of 2463/// true -> case expr2 of 2464/// true -> true; 2465/// false -> <throw exception> 2466/// end; 2467/// false -> <throw exception> 2468/// end 2469/// ``` 2470/// 2471fn assert_and<'a>( 2472 left: &'a TypedExpr, 2473 right: &'a TypedExpr, 2474 message: Document<'a>, 2475 location: SrcSpan, 2476 env: &mut Env<'a>, 2477) -> Document<'a> { 2478 let left_kind = AssertExpression::from_expression(left); 2479 let right_kind = AssertExpression::from_expression(right); 2480 2481 let fields_if_short_circuiting = vec![ 2482 ("kind", atom("binary_operator")), 2483 ("operator", atom("&&")), 2484 ( 2485 "left", 2486 asserted_expression(left_kind, Some("false".to_doc()), left.location()), 2487 ), 2488 ( 2489 "right", 2490 asserted_expression(AssertExpression::Unevaluated, None, right.location()), 2491 ), 2492 ("start", location.start.to_doc()), 2493 ("'end'", right.location().end.to_doc()), 2494 ("expression_start", left.location().start.to_doc()), 2495 ]; 2496 2497 let fields = vec![ 2498 ("kind", atom("binary_operator")), 2499 ("operator", atom("&&")), 2500 ( 2501 "left", 2502 asserted_expression(left_kind, Some("true".to_doc()), left.location()), 2503 ), 2504 ( 2505 "right", 2506 asserted_expression(right_kind, Some("false".to_doc()), right.location()), 2507 ), 2508 ("start", location.start.to_doc()), 2509 ("'end'", right.location().end.to_doc()), 2510 ("expression_start", left.location().start.to_doc()), 2511 ]; 2512 2513 let right_clauses = docvec![ 2514 line(), 2515 "true -> nil;", 2516 line(), 2517 "false -> ", 2518 erlang_error("assert", &message, location, fields, env), 2519 ]; 2520 2521 let left_clauses = docvec![ 2522 line(), 2523 "true -> ", 2524 docvec![ 2525 "case ", 2526 maybe_block_expr(right, env), 2527 " of", 2528 right_clauses.nest(INDENT), 2529 line(), 2530 "end" 2531 ] 2532 .nest(INDENT), 2533 ";", 2534 line(), 2535 "false -> ", 2536 erlang_error( 2537 "assert", 2538 &message, 2539 location, 2540 fields_if_short_circuiting, 2541 env 2542 ), 2543 ]; 2544 2545 docvec![ 2546 "case ", 2547 maybe_block_expr(left, env), 2548 " of", 2549 left_clauses.nest(INDENT), 2550 line(), 2551 "end" 2552 ] 2553} 2554 2555/// Similar to `&&`, `||` is also short-circuiting in Gleam. However, if `||` 2556/// short-circuits, that's because the first expression evaluated to `true`, 2557/// meaning the whole assertion succeeds. This allows us to directly use Erlang's 2558/// `orelse` operator as the subject of the `case` expression. 2559/// 2560/// The only difference is that due to the nature of `||`, if the assertion fails, 2561/// we know that both sides must have evaluated to `false`, so we don't 2562/// need to store the values of them in variables beforehand. 2563fn assert_or<'a>( 2564 left: &'a TypedExpr, 2565 right: &'a TypedExpr, 2566 message: Document<'a>, 2567 location: SrcSpan, 2568 env: &mut Env<'a>, 2569) -> Document<'a> { 2570 let fields = vec![ 2571 ("kind", atom("binary_operator")), 2572 ("operator", atom("||")), 2573 ( 2574 "left", 2575 asserted_expression( 2576 AssertExpression::from_expression(left), 2577 Some("false".to_doc()), 2578 left.location(), 2579 ), 2580 ), 2581 ( 2582 "right", 2583 asserted_expression( 2584 AssertExpression::from_expression(right), 2585 Some("false".to_doc()), 2586 right.location(), 2587 ), 2588 ), 2589 ("start", location.start.to_doc()), 2590 ("'end'", right.location().end.to_doc()), 2591 ("expression_start", left.location().start.to_doc()), 2592 ]; 2593 2594 let clauses = docvec![ 2595 line(), 2596 "true -> nil;", 2597 line(), 2598 "false -> ", 2599 erlang_error("assert", &message, location, fields, env), 2600 ]; 2601 2602 docvec![ 2603 "case ", 2604 docvec![ 2605 maybe_block_expr(left, env), 2606 " orelse ", 2607 maybe_block_expr(right, env) 2608 ] 2609 .nest(INDENT), 2610 " of", 2611 clauses.nest(INDENT), 2612 line(), 2613 "end" 2614 ] 2615} 2616 2617fn assign_to_variable<'a>( 2618 value: &'a TypedExpr, 2619 assignments: &mut Vec<Document<'a>>, 2620 env: &mut Env<'a>, 2621) -> Document<'a> { 2622 if value.is_var() { 2623 expr(value, env) 2624 } else { 2625 let value = maybe_block_expr(value, env); 2626 let variable = env.next_local_var_name(ASSERT_SUBJECT_VARIABLE); 2627 let definition = docvec![variable.clone(), " = ", value, ",", line()]; 2628 assignments.push(definition); 2629 variable 2630 } 2631} 2632 2633#[derive(Debug, Clone, Copy)] 2634enum AssertExpression { 2635 Literal, 2636 Expression, 2637 Unevaluated, 2638} 2639 2640impl AssertExpression { 2641 fn from_expression(expression: &TypedExpr) -> Self { 2642 if expression.is_literal() { 2643 Self::Literal 2644 } else { 2645 Self::Expression 2646 } 2647 } 2648} 2649 2650fn asserted_expression( 2651 kind: AssertExpression, 2652 value: Option<Document<'_>>, 2653 location: SrcSpan, 2654) -> Document<'_> { 2655 let kind = match kind { 2656 AssertExpression::Literal => atom("literal"), 2657 AssertExpression::Expression => atom("expression"), 2658 AssertExpression::Unevaluated => atom("unevaluated"), 2659 }; 2660 2661 let start = location.start.to_doc(); 2662 let end = location.end.to_doc(); 2663 2664 let value_field = if let Some(value) = value { 2665 docvec!["value => ", value, ",", line()] 2666 } else { 2667 nil() 2668 }; 2669 2670 let fields_doc = docvec![ 2671 "kind => ", 2672 kind, 2673 ",", 2674 line(), 2675 value_field, 2676 "start => ", 2677 start, 2678 ",", 2679 line(), 2680 // `end` is a keyword in Erlang, so we have to quote it 2681 "'end' => ", 2682 end, 2683 line(), 2684 ]; 2685 2686 "#{".to_doc() 2687 .append(fields_doc.group().nest(INDENT)) 2688 .append("}") 2689} 2690 2691fn negate_with<'a>(op: &'static str, value: &'a TypedExpr, env: &mut Env<'a>) -> Document<'a> { 2692 docvec![op, maybe_block_expr(value, env)] 2693} 2694 2695fn tuple_index<'a>(tuple: &'a TypedExpr, index: u64, env: &mut Env<'a>) -> Document<'a> { 2696 let index_doc = eco_format!("{}", (index + 1)).to_doc(); 2697 let tuple_doc = maybe_block_expr(tuple, env); 2698 "erlang:element" 2699 .to_doc() 2700 .append(wrap_args([index_doc, tuple_doc])) 2701} 2702 2703fn module_select_fn<'a>(type_: Arc<Type>, module_name: &'a str, label: &'a str) -> Document<'a> { 2704 match crate::type_::collapse_links(type_).as_ref() { 2705 Type::Fn { args, .. } => function_reference(Some(module_name), label, args.len()), 2706 2707 _ => module_name_atom(module_name) 2708 .append(":") 2709 .append(atom(label)) 2710 .append("()"), 2711 } 2712} 2713 2714fn fun<'a>(args: &'a [TypedArg], body: &'a [TypedStatement], env: &mut Env<'a>) -> Document<'a> { 2715 let current_scope_vars = env.current_scope_vars.clone(); 2716 let doc = "fun" 2717 .to_doc() 2718 .append(fun_args(args, env).append(" ->")) 2719 .append( 2720 break_("", " ") 2721 .append(statement_sequence(body, env)) 2722 .nest(INDENT), 2723 ) 2724 .append(break_("", " ")) 2725 .append("end") 2726 .group(); 2727 env.current_scope_vars = current_scope_vars; 2728 doc 2729} 2730 2731fn incrementing_args_list(arity: usize) -> EcoString { 2732 let arguments = (0..arity).map(|c| format!("Field@{c}")); 2733 Itertools::intersperse(arguments, ", ".into()) 2734 .collect::<String>() 2735 .into() 2736} 2737 2738fn variable_name(name: &str) -> EcoString { 2739 let mut chars = name.chars(); 2740 let first_char = chars.next(); 2741 let first_uppercased = first_char.into_iter().flat_map(char::to_uppercase); 2742 2743 first_uppercased.chain(chars).collect::<EcoString>() 2744} 2745 2746/// When rendering a type variable to an erlang type spec we need all type variables with the 2747/// same id to end up with the same name in the generated erlang. 2748/// This function converts a usize into base 26 A-Z for this purpose. 2749fn id_to_type_var(id: u64) -> Document<'static> { 2750 if id < 26 { 2751 let mut name = EcoString::from(""); 2752 name.push(char::from_u32((id % 26 + 65) as u32).expect("id_to_type_var 0")); 2753 return name.to_doc(); 2754 } 2755 let mut name = vec![]; 2756 let mut last_char = id; 2757 while last_char >= 26 { 2758 name.push(char::from_u32((last_char % 26 + 65) as u32).expect("id_to_type_var 1")); 2759 last_char /= 26; 2760 } 2761 name.push(char::from_u32((last_char % 26 + 64) as u32).expect("id_to_type_var 2")); 2762 name.reverse(); 2763 name.into_iter().collect::<EcoString>().to_doc() 2764} 2765 2766pub fn is_erlang_reserved_word(name: &str) -> bool { 2767 matches!( 2768 name, 2769 "!" | "receive" 2770 | "bnot" 2771 | "div" 2772 | "rem" 2773 | "band" 2774 | "bor" 2775 | "bxor" 2776 | "bsl" 2777 | "bsr" 2778 | "not" 2779 | "and" 2780 | "or" 2781 | "xor" 2782 | "orelse" 2783 | "andalso" 2784 | "when" 2785 | "end" 2786 | "fun" 2787 | "try" 2788 | "catch" 2789 | "after" 2790 | "begin" 2791 | "let" 2792 | "query" 2793 | "cond" 2794 | "if" 2795 | "of" 2796 | "case" 2797 | "maybe" 2798 | "else" 2799 ) 2800} 2801 2802// Includes shell_default & user_default which are looked for by the erlang shell 2803pub fn is_erlang_standard_library_module(name: &str) -> bool { 2804 matches!( 2805 name, 2806 "array" 2807 | "base64" 2808 | "beam_lib" 2809 | "binary" 2810 | "c" 2811 | "calendar" 2812 | "dets" 2813 | "dict" 2814 | "digraph" 2815 | "digraph_utils" 2816 | "epp" 2817 | "erl_anno" 2818 | "erl_eval" 2819 | "erl_expand_records" 2820 | "erl_id_trans" 2821 | "erl_internal" 2822 | "erl_lint" 2823 | "erl_parse" 2824 | "erl_pp" 2825 | "erl_scan" 2826 | "erl_tar" 2827 | "ets" 2828 | "file_sorter" 2829 | "filelib" 2830 | "filename" 2831 | "gb_sets" 2832 | "gb_trees" 2833 | "gen_event" 2834 | "gen_fsm" 2835 | "gen_server" 2836 | "gen_statem" 2837 | "io" 2838 | "io_lib" 2839 | "lists" 2840 | "log_mf_h" 2841 | "maps" 2842 | "math" 2843 | "ms_transform" 2844 | "orddict" 2845 | "ordsets" 2846 | "pool" 2847 | "proc_lib" 2848 | "proplists" 2849 | "qlc" 2850 | "queue" 2851 | "rand" 2852 | "random" 2853 | "re" 2854 | "sets" 2855 | "shell" 2856 | "shell_default" 2857 | "shell_docs" 2858 | "slave" 2859 | "sofs" 2860 | "string" 2861 | "supervisor" 2862 | "supervisor_bridge" 2863 | "sys" 2864 | "timer" 2865 | "unicode" 2866 | "uri_string" 2867 | "user_default" 2868 | "win32reg" 2869 | "zip" 2870 ) 2871} 2872 2873// Includes the functions that are autogenerated by erlang itself 2874pub fn escape_erlang_existing_name(name: &str) -> &str { 2875 match name { 2876 "module_info" => "moduleInfo", 2877 _ => name, 2878 } 2879} 2880 2881// A TypeVar can either be rendered as an actual type variable such as `A` or `B`, 2882// or it can be rendered as `any()` depending on how many usages it has. If it 2883// has only 1 usage it is an `any()` type. If it has more than 1 usage it is a 2884// type variable. This function gathers usages for this determination. 2885// 2886// Examples: 2887// fn(a) -> String // `a` is `any()` 2888// fn() -> Result(a, b) // `a` and `b` are `any()` 2889// fn(a) -> a // `a` is a type var 2890fn collect_type_var_usages<'a>( 2891 mut ids: HashMap<u64, u64>, 2892 types: impl IntoIterator<Item = &'a Arc<Type>>, 2893) -> HashMap<u64, u64> { 2894 for type_ in types { 2895 type_var_ids(type_, &mut ids); 2896 } 2897 ids 2898} 2899 2900fn result_type_var_ids(ids: &mut HashMap<u64, u64>, arg_ok: &Type, arg_err: &Type) { 2901 let mut ok_ids = HashMap::new(); 2902 type_var_ids(arg_ok, &mut ok_ids); 2903 2904 let mut err_ids = HashMap::new(); 2905 type_var_ids(arg_err, &mut err_ids); 2906 2907 let mut result_counts = ok_ids; 2908 for (id, count) in err_ids { 2909 let _ = result_counts 2910 .entry(id) 2911 .and_modify(|current_count| { 2912 if *current_count < count { 2913 *current_count = count; 2914 } 2915 }) 2916 .or_insert(count); 2917 } 2918 for (id, count) in result_counts { 2919 let _ = ids 2920 .entry(id) 2921 .and_modify(|current_count| { 2922 *current_count += count; 2923 }) 2924 .or_insert(count); 2925 } 2926} 2927 2928fn type_var_ids(type_: &Type, ids: &mut HashMap<u64, u64>) { 2929 match type_ { 2930 Type::Var { type_ } => match type_.borrow().deref() { 2931 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => { 2932 let count = ids.entry(*id).or_insert(0); 2933 *count += 1; 2934 } 2935 TypeVar::Link { type_ } => type_var_ids(type_, ids), 2936 }, 2937 Type::Named { 2938 args, module, name, .. 2939 } => match args[..] { 2940 [ref arg_ok, ref arg_err] if is_prelude_module(module) && name == "Result" => { 2941 result_type_var_ids(ids, arg_ok, arg_err) 2942 } 2943 _ => { 2944 for arg in args { 2945 type_var_ids(arg, ids) 2946 } 2947 } 2948 }, 2949 Type::Fn { args, return_ } => { 2950 for arg in args { 2951 type_var_ids(arg, ids) 2952 } 2953 type_var_ids(return_, ids); 2954 } 2955 Type::Tuple { elements } => { 2956 for element in elements { 2957 type_var_ids(element, ids) 2958 } 2959 } 2960 } 2961} 2962 2963fn erl_safe_type_name(mut name: EcoString) -> EcoString { 2964 if matches!( 2965 name.as_str(), 2966 "any" 2967 | "arity" 2968 | "atom" 2969 | "binary" 2970 | "bitstring" 2971 | "boolean" 2972 | "byte" 2973 | "char" 2974 | "dynamic" 2975 | "float" 2976 | "function" 2977 | "identifier" 2978 | "integer" 2979 | "iodata" 2980 | "iolist" 2981 | "list" 2982 | "map" 2983 | "maybe_improper_list" 2984 | "mfa" 2985 | "module" 2986 | "neg_integer" 2987 | "nil" 2988 | "no_return" 2989 | "node" 2990 | "non_neg_integer" 2991 | "none" 2992 | "nonempty_improper_list" 2993 | "nonempty_list" 2994 | "nonempty_string" 2995 | "number" 2996 | "pid" 2997 | "port" 2998 | "pos_integer" 2999 | "reference" 3000 | "string" 3001 | "term" 3002 | "timeout" 3003 | "tuple" 3004 ) { 3005 name.push('_'); 3006 name 3007 } else { 3008 escape_atom_string(name) 3009 } 3010} 3011 3012#[derive(Debug)] 3013struct TypePrinter<'a> { 3014 var_as_any: bool, 3015 current_module: &'a str, 3016 var_usages: Option<&'a HashMap<u64, u64>>, 3017} 3018 3019impl<'a> TypePrinter<'a> { 3020 fn new(current_module: &'a str) -> Self { 3021 Self { 3022 current_module, 3023 var_usages: None, 3024 var_as_any: false, 3025 } 3026 } 3027 3028 pub fn with_var_usages(mut self, var_usages: &'a HashMap<u64, u64>) -> Self { 3029 self.var_usages = Some(var_usages); 3030 self 3031 } 3032 3033 pub fn print(&self, type_: &Type) -> Document<'static> { 3034 match type_ { 3035 Type::Var { type_ } => self.print_var(&type_.borrow()), 3036 3037 Type::Named { 3038 name, module, args, .. 3039 } if is_prelude_module(module) => self.print_prelude_type(name, args), 3040 3041 Type::Named { 3042 name, module, args, .. 3043 } => self.print_type_app(module, name, args), 3044 3045 Type::Fn { args, return_ } => self.print_fn(args, return_), 3046 3047 Type::Tuple { elements } => tuple(elements.iter().map(|element| self.print(element))), 3048 } 3049 } 3050 3051 fn print_var(&self, type_: &TypeVar) -> Document<'static> { 3052 match type_ { 3053 TypeVar::Generic { .. } | TypeVar::Unbound { .. } if self.var_as_any => { 3054 "any()".to_doc() 3055 } 3056 TypeVar::Generic { id, .. } | TypeVar::Unbound { id, .. } => match &self.var_usages { 3057 Some(usages) => match usages.get(id) { 3058 Some(&0) => nil(), 3059 Some(&1) => "any()".to_doc(), 3060 _ => id_to_type_var(*id), 3061 }, 3062 None => id_to_type_var(*id), 3063 }, 3064 TypeVar::Link { type_ } => self.print(type_), 3065 } 3066 } 3067 3068 fn print_prelude_type(&self, name: &str, args: &[Arc<Type>]) -> Document<'static> { 3069 match name { 3070 "Nil" => "nil".to_doc(), 3071 "Int" | "UtfCodepoint" => "integer()".to_doc(), 3072 "String" => "binary()".to_doc(), 3073 "Bool" => "boolean()".to_doc(), 3074 "Float" => "float()".to_doc(), 3075 "BitArray" => "bitstring()".to_doc(), 3076 "List" => { 3077 let arg0 = self.print(args.first().expect("print_prelude_type list")); 3078 "list(".to_doc().append(arg0).append(")") 3079 } 3080 "Result" => match args { 3081 [arg_ok, arg_err] => { 3082 let ok = tuple(["ok".to_doc(), self.print(arg_ok)]); 3083 let error = tuple(["error".to_doc(), self.print(arg_err)]); 3084 docvec![ok, break_(" |", " | "), error].nest(INDENT).group() 3085 } 3086 _ => panic!("print_prelude_type result expects ok and err"), 3087 }, 3088 // Getting here should mean we either forgot a built-in type or there is a 3089 // compiler error 3090 name => panic!("{name} is not a built-in type."), 3091 } 3092 } 3093 3094 fn print_type_app(&self, module: &str, name: &str, args: &[Arc<Type>]) -> Document<'static> { 3095 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc()); 3096 let name = erl_safe_type_name(to_snake_case(name)).to_doc(); 3097 if self.current_module == module { 3098 docvec![name, "(", args, ")"] 3099 } else { 3100 docvec![module_name_atom(module), ":", name, "(", args, ")"] 3101 } 3102 } 3103 3104 fn print_fn(&self, args: &[Arc<Type>], return_: &Type) -> Document<'static> { 3105 let args = join(args.iter().map(|a| self.print(a)), ", ".to_doc()); 3106 let return_ = self.print(return_); 3107 "fun((" 3108 .to_doc() 3109 .append(args) 3110 .append(") -> ") 3111 .append(return_) 3112 .append(")") 3113 } 3114 3115 /// Print type vars as `any()`. 3116 fn var_as_any(mut self) -> Self { 3117 self.var_as_any = true; 3118 self 3119 } 3120} 3121 3122fn find_private_functions_referenced_in_importable_constants( 3123 module: &TypedModule, 3124) -> im::HashSet<EcoString> { 3125 let mut overridden_publicity = im::HashSet::new(); 3126 3127 for definition in module.definitions.iter() { 3128 if let Definition::ModuleConstant(constant) = definition { 3129 if constant.publicity.is_importable() { 3130 find_referenced_private_functions(&constant.value, &mut overridden_publicity) 3131 } 3132 } 3133 } 3134 overridden_publicity 3135} 3136 3137fn find_referenced_private_functions( 3138 constant: &TypedConstant, 3139 already_found: &mut im::HashSet<EcoString>, 3140) { 3141 match constant { 3142 Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 3143 3144 Constant::Int { .. } 3145 | Constant::Float { .. } 3146 | Constant::String { .. } 3147 | Constant::BitArray { .. } => (), 3148 3149 TypedConstant::Var { 3150 name, constructor, .. 3151 } => { 3152 if let Some(ValueConstructor { type_, .. }) = constructor.as_deref() { 3153 if let Type::Fn { .. } = **type_ { 3154 let _ = already_found.insert(name.clone()); 3155 } 3156 } 3157 } 3158 3159 TypedConstant::Record { args, .. } => args 3160 .iter() 3161 .for_each(|arg| find_referenced_private_functions(&arg.value, already_found)), 3162 3163 TypedConstant::StringConcatenation { left, right, .. } => { 3164 find_referenced_private_functions(left, already_found); 3165 find_referenced_private_functions(right, already_found); 3166 } 3167 3168 Constant::Tuple { elements, .. } | Constant::List { elements, .. } => elements 3169 .iter() 3170 .for_each(|element| find_referenced_private_functions(element, already_found)), 3171 } 3172}