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