Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

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