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