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