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