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