Fork of daniellemaywood.uk/gleam — Wasm codegen work
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(¤t_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(¤t_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}