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