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 / format.rs
133 kB 3710 lines
1#[cfg(test)] 2mod tests; 3 4use crate::{ 5 Error, Result, 6 ast::{ 7 CustomType, Import, ModuleConstant, TypeAlias, TypeAstConstructor, TypeAstFn, TypeAstHole, 8 TypeAstTuple, TypeAstVar, *, 9 }, 10 build::Target, 11 docvec, 12 io::Utf8Writer, 13 parse::extra::{Comment, ModuleExtra}, 14 pretty::{self, *}, 15 warning::WarningEmitter, 16}; 17use ecow::{EcoString, eco_format}; 18use itertools::Itertools; 19use std::cmp::Ordering; 20use vec1::Vec1; 21 22use crate::type_::Deprecation; 23use camino::Utf8Path; 24 25const INDENT: isize = 2; 26 27pub fn pretty(writer: &mut impl Utf8Writer, src: &EcoString, path: &Utf8Path) -> Result<()> { 28 let parsed = crate::parse::parse_module(path.to_owned(), src, &WarningEmitter::null()) 29 .map_err(|error| Error::Parse { 30 path: path.to_path_buf(), 31 src: src.clone(), 32 error: Box::new(error), 33 })?; 34 let intermediate = Intermediate::from_extra(&parsed.extra, src); 35 Formatter::with_comments(&intermediate) 36 .module(&parsed.module) 37 .pretty_print(80, writer) 38} 39 40pub(crate) struct Intermediate<'a> { 41 comments: Vec<Comment<'a>>, 42 doc_comments: Vec<Comment<'a>>, 43 module_comments: Vec<Comment<'a>>, 44 empty_lines: &'a [u32], 45 new_lines: &'a [u32], 46 trailing_commas: &'a [u32], 47} 48 49impl<'a> Intermediate<'a> { 50 pub fn from_extra(extra: &'a ModuleExtra, src: &'a EcoString) -> Intermediate<'a> { 51 Intermediate { 52 comments: extra 53 .comments 54 .iter() 55 .map(|span| Comment::from((span, src))) 56 .collect(), 57 doc_comments: extra 58 .doc_comments 59 .iter() 60 .map(|span| Comment::from((span, src))) 61 .collect(), 62 empty_lines: &extra.empty_lines, 63 module_comments: extra 64 .module_comments 65 .iter() 66 .map(|span| Comment::from((span, src))) 67 .collect(), 68 new_lines: &extra.new_lines, 69 trailing_commas: &extra.trailing_commas, 70 } 71 } 72} 73 74#[derive(Debug)] 75enum FnCapturePosition { 76 RightHandSideOfPipe, 77 EverywhereElse, 78} 79 80#[derive(Debug)] 81/// One of the pieces making a record update arg list: it could be the starting 82/// record being updated, or one of the subsequent arguments. 83/// 84enum RecordUpdatePiece<'a, A> { 85 Record(&'a RecordBeingUpdated<A>), 86 Argument(&'a RecordUpdateArg<A>), 87} 88 89impl<A> HasLocation for RecordUpdatePiece<'_, A> { 90 fn location(&self) -> SrcSpan { 91 match self { 92 RecordUpdatePiece::Record(record) => record.location, 93 RecordUpdatePiece::Argument(arg) => arg.location, 94 } 95 } 96} 97 98type UntypedRecordUpdatePiece<'a> = RecordUpdatePiece<'a, UntypedExpr>; 99 100/// Hayleigh's bane 101#[derive(Debug, Clone, Default)] 102pub struct Formatter<'a> { 103 comments: &'a [Comment<'a>], 104 doc_comments: &'a [Comment<'a>], 105 module_comments: &'a [Comment<'a>], 106 empty_lines: &'a [u32], 107 new_lines: &'a [u32], 108 trailing_commas: &'a [u32], 109} 110 111impl<'comments> Formatter<'comments> { 112 pub fn new() -> Self { 113 Default::default() 114 } 115 116 pub(crate) fn with_comments(extra: &'comments Intermediate<'comments>) -> Self { 117 Self { 118 comments: &extra.comments, 119 doc_comments: &extra.doc_comments, 120 module_comments: &extra.module_comments, 121 empty_lines: extra.empty_lines, 122 new_lines: extra.new_lines, 123 trailing_commas: extra.trailing_commas, 124 } 125 } 126 127 /// Returns true if there's any comment that comes before the given 128 /// position. 129 /// 130 fn any_comments(&self, limit: u32) -> bool { 131 self.comments 132 .first() 133 .is_some_and(|comment| comment.start < limit) 134 } 135 136 /// Returns true if there's any comment that appears inside the given span. 137 /// 138 fn any_comment_between(&self, start: u32, end: u32) -> bool { 139 self.comments 140 .binary_search_by(|comment| { 141 if comment.start < start { 142 Ordering::Less 143 } else if comment.start > end { 144 Ordering::Greater 145 } else { 146 Ordering::Equal 147 } 148 }) 149 .is_ok() 150 } 151 152 fn any_empty_lines(&self, limit: u32) -> bool { 153 self.empty_lines.first().is_some_and(|line| *line < limit) 154 } 155 156 /// Pop comments that occur before a byte-index in the source, consuming 157 /// and retaining any empty lines contained within. 158 /// Returns an iterator of comments with their start position. 159 fn pop_comments_with_position( 160 &mut self, 161 limit: u32, 162 ) -> impl Iterator<Item = (u32, Option<&'comments str>)> + use<'comments> { 163 let (popped, rest, empty_lines) = 164 comments_before(self.comments, self.empty_lines, limit, true); 165 self.comments = rest; 166 self.empty_lines = empty_lines; 167 popped 168 } 169 170 /// Pop comments that occur before a byte-index in the source, consuming 171 /// and retaining any empty lines contained within. 172 fn pop_comments( 173 &mut self, 174 limit: u32, 175 ) -> impl Iterator<Item = Option<&'comments str>> + use<'comments> { 176 self.pop_comments_with_position(limit) 177 .map(|(_position, comment)| comment) 178 } 179 180 /// Pop doc comments that occur before a byte-index in the source, consuming 181 /// and dropping any empty lines contained within. 182 fn pop_doc_comments( 183 &mut self, 184 limit: u32, 185 ) -> impl Iterator<Item = Option<&'comments str>> + use<'comments> { 186 let (popped, rest, empty_lines) = 187 comments_before(self.doc_comments, self.empty_lines, limit, false); 188 self.doc_comments = rest; 189 self.empty_lines = empty_lines; 190 popped.map(|(_position, comment)| comment) 191 } 192 193 /// Remove between 0 and `limit` empty lines following the current position, 194 /// returning true if any empty lines were removed. 195 fn pop_empty_lines(&mut self, limit: u32) -> bool { 196 let mut end = 0; 197 for (i, &position) in self.empty_lines.iter().enumerate() { 198 if position > limit { 199 break; 200 } 201 end = i + 1; 202 } 203 204 self.empty_lines = self 205 .empty_lines 206 .get(end..) 207 .expect("Pop empty lines slicing"); 208 end != 0 209 } 210 211 fn targeted_definition<'a>(&mut self, definition: &'a TargetedDefinition) -> Document<'a> { 212 let target = definition.target; 213 let definition = &definition.definition; 214 let start = definition.location().start; 215 216 let comments = self.pop_comments_with_position(start); 217 let comments = self.printed_documented_comments(comments); 218 let document = self.documented_definition(definition); 219 let document = match target { 220 None => document, 221 Some(Target::Erlang) => docvec!["@target(erlang)", line(), document], 222 Some(Target::JavaScript) => docvec!["@target(javascript)", line(), document], 223 }; 224 225 comments.to_doc().append(document.group()) 226 } 227 228 pub(crate) fn module<'a>(&mut self, module: &'a UntypedModule) -> Document<'a> { 229 let mut documents = vec![]; 230 let mut previous_was_a_definition = false; 231 232 // Here we take consecutive groups of imports so that they can be sorted 233 // alphabetically. 234 for (is_import_group, definitions) in &module 235 .definitions 236 .iter() 237 .chunk_by(|definition| definition.definition.is_import()) 238 { 239 if is_import_group { 240 if previous_was_a_definition { 241 documents.push(lines(2)); 242 } 243 documents.append(&mut self.imports(definitions.collect_vec())); 244 previous_was_a_definition = false; 245 } else { 246 for definition in definitions { 247 if !documents.is_empty() { 248 documents.push(lines(2)); 249 } 250 documents.push(self.targeted_definition(definition)); 251 } 252 previous_was_a_definition = true; 253 } 254 } 255 256 let definitions = concat(documents); 257 258 // Now that definitions has been collected, only freestanding comments (//) 259 // and doc comments (///) remain. Freestanding comments aren't associated 260 // with any statement, and are moved to the bottom of the module. 261 let doc_comments = join( 262 self.doc_comments 263 .iter() 264 .map(|comment| "///".to_doc().append(EcoString::from(comment.content))), 265 line(), 266 ); 267 268 let comments = match printed_comments(self.pop_comments(u32::MAX), false) { 269 Some(comments) => comments, 270 None => nil(), 271 }; 272 273 let module_comments = if !self.module_comments.is_empty() { 274 let comments = self 275 .module_comments 276 .iter() 277 .map(|s| "////".to_doc().append(EcoString::from(s.content))); 278 join(comments, line()).append(line()) 279 } else { 280 nil() 281 }; 282 283 let non_empty = vec![module_comments, definitions, doc_comments, comments] 284 .into_iter() 285 .filter(|doc| !doc.is_empty()); 286 287 join(non_empty, line()).append(line()) 288 } 289 290 /// Separates the imports in groups delimited by comments or empty lines and 291 /// sorts each group alphabetically. 292 /// 293 /// The formatter needs to play nicely with import groups defined by the 294 /// programmer. If one puts a comment before an import then that's a clue 295 /// for the formatter that it has run into a gorup of related imports. 296 /// 297 /// So we can't just sort `imports` and format each one, we have to be a 298 /// bit smarter and see if each import is preceded by a comment. 299 /// Once we find a comment we know we're done with the current import 300 /// group and a new one has started. 301 /// 302 /// ```gleam 303 /// // This is an import group. 304 /// import gleam/int 305 /// import gleam/string 306 /// 307 /// // This marks the beginning of a new import group that can't 308 /// // be mushed together with the previous one! 309 /// import wibble 310 /// import wobble 311 /// ``` 312 fn imports<'a>(&mut self, imports: Vec<&'a TargetedDefinition>) -> Vec<Document<'a>> { 313 let mut import_groups_docs = vec![]; 314 let mut current_group = vec![]; 315 let mut current_group_delimiter = nil(); 316 317 for import in imports { 318 let start = import.definition.location().start; 319 320 // We need to start a new group if the `import` is preceded by one or 321 // more empty lines or a `//` comment. 322 let start_new_group = self.any_comments(start) || self.any_empty_lines(start); 323 if start_new_group { 324 // First we print the previous group and clear it out to start a 325 // new empty group containing the import we've just ran into. 326 if !current_group.is_empty() { 327 import_groups_docs.push(docvec![ 328 current_group_delimiter, 329 self.sorted_import_group(&current_group) 330 ]); 331 current_group.clear(); 332 } 333 334 // Now that we've taken care of the previous group we can start 335 // the new one. We know it's preceded either by an empty line or 336 // some comments se we have to be a bit more precise and save the 337 // actual delimiter that we're going to put at the top of this 338 // group. 339 340 let comments = self.pop_comments(start); 341 let _ = self.pop_empty_lines(start); 342 current_group_delimiter = printed_comments(comments, true).unwrap_or(nil()); 343 } 344 // Lastly we add the import to the group. 345 current_group.push(import); 346 } 347 348 // Let's not forget about the last import group! 349 if !current_group.is_empty() { 350 import_groups_docs.push(docvec![ 351 current_group_delimiter, 352 self.sorted_import_group(&current_group) 353 ]); 354 } 355 356 // We want all consecutive import groups to be separated by an empty line. 357 // This should really be `.intersperse(line())` but I can't do that 358 // because of https://github.com/rust-lang/rust/issues/48919. 359 Itertools::intersperse(import_groups_docs.into_iter(), lines(2)).collect_vec() 360 } 361 362 /// Prints the imports as a single sorted group of import statements. 363 /// 364 fn sorted_import_group<'a>(&mut self, imports: &[&'a TargetedDefinition]) -> Document<'a> { 365 let imports = imports 366 .iter() 367 .sorted_by(|one, other| match (&one.definition, &other.definition) { 368 (Definition::Import(one), Definition::Import(other)) => { 369 one.module.cmp(&other.module) 370 } 371 // It shouldn't really be possible for a non import to be here so 372 // we just return a default value. 373 _ => Ordering::Equal, 374 }) 375 .map(|import| self.targeted_definition(import)); 376 377 // This should really be `.intersperse(line())` but I can't do that 378 // because of https://github.com/rust-lang/rust/issues/48919. 379 Itertools::intersperse(imports, line()) 380 .collect_vec() 381 .to_doc() 382 } 383 384 fn definition<'a>(&mut self, statement: &'a UntypedDefinition) -> Document<'a> { 385 match statement { 386 Definition::Function(function) => self.statement_fn(function), 387 388 Definition::TypeAlias(alias) => self.type_alias(alias), 389 390 Definition::CustomType(ct) => self.custom_type(ct), 391 392 Definition::Import(Import { 393 module, 394 as_name, 395 unqualified_values, 396 unqualified_types, 397 .. 398 }) => { 399 let second = if unqualified_values.is_empty() && unqualified_types.is_empty() { 400 nil() 401 } else { 402 let unqualified_types = unqualified_types 403 .iter() 404 .sorted_by(|a, b| a.name.cmp(&b.name)) 405 .map(|type_| docvec!["type ", type_]); 406 let unqualified_values = unqualified_values 407 .iter() 408 .sorted_by(|a, b| a.name.cmp(&b.name)) 409 .map(|value| value.to_doc()); 410 let unqualified = join( 411 unqualified_types.chain(unqualified_values), 412 flex_break(",", ", "), 413 ); 414 let unqualified = break_("", "") 415 .append(unqualified) 416 .nest(INDENT) 417 .append(break_(",", "")) 418 .group(); 419 ".{".to_doc().append(unqualified).append("}") 420 }; 421 422 let doc = docvec!["import ", module.as_str(), second]; 423 let default_module_access_name = module.split('/').next_back().map(EcoString::from); 424 match (default_module_access_name, as_name) { 425 // If the `as name` is the same as the module name that would be 426 // used anyways we won't render it. For example: 427 // ```gleam 428 // import gleam/int as int 429 // ^^^^^^ this is redundant and removed 430 // ``` 431 (Some(module_name), Some((AssignName::Variable(name), _))) 432 if &module_name == name => 433 { 434 doc 435 } 436 (_, None) => doc, 437 (_, Some((AssignName::Variable(name) | AssignName::Discard(name), _))) => { 438 doc.append(" as ").append(name) 439 } 440 } 441 } 442 443 Definition::ModuleConstant(ModuleConstant { 444 publicity, 445 name, 446 annotation, 447 value, 448 deprecation, 449 documentation: _, 450 location: _, 451 name_location: _, 452 type_: _, 453 implementations: _, 454 }) => { 455 let attributes = AttributesPrinter::new() 456 .set_internal(*publicity) 457 .set_deprecation(deprecation) 458 .to_doc(); 459 let head = attributes 460 .append(pub_(*publicity)) 461 .append("const ") 462 .append(name.as_str()); 463 let head = match annotation { 464 None => head, 465 Some(t) => head.append(": ").append(self.type_ast(t)), 466 }; 467 head.append(" = ").append(self.const_expr(value).group()) 468 } 469 } 470 } 471 472 fn const_expr<'a, A, B>(&mut self, value: &'a Constant<A, B>) -> Document<'a> { 473 let comments = self.pop_comments(value.location().start); 474 let document = match value { 475 Constant::Int { value, .. } => self.int(value), 476 477 Constant::Float { value, .. } => self.float(value), 478 479 Constant::String { value, .. } => self.string(value), 480 481 Constant::List { 482 elements, 483 location, 484 tail, 485 .. 486 } => self.const_list(elements, location, tail), 487 488 Constant::Tuple { 489 elements, location, .. 490 } => self.const_tuple(elements, location), 491 492 Constant::BitArray { 493 segments, location, .. 494 } => { 495 let segment_docs = segments 496 .iter() 497 .map(|segment| bit_array_segment(segment, |e| self.const_expr(e))) 498 .collect_vec(); 499 500 let packing = self.items_sequence_packing( 501 segments, 502 None, 503 |segment| segment.value.can_have_multiple_per_line(), 504 *location, 505 ); 506 507 self.bit_array(segment_docs, packing, location) 508 } 509 510 Constant::Record { 511 name, 512 arguments, 513 module: None, 514 .. 515 } if arguments.is_empty() => name.to_doc(), 516 517 Constant::Record { 518 name, 519 arguments, 520 module: Some((m, _)), 521 .. 522 } if arguments.is_empty() => m.to_doc().append(".").append(name.as_str()), 523 524 Constant::Record { 525 name, 526 arguments, 527 module: None, 528 location, 529 .. 530 } => { 531 let arguments = arguments 532 .iter() 533 .map(|argument| self.constant_call_arg(argument)) 534 .collect_vec(); 535 name.to_doc() 536 .append(self.wrap_arguments(arguments, location.end)) 537 .group() 538 } 539 540 Constant::Record { 541 name, 542 arguments, 543 module: Some((m, _)), 544 location, 545 .. 546 } => { 547 let arguments = arguments 548 .iter() 549 .map(|argument| self.constant_call_arg(argument)) 550 .collect_vec(); 551 m.to_doc() 552 .append(".") 553 .append(name.as_str()) 554 .append(self.wrap_arguments(arguments, location.end)) 555 .group() 556 } 557 558 Constant::Var { 559 name, module: None, .. 560 } => name.to_doc(), 561 562 Constant::Var { 563 name, 564 module: Some((module, _)), 565 .. 566 } => docvec![module, ".", name], 567 568 Constant::StringConcatenation { left, right, .. } => self 569 .const_expr(left) 570 .append(break_("", " ").append("<>".to_doc())) 571 .nest(INDENT) 572 .append(" ") 573 .append(self.const_expr(right)), 574 575 Constant::RecordUpdate { 576 module, 577 name, 578 record, 579 arguments, 580 location, 581 .. 582 } => self.const_record_update(module, name, record, arguments, location), 583 584 Constant::Invalid { .. } => panic!("invalid constants can not be in an untyped ast"), 585 }; 586 commented(document, comments) 587 } 588 589 fn const_list<'a, A, B>( 590 &mut self, 591 elements: &'a [Constant<A, B>], 592 location: &SrcSpan, 593 tail: &'a Option<Box<Constant<A, B>>>, 594 ) -> Document<'a> { 595 if elements.is_empty() { 596 // We take all comments that come _before_ the end of the list, 597 // that is all comments that are inside "[" and "]", if there's 598 // any comment we want to put it inside the empty list! 599 return match printed_comments(self.pop_comments(location.end), false) { 600 None => "[]".to_doc(), 601 Some(comments) => "[" 602 .to_doc() 603 .append(break_("", "").nest(INDENT)) 604 .append(comments) 605 .append(break_("", "")) 606 .append("]") 607 // vvv We want to make sure the comments are on a separate 608 // line from the opening and closing brackets so we 609 // force the breaks to be split on newlines. 610 .force_break(), 611 }; 612 } 613 614 let list_packing = self.items_sequence_packing( 615 elements, 616 tail.as_deref(), 617 |element| element.can_have_multiple_per_line(), 618 *location, 619 ); 620 let comma = match list_packing { 621 ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), 622 ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), 623 }; 624 625 let mut elements_doc = nil(); 626 for element in elements.iter() { 627 let empty_lines = self.pop_empty_lines(element.location().start); 628 let element_doc = self.const_expr(element); 629 630 elements_doc = if elements_doc.is_empty() { 631 element_doc 632 } else if empty_lines { 633 // If there's empty lines before the list item we want to add an 634 // empty line here. Notice how we're making sure no nesting is 635 // added after the comma, otherwise we would be adding needless 636 // whitespace in the empty line! 637 docvec![ 638 elements_doc, 639 comma.clone().set_nesting(0), 640 line(), 641 element_doc 642 ] 643 } else { 644 docvec![elements_doc, comma.clone(), element_doc] 645 }; 646 } 647 elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); 648 649 let doc = break_("[", "[").append(elements_doc); 650 let (doc, final_break) = match tail { 651 None => (doc.nest(INDENT), break_(",", "")), 652 Some(tail) => { 653 let comments = self.pop_comments(tail.location().start); 654 655 let tail = commented(docvec!["..", self.const_expr(tail)], comments); 656 ( 657 doc.append(break_(",", ", ")).append(tail).nest(INDENT), 658 break_("", ""), 659 ) 660 } 661 }; 662 663 // We get all remaining comments that come before the list's closing 664 // square bracket. 665 // If there's any we add those before the closing square bracket instead 666 // of moving those out of the list. 667 // Otherwise those would be moved out of the list. 668 let comments = self.pop_comments(location.end); 669 let doc = match printed_comments(comments, false) { 670 None => doc.append(final_break).append("]"), 671 Some(comment) => doc 672 .append(final_break.nest(INDENT)) 673 // ^ See how here we're adding the missing indentation to the 674 // final break so that the final comment is as indented as the 675 // list's items. 676 .append(comment) 677 .append(line()) 678 .append("]") 679 .force_break(), 680 }; 681 682 match list_packing { 683 ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), 684 ItemsPacking::BreakOnePerLine => doc.force_break(), 685 } 686 } 687 688 pub fn const_tuple<'a, A, B>( 689 &mut self, 690 elements: &'a [Constant<A, B>], 691 location: &SrcSpan, 692 ) -> Document<'a> { 693 if elements.is_empty() { 694 // We take all comments that come _before_ the end of the tuple, 695 // that is all comments that are inside "#(" and ")", if there's 696 // any comment we want to put it inside the empty list! 697 return match printed_comments(self.pop_comments(location.end), false) { 698 None => "#()".to_doc(), 699 Some(comments) => "#(" 700 .to_doc() 701 .append(break_("", "").nest(INDENT)) 702 .append(comments) 703 .append(break_("", "")) 704 .append(")") 705 // vvv We want to make sure the comments are on a separate 706 // line from the opening and closing parentheses so we 707 // force the breaks to be split on newlines. 708 .force_break(), 709 }; 710 } 711 712 let arguments_docs = elements.iter().map(|element| self.const_expr(element)); 713 let tuple_doc = break_("#(", "#(") 714 .append( 715 join(arguments_docs, break_(",", ", ")) 716 .next_break_fits(NextBreakFitsMode::Disabled), 717 ) 718 .nest(INDENT); 719 720 let comments = self.pop_comments(location.end); 721 match printed_comments(comments, false) { 722 None => tuple_doc.append(break_(",", "")).append(")").group(), 723 Some(comments) => tuple_doc 724 .append(break_(",", "").nest(INDENT)) 725 .append(comments) 726 .append(line()) 727 .append(")") 728 .force_break(), 729 } 730 } 731 732 fn documented_definition<'a>(&mut self, s: &'a UntypedDefinition) -> Document<'a> { 733 let comments = self.doc_comments(s.location().start); 734 comments.append(self.definition(s).group()).group() 735 } 736 737 fn doc_comments<'a>(&mut self, limit: u32) -> Document<'a> { 738 let mut comments = self.pop_doc_comments(limit).peekable(); 739 match comments.peek() { 740 None => nil(), 741 Some(_) => join( 742 comments.map(|c| match c { 743 Some(c) => "///".to_doc().append(EcoString::from(c)), 744 None => unreachable!("empty lines dropped by pop_doc_comments"), 745 }), 746 line(), 747 ) 748 .append(line()) 749 .force_break(), 750 } 751 } 752 753 fn type_ast_constructor<'a>( 754 &mut self, 755 name: &'a TypeAstConstructorName, 756 arguments: &'a [TypeAst], 757 location: &SrcSpan, 758 ) -> Document<'a> { 759 let head = match name { 760 TypeAstConstructorName::Unqualified { name, .. } => name.to_doc(), 761 TypeAstConstructorName::Qualified { module, name, .. } => { 762 module.to_doc().append(".").append( 763 name.as_ref() 764 .map_or(nil(), |(name, _name_location)| name.to_doc()), 765 ) 766 } 767 }; 768 769 if arguments.is_empty() { 770 head 771 } else { 772 head.append(self.type_arguments(arguments, location)) 773 } 774 } 775 776 fn type_ast<'a>(&mut self, type_: &'a TypeAst) -> Document<'a> { 777 let comments = self.pop_comments(type_.location().start); 778 779 let type_ = match type_ { 780 TypeAst::Hole(TypeAstHole { name, .. }) => name.to_doc(), 781 782 TypeAst::Constructor(TypeAstConstructor { 783 name, 784 arguments, 785 location, 786 start_parentheses: _, 787 }) => self.type_ast_constructor(name, arguments, location), 788 789 TypeAst::Fn(TypeAstFn { 790 arguments, 791 return_, 792 location, 793 }) => "fn" 794 .to_doc() 795 .append(self.type_arguments(arguments, location)) 796 .group() 797 .append(" ->") 798 .append( 799 break_("", " ") 800 .append(self.type_ast(return_)) 801 .group() 802 .nest(INDENT), 803 ), 804 805 TypeAst::Var(TypeAstVar { name, .. }) => name.to_doc(), 806 807 TypeAst::Tuple(TypeAstTuple { elements, location }) => { 808 "#".to_doc().append(self.type_arguments(elements, location)) 809 } 810 }; 811 812 commented(type_.group(), comments) 813 } 814 815 fn type_arguments<'a>(&mut self, arguments: &'a [TypeAst], location: &SrcSpan) -> Document<'a> { 816 let arguments = arguments 817 .iter() 818 .map(|type_| self.type_ast(type_)) 819 .collect_vec(); 820 self.wrap_arguments(arguments, location.end) 821 } 822 823 pub fn type_alias<'a, A>(&mut self, alias: &'a TypeAlias<A>) -> Document<'a> { 824 let TypeAlias { 825 alias: name, 826 parameters: arguments, 827 type_ast: type_, 828 publicity, 829 deprecation, 830 location, 831 name_location: _, 832 type_: _, 833 documentation: _, 834 } = alias; 835 836 let attributes = AttributesPrinter::new() 837 .set_deprecation(deprecation) 838 .set_internal(*publicity) 839 .to_doc(); 840 841 let head = docvec![attributes, pub_(*publicity), "type ", name]; 842 let head = if arguments.is_empty() { 843 head 844 } else { 845 let arguments = arguments.iter().map(|(_, e)| e.to_doc()).collect_vec(); 846 head.append(self.wrap_arguments(arguments, location.end).group()) 847 }; 848 849 head.append(" =") 850 .append(line().append(self.type_ast(type_)).group().nest(INDENT)) 851 } 852 853 fn fn_arg<'a, A>(&mut self, argument: &'a Arg<A>) -> Document<'a> { 854 let comments = self.pop_comments(argument.location.start); 855 let doc = match &argument.annotation { 856 None => argument.names.to_doc(), 857 Some(a) => argument 858 .names 859 .to_doc() 860 .append(": ") 861 .append(self.type_ast(a)), 862 } 863 .group(); 864 commented(doc, comments) 865 } 866 867 fn statement_fn<'a>(&mut self, function: &'a UntypedFunction) -> Document<'a> { 868 let Function { 869 location, 870 body_start: _, 871 end_position, 872 name, 873 arguments, 874 body, 875 publicity, 876 deprecation, 877 return_annotation, 878 return_type: _, 879 documentation: _, 880 external_erlang, 881 external_javascript, 882 implementations: _, 883 purity: _, 884 } = function; 885 886 let attributes = AttributesPrinter::new() 887 .set_deprecation(deprecation) 888 .set_internal(*publicity) 889 .set_external_erlang(external_erlang) 890 .set_external_javascript(external_javascript) 891 .to_doc(); 892 893 // Fn name and args 894 let arguments = arguments 895 .iter() 896 .map(|argument| self.fn_arg(argument)) 897 .collect_vec(); 898 let signature = pub_(*publicity) 899 .append("fn ") 900 .append( 901 &name 902 .as_ref() 903 .expect("Function in a statement must be named") 904 .1, 905 ) 906 .append( 907 self.wrap_arguments( 908 arguments, 909 // Calculate end location of arguments to not consume comments in 910 // return annotation 911 return_annotation 912 .as_ref() 913 .map_or(location.end, |ann| ann.location().start), 914 ), 915 ); 916 917 // Add return annotation 918 let signature = match &return_annotation { 919 Some(anno) => signature.append(" -> ").append(self.type_ast(anno)), 920 None => signature, 921 } 922 .group(); 923 924 if body.is_empty() { 925 return attributes.append(signature); 926 } 927 928 let head = attributes.append(signature); 929 930 // Format body 931 932 let body = self.statements(body); 933 934 // Add any trailing comments 935 let body = match printed_comments(self.pop_comments(*end_position), false) { 936 Some(comments) => body.append(line()).append(comments), 937 None => body, 938 }; 939 940 // Stick it all together 941 head.append(" {") 942 .append(line().append(body).nest(INDENT).group()) 943 .append(line()) 944 .append("}") 945 } 946 947 fn expr_fn<'a>( 948 &mut self, 949 arguments: &'a [UntypedArg], 950 return_annotation: Option<&'a TypeAst>, 951 body: &'a Vec1<UntypedStatement>, 952 location: &SrcSpan, 953 end_of_head_byte_index: &u32, 954 ) -> Document<'a> { 955 let arguments_docs = arguments 956 .iter() 957 .map(|argument| self.fn_arg(argument)) 958 .collect_vec(); 959 let arguments = self 960 .wrap_arguments(arguments_docs, *end_of_head_byte_index) 961 .group() 962 .next_break_fits(NextBreakFitsMode::Disabled); 963 // ^^^ We add this so that when an expression function is passed as 964 // the last argument of a function and it goes over the line 965 // limit with just its arguments we don't get some strange 966 // splitting. 967 // See https://github.com/gleam-lang/gleam/issues/2571 968 // 969 // There's many ways we could be smarter than this. For example: 970 // - still split the arguments like it did in the example shown in the 971 // issue if the expr_fn has more than one argument 972 // - make sure that an anonymous function whose body is made up of a 973 // single expression doesn't get split (I think that could boil down 974 // to wrapping the body with a `next_break_fits(Disabled)`) 975 // 976 // These are some of the ways we could tweak the look of expression 977 // functions in the future if people are not satisfied with it. 978 979 let header = "fn".to_doc().append(arguments); 980 981 let header = match return_annotation { 982 None => header, 983 Some(t) => header.append(" -> ").append(self.type_ast(t)), 984 }; 985 986 let statements = self.statements(body.as_vec()); 987 let body = match printed_comments(self.pop_comments(location.end), false) { 988 None => statements, 989 Some(comments) => statements.append(line()).append(comments).force_break(), 990 }; 991 992 header.append(" ").append(wrap_block(body)).group() 993 } 994 995 fn statements<'a>(&mut self, statements: &'a [UntypedStatement]) -> Document<'a> { 996 let mut previous_position = 0; 997 let count = statements.len(); 998 let mut documents = Vec::with_capacity(count * 2); 999 for (i, statement) in statements.iter().enumerate() { 1000 let preceding_newline = self.pop_empty_lines(previous_position + 1); 1001 if i != 0 && preceding_newline { 1002 documents.push(lines(2)); 1003 } else if i != 0 { 1004 documents.push(line()); 1005 } 1006 previous_position = statement.location().end; 1007 documents.push(self.statement(statement).group()); 1008 1009 // If the last statement is a use we make sure it's followed by a 1010 // todo to make it explicit it has an unimplemented callback. 1011 if statement.is_use() && i == count - 1 { 1012 documents.push(line()); 1013 documents.push("todo".to_doc()); 1014 } 1015 } 1016 1017 if count == 1 1018 && statements 1019 .first() 1020 .is_some_and(|statement| statement.is_expression()) 1021 { 1022 documents.to_doc() 1023 } else { 1024 documents.to_doc().force_break() 1025 } 1026 } 1027 1028 fn assignment<'a>(&mut self, assignment: &'a UntypedAssignment) -> Document<'a> { 1029 let comments = self.pop_comments(assignment.location.start); 1030 let Assignment { 1031 pattern, 1032 value, 1033 kind, 1034 annotation, 1035 .. 1036 } = assignment; 1037 1038 let _ = self.pop_empty_lines(pattern.location().end); 1039 1040 let (keyword, message) = match kind { 1041 AssignmentKind::Let | AssignmentKind::Generated => ("let ", None), 1042 AssignmentKind::Assert { message, .. } => ("let assert ", message.as_ref()), 1043 }; 1044 1045 let pattern = self.pattern(pattern); 1046 1047 let annotation = annotation 1048 .as_ref() 1049 .map(|a| ": ".to_doc().append(self.type_ast(a))); 1050 1051 let doc = keyword 1052 .to_doc() 1053 .append(pattern.append(annotation).group()) 1054 .append(" =") 1055 .append(self.assigned_value(value)); 1056 1057 commented( 1058 self.append_as_message(doc, PrecedingAs::Expression, message), 1059 comments, 1060 ) 1061 } 1062 1063 fn expr<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 1064 let comments = self.pop_comments(expression.start_byte_index()); 1065 1066 let document = match expression { 1067 UntypedExpr::Panic { message, .. } => { 1068 self.append_as_message("panic".to_doc(), PrecedingAs::Keyword, message.as_deref()) 1069 } 1070 1071 UntypedExpr::Todo { message, .. } => { 1072 self.append_as_message("todo".to_doc(), PrecedingAs::Keyword, message.as_deref()) 1073 } 1074 1075 UntypedExpr::Echo { 1076 expression, 1077 location: _, 1078 keyword_end: _, 1079 message, 1080 } => self.echo(expression, message), 1081 1082 UntypedExpr::PipeLine { expressions, .. } => self.pipeline(expressions, false), 1083 1084 UntypedExpr::Int { value, .. } => self.int(value), 1085 1086 UntypedExpr::Float { value, .. } => self.float(value), 1087 1088 UntypedExpr::String { value, .. } => self.string(value), 1089 1090 UntypedExpr::Block { 1091 statements, 1092 location, 1093 .. 1094 } => self.block(location, statements, false), 1095 1096 UntypedExpr::Var { name, .. } if name == CAPTURE_VARIABLE => "_".to_doc(), 1097 1098 UntypedExpr::Var { name, .. } => name.to_doc(), 1099 1100 UntypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 1101 1102 UntypedExpr::NegateInt { value, .. } => self.negate_int(value), 1103 1104 UntypedExpr::NegateBool { value, .. } => self.negate_bool(value), 1105 1106 UntypedExpr::Fn { kind, body, .. } if kind.is_capture() => { 1107 self.fn_capture(body, FnCapturePosition::EverywhereElse) 1108 } 1109 1110 UntypedExpr::Fn { 1111 return_annotation, 1112 arguments, 1113 body, 1114 location, 1115 end_of_head_byte_index, 1116 .. 1117 } => self.expr_fn( 1118 arguments, 1119 return_annotation.as_ref(), 1120 body, 1121 location, 1122 end_of_head_byte_index, 1123 ), 1124 1125 UntypedExpr::List { 1126 elements, 1127 tail, 1128 location, 1129 } => self.list(elements, tail.as_deref(), location), 1130 1131 UntypedExpr::Call { 1132 fun, 1133 arguments, 1134 location, 1135 .. 1136 } => self.call(fun, arguments, location), 1137 1138 UntypedExpr::BinOp { 1139 name, left, right, .. 1140 } => self.bin_op(name, left, right, false), 1141 1142 UntypedExpr::Case { 1143 subjects, 1144 clauses, 1145 location, 1146 } => self.case(subjects, clauses.as_deref().unwrap_or_default(), location), 1147 1148 UntypedExpr::FieldAccess { 1149 label, container, .. 1150 } => self.expr(container).append(".").append(label.as_str()), 1151 1152 UntypedExpr::Tuple { elements, location } => self.tuple(elements, location), 1153 1154 UntypedExpr::BitArray { 1155 segments, location, .. 1156 } => { 1157 let segment_docs = segments 1158 .iter() 1159 .map(|segment| bit_array_segment(segment, |e| self.bit_array_segment_expr(e))) 1160 .collect_vec(); 1161 1162 let packing = self.items_sequence_packing( 1163 segments, 1164 None, 1165 |segment| segment.value.can_have_multiple_per_line(), 1166 *location, 1167 ); 1168 1169 self.bit_array(segment_docs, packing, location) 1170 } 1171 UntypedExpr::RecordUpdate { 1172 constructor, 1173 record, 1174 arguments, 1175 location, 1176 .. 1177 } => self.record_update(constructor, record, arguments, location), 1178 }; 1179 commented(document, comments) 1180 } 1181 1182 fn string<'a>(&self, string: &'a EcoString) -> Document<'a> { 1183 let doc = string.to_doc().surround("\"", "\""); 1184 if string.contains('\n') { 1185 doc.force_break() 1186 } else { 1187 doc 1188 } 1189 } 1190 1191 fn bin_op_string<'a>(&self, string: &'a EcoString) -> Document<'a> { 1192 let lines = string.split('\n').collect_vec(); 1193 match lines.as_slice() { 1194 [] | [_] => string.to_doc().surround("\"", "\""), 1195 [first_line, lines @ ..] => { 1196 let mut doc = docvec!["\"", first_line]; 1197 for line in lines { 1198 doc = doc 1199 .append(pretty::line().set_nesting(0)) 1200 .append(line.to_doc()) 1201 } 1202 doc.append("\"".to_doc()).group() 1203 } 1204 } 1205 } 1206 1207 fn float<'a>(&self, value: &'a str) -> Document<'a> { 1208 // Create parts 1209 let mut parts = value.split('.'); 1210 let integer_part = parts.next().unwrap_or_default(); 1211 // floating point part 1212 let fp_part = parts.next().unwrap_or_default(); 1213 let integer_doc = self.underscore_integer_string(integer_part); 1214 let dot_doc = ".".to_doc(); 1215 1216 // Split fp_part into a regular fractional and maybe a scientific part 1217 let (fp_part_fractional, fp_part_scientific) = fp_part.split_at( 1218 fp_part 1219 .chars() 1220 .position(|ch| ch == 'e') 1221 .unwrap_or(fp_part.len()), 1222 ); 1223 1224 // Trim right any consequtive '0's 1225 let mut fp_part_fractional = fp_part_fractional.trim_end_matches('0').to_string(); 1226 // If there is no fractional part left, add a '0', thus that 1. becomes 1.0 etc. 1227 if fp_part_fractional.is_empty() { 1228 fp_part_fractional.push('0'); 1229 } 1230 let fp_doc = fp_part_fractional.chars().collect::<EcoString>(); 1231 1232 integer_doc 1233 .append(dot_doc) 1234 .append(fp_doc) 1235 .append(fp_part_scientific) 1236 } 1237 1238 fn int<'a>(&self, value: &'a str) -> Document<'a> { 1239 if value.starts_with("0x") || value.starts_with("0b") || value.starts_with("0o") { 1240 return value.to_doc(); 1241 } 1242 1243 self.underscore_integer_string(value) 1244 } 1245 1246 fn underscore_integer_string<'a>(&self, value: &'a str) -> Document<'a> { 1247 let underscore_ch = '_'; 1248 let minus_ch = '-'; 1249 1250 let len = value.len(); 1251 let underscore_ch_cnt = value.matches(underscore_ch).count(); 1252 let reformat_watershed = if value.starts_with(minus_ch) { 6 } else { 5 }; 1253 let insert_underscores = (len - underscore_ch_cnt) >= reformat_watershed; 1254 1255 let mut new_value = String::new(); 1256 let mut j = 0; 1257 for (i, ch) in value.chars().rev().enumerate() { 1258 if ch == '_' { 1259 continue; 1260 } 1261 1262 if insert_underscores && i != 0 && ch != minus_ch && i < len && j % 3 == 0 { 1263 new_value.push(underscore_ch); 1264 } 1265 new_value.push(ch); 1266 1267 j += 1; 1268 } 1269 1270 new_value.chars().rev().collect::<EcoString>().to_doc() 1271 } 1272 1273 fn pattern_constructor<'a>( 1274 &mut self, 1275 name: &'a str, 1276 arguments: &'a [CallArg<UntypedPattern>], 1277 module: &'a Option<(EcoString, SrcSpan)>, 1278 spread: Option<SrcSpan>, 1279 location: &SrcSpan, 1280 ) -> Document<'a> { 1281 fn is_breakable(expression: &UntypedPattern) -> bool { 1282 match expression { 1283 Pattern::Tuple { .. } | Pattern::List { .. } | Pattern::BitArray { .. } => true, 1284 Pattern::Constructor { arguments, .. } => !arguments.is_empty(), 1285 Pattern::Int { .. } 1286 | Pattern::Float { .. } 1287 | Pattern::String { .. } 1288 | Pattern::Variable { .. } 1289 | Pattern::BitArraySize(_) 1290 | Pattern::Assign { .. } 1291 | Pattern::Discard { .. } 1292 | Pattern::StringPrefix { .. } 1293 | Pattern::Invalid { .. } => false, 1294 } 1295 } 1296 1297 let name = match module { 1298 Some((m, _)) => m.to_doc().append(".").append(name), 1299 None => name.to_doc(), 1300 }; 1301 1302 if arguments.is_empty() && spread.is_some() { 1303 name.append("(..)") 1304 } else if arguments.is_empty() { 1305 name 1306 } else if spread.is_some() { 1307 let arguments = arguments 1308 .iter() 1309 .map(|argument| self.pattern_call_arg(argument)) 1310 .collect_vec(); 1311 name.append(self.wrap_arguments_with_spread(arguments, location.end)) 1312 .group() 1313 } else { 1314 match arguments { 1315 [argument] if is_breakable(&argument.value) => name 1316 .append("(") 1317 .append(self.pattern_call_arg(argument)) 1318 .append(")") 1319 .group(), 1320 1321 _ => { 1322 let arguments = arguments 1323 .iter() 1324 .map(|argument| self.pattern_call_arg(argument)) 1325 .collect_vec(); 1326 name.append(self.wrap_arguments(arguments, location.end)) 1327 .group() 1328 } 1329 } 1330 } 1331 } 1332 1333 fn call<'a>( 1334 &mut self, 1335 fun: &'a UntypedExpr, 1336 arguments: &'a [CallArg<UntypedExpr>], 1337 location: &SrcSpan, 1338 ) -> Document<'a> { 1339 let expression = match fun { 1340 UntypedExpr::PipeLine { .. } => break_block(self.expr(fun)), 1341 1342 UntypedExpr::BinOp { .. } 1343 | UntypedExpr::Int { .. } 1344 | UntypedExpr::Float { .. } 1345 | UntypedExpr::String { .. } 1346 | UntypedExpr::Block { .. } 1347 | UntypedExpr::Var { .. } 1348 | UntypedExpr::Fn { .. } 1349 | UntypedExpr::List { .. } 1350 | UntypedExpr::Call { .. } 1351 | UntypedExpr::Case { .. } 1352 | UntypedExpr::FieldAccess { .. } 1353 | UntypedExpr::Tuple { .. } 1354 | UntypedExpr::TupleIndex { .. } 1355 | UntypedExpr::Todo { .. } 1356 | UntypedExpr::Echo { .. } 1357 | UntypedExpr::Panic { .. } 1358 | UntypedExpr::BitArray { .. } 1359 | UntypedExpr::RecordUpdate { .. } 1360 | UntypedExpr::NegateBool { .. } 1361 | UntypedExpr::NegateInt { .. } => self.expr(fun), 1362 }; 1363 1364 let arity = arguments.len(); 1365 self.append_inlinable_wrapped_arguments( 1366 expression, 1367 arguments, 1368 location, 1369 |argument| is_breakable_argument(&argument.value, arguments.len()), 1370 |self_, argument| self_.call_arg(argument, arity), 1371 ) 1372 } 1373 1374 fn tuple<'a>(&mut self, elements: &'a [UntypedExpr], location: &SrcSpan) -> Document<'a> { 1375 if elements.is_empty() { 1376 // We take all comments that come _before_ the end of the tuple, 1377 // that is all comments that are inside "#(" and ")", if there's 1378 // any comment we want to put it inside the empty tuple! 1379 return match printed_comments(self.pop_comments(location.end), false) { 1380 None => "#()".to_doc(), 1381 Some(comments) => "#(" 1382 .to_doc() 1383 .append(break_("", "").nest(INDENT)) 1384 .append(comments) 1385 .append(break_("", "")) 1386 .append(")") 1387 // vvv We want to make sure the comments are on a separate 1388 // line from the opening and closing parentheses so we 1389 // force the breaks to be split on newlines. 1390 .force_break(), 1391 }; 1392 } 1393 1394 self.append_inlinable_wrapped_arguments( 1395 "#".to_doc(), 1396 elements, 1397 location, 1398 |expression| { 1399 !expression.is_tuple() && is_breakable_argument(expression, elements.len()) 1400 }, 1401 |self_, expression| self_.comma_separated_item(expression, elements.len()), 1402 ) 1403 } 1404 1405 // Appends to the given docs a comma-separated list of documents wrapped by 1406 // parentheses. If the last item of the argument list is splittable the 1407 // resulting document will try to first split that before splitting all the 1408 // other arguments. 1409 // This is used for function calls and tuples. 1410 fn append_inlinable_wrapped_arguments<'a, 'b, T, Predicate, ToDoc>( 1411 &mut self, 1412 doc: Document<'a>, 1413 values: &'b [T], 1414 location: &SrcSpan, 1415 is_breakable_argument: Predicate, 1416 to_doc: ToDoc, 1417 ) -> Document<'a> 1418 where 1419 T: HasLocation, 1420 T: std::fmt::Debug, 1421 Predicate: Fn(&T) -> bool, 1422 ToDoc: Fn(&mut Self, &'b T) -> Document<'a>, 1423 { 1424 match init_and_last(values) { 1425 Some((initial_values, last_value)) 1426 if is_breakable_argument(last_value) 1427 && !self.any_comments(last_value.location().start) 1428 && !self.any_comment_between(last_value.location().end, location.end) => 1429 { 1430 let mut docs = initial_values 1431 .iter() 1432 .map(|value| to_doc(self, value)) 1433 .collect_vec(); 1434 1435 let last_value_doc = to_doc(self, last_value) 1436 .group() 1437 .next_break_fits(NextBreakFitsMode::Enabled); 1438 1439 docs.append(&mut vec![last_value_doc]); 1440 1441 doc.append(self.wrap_function_call_arguments(docs, location)) 1442 .next_break_fits(NextBreakFitsMode::Disabled) 1443 .group() 1444 } 1445 1446 Some(_) | None => { 1447 let docs = values.iter().map(|value| to_doc(self, value)).collect_vec(); 1448 doc.append(self.wrap_function_call_arguments(docs, location)) 1449 .group() 1450 } 1451 } 1452 } 1453 1454 pub fn case<'a>( 1455 &mut self, 1456 subjects: &'a [UntypedExpr], 1457 clauses: &'a [UntypedClause], 1458 location: &'a SrcSpan, 1459 ) -> Document<'a> { 1460 let subjects_doc = break_("case", "case ") 1461 .append(join( 1462 subjects.iter().map(|s| self.expr(s).group()), 1463 break_(",", ", "), 1464 )) 1465 .nest(INDENT) 1466 .append(break_("", " ")) 1467 .append("{") 1468 .next_break_fits(NextBreakFitsMode::Disabled) 1469 .group(); 1470 1471 let clauses_doc = concat( 1472 clauses 1473 .iter() 1474 .enumerate() 1475 .map(|(i, c)| self.clause(c, i as u32).group()), 1476 ); 1477 1478 // We get all remaining comments that come before the case's closing 1479 // bracket. If there's any we add those before the closing bracket 1480 // instead of moving those out of the case expression. 1481 // Otherwise those would be moved out of the case expression. 1482 let comments = self.pop_comments(location.end); 1483 let closing_bracket = match printed_comments(comments, false) { 1484 None => docvec![line(), "}"], 1485 Some(comment) => docvec![line(), comment] 1486 .nest(INDENT) 1487 .append(line()) 1488 .append("}"), 1489 }; 1490 1491 subjects_doc 1492 .append(line().append(clauses_doc).nest(INDENT)) 1493 .append(closing_bracket) 1494 .force_break() 1495 } 1496 1497 pub fn record_update<'a>( 1498 &mut self, 1499 constructor: &'a UntypedExpr, 1500 record: &'a RecordBeingUpdated<UntypedExpr>, 1501 arguments: &'a [UntypedRecordUpdateArg], 1502 location: &SrcSpan, 1503 ) -> Document<'a> { 1504 let constructor_doc: Document<'a> = self.expr(constructor); 1505 let pieces = std::iter::once(UntypedRecordUpdatePiece::Record(record)) 1506 .chain(arguments.iter().map(UntypedRecordUpdatePiece::Argument)) 1507 .collect_vec(); 1508 1509 self.append_inlinable_wrapped_arguments( 1510 constructor_doc, 1511 &pieces, 1512 location, 1513 |argument| { 1514 let expression = match argument { 1515 UntypedRecordUpdatePiece::Argument(argument) => &argument.value, 1516 UntypedRecordUpdatePiece::Record(record) => &record.base, 1517 }; 1518 is_breakable_argument(expression, pieces.len()) 1519 }, 1520 |this, argument| match argument { 1521 UntypedRecordUpdatePiece::Argument(arg) => this.record_update_arg(arg), 1522 UntypedRecordUpdatePiece::Record(record) => { 1523 let comments = this.pop_comments(record.location.start); 1524 commented("..".to_doc().append(this.expr(&record.base)), comments) 1525 } 1526 }, 1527 ) 1528 } 1529 1530 pub fn const_record_update<'a, A, B>( 1531 &mut self, 1532 module: &Option<(EcoString, SrcSpan)>, 1533 name: &'a EcoString, 1534 record: &'a RecordBeingUpdated<Constant<A, B>>, 1535 arguments: &'a [RecordUpdateArg<Constant<A, B>>], 1536 location: &SrcSpan, 1537 ) -> Document<'a> { 1538 let constructor_doc = match module { 1539 Some((m, _)) => m.to_doc().append(".").append(name.as_str()), 1540 None => name.to_doc(), 1541 }; 1542 1543 let pieces = std::iter::once(RecordUpdatePiece::Record(record)) 1544 .chain(arguments.iter().map(RecordUpdatePiece::Argument)) 1545 .collect_vec(); 1546 1547 let docs = pieces 1548 .iter() 1549 .map(|piece| match piece { 1550 RecordUpdatePiece::Argument(argument) => { 1551 let comments = self.pop_comments(argument.location.start); 1552 let doc = match argument { 1553 _ if argument.uses_label_shorthand() => { 1554 argument.label.as_str().to_doc().append(":") 1555 } 1556 _ => argument 1557 .label 1558 .as_str() 1559 .to_doc() 1560 .append(": ") 1561 .append(self.const_expr(&argument.value)) 1562 .group(), 1563 }; 1564 commented(doc, comments) 1565 } 1566 RecordUpdatePiece::Record(record) => { 1567 let comments = self.pop_comments(record.location.start); 1568 commented( 1569 "..".to_doc().append(self.const_expr(&record.base)), 1570 comments, 1571 ) 1572 } 1573 }) 1574 .collect_vec(); 1575 1576 constructor_doc 1577 .append(self.wrap_arguments(docs, location.end)) 1578 .group() 1579 } 1580 1581 pub fn bin_op<'a>( 1582 &mut self, 1583 name: &'a BinOp, 1584 left: &'a UntypedExpr, 1585 right: &'a UntypedExpr, 1586 nest_steps: bool, 1587 ) -> Document<'a> { 1588 let left_side = self.bin_op_side(name, left, nest_steps); 1589 1590 let comments = self.pop_comments(right.start_byte_index()); 1591 let name_doc = break_("", " ").append(commented(name.to_doc(), comments)); 1592 1593 let right_side = self.bin_op_side(name, right, nest_steps); 1594 1595 left_side 1596 .append(if nest_steps { 1597 name_doc.nest(INDENT) 1598 } else { 1599 name_doc 1600 }) 1601 .append(" ") 1602 .append(right_side) 1603 } 1604 1605 fn bin_op_side<'a>( 1606 &mut self, 1607 operator: &'a BinOp, 1608 side: &'a UntypedExpr, 1609 nest_steps: bool, 1610 ) -> Document<'a> { 1611 let side_doc = match side { 1612 UntypedExpr::String { value, .. } => self.bin_op_string(value), 1613 UntypedExpr::BinOp { 1614 name, left, right, .. 1615 } => self.bin_op(name, left, right, nest_steps), 1616 UntypedExpr::Int { .. } 1617 | UntypedExpr::Float { .. } 1618 | UntypedExpr::Block { .. } 1619 | UntypedExpr::Var { .. } 1620 | UntypedExpr::Fn { .. } 1621 | UntypedExpr::List { .. } 1622 | UntypedExpr::Call { .. } 1623 | UntypedExpr::PipeLine { .. } 1624 | UntypedExpr::Case { .. } 1625 | UntypedExpr::FieldAccess { .. } 1626 | UntypedExpr::Tuple { .. } 1627 | UntypedExpr::TupleIndex { .. } 1628 | UntypedExpr::Todo { .. } 1629 | UntypedExpr::Panic { .. } 1630 | UntypedExpr::Echo { .. } 1631 | UntypedExpr::BitArray { .. } 1632 | UntypedExpr::RecordUpdate { .. } 1633 | UntypedExpr::NegateBool { .. } 1634 | UntypedExpr::NegateInt { .. } => self.expr(side), 1635 }; 1636 match side.bin_op_name() { 1637 // In case the other side is a binary operation as well and it can 1638 // be grouped together with the current binary operation, the two 1639 // docs are simply concatenated, so that they will end up in the 1640 // same group and the formatter will try to keep those on a single 1641 // line. 1642 Some(side_name) if side_name.can_be_grouped_with(operator) => side_doc, 1643 // In case the binary operations cannot be grouped together the 1644 // other side is treated as a group on its own so that it can be 1645 // broken independently of other pieces of the binary operations 1646 // chain. 1647 _ => self.operator_side( 1648 side_doc.group(), 1649 operator.precedence(), 1650 side.bin_op_precedence(), 1651 ), 1652 } 1653 } 1654 1655 pub fn operator_side<'a>(&self, doc: Document<'a>, op: u8, side: u8) -> Document<'a> { 1656 if op > side { 1657 wrap_block(doc).group() 1658 } else { 1659 doc 1660 } 1661 } 1662 1663 fn spans_multiple_lines(&self, start: u32, end: u32) -> bool { 1664 self.new_lines 1665 .binary_search_by(|newline| { 1666 if *newline <= start { 1667 Ordering::Less 1668 } else if *newline >= end { 1669 Ordering::Greater 1670 } else { 1671 // If the newline is in between the pipe start and end 1672 // then we've found it! 1673 Ordering::Equal 1674 } 1675 }) 1676 // If we couldn't find any newline between the start and end of 1677 // the pipeline then we will try and keep it on a single line. 1678 .is_ok() 1679 } 1680 1681 /// Returns true if there's a trailing comma between `start` and `end`. 1682 /// 1683 fn has_trailing_comma(&self, start: u32, end: u32) -> bool { 1684 self.trailing_commas 1685 .binary_search_by(|comma| { 1686 if *comma < start { 1687 Ordering::Less 1688 } else if *comma > end { 1689 Ordering::Greater 1690 } else { 1691 Ordering::Equal 1692 } 1693 }) 1694 .is_ok() 1695 } 1696 1697 fn pipeline<'a>( 1698 &mut self, 1699 expressions: &'a Vec1<UntypedExpr>, 1700 nest_pipe: bool, 1701 ) -> Document<'a> { 1702 let mut docs = Vec::with_capacity(expressions.len() * 3); 1703 let first = expressions.first(); 1704 let first_precedence = first.bin_op_precedence(); 1705 let first = self.expr(first).group(); 1706 docs.push(self.operator_side(first, 5, first_precedence)); 1707 1708 let pipeline_start = expressions.first().location().start; 1709 let pipeline_end = expressions.last().location().end; 1710 let try_to_keep_on_one_line = !self.spans_multiple_lines(pipeline_start, pipeline_end); 1711 1712 for expression in expressions.iter().skip(1) { 1713 let comments = self.pop_comments(expression.location().start); 1714 let doc = if let UntypedExpr::Fn { kind, body, .. } = expression 1715 && kind.is_capture() 1716 { 1717 self.fn_capture(body, FnCapturePosition::RightHandSideOfPipe) 1718 } else { 1719 self.expr(expression) 1720 }; 1721 let doc = if nest_pipe { doc.nest(INDENT) } else { doc }; 1722 let space = if try_to_keep_on_one_line { 1723 break_("", " ") 1724 } else { 1725 line() 1726 }; 1727 let pipe = space.append(commented("|> ".to_doc(), comments)); 1728 let pipe = if nest_pipe { pipe.nest(INDENT) } else { pipe }; 1729 docs.push(pipe); 1730 docs.push(self.operator_side(doc, 4, expression.bin_op_precedence())); 1731 } 1732 1733 if try_to_keep_on_one_line { 1734 docs.to_doc() 1735 } else { 1736 docs.to_doc().force_break() 1737 } 1738 } 1739 1740 fn fn_capture<'a>( 1741 &mut self, 1742 call: &'a [UntypedStatement], 1743 position: FnCapturePosition, 1744 ) -> Document<'a> { 1745 // The body of a capture being multiple statements shouldn't be possible... 1746 if call.len() != 1 { 1747 panic!("Function capture found not to have a single statement call"); 1748 } 1749 1750 let Some(Statement::Expression(UntypedExpr::Call { 1751 fun, 1752 arguments, 1753 location, 1754 })) = call.first() 1755 else { 1756 // The body of a capture being not a fn shouldn't be possible... 1757 panic!("Function capture body found not to be a call in the formatter") 1758 }; 1759 1760 match (position, arguments.as_slice()) { 1761 // The capture has a single unlabelled hole: 1762 // 1763 // wibble |> wobble(_) 1764 // list.map([], wobble(_)) 1765 // 1766 // We want these to become: 1767 // 1768 // wibble |> wobble 1769 // list.map([], wobble) 1770 // 1771 ( 1772 FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, 1773 [argument], 1774 ) if argument.is_capture_hole() && argument.label.is_none() => self.expr(fun), 1775 1776 // The capture is on the right hand side of a pipe and its first 1777 // argument it an unlabelled capture hole: 1778 // 1779 // wibble |> wobble(_, woo) 1780 // 1781 // We want it to become: 1782 // 1783 // wibble |> wobble(woo) 1784 // 1785 (FnCapturePosition::RightHandSideOfPipe, [argument, rest @ ..]) 1786 if argument.is_capture_hole() && argument.label.is_none() => 1787 { 1788 let expression = self.expr(fun); 1789 let arity = rest.len(); 1790 self.append_inlinable_wrapped_arguments( 1791 expression, 1792 rest, 1793 location, 1794 |argument| is_breakable_argument(&argument.value, rest.len()), 1795 |self_, argument| self_.call_arg(argument, arity), 1796 ) 1797 } 1798 1799 // In all other cases we print it like a regular function call 1800 // without changing it. 1801 // 1802 ( 1803 FnCapturePosition::RightHandSideOfPipe | FnCapturePosition::EverywhereElse, 1804 arguments, 1805 ) => { 1806 let expression = self.expr(fun); 1807 let arity = arguments.len(); 1808 self.append_inlinable_wrapped_arguments( 1809 expression, 1810 arguments, 1811 location, 1812 |argument| is_breakable_argument(&argument.value, arguments.len()), 1813 |self_, argument| self_.call_arg(argument, arity), 1814 ) 1815 } 1816 } 1817 } 1818 1819 pub fn record_constructor<'a, A>( 1820 &mut self, 1821 constructor: &'a RecordConstructor<A>, 1822 ) -> Document<'a> { 1823 let comments = self.pop_comments(constructor.location.start); 1824 let doc_comments = self.doc_comments(constructor.location.start); 1825 let attributes = AttributesPrinter::new() 1826 .set_deprecation(&constructor.deprecation) 1827 .to_doc(); 1828 1829 let doc = if constructor.arguments.is_empty() { 1830 if self.any_comments(constructor.location.end) { 1831 attributes 1832 .append(constructor.name.as_str().to_doc()) 1833 .append(self.wrap_arguments(vec![], constructor.location.end)) 1834 .group() 1835 } else { 1836 attributes.append(constructor.name.as_str().to_doc()) 1837 } 1838 } else { 1839 let arguments = constructor 1840 .arguments 1841 .iter() 1842 .map( 1843 |RecordConstructorArg { 1844 label, 1845 ast, 1846 location, 1847 .. 1848 }| { 1849 let arg_comments = self.pop_comments(location.start); 1850 let argument = match label { 1851 Some((_, l)) => l.to_doc().append(": ").append(self.type_ast(ast)), 1852 None => self.type_ast(ast), 1853 }; 1854 1855 commented( 1856 self.doc_comments(location.start).append(argument).group(), 1857 arg_comments, 1858 ) 1859 }, 1860 ) 1861 .collect_vec(); 1862 1863 attributes 1864 .append(constructor.name.as_str().to_doc()) 1865 .append( 1866 self.wrap_arguments(arguments, constructor.location.end) 1867 .group(), 1868 ) 1869 }; 1870 1871 commented(doc_comments.append(doc).group(), comments) 1872 } 1873 1874 pub fn custom_type<'a, A>(&mut self, type_: &'a CustomType<A>) -> Document<'a> { 1875 let CustomType { 1876 location, 1877 end_position, 1878 name, 1879 name_location: _, 1880 publicity, 1881 constructors, 1882 documentation: _, 1883 deprecation, 1884 opaque, 1885 parameters, 1886 typed_parameters: _, 1887 external_erlang, 1888 external_javascript, 1889 } = type_; 1890 1891 let _ = self.pop_empty_lines(location.end); 1892 1893 let attributes = AttributesPrinter::new() 1894 .set_deprecation(deprecation) 1895 .set_internal(*publicity) 1896 .set_external_erlang(external_erlang) 1897 .set_external_javascript(external_javascript) 1898 .to_doc(); 1899 1900 let doc = attributes 1901 .append(pub_(*publicity)) 1902 .append(if *opaque { "opaque type " } else { "type " }) 1903 .append(if parameters.is_empty() { 1904 name.clone().to_doc() 1905 } else { 1906 let arguments = type_ 1907 .parameters 1908 .iter() 1909 .map(|(_, e)| e.to_doc()) 1910 .collect_vec(); 1911 type_ 1912 .name 1913 .clone() 1914 .to_doc() 1915 .append(self.wrap_arguments(arguments, location.end)) 1916 .group() 1917 }); 1918 1919 if constructors.is_empty() { 1920 return doc; 1921 } 1922 let doc = doc.append(" {"); 1923 1924 let inner = concat(constructors.iter().map(|c| { 1925 if self.pop_empty_lines(c.location.start) { 1926 lines(2) 1927 } else { 1928 line() 1929 } 1930 .append(self.record_constructor(c)) 1931 })); 1932 1933 // Add any trailing comments 1934 let inner = match printed_comments(self.pop_comments(*end_position), false) { 1935 Some(comments) => inner.append(line()).append(comments), 1936 None => inner, 1937 } 1938 .nest(INDENT) 1939 .group(); 1940 1941 doc.append(inner).append(line()).append("}") 1942 } 1943 1944 fn call_arg<'a>(&mut self, argument: &'a CallArg<UntypedExpr>, arity: usize) -> Document<'a> { 1945 self.format_call_arg(argument, expr_call_arg_formatting, |this, value| { 1946 this.comma_separated_item(value, arity) 1947 }) 1948 } 1949 1950 fn format_call_arg<'a, A, F, G>( 1951 &mut self, 1952 argument: &'a CallArg<A>, 1953 figure_formatting: F, 1954 format_value: G, 1955 ) -> Document<'a> 1956 where 1957 F: Fn(&'a CallArg<A>) -> CallArgFormatting<'a, A>, 1958 G: Fn(&mut Self, &'a A) -> Document<'a>, 1959 { 1960 match figure_formatting(argument) { 1961 CallArgFormatting::Unlabelled(value) => format_value(self, value), 1962 CallArgFormatting::ShorthandLabelled(label) => { 1963 let comments = self.pop_comments(argument.location.start); 1964 let label = label.as_ref().to_doc().append(":"); 1965 commented(label, comments) 1966 } 1967 CallArgFormatting::Labelled(label, value) => { 1968 let comments = self.pop_comments(argument.location.start); 1969 let label = label.as_ref().to_doc().append(": "); 1970 let value = format_value(self, value); 1971 commented(label, comments).append(value) 1972 } 1973 } 1974 } 1975 1976 fn record_update_arg<'a>(&mut self, argument: &'a UntypedRecordUpdateArg) -> Document<'a> { 1977 let comments = self.pop_comments(argument.location.start); 1978 match argument { 1979 // Argument supplied with a label shorthand. 1980 _ if argument.uses_label_shorthand() => { 1981 commented(argument.label.as_str().to_doc().append(":"), comments) 1982 } 1983 // Labelled argument. 1984 _ => { 1985 let doc = argument 1986 .label 1987 .as_str() 1988 .to_doc() 1989 .append(": ") 1990 .append(self.expr(&argument.value)) 1991 .group(); 1992 1993 if argument.value.is_binop() || argument.value.is_pipeline() { 1994 commented(doc, comments).nest(INDENT) 1995 } else { 1996 commented(doc, comments) 1997 } 1998 } 1999 } 2000 } 2001 2002 fn tuple_index<'a>(&mut self, tuple: &'a UntypedExpr, index: u64) -> Document<'a> { 2003 // In case we have a block with a single variable tuple access we 2004 // remove that redundant wrapper: 2005 // 2006 // {tuple.1}.0 becomes 2007 // tuple.1.0 2008 // 2009 if let UntypedExpr::Block { statements, .. } = tuple { 2010 match statements.as_slice() { 2011 [Statement::Expression(tuple @ UntypedExpr::TupleIndex { tuple: inner, .. })] 2012 // We can't apply this change if the inner thing is a 2013 // literal tuple because the compiler cannot currently parse 2014 // it: `#(1, #(2, 3)).1.0` is a syntax error at the moment. 2015 if !inner.is_tuple() => 2016 { 2017 self.expr(tuple) 2018 } 2019 _ => self.expr(tuple), 2020 } 2021 } else { 2022 self.expr(tuple) 2023 } 2024 .append(".") 2025 .append(index) 2026 } 2027 2028 fn case_clause_value<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 2029 match expression { 2030 UntypedExpr::Fn { .. } 2031 | UntypedExpr::List { .. } 2032 | UntypedExpr::Tuple { .. } 2033 | UntypedExpr::BitArray { .. } => { 2034 let expression_comments = self.pop_comments(expression.location().start); 2035 let expression_doc = self.expr(expression); 2036 match printed_comments(expression_comments, true) { 2037 Some(comments) => line().append(comments).append(expression_doc).nest(INDENT), 2038 None => " ".to_doc().append(expression_doc), 2039 } 2040 } 2041 2042 UntypedExpr::Case { .. } => line().append(self.expr(expression)).nest(INDENT), 2043 2044 UntypedExpr::Block { 2045 statements, 2046 location, 2047 .. 2048 } => " ".to_doc().append(self.block(location, statements, true)), 2049 2050 UntypedExpr::Int { .. } 2051 | UntypedExpr::Float { .. } 2052 | UntypedExpr::String { .. } 2053 | UntypedExpr::Var { .. } 2054 | UntypedExpr::Call { .. } 2055 | UntypedExpr::BinOp { .. } 2056 | UntypedExpr::PipeLine { .. } 2057 | UntypedExpr::FieldAccess { .. } 2058 | UntypedExpr::TupleIndex { .. } 2059 | UntypedExpr::Todo { .. } 2060 | UntypedExpr::Panic { .. } 2061 | UntypedExpr::Echo { .. } 2062 | UntypedExpr::RecordUpdate { .. } 2063 | UntypedExpr::NegateBool { .. } 2064 | UntypedExpr::NegateInt { .. } => break_("", " ") 2065 .append(self.expr(expression).group()) 2066 .nest(INDENT), 2067 } 2068 .next_break_fits(NextBreakFitsMode::Disabled) 2069 .group() 2070 } 2071 2072 fn assigned_value<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 2073 match expression { 2074 UntypedExpr::Case { .. } => " ".to_doc().append(self.expr(expression)).group(), 2075 UntypedExpr::Int { .. } 2076 | UntypedExpr::Float { .. } 2077 | UntypedExpr::String { .. } 2078 | UntypedExpr::Block { .. } 2079 | UntypedExpr::Var { .. } 2080 | UntypedExpr::Fn { .. } 2081 | UntypedExpr::List { .. } 2082 | UntypedExpr::Call { .. } 2083 | UntypedExpr::BinOp { .. } 2084 | UntypedExpr::PipeLine { .. } 2085 | UntypedExpr::FieldAccess { .. } 2086 | UntypedExpr::Tuple { .. } 2087 | UntypedExpr::TupleIndex { .. } 2088 | UntypedExpr::Todo { .. } 2089 | UntypedExpr::Panic { .. } 2090 | UntypedExpr::Echo { .. } 2091 | UntypedExpr::BitArray { .. } 2092 | UntypedExpr::RecordUpdate { .. } 2093 | UntypedExpr::NegateBool { .. } 2094 | UntypedExpr::NegateInt { .. } => self.case_clause_value(expression), 2095 } 2096 } 2097 2098 fn clause<'a>(&mut self, clause: &'a UntypedClause, index: u32) -> Document<'a> { 2099 let space_before = self.pop_empty_lines(clause.location.start); 2100 let comments = self.pop_comments(clause.location.start); 2101 2102 let clause_doc = match &clause.guard { 2103 None => self.alternative_patterns(clause), 2104 Some(guard) => self 2105 .alternative_patterns(clause) 2106 .append(break_("", " ").nest(INDENT)) 2107 .append("if ") 2108 .append(self.clause_guard(guard).group().nest(INDENT)), 2109 }; 2110 2111 // In case there's a guard or multiple subjects, if we decide to break 2112 // the patterns on multiple lines we also want the arrow to end up on 2113 // its own line to improve legibility. 2114 // 2115 // This looks like this: 2116 // ```gleam 2117 // case wibble, wobble { 2118 // Wibble(_), // pretend this goes over the line limit 2119 // Wobble(_) 2120 // -> todo 2121 // // Notice how the arrow is broken on its own line, the same goes 2122 // // for patterns with `if` guards. 2123 // } 2124 // ``` 2125 2126 let has_guard = clause.guard.is_some(); 2127 let has_multiple_subjects = clause.pattern.len() > 1; 2128 let arrow_break = if has_guard || has_multiple_subjects { 2129 break_("", " ") 2130 } else { 2131 " ".to_doc() 2132 }; 2133 2134 let clause_doc = clause_doc 2135 .append(arrow_break) 2136 .group() 2137 .append("->") 2138 .append(self.case_clause_value(&clause.then).group()) 2139 .group(); 2140 2141 let clause_doc = match printed_comments(comments, false) { 2142 Some(comments) => comments.append(line()).append(clause_doc), 2143 None => clause_doc, 2144 }; 2145 2146 if index == 0 { 2147 clause_doc 2148 } else if space_before { 2149 lines(2).append(clause_doc) 2150 } else { 2151 line().append(clause_doc) 2152 } 2153 } 2154 2155 fn alternative_patterns<'a>(&mut self, clause: &'a UntypedClause) -> Document<'a> { 2156 let has_guard = clause.guard.is_some(); 2157 let has_multiple_subjects = clause.pattern.len() > 1; 2158 2159 // In case there's an `if` guard but no multiple subjects we want to add 2160 // additional indentation before the vartical bar separating alternative 2161 // patterns `|`. 2162 // We're not adding the indentation if there's multiple subjects as that 2163 // would make things harder to read, aligning the vertical bar with the 2164 // different subjects: 2165 // ``` 2166 // case wibble, wobble { 2167 // Wibble, 2168 // Wobble 2169 // | Wibble, // <- we don't want this indentation! 2170 // Wobble -> todo 2171 // } 2172 // ``` 2173 let alternatives_separator = if has_guard && !has_multiple_subjects { 2174 break_("", " ").nest(INDENT).append("| ") 2175 } else { 2176 break_("", " ").append("| ") 2177 }; 2178 2179 let alternative_patterns = std::iter::once(&clause.pattern) 2180 .chain(&clause.alternative_patterns) 2181 .enumerate() 2182 .map(|(alternative_index, p)| { 2183 // Here `p` is a single pattern that can be comprised of 2184 // multiple subjects. 2185 // ```gleam 2186 // case wibble, wobble { 2187 // True, False 2188 // //^^^^^^^^^^^ This is a single pattern with multiple subjects 2189 // | _, _ -> todo 2190 // } 2191 // ``` 2192 let is_first_alternative = alternative_index == 0; 2193 let subject_docs = p.iter().enumerate().map(|(subject_index, subject)| { 2194 // There's a small catch in turning each subject into a document. 2195 // Sadly we can't simply call `self.pattern` on each subject and 2196 // then nest each one in case it gets broken. 2197 // The first ever pattern that appears in a case clause (that is 2198 // the first subject of the first alternative) must not be nested 2199 // further; otherwise, when broken, it would have 2 extra spaces 2200 // of indentation: https://github.com/gleam-lang/gleam/issues/2940. 2201 let is_first_subject = subject_index == 0; 2202 let is_first_pattern_of_clause = is_first_subject && is_first_alternative; 2203 let subject_doc = self.pattern(subject); 2204 if is_first_pattern_of_clause { 2205 subject_doc 2206 } else { 2207 subject_doc.nest(INDENT) 2208 } 2209 }); 2210 // We join all subjects with a breakable comma (that's also 2211 // going to be nested) and make the subjects into a group to 2212 // make sure the formatter tries to keep them on a single line. 2213 join(subject_docs, break_(",", ", ").nest(INDENT)).group() 2214 }); 2215 // Last, we make sure that the formatter tries to keep each 2216 // alternative on a single line by making it a group! 2217 join(alternative_patterns, alternatives_separator).group() 2218 } 2219 2220 fn list<'a>( 2221 &mut self, 2222 elements: &'a [UntypedExpr], 2223 tail: Option<&'a UntypedExpr>, 2224 location: &SrcSpan, 2225 ) -> Document<'a> { 2226 if elements.is_empty() { 2227 return match tail { 2228 Some(tail) => self.expr(tail), 2229 // We take all comments that come _before_ the end of the list, 2230 // that is all comments that are inside "[" and "]", if there's 2231 // any comment we want to put it inside the empty list! 2232 None => match printed_comments(self.pop_comments(location.end), false) { 2233 None => "[]".to_doc(), 2234 Some(comments) => "[" 2235 .to_doc() 2236 .append(break_("", "").nest(INDENT)) 2237 .append(comments) 2238 .append(break_("", "")) 2239 .append("]") 2240 // vvv We want to make sure the comments are on a separate 2241 // line from the opening and closing brackets so we 2242 // force the breaks to be split on newlines. 2243 .force_break(), 2244 }, 2245 }; 2246 } 2247 2248 let list_packing = self.items_sequence_packing( 2249 elements, 2250 tail, 2251 UntypedExpr::can_have_multiple_per_line, 2252 *location, 2253 ); 2254 2255 let comma = match list_packing { 2256 ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), 2257 ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), 2258 }; 2259 2260 let list_size = elements.len() 2261 + match tail { 2262 Some(_) => 1, 2263 None => 0, 2264 }; 2265 2266 let mut elements_doc = nil(); 2267 for element in elements.iter() { 2268 let empty_lines = self.pop_empty_lines(element.location().start); 2269 let element_doc = self.comma_separated_item(element, list_size); 2270 2271 elements_doc = if elements_doc.is_empty() { 2272 element_doc 2273 } else if empty_lines { 2274 // If there's empty lines before the list item we want to add an 2275 // empty line here. Notice how we're making sure no nesting is 2276 // added after the comma, otherwise we would be adding needless 2277 // whitespace in the empty line! 2278 docvec![ 2279 elements_doc, 2280 comma.clone().set_nesting(0), 2281 line(), 2282 element_doc 2283 ] 2284 } else { 2285 docvec![elements_doc, comma.clone(), element_doc] 2286 }; 2287 } 2288 elements_doc = elements_doc.next_break_fits(NextBreakFitsMode::Disabled); 2289 2290 let doc = break_("[", "[").append(elements_doc); 2291 // We need to keep the last break aside and do not add it immediately 2292 // because in case there's a final comment before the closing square 2293 // bracket we want to add indentation (to just that break). Otherwise, 2294 // the final comment would be less indented than list's elements. 2295 let (doc, last_break) = match tail { 2296 None => (doc.nest(INDENT), break_(",", "")), 2297 2298 Some(tail) => { 2299 let comments = self.pop_comments(tail.location().start); 2300 let tail = commented(docvec!["..", self.expr(tail)], comments); 2301 ( 2302 doc.append(break_(",", ", ")).append(tail).nest(INDENT), 2303 break_("", ""), 2304 ) 2305 } 2306 }; 2307 2308 // We get all remaining comments that come before the list's closing 2309 // square bracket. 2310 // If there's any we add those before the closing square bracket instead 2311 // of moving those out of the list. 2312 // Otherwise those would be moved out of the list. 2313 let comments = self.pop_comments(location.end); 2314 let doc = match printed_comments(comments, false) { 2315 None => doc.append(last_break).append("]"), 2316 Some(comment) => doc 2317 .append(last_break.nest(INDENT)) 2318 // ^ See how here we're adding the missing indentation to the 2319 // final break so that the final comment is as indented as the 2320 // list's items. 2321 .append(comment) 2322 .append(line()) 2323 .append("]") 2324 .force_break(), 2325 }; 2326 2327 match list_packing { 2328 ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), 2329 ItemsPacking::BreakOnePerLine => doc.force_break(), 2330 } 2331 } 2332 2333 fn items_sequence_packing<'a, T: HasLocation>( 2334 &self, 2335 items: &'a [T], 2336 tail: Option<&'a T>, 2337 can_have_multiple_per_line: impl Fn(&'a T) -> bool, 2338 list_location: SrcSpan, 2339 ) -> ItemsPacking { 2340 let ends_with_trailing_comma = tail 2341 .map(|tail| tail.location().end) 2342 .or_else(|| items.last().map(|last| last.location().end)) 2343 .is_some_and(|last_element_end| { 2344 self.has_trailing_comma(last_element_end, list_location.end) 2345 }); 2346 2347 let has_multiple_elements_per_line = 2348 self.has_items_on_the_same_line(items.iter().chain(tail)); 2349 2350 let has_empty_lines_between_elements = match (items.first(), items.last().or(tail)) { 2351 (Some(first), Some(last)) => self.empty_lines.first().is_some_and(|empty_line| { 2352 *empty_line >= first.location().end && *empty_line < last.location().start 2353 }), 2354 _ => false, 2355 }; 2356 2357 if has_empty_lines_between_elements { 2358 // If there's any empty line between elements we want to force each 2359 // item onto its own line to preserve the empty lines that were 2360 // intentionally added. 2361 ItemsPacking::BreakOnePerLine 2362 } else if !ends_with_trailing_comma { 2363 // If the list doesn't end with a trailing comma we try and pack it in 2364 // a single line; if we can't we'll put one item per line, no matter 2365 // the content of the list. 2366 ItemsPacking::FitOnePerLine 2367 } else if tail.is_none() 2368 && items.iter().all(can_have_multiple_per_line) 2369 && has_multiple_elements_per_line 2370 && self.spans_multiple_lines(list_location.start, list_location.end) 2371 { 2372 // If there's a trailing comma, we can have multiple items per line, 2373 // and there's already multiple items per line, we try and pack as 2374 // many items as possible on each line. 2375 // 2376 // Note how we only ever try and pack lists where all items are 2377 // unbreakable primitives. To pack a list we need to use put 2378 // `flex_break`s between each item. 2379 // If the items themselves had breaks we could end up in a situation 2380 // where an item gets broken making it span multiple lines and the 2381 // spaces are not, for example: 2382 // 2383 // ```gleam 2384 // [Constructor("wibble", "lorem ipsum dolor sit amet something something"), Other(1)] 2385 // ``` 2386 // 2387 // If we used flex breaks here the list would be formatted as: 2388 // 2389 // ```gleam 2390 // [ 2391 // Constructor( 2392 // "wibble", 2393 // "lorem ipsum dolor sit amet something something", 2394 // ), Other(1) 2395 // ] 2396 // ``` 2397 // 2398 // The first item is broken, meaning that once we get to the flex 2399 // space separating it from the following one the formatter is not 2400 // going to break it since there's enough space in the current line! 2401 ItemsPacking::FitMultiplePerLine 2402 } else { 2403 // If it ends with a trailing comma we will force the list on 2404 // multiple lines, with one item per line. 2405 ItemsPacking::BreakOnePerLine 2406 } 2407 } 2408 2409 fn has_items_on_the_same_line<'a, L: HasLocation + 'a, T: Iterator<Item = &'a L>>( 2410 &self, 2411 items: T, 2412 ) -> bool { 2413 let mut previous: Option<SrcSpan> = None; 2414 for item in items { 2415 let item_location = item.location(); 2416 // A list has multiple items on the same line if two consecutive 2417 // ones do not span multiple lines. 2418 if let Some(previous) = previous 2419 && !self.spans_multiple_lines(previous.end, item_location.start) 2420 { 2421 return true; 2422 } 2423 previous = Some(item_location); 2424 } 2425 false 2426 } 2427 2428 /// Pretty prints an expression to be used in a comma separated list; for 2429 /// example as a list item, a tuple item or as an argument of a function call. 2430 fn comma_separated_item<'a>( 2431 &mut self, 2432 expression: &'a UntypedExpr, 2433 siblings: usize, 2434 ) -> Document<'a> { 2435 // If there's more than one item in the comma separated list and there's a 2436 // pipeline or long binary chain, we want to indent those to make it 2437 // easier to tell where one item ends and the other starts. 2438 // Othewise we just print the expression as a normal expr. 2439 match expression { 2440 UntypedExpr::BinOp { 2441 name, left, right, .. 2442 } if siblings > 1 => { 2443 let comments = self.pop_comments(expression.start_byte_index()); 2444 let doc = self.bin_op(name, left, right, true).group(); 2445 commented(doc, comments) 2446 } 2447 UntypedExpr::PipeLine { expressions } if siblings > 1 => { 2448 let comments = self.pop_comments(expression.start_byte_index()); 2449 let doc = self.pipeline(expressions, true).group(); 2450 commented(doc, comments) 2451 } 2452 UntypedExpr::Int { .. } 2453 | UntypedExpr::Float { .. } 2454 | UntypedExpr::String { .. } 2455 | UntypedExpr::Block { .. } 2456 | UntypedExpr::Var { .. } 2457 | UntypedExpr::Fn { .. } 2458 | UntypedExpr::List { .. } 2459 | UntypedExpr::Call { .. } 2460 | UntypedExpr::BinOp { .. } 2461 | UntypedExpr::PipeLine { .. } 2462 | UntypedExpr::Case { .. } 2463 | UntypedExpr::FieldAccess { .. } 2464 | UntypedExpr::Tuple { .. } 2465 | UntypedExpr::TupleIndex { .. } 2466 | UntypedExpr::Todo { .. } 2467 | UntypedExpr::Panic { .. } 2468 | UntypedExpr::Echo { .. } 2469 | UntypedExpr::BitArray { .. } 2470 | UntypedExpr::RecordUpdate { .. } 2471 | UntypedExpr::NegateBool { .. } 2472 | UntypedExpr::NegateInt { .. } => self.expr(expression).group(), 2473 } 2474 } 2475 2476 fn pattern<'a>(&mut self, pattern: &'a UntypedPattern) -> Document<'a> { 2477 let comments = self.pop_comments(pattern.location().start); 2478 let doc = match pattern { 2479 Pattern::Int { value, .. } => self.int(value), 2480 2481 Pattern::Float { value, .. } => self.float(value), 2482 2483 Pattern::String { value, .. } => self.string(value), 2484 2485 Pattern::Variable { name, .. } => name.to_doc(), 2486 2487 Pattern::BitArraySize(size) => self.bit_array_size(size), 2488 2489 Pattern::Assign { name, pattern, .. } => { 2490 if pattern.is_discard() { 2491 name.to_doc() 2492 } else { 2493 self.pattern(pattern).append(" as ").append(name.as_str()) 2494 } 2495 } 2496 2497 Pattern::Discard { name, .. } => name.to_doc(), 2498 2499 Pattern::List { elements, tail, .. } => self.list_pattern(elements, tail), 2500 2501 Pattern::Constructor { 2502 name, 2503 arguments, 2504 module, 2505 spread, 2506 location, 2507 .. 2508 } => self.pattern_constructor(name, arguments, module, *spread, location), 2509 2510 Pattern::Tuple { 2511 elements, location, .. 2512 } => { 2513 let arguments = elements 2514 .iter() 2515 .map(|element| self.pattern(element)) 2516 .collect_vec(); 2517 "#".to_doc() 2518 .append(self.wrap_arguments(arguments, location.end)) 2519 .group() 2520 } 2521 2522 Pattern::BitArray { 2523 segments, location, .. 2524 } => { 2525 let segment_docs = segments 2526 .iter() 2527 .map(|segment| bit_array_segment(segment, |pattern| self.pattern(pattern))) 2528 .collect_vec(); 2529 2530 self.bit_array(segment_docs, ItemsPacking::FitOnePerLine, location) 2531 } 2532 2533 Pattern::StringPrefix { 2534 left_side_string: left, 2535 right_side_assignment: right, 2536 left_side_assignment: left_assign, 2537 .. 2538 } => { 2539 let left = self.string(left); 2540 let right = match right { 2541 AssignName::Variable(name) => name.to_doc(), 2542 AssignName::Discard(name) => name.to_doc(), 2543 }; 2544 match left_assign { 2545 Some((name, _)) => docvec![left, " as ", name, " <> ", right], 2546 None => docvec![left, " <> ", right], 2547 } 2548 } 2549 2550 Pattern::Invalid { .. } => panic!("invalid patterns can not be in an untyped ast"), 2551 }; 2552 commented(doc, comments) 2553 } 2554 2555 fn bit_array_size<'a>(&mut self, size: &'a BitArraySize<()>) -> Document<'a> { 2556 match size { 2557 BitArraySize::Int { value, .. } => self.int(value), 2558 BitArraySize::Variable { name, .. } => name.to_doc(), 2559 BitArraySize::BinaryOperator { 2560 left, 2561 right, 2562 operator, 2563 .. 2564 } => { 2565 let operator = match operator { 2566 IntOperator::Add => " + ", 2567 IntOperator::Subtract => " - ", 2568 IntOperator::Multiply => " * ", 2569 IntOperator::Divide => " / ", 2570 IntOperator::Remainder => " % ", 2571 }; 2572 2573 docvec![ 2574 self.bit_array_size(left), 2575 operator, 2576 self.bit_array_size(right) 2577 ] 2578 } 2579 BitArraySize::Block { inner, .. } => self.bit_array_size(inner).surround("{ ", " }"), 2580 } 2581 } 2582 2583 fn list_pattern<'a>( 2584 &mut self, 2585 elements: &'a [UntypedPattern], 2586 tail: &'a Option<Box<UntypedTailPattern>>, 2587 ) -> Document<'a> { 2588 if elements.is_empty() { 2589 return match tail { 2590 Some(tail) => self.pattern(&tail.pattern), 2591 None => "[]".to_doc(), 2592 }; 2593 } 2594 let elements = join( 2595 elements.iter().map(|element| self.pattern(element)), 2596 break_(",", ", "), 2597 ); 2598 let doc = break_("[", "[").append(elements); 2599 match tail { 2600 None => doc.nest(INDENT).append(break_(",", "")), 2601 2602 Some(tail) => { 2603 let tail = &tail.pattern; 2604 let comments = self.pop_comments(tail.location().start); 2605 let tail = if tail.is_discard() { 2606 "..".to_doc() 2607 } else { 2608 docvec!["..", self.pattern(tail)] 2609 }; 2610 let tail = commented(tail, comments); 2611 doc.append(break_(",", ", ")) 2612 .append(tail) 2613 .nest(INDENT) 2614 .append(break_("", "")) 2615 } 2616 } 2617 .append("]") 2618 .group() 2619 } 2620 2621 fn pattern_call_arg<'a>(&mut self, argument: &'a CallArg<UntypedPattern>) -> Document<'a> { 2622 self.format_call_arg(argument, pattern_call_arg_formatting, |this, value| { 2623 this.pattern(value) 2624 }) 2625 } 2626 2627 pub fn clause_guard_bin_op<'a>( 2628 &mut self, 2629 name: &'a BinOp, 2630 left: &'a UntypedClauseGuard, 2631 right: &'a UntypedClauseGuard, 2632 ) -> Document<'a> { 2633 self.clause_guard_bin_op_side(name, left, left.precedence()) 2634 .append(break_("", " ")) 2635 .append(name.to_doc()) 2636 .append(" ") 2637 .append(self.clause_guard_bin_op_side(name, right, right.precedence() - 1)) 2638 } 2639 2640 fn clause_guard_bin_op_side<'a>( 2641 &mut self, 2642 name: &BinOp, 2643 side: &'a UntypedClauseGuard, 2644 // As opposed to `bin_op_side`, here we take the side precedence as an 2645 // argument instead of computing it ourselves. That's because 2646 // `clause_guard_bin_op` will reduce the precedence of any right side to 2647 // make sure the formatter doesn't remove any needed curly bracket. 2648 side_precedence: u8, 2649 ) -> Document<'a> { 2650 let side_doc = self.clause_guard(side); 2651 match side.bin_op_name() { 2652 // In case the other side is a binary operation as well and it can 2653 // be grouped together with the current binary operation, the two 2654 // docs are simply concatenated, so that they will end up in the 2655 // same group and the formatter will try to keep those on a single 2656 // line. 2657 Some(side_name) if side_name.can_be_grouped_with(name) => { 2658 self.operator_side(side_doc, name.precedence(), side_precedence) 2659 } 2660 // In case the binary operations cannot be grouped together the 2661 // other side is treated as a group on its own so that it can be 2662 // broken independently of other pieces of the binary operations 2663 // chain. 2664 _ => self.operator_side(side_doc.group(), name.precedence(), side_precedence), 2665 } 2666 } 2667 2668 fn clause_guard<'a>(&mut self, clause_guard: &'a UntypedClauseGuard) -> Document<'a> { 2669 match clause_guard { 2670 ClauseGuard::BinaryOperator { 2671 operator, 2672 left, 2673 right, 2674 .. 2675 } => self.clause_guard_bin_op(operator, left, right), 2676 2677 ClauseGuard::Var { name, .. } => name.to_doc(), 2678 2679 ClauseGuard::TupleIndex { tuple, index, .. } => { 2680 self.clause_guard(tuple).append(".").append(*index).to_doc() 2681 } 2682 2683 ClauseGuard::FieldAccess { 2684 container, label, .. 2685 } => self 2686 .clause_guard(container) 2687 .append(".") 2688 .append(label) 2689 .to_doc(), 2690 2691 ClauseGuard::ModuleSelect { 2692 module_name, label, .. 2693 } => module_name.to_doc().append(".").append(label).to_doc(), 2694 2695 ClauseGuard::Constant(constant) => self.const_expr(constant), 2696 2697 ClauseGuard::Not { expression, .. } => docvec!["!", self.clause_guard(expression)], 2698 2699 ClauseGuard::Block { value, .. } => wrap_block(self.clause_guard(value)).group(), 2700 } 2701 } 2702 2703 fn constant_call_arg<'a, A, B>( 2704 &mut self, 2705 argument: &'a CallArg<Constant<A, B>>, 2706 ) -> Document<'a> { 2707 self.format_call_arg(argument, constant_call_arg_formatting, |this, value| { 2708 this.const_expr(value) 2709 }) 2710 } 2711 2712 fn negate_bool<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 2713 match expression { 2714 UntypedExpr::NegateBool { value, .. } => self.expr(value), 2715 UntypedExpr::BinOp { .. } => "!".to_doc().append(wrap_block(self.expr(expression))), 2716 UntypedExpr::Int { .. } 2717 | UntypedExpr::Float { .. } 2718 | UntypedExpr::String { .. } 2719 | UntypedExpr::Block { .. } 2720 | UntypedExpr::Var { .. } 2721 | UntypedExpr::Fn { .. } 2722 | UntypedExpr::List { .. } 2723 | UntypedExpr::Call { .. } 2724 | UntypedExpr::PipeLine { .. } 2725 | UntypedExpr::Case { .. } 2726 | UntypedExpr::FieldAccess { .. } 2727 | UntypedExpr::Tuple { .. } 2728 | UntypedExpr::TupleIndex { .. } 2729 | UntypedExpr::Todo { .. } 2730 | UntypedExpr::Panic { .. } 2731 | UntypedExpr::Echo { .. } 2732 | UntypedExpr::BitArray { .. } 2733 | UntypedExpr::RecordUpdate { .. } 2734 | UntypedExpr::NegateInt { .. } => docvec!["!", self.expr(expression)], 2735 } 2736 } 2737 2738 fn negate_int<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 2739 match expression { 2740 UntypedExpr::NegateInt { value, .. } => self.expr(value), 2741 UntypedExpr::Int { value, .. } if value.starts_with('-') => self.int(&value[1..]), 2742 UntypedExpr::BinOp { .. } => "- ".to_doc().append(self.expr(expression)), 2743 2744 UntypedExpr::Int { .. } 2745 | UntypedExpr::Float { .. } 2746 | UntypedExpr::String { .. } 2747 | UntypedExpr::Block { .. } 2748 | UntypedExpr::Var { .. } 2749 | UntypedExpr::Fn { .. } 2750 | UntypedExpr::List { .. } 2751 | UntypedExpr::Call { .. } 2752 | UntypedExpr::PipeLine { .. } 2753 | UntypedExpr::Case { .. } 2754 | UntypedExpr::FieldAccess { .. } 2755 | UntypedExpr::Tuple { .. } 2756 | UntypedExpr::TupleIndex { .. } 2757 | UntypedExpr::Todo { .. } 2758 | UntypedExpr::Panic { .. } 2759 | UntypedExpr::Echo { .. } 2760 | UntypedExpr::BitArray { .. } 2761 | UntypedExpr::RecordUpdate { .. } 2762 | UntypedExpr::NegateBool { .. } => docvec!["-", self.expr(expression)], 2763 } 2764 } 2765 2766 fn use_<'a>(&mut self, use_: &'a UntypedUse) -> Document<'a> { 2767 let comments = self.pop_comments(use_.location.start); 2768 2769 let call = if use_.call.is_call() { 2770 docvec![" ", self.expr(&use_.call)] 2771 } else { 2772 docvec![break_("", " "), self.expr(&use_.call)].nest(INDENT) 2773 } 2774 .group(); 2775 2776 let doc = if use_.assignments.is_empty() { 2777 docvec!["use <-", call] 2778 } else { 2779 let assignments = use_.assignments.iter().map(|use_assignment| { 2780 let pattern = self.pattern(&use_assignment.pattern); 2781 let annotation = use_assignment 2782 .annotation 2783 .as_ref() 2784 .map(|a| ": ".to_doc().append(self.type_ast(a))); 2785 2786 pattern.append(annotation).group() 2787 }); 2788 let assignments = Itertools::intersperse(assignments, break_(",", ", ")); 2789 let left = ["use".to_doc(), break_("", " ")] 2790 .into_iter() 2791 .chain(assignments); 2792 let left = concat(left).nest(INDENT).append(break_("", " ")).group(); 2793 docvec![left, "<-", call].group() 2794 }; 2795 2796 commented(doc, comments) 2797 } 2798 2799 fn assert<'a>(&mut self, assert: &'a UntypedAssert) -> Document<'a> { 2800 let comments = self.pop_comments(assert.location.start); 2801 2802 let expression = if assert.value.is_binop() || assert.value.is_pipeline() { 2803 self.expr(&assert.value).nest(INDENT) 2804 } else { 2805 self.expr(&assert.value) 2806 }; 2807 2808 let doc = 2809 self.append_as_message(expression, PrecedingAs::Expression, assert.message.as_ref()); 2810 commented(docvec!["assert ", doc], comments) 2811 } 2812 2813 fn bit_array<'a>( 2814 &mut self, 2815 segments: Vec<Document<'a>>, 2816 packing: ItemsPacking, 2817 location: &SrcSpan, 2818 ) -> Document<'a> { 2819 let comments = self.pop_comments(location.end); 2820 let comments_doc = printed_comments(comments, false); 2821 2822 // Avoid adding illegal comma in empty bit array by explicitly handling it 2823 if segments.is_empty() { 2824 // We take all comments that come _before_ the end of the bit array, 2825 // that is all comments that are inside "<<" and ">>", if there's 2826 // any comment we want to put it inside the empty bit array! 2827 // Refer to the `list` function for a similar procedure. 2828 return match comments_doc { 2829 None => "<<>>".to_doc(), 2830 Some(comments) => "<<" 2831 .to_doc() 2832 .append(break_("", "").nest(INDENT)) 2833 .append(comments) 2834 .append(break_("", "")) 2835 .append(">>") 2836 // vvv We want to make sure the comments are on a separate 2837 // line from the opening and closing angle brackets so 2838 // we force the breaks to be split on newlines. 2839 .force_break(), 2840 }; 2841 } 2842 2843 let comma = match packing { 2844 ItemsPacking::FitMultiplePerLine => flex_break(",", ", "), 2845 ItemsPacking::FitOnePerLine | ItemsPacking::BreakOnePerLine => break_(",", ", "), 2846 }; 2847 2848 let last_break = break_(",", ""); 2849 let doc = break_("<<", "<<") 2850 .append(join(segments, comma)) 2851 .nest(INDENT); 2852 2853 let doc = match comments_doc { 2854 None => doc.append(last_break).append(">>"), 2855 Some(comments) => doc 2856 .append(last_break.nest(INDENT)) 2857 // ^ Notice how in this case we nest the final break before 2858 // adding it: this way the comments are going to be as 2859 // indented as the bit array items. 2860 .append(comments.nest(INDENT)) 2861 .append(line()) 2862 .append(">>") 2863 .force_break(), 2864 }; 2865 2866 match packing { 2867 ItemsPacking::FitOnePerLine | ItemsPacking::FitMultiplePerLine => doc.group(), 2868 ItemsPacking::BreakOnePerLine => doc.force_break(), 2869 } 2870 } 2871 2872 fn bit_array_segment_expr<'a>(&mut self, expression: &'a UntypedExpr) -> Document<'a> { 2873 match expression { 2874 UntypedExpr::BinOp { .. } => wrap_block(self.expr(expression)), 2875 2876 UntypedExpr::Int { .. } 2877 | UntypedExpr::Float { .. } 2878 | UntypedExpr::String { .. } 2879 | UntypedExpr::Var { .. } 2880 | UntypedExpr::Fn { .. } 2881 | UntypedExpr::List { .. } 2882 | UntypedExpr::Call { .. } 2883 | UntypedExpr::PipeLine { .. } 2884 | UntypedExpr::Case { .. } 2885 | UntypedExpr::FieldAccess { .. } 2886 | UntypedExpr::Tuple { .. } 2887 | UntypedExpr::TupleIndex { .. } 2888 | UntypedExpr::Todo { .. } 2889 | UntypedExpr::Panic { .. } 2890 | UntypedExpr::Echo { .. } 2891 | UntypedExpr::BitArray { .. } 2892 | UntypedExpr::RecordUpdate { .. } 2893 | UntypedExpr::NegateBool { .. } 2894 | UntypedExpr::NegateInt { .. } 2895 | UntypedExpr::Block { .. } => self.expr(expression), 2896 } 2897 } 2898 2899 fn statement<'a>(&mut self, statement: &'a UntypedStatement) -> Document<'a> { 2900 match statement { 2901 Statement::Expression(expression) => self.expr(expression), 2902 Statement::Assignment(assignment) => self.assignment(assignment), 2903 Statement::Use(use_) => self.use_(use_), 2904 Statement::Assert(assert) => self.assert(assert), 2905 } 2906 } 2907 2908 fn block<'a>( 2909 &mut self, 2910 location: &SrcSpan, 2911 statements: &'a Vec1<UntypedStatement>, 2912 force_breaks: bool, 2913 ) -> Document<'a> { 2914 let statements_doc = 2915 docvec![break_("", " "), self.statements(statements.as_vec())].nest(INDENT); 2916 let trailing_comments = self.pop_comments(location.end); 2917 let trailing_comments = printed_comments(trailing_comments, false); 2918 let block_doc = match trailing_comments { 2919 Some(trailing_comments_doc) => docvec![ 2920 "{", 2921 statements_doc, 2922 line().nest(INDENT), 2923 trailing_comments_doc.nest(INDENT), 2924 line(), 2925 "}" 2926 ] 2927 .force_break(), 2928 None => docvec!["{", statements_doc, break_("", " "), "}"], 2929 }; 2930 2931 if force_breaks { 2932 block_doc.force_break().group() 2933 } else { 2934 block_doc.group() 2935 } 2936 } 2937 2938 pub fn wrap_function_call_arguments<'a, I>( 2939 &mut self, 2940 arguments: I, 2941 location: &SrcSpan, 2942 ) -> Document<'a> 2943 where 2944 I: IntoIterator<Item = Document<'a>>, 2945 { 2946 let mut arguments = arguments.into_iter().peekable(); 2947 if arguments.peek().is_none() { 2948 return "()".to_doc(); 2949 } 2950 2951 let arguments_doc = break_("", "") 2952 .append(join(arguments, break_(",", ", "))) 2953 .nest_if_broken(INDENT); 2954 2955 // We get all remaining comments that come before the call's closing 2956 // parenthesis. 2957 // If there's any we add those before the closing parenthesis instead 2958 // of moving those out of the call. 2959 // Otherwise those would be moved out of the call. 2960 let comments = self.pop_comments(location.end); 2961 let closing_parens = match printed_comments(comments, false) { 2962 None => docvec![break_(",", ""), ")"], 2963 Some(comment) => { 2964 docvec![break_(",", "").nest(INDENT), comment, line(), ")"].force_break() 2965 } 2966 }; 2967 2968 "(".to_doc() 2969 .append(arguments_doc) 2970 .append(closing_parens) 2971 .group() 2972 } 2973 2974 pub fn wrap_arguments<'a, I>(&mut self, arguments: I, comments_limit: u32) -> Document<'a> 2975 where 2976 I: IntoIterator<Item = Document<'a>>, 2977 { 2978 let mut arguments = arguments.into_iter().peekable(); 2979 if arguments.peek().is_none() { 2980 let comments = self.pop_comments(comments_limit); 2981 return match printed_comments(comments, false) { 2982 Some(comments) => "(" 2983 .to_doc() 2984 .append(break_("", "")) 2985 .append(comments) 2986 .nest_if_broken(INDENT) 2987 .force_break() 2988 .append(break_("", "")) 2989 .append(")"), 2990 None => "()".to_doc(), 2991 }; 2992 } 2993 let doc = break_("(", "(").append(join(arguments, break_(",", ", "))); 2994 2995 // Include trailing comments if there are any 2996 let comments = self.pop_comments(comments_limit); 2997 match printed_comments(comments, false) { 2998 Some(comments) => doc 2999 .append(break_(",", "")) 3000 .append(comments) 3001 .nest_if_broken(INDENT) 3002 .force_break() 3003 .append(break_("", "")) 3004 .append(")"), 3005 None => doc 3006 .nest_if_broken(INDENT) 3007 .append(break_(",", "")) 3008 .append(")"), 3009 } 3010 } 3011 3012 pub fn wrap_arguments_with_spread<'a, I>( 3013 &mut self, 3014 arguments: I, 3015 comments_limit: u32, 3016 ) -> Document<'a> 3017 where 3018 I: IntoIterator<Item = Document<'a>>, 3019 { 3020 let mut arguments = arguments.into_iter().peekable(); 3021 if arguments.peek().is_none() { 3022 return self.wrap_arguments(arguments, comments_limit); 3023 } 3024 let doc = break_("(", "(") 3025 .append(join(arguments, break_(",", ", "))) 3026 .append(break_(",", ", ")) 3027 .append(".."); 3028 3029 // Include trailing comments if there are any 3030 let comments = self.pop_comments(comments_limit); 3031 match printed_comments(comments, false) { 3032 Some(comments) => doc 3033 .append(break_(",", "")) 3034 .append(comments) 3035 .nest_if_broken(INDENT) 3036 .force_break() 3037 .append(break_("", "")) 3038 .append(")"), 3039 None => doc 3040 .nest_if_broken(INDENT) 3041 .append(break_(",", "")) 3042 .append(")"), 3043 } 3044 } 3045 3046 /// Given some regular comments it pretty prints those with any respective 3047 /// doc comment that might be preceding those. 3048 /// For example: 3049 /// 3050 /// ```gleam 3051 /// /// Doc 3052 /// // comment 3053 /// 3054 /// /// Doc 3055 /// pub fn wibble() {} 3056 /// ``` 3057 /// 3058 /// We don't want the first doc comment to be merged together with 3059 /// `wibble`'s doc comment, so when we run into comments like `// comment` 3060 /// we need to first print all documentation comments that come before it. 3061 /// 3062 fn printed_documented_comments<'a, 'b>( 3063 &mut self, 3064 comments: impl IntoIterator<Item = (u32, Option<&'b str>)>, 3065 ) -> Option<Document<'a>> { 3066 let mut comments = comments.into_iter().peekable(); 3067 let _ = comments.peek()?; 3068 3069 let mut doc = Vec::new(); 3070 while let Some(c) = comments.next() { 3071 let (is_doc_commented, c) = match c { 3072 (comment_start, Some(c)) => { 3073 let doc_comment = self.doc_comments(comment_start); 3074 let is_doc_commented = !doc_comment.is_empty(); 3075 doc.push(doc_comment); 3076 (is_doc_commented, c) 3077 } 3078 (_, None) => continue, 3079 }; 3080 doc.push("//".to_doc().append(EcoString::from(c))); 3081 match comments.peek() { 3082 // Next line is a comment 3083 Some((_, Some(_))) => doc.push(line()), 3084 // Next line is empty 3085 Some((_, None)) => { 3086 let _ = comments.next(); 3087 doc.push(lines(2)); 3088 } 3089 // We've reached the end, there are no more lines 3090 None => { 3091 if is_doc_commented { 3092 doc.push(lines(2)); 3093 } else { 3094 doc.push(line()); 3095 } 3096 } 3097 } 3098 } 3099 let doc = concat(doc); 3100 Some(doc.force_break()) 3101 } 3102 3103 fn append_as_message<'a>( 3104 &mut self, 3105 doc: Document<'a>, 3106 preceding_as: PrecedingAs, 3107 message: Option<&'a UntypedExpr>, 3108 ) -> Document<'a> { 3109 let Some(message) = message else { return doc }; 3110 3111 let comments = self.pop_comments(message.location().start); 3112 let comments = printed_comments(comments, false); 3113 3114 let as_ = match preceding_as { 3115 PrecedingAs::Keyword => " as".to_doc(), 3116 PrecedingAs::Expression => docvec![break_("", " "), "as"].nest(INDENT), 3117 }; 3118 3119 let doc = match comments { 3120 // If there's comments between the document and the message we want 3121 // the `as` bit to be on the same line as the original document and 3122 // go on a new indented line with the message and comments: 3123 // ```gleam 3124 // todo as 3125 // // comment! 3126 // "wibble" 3127 // ``` 3128 Some(comments) => docvec![ 3129 doc.group(), 3130 as_, 3131 docvec![line(), comments, line(), self.expr(message).group()].nest(INDENT) 3132 ], 3133 3134 None => { 3135 let message = match (preceding_as, message) { 3136 // If we have `as` preceded by a keyword (like with `panic` and `todo`) 3137 // and the message is a block, we don't want to nest it any further. That is, 3138 // we want it to look like this: 3139 // ```gleam 3140 // panic as { 3141 // wibble wobble 3142 // } 3143 // ``` 3144 // instead of this: 3145 // ```gleam 3146 // panic as { 3147 // wibble wobble 3148 // } 3149 // ``` 3150 (PrecedingAs::Keyword, UntypedExpr::Block { .. }) => self.expr(message).group(), 3151 _ => self.expr(message).group().nest(INDENT), 3152 }; 3153 docvec![doc.group(), as_, " ", message] 3154 } 3155 }; 3156 3157 doc.group() 3158 } 3159 3160 fn echo<'a>( 3161 &mut self, 3162 expression: &'a Option<Box<UntypedExpr>>, 3163 message: &'a Option<Box<UntypedExpr>>, 3164 ) -> Document<'a> { 3165 let Some(expression) = expression else { 3166 return self.append_as_message( 3167 "echo".to_doc(), 3168 PrecedingAs::Keyword, 3169 message.as_deref(), 3170 ); 3171 }; 3172 3173 // When a binary expression gets broken on multiple lines we don't want 3174 // it to be on the same line as echo, or it would look confusing; 3175 // instead it's nested onto a new line: 3176 // 3177 // ```gleam 3178 // echo first 3179 // |> wobble 3180 // |> wibble 3181 // ``` 3182 // 3183 // So it's easier to see echo is printing the whole thing. Otherwise, 3184 // it would look like echo is printing just the first item: 3185 // 3186 // ```gleam 3187 // echo first 3188 // |> wobble 3189 // |> wibble 3190 // ``` 3191 // 3192 let doc = self.expr(expression); 3193 if expression.is_binop() || expression.is_pipeline() { 3194 let doc = self.append_as_message( 3195 doc.nest(INDENT), 3196 PrecedingAs::Expression, 3197 message.as_deref(), 3198 ); 3199 docvec!["echo ", doc] 3200 } else { 3201 docvec![ 3202 "echo ", 3203 self.append_as_message(doc, PrecedingAs::Expression, message.as_deref()) 3204 ] 3205 } 3206 } 3207} 3208 3209/// This is used to describe the kind of things that might preceding an `as` 3210/// message that can be added to various places: `panic`, `echo`, `let assert`, 3211/// `assert`, `todo`. 3212/// 3213/// It might be preceded by a keyword, like with `echo` and `panic`, or by 3214/// an expression, like in `assert` or `let assert`. 3215/// 3216enum PrecedingAs { 3217 /// An expression is preceding the `as` message: 3218 /// ```gleam 3219 /// echo 1 as "message" 3220 /// assert 1 == 2 as "message" 3221 /// let assert Ok(_) = result as "message" 3222 /// ``` 3223 /// 3224 Expression, 3225 3226 /// A keyword is preceding the `as` message: 3227 /// ```gleam 3228 /// 1 |> echo as "message" 3229 /// panic as "message" 3230 /// todo as "message" 3231 /// ``` 3232 /// 3233 Keyword, 3234} 3235 3236fn init_and_last<T>(vec: &[T]) -> Option<(&[T], &T)> { 3237 match vec { 3238 [] => None, 3239 _ => match vec.split_at(vec.len() - 1) { 3240 (init, [last]) => Some((init, last)), 3241 _ => panic!("unreachable"), 3242 }, 3243 } 3244} 3245 3246impl<'a> Documentable<'a> for &'a ArgNames { 3247 fn to_doc(self) -> Document<'a> { 3248 match self { 3249 ArgNames::Named { name, .. } | ArgNames::Discard { name, .. } => name.to_doc(), 3250 ArgNames::LabelledDiscard { label, name, .. } 3251 | ArgNames::NamedLabelled { label, name, .. } => { 3252 docvec![label, " ", name] 3253 } 3254 } 3255 } 3256} 3257 3258fn pub_(publicity: Publicity) -> Document<'static> { 3259 match publicity { 3260 Publicity::Public | Publicity::Internal { .. } => "pub ".to_doc(), 3261 Publicity::Private => nil(), 3262 } 3263} 3264 3265impl<'a> Documentable<'a> for &'a UnqualifiedImport { 3266 fn to_doc(self) -> Document<'a> { 3267 self.name.as_str().to_doc().append(match &self.as_name { 3268 None => nil(), 3269 Some(s) => " as ".to_doc().append(s.as_str()), 3270 }) 3271 } 3272} 3273 3274impl<'a> Documentable<'a> for &'a BinOp { 3275 fn to_doc(self) -> Document<'a> { 3276 match self { 3277 BinOp::And => "&&", 3278 BinOp::Or => "||", 3279 BinOp::LtInt => "<", 3280 BinOp::LtEqInt => "<=", 3281 BinOp::LtFloat => "<.", 3282 BinOp::LtEqFloat => "<=.", 3283 BinOp::Eq => "==", 3284 BinOp::NotEq => "!=", 3285 BinOp::GtEqInt => ">=", 3286 BinOp::GtInt => ">", 3287 BinOp::GtEqFloat => ">=.", 3288 BinOp::GtFloat => ">.", 3289 BinOp::AddInt => "+", 3290 BinOp::AddFloat => "+.", 3291 BinOp::SubInt => "-", 3292 BinOp::SubFloat => "-.", 3293 BinOp::MultInt => "*", 3294 BinOp::MultFloat => "*.", 3295 BinOp::DivInt => "/", 3296 BinOp::DivFloat => "/.", 3297 BinOp::RemainderInt => "%", 3298 BinOp::Concatenate => "<>", 3299 } 3300 .to_doc() 3301 } 3302} 3303 3304#[allow(clippy::enum_variant_names)] 3305#[derive(Debug)] 3306/// This is used to determine how to fit the items of a list, or the segments of 3307/// a bit array in a line. 3308/// 3309enum ItemsPacking { 3310 /// Try and fit everything on a single line; if the items don't fit, break 3311 /// the list putting each item into its own line. 3312 /// 3313 /// ```gleam 3314 /// // unbroken 3315 /// [1, 2, 3] 3316 /// 3317 /// // broken 3318 /// [ 3319 /// 1, 3320 /// 2, 3321 /// 3, 3322 /// ] 3323 /// ``` 3324 /// 3325 FitOnePerLine, 3326 3327 /// Try and fit everything on a single line; if the items don't fit, break 3328 /// the list putting as many items as possible in a single line. 3329 /// 3330 /// ```gleam 3331 /// // unbroken 3332 /// [1, 2, 3] 3333 /// 3334 /// // broken 3335 /// [ 3336 /// 1, 2, 3, ... 3337 /// 4, 100, 3338 /// ] 3339 /// ``` 3340 /// 3341 FitMultiplePerLine, 3342 3343 /// Always break the list, putting each item into its own line: 3344 /// 3345 /// ```gleam 3346 /// [ 3347 /// 1, 3348 /// 2, 3349 /// 3, 3350 /// ] 3351 /// ``` 3352 /// 3353 BreakOnePerLine, 3354} 3355 3356pub fn break_block(doc: Document<'_>) -> Document<'_> { 3357 "{".to_doc() 3358 .append(line().append(doc).nest(INDENT)) 3359 .append(line()) 3360 .append("}") 3361 .force_break() 3362} 3363 3364pub fn wrap_block(doc: Document<'_>) -> Document<'_> { 3365 break_("{", "{ ") 3366 .append(doc) 3367 .nest(INDENT) 3368 .append(break_("", " ")) 3369 .append("}") 3370} 3371 3372fn printed_comments<'a, 'comments>( 3373 comments: impl IntoIterator<Item = Option<&'comments str>>, 3374 trailing_newline: bool, 3375) -> Option<Document<'a>> { 3376 let mut comments = comments.into_iter().peekable(); 3377 let _ = comments.peek()?; 3378 3379 let mut doc = Vec::new(); 3380 while let Some(c) = comments.next() { 3381 let c = match c { 3382 Some(c) => c, 3383 None => continue, 3384 }; 3385 doc.push("//".to_doc().append(EcoString::from(c))); 3386 match comments.peek() { 3387 // Next line is a comment 3388 Some(Some(_)) => doc.push(line()), 3389 // Next line is empty 3390 Some(None) => { 3391 let _ = comments.next(); 3392 match comments.peek() { 3393 Some(_) => doc.push(lines(2)), 3394 None => { 3395 if trailing_newline { 3396 doc.push(lines(2)); 3397 } 3398 } 3399 } 3400 } 3401 // We've reached the end, there are no more lines 3402 None => { 3403 if trailing_newline { 3404 doc.push(line()); 3405 } 3406 } 3407 } 3408 } 3409 let doc = concat(doc); 3410 if trailing_newline { 3411 Some(doc.force_break()) 3412 } else { 3413 Some(doc) 3414 } 3415} 3416 3417fn commented<'a, 'comments>( 3418 doc: Document<'a>, 3419 comments: impl IntoIterator<Item = Option<&'comments str>>, 3420) -> Document<'a> { 3421 match printed_comments(comments, true) { 3422 Some(comments) => comments.append(doc.group()), 3423 None => doc, 3424 } 3425} 3426 3427fn bit_array_segment<Value, Type, ToDoc>( 3428 segment: &BitArraySegment<Value, Type>, 3429 mut to_doc: ToDoc, 3430) -> Document<'_> 3431where 3432 ToDoc: FnMut(&Value) -> Document<'_>, 3433{ 3434 match segment { 3435 BitArraySegment { value, options, .. } if options.is_empty() => to_doc(value), 3436 3437 BitArraySegment { value, options, .. } => to_doc(value).append(":").append(join( 3438 options 3439 .iter() 3440 .map(|option| segment_option(option, |value| to_doc(value))), 3441 "-".to_doc(), 3442 )), 3443 } 3444} 3445 3446fn segment_option<ToDoc, Value>(option: &BitArrayOption<Value>, mut to_doc: ToDoc) -> Document<'_> 3447where 3448 ToDoc: FnMut(&Value) -> Document<'_>, 3449{ 3450 match option { 3451 BitArrayOption::Bytes { .. } => "bytes".to_doc(), 3452 BitArrayOption::Bits { .. } => "bits".to_doc(), 3453 BitArrayOption::Int { .. } => "int".to_doc(), 3454 BitArrayOption::Float { .. } => "float".to_doc(), 3455 BitArrayOption::Utf8 { .. } => "utf8".to_doc(), 3456 BitArrayOption::Utf16 { .. } => "utf16".to_doc(), 3457 BitArrayOption::Utf32 { .. } => "utf32".to_doc(), 3458 BitArrayOption::Utf8Codepoint { .. } => "utf8_codepoint".to_doc(), 3459 BitArrayOption::Utf16Codepoint { .. } => "utf16_codepoint".to_doc(), 3460 BitArrayOption::Utf32Codepoint { .. } => "utf32_codepoint".to_doc(), 3461 BitArrayOption::Signed { .. } => "signed".to_doc(), 3462 BitArrayOption::Unsigned { .. } => "unsigned".to_doc(), 3463 BitArrayOption::Big { .. } => "big".to_doc(), 3464 BitArrayOption::Little { .. } => "little".to_doc(), 3465 BitArrayOption::Native { .. } => "native".to_doc(), 3466 3467 BitArrayOption::Size { 3468 value, 3469 short_form: false, 3470 .. 3471 } => "size" 3472 .to_doc() 3473 .append("(") 3474 .append(to_doc(value)) 3475 .append(")"), 3476 3477 BitArrayOption::Size { 3478 value, 3479 short_form: true, 3480 .. 3481 } => to_doc(value), 3482 3483 BitArrayOption::Unit { value, .. } => "unit" 3484 .to_doc() 3485 .append("(") 3486 .append(eco_format!("{value}")) 3487 .append(")"), 3488 } 3489} 3490 3491pub fn comments_before<'a>( 3492 comments: &'a [Comment<'a>], 3493 empty_lines: &'a [u32], 3494 limit: u32, 3495 retain_empty_lines: bool, 3496) -> ( 3497 impl Iterator<Item = (u32, Option<&'a str>)>, 3498 &'a [Comment<'a>], 3499 &'a [u32], 3500) { 3501 let end_comments = comments 3502 .iter() 3503 .position(|c| c.start > limit) 3504 .unwrap_or(comments.len()); 3505 let end_empty_lines = empty_lines 3506 .iter() 3507 .position(|l| *l > limit) 3508 .unwrap_or(empty_lines.len()); 3509 let popped_comments = comments 3510 .get(0..end_comments) 3511 .expect("0..end_comments is guaranteed to be in bounds") 3512 .iter() 3513 .map(|c| (c.start, Some(c.content))); 3514 let popped_empty_lines = if retain_empty_lines { empty_lines } else { &[] } 3515 .get(0..end_empty_lines) 3516 .unwrap_or(&[]) 3517 .iter() 3518 .map(|i| (i, i)) 3519 // compact consecutive empty lines into a single line 3520 .coalesce(|(a_start, a_end), (b_start, b_end)| { 3521 if *a_end + 1 == *b_start { 3522 Ok((a_start, b_end)) 3523 } else { 3524 Err(((a_start, a_end), (b_start, b_end))) 3525 } 3526 }) 3527 .map(|l| (*l.0, None)); 3528 let popped = popped_comments 3529 .merge_by(popped_empty_lines, |(a, _), (b, _)| a < b) 3530 .skip_while(|(_, comment_or_line)| comment_or_line.is_none()); 3531 ( 3532 popped, 3533 comments.get(end_comments..).expect("in bounds"), 3534 empty_lines.get(end_empty_lines..).expect("in bounds"), 3535 ) 3536} 3537 3538fn is_breakable_argument(expression: &UntypedExpr, arity: usize) -> bool { 3539 match expression { 3540 // A call is only breakable if it is the only argument 3541 UntypedExpr::Call { .. } => arity == 1, 3542 3543 UntypedExpr::Fn { .. } 3544 | UntypedExpr::Block { .. } 3545 | UntypedExpr::Case { .. } 3546 | UntypedExpr::List { .. } 3547 | UntypedExpr::Tuple { .. } 3548 | UntypedExpr::BitArray { .. } => true, 3549 3550 UntypedExpr::Int { .. } 3551 | UntypedExpr::Float { .. } 3552 | UntypedExpr::String { .. } 3553 | UntypedExpr::Var { .. } 3554 | UntypedExpr::BinOp { .. } 3555 | UntypedExpr::PipeLine { .. } 3556 | UntypedExpr::FieldAccess { .. } 3557 | UntypedExpr::TupleIndex { .. } 3558 | UntypedExpr::Todo { .. } 3559 | UntypedExpr::Panic { .. } 3560 | UntypedExpr::Echo { .. } 3561 | UntypedExpr::RecordUpdate { .. } 3562 | UntypedExpr::NegateBool { .. } 3563 | UntypedExpr::NegateInt { .. } => false, 3564 } 3565} 3566 3567enum CallArgFormatting<'a, A> { 3568 ShorthandLabelled(&'a EcoString), 3569 Unlabelled(&'a A), 3570 Labelled(&'a EcoString, &'a A), 3571} 3572 3573fn expr_call_arg_formatting(argument: &CallArg<UntypedExpr>) -> CallArgFormatting<'_, UntypedExpr> { 3574 match argument { 3575 // An argument supplied using label shorthand syntax. 3576 _ if argument.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3577 argument 3578 .label 3579 .as_ref() 3580 .expect("label shorthand with no label"), 3581 ), 3582 // A labelled argument. 3583 CallArg { 3584 label: Some(label), 3585 value, 3586 .. 3587 } => CallArgFormatting::Labelled(label, value), 3588 // An unlabelled argument. 3589 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3590 } 3591} 3592 3593fn pattern_call_arg_formatting( 3594 argument: &CallArg<UntypedPattern>, 3595) -> CallArgFormatting<'_, UntypedPattern> { 3596 match argument { 3597 // An argument supplied using label shorthand syntax. 3598 _ if argument.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3599 argument 3600 .label 3601 .as_ref() 3602 .expect("label shorthand with no label"), 3603 ), 3604 // A labelled argument. 3605 CallArg { 3606 label: Some(label), 3607 value, 3608 .. 3609 } => CallArgFormatting::Labelled(label, value), 3610 // An unlabelled argument. 3611 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3612 } 3613} 3614 3615fn constant_call_arg_formatting<A, B>( 3616 argument: &CallArg<Constant<A, B>>, 3617) -> CallArgFormatting<'_, Constant<A, B>> { 3618 match argument { 3619 // An argument supplied using label shorthand syntax. 3620 _ if argument.uses_label_shorthand() => CallArgFormatting::ShorthandLabelled( 3621 argument 3622 .label 3623 .as_ref() 3624 .expect("label shorthand with no label"), 3625 ), 3626 // A labelled argument. 3627 CallArg { 3628 label: Some(label), 3629 value, 3630 .. 3631 } => CallArgFormatting::Labelled(label, value), 3632 // An unlabelled argument. 3633 CallArg { value, .. } => CallArgFormatting::Unlabelled(value), 3634 } 3635} 3636 3637struct AttributesPrinter<'a> { 3638 external_erlang: &'a Option<(EcoString, EcoString, SrcSpan)>, 3639 external_javascript: &'a Option<(EcoString, EcoString, SrcSpan)>, 3640 deprecation: &'a Deprecation, 3641 internal: bool, 3642} 3643 3644impl<'a> AttributesPrinter<'a> { 3645 pub fn new() -> Self { 3646 Self { 3647 external_erlang: &None, 3648 external_javascript: &None, 3649 deprecation: &Deprecation::NotDeprecated, 3650 internal: false, 3651 } 3652 } 3653 3654 pub fn set_external_erlang( 3655 mut self, 3656 external: &'a Option<(EcoString, EcoString, SrcSpan)>, 3657 ) -> Self { 3658 self.external_erlang = external; 3659 self 3660 } 3661 3662 pub fn set_external_javascript( 3663 mut self, 3664 external: &'a Option<(EcoString, EcoString, SrcSpan)>, 3665 ) -> Self { 3666 self.external_javascript = external; 3667 self 3668 } 3669 3670 pub fn set_internal(mut self, publicity: Publicity) -> Self { 3671 self.internal = publicity.is_internal(); 3672 self 3673 } 3674 3675 pub fn set_deprecation(mut self, deprecation: &'a Deprecation) -> Self { 3676 self.deprecation = deprecation; 3677 self 3678 } 3679} 3680 3681impl<'a> Documentable<'a> for AttributesPrinter<'a> { 3682 fn to_doc(self) -> Document<'a> { 3683 let mut attributes = vec![]; 3684 3685 // @deprecated attribute 3686 if let Deprecation::Deprecated { message } = self.deprecation { 3687 attributes.push(docvec!["@deprecated(\"", message, "\")"]) 3688 }; 3689 3690 // @external attributes 3691 if let Some((m, f, _)) = self.external_erlang { 3692 attributes.push(docvec!["@external(erlang, \"", m, "\", \"", f, "\")"]) 3693 }; 3694 3695 if let Some((m, f, _)) = self.external_javascript { 3696 attributes.push(docvec!["@external(javascript, \"", m, "\", \"", f, "\")"]) 3697 }; 3698 3699 // @internal attribute 3700 if self.internal { 3701 attributes.push("@internal".to_doc()); 3702 }; 3703 3704 if attributes.is_empty() { 3705 nil() 3706 } else { 3707 join(attributes, line()).append(line()) 3708 } 3709 } 3710}