Skip to main content

lisette_format/formatter/
expression.rs

1use super::Formatter;
2use super::sequence::{PatternEntry, SiblingEntry};
3use crate::INDENT_WIDTH;
4use crate::comments::prepend_comments;
5use crate::lindig::{Document, concat, flex_break, join, strict_break};
6use syntax::ast::{
7    Annotation, BinaryOperator, Binding, Expression, FormatStringPart, Literal, MatchArm, Pattern,
8    SelectArm, SelectArmPattern, Span, StructFieldAssignment, StructSpread, UnaryOperator,
9};
10
11impl<'a> Formatter<'a> {
12    pub fn expression(&mut self, expression: &'a Expression) -> Document<'a> {
13        let start = expression.get_span().byte_offset;
14        let comments = self.comments.take_comments_before(start);
15
16        let doc = match expression {
17            Expression::Literal { literal, .. } => self.literal(literal),
18            Expression::Identifier { value, .. } => Document::string(value.to_string()),
19            Expression::Unit { .. } => Document::str("()"),
20            Expression::Break { value, .. } => {
21                if let Some(val) = value {
22                    Document::str("break ").append(self.expression(val))
23                } else {
24                    Document::str("break")
25                }
26            }
27            Expression::Continue { .. } => Document::str("continue"),
28            Expression::NoOp => Document::Sequence(vec![]),
29
30            Expression::Paren { expression, .. } => Document::str("(")
31                .append(self.expression(expression))
32                .append(")"),
33
34            Expression::Block { items, span, .. } => self.block(items, span),
35
36            Expression::Let {
37                binding,
38                value,
39                mutable,
40                else_block,
41                ..
42            } => self.let_(binding, value, *mutable, else_block.as_deref()),
43
44            Expression::Return { expression, .. } => self.return_(expression),
45
46            Expression::If {
47                condition,
48                consequence,
49                alternative,
50                ..
51            } => self.if_(condition, consequence, alternative),
52
53            Expression::IfLet {
54                pattern,
55                scrutinee,
56                consequence,
57                alternative,
58                ..
59            } => self.if_let(pattern, scrutinee, consequence, alternative),
60
61            Expression::Match {
62                subject,
63                arms,
64                span,
65                ..
66            } => self.match_(subject, arms, span),
67
68            Expression::Binary {
69                operator,
70                left,
71                right,
72                ..
73            } => self.binary_operator(operator, left, right),
74
75            Expression::Unary {
76                operator,
77                expression,
78                ..
79            } => self.unary_operator(operator, expression),
80
81            Expression::Call {
82                expression,
83                args,
84                spread,
85                type_args,
86                ..
87            } => self.call(expression, args, spread, type_args),
88
89            Expression::DotAccess {
90                expression, member, ..
91            } => self.dot_access(expression, member),
92
93            Expression::IndexedAccess {
94                expression,
95                index,
96                from_colon_syntax,
97                ..
98            } => self.indexed_access(expression, index, *from_colon_syntax),
99
100            Expression::Tuple { elements, .. } => self.tuple(elements),
101
102            Expression::StructCall {
103                name,
104                field_assignments,
105                spread,
106                ..
107            } => self.struct_call(name, field_assignments, spread),
108
109            Expression::Assignment {
110                target,
111                value,
112                compound_operator,
113                ..
114            } => self.assignment(target, value, *compound_operator),
115
116            Expression::Loop { body, .. } => self.loop_(body),
117
118            Expression::While {
119                condition, body, ..
120            } => self.while_(condition, body),
121
122            Expression::WhileLet {
123                pattern,
124                scrutinee,
125                body,
126                ..
127            } => self.while_let(pattern, scrutinee, body),
128
129            Expression::For {
130                binding,
131                iterable,
132                body,
133                ..
134            } => self.for_(binding, iterable, body),
135
136            Expression::Task { expression, .. } => self.task(expression),
137            Expression::Defer { expression, .. } => self.defer_(expression),
138            Expression::Select { arms, span, .. } => self.select(arms, span),
139            Expression::Propagate { expression, .. } => self.propagate_(expression),
140            Expression::Reference { expression, .. } => self.ref_(expression),
141            Expression::RawGo { text } => Self::raw_go(text),
142
143            Expression::TryBlock { items, span, .. } => self.try_block(items, span),
144            Expression::RecoverBlock { items, span, .. } => self.recover_block(items, span),
145            Expression::Range {
146                start,
147                end,
148                inclusive,
149                ..
150            } => self.range(start, end, *inclusive),
151            Expression::Cast {
152                expression,
153                target_type,
154                ..
155            } => self.cast(expression, target_type),
156
157            Expression::Lambda {
158                params,
159                return_annotation,
160                body,
161                span,
162                ..
163            } => self.lambda(params, return_annotation, body, span),
164
165            _ => self.definition(expression),
166        };
167
168        prepend_comments(doc, comments)
169    }
170
171    pub(super) fn literal(&mut self, literal: &'a Literal) -> Document<'a> {
172        match literal {
173            Literal::Integer { value, text } => {
174                if let Some(original) = text {
175                    Document::string(original.clone())
176                } else {
177                    Document::string(value.to_string())
178                }
179            }
180            Literal::Float { value, text } => match text {
181                Some(t) => Document::string(t.clone()),
182                None => {
183                    let s = value.to_string();
184                    if s.contains('.') || s.contains('e') || s.contains('E') {
185                        Document::string(s)
186                    } else {
187                        Document::string(format!("{}.0", s))
188                    }
189                }
190            },
191            Literal::Imaginary(coef) => {
192                if *coef == coef.trunc() && coef.abs() < 1e15 {
193                    Document::string(format!("{}i", *coef as i64))
194                } else {
195                    Document::string(format!("{}i", coef))
196                }
197            }
198            Literal::Boolean(b) => Document::str(if *b { "true" } else { "false" }),
199            Literal::String { value, raw: true } if value.contains('\n') => {
200                Document::verbatim(format!("r\"{value}\""))
201            }
202            Literal::String { value, raw: true } => Document::string(format!("r\"{value}\"")),
203            Literal::String { value, raw: false } if value.contains('\n') => {
204                Document::verbatim(format!("\"{value}\""))
205            }
206            Literal::String { value, raw: false } => Document::string(format!("\"{value}\"")),
207            Literal::Char(c) => Document::string(format!("'{c}'")),
208            Literal::Slice(elements) => self.slice(elements),
209            Literal::FormatString(parts) => self.format_string(parts),
210        }
211    }
212
213    pub(super) fn slice(&mut self, elements: &'a [Expression]) -> Document<'a> {
214        if elements.is_empty() {
215            return Document::str("[]");
216        }
217
218        let elements_docs: Vec<_> = elements.iter().map(|e| self.expression(e)).collect();
219        let elements_doc = join(elements_docs, strict_break(",", ", "));
220
221        Document::str("[")
222            .append(strict_break("", ""))
223            .append(elements_doc)
224            .nest(INDENT_WIDTH)
225            .append(strict_break(",", ""))
226            .append("]")
227            .group()
228    }
229
230    pub(super) fn format_string(&mut self, parts: &'a [FormatStringPart]) -> Document<'a> {
231        let mut docs = vec![Document::str("f\"")];
232
233        for part in parts {
234            match part {
235                FormatStringPart::Text(s) if s.contains('\n') => {
236                    docs.push(Document::verbatim(s.clone()))
237                }
238                FormatStringPart::Text(s) => docs.push(Document::string(s.clone())),
239                FormatStringPart::Expression(e) => {
240                    docs.push(Document::str("{"));
241                    docs.push(self.expression(e));
242                    docs.push(Document::str("}"));
243                }
244            }
245        }
246
247        docs.push(Document::str("\""));
248        concat(docs)
249    }
250
251    pub(super) fn block(&mut self, items: &'a [Expression], span: &Span) -> Document<'a> {
252        let block_end = span.byte_offset + span.byte_length;
253
254        if items.is_empty() {
255            return match self.comments.take_comments_before(block_end) {
256                Some(c) => Document::str("{")
257                    .append(Document::Newline.append(c).nest(INDENT_WIDTH))
258                    .append(Document::Newline)
259                    .append("}")
260                    .force_break(),
261                None => Document::str("{}"),
262            };
263        }
264
265        let mut docs = Vec::new();
266
267        for (i, item) in items.iter().enumerate() {
268            let start = item.get_span().byte_offset;
269
270            if i > 0 {
271                if self.comments.take_empty_lines_before(start) {
272                    docs.push(Document::Newline);
273                    docs.push(Document::Newline);
274                } else {
275                    docs.push(Document::Newline);
276                }
277            }
278
279            docs.push(self.expression(item));
280        }
281
282        let (same_line, standalone, _) = self.comments.take_split_at_line_start(block_end);
283        if let Some(t) = same_line {
284            docs.push(Document::str(" "));
285            docs.push(t);
286        }
287        if let Some(t) = standalone {
288            docs.push(Document::Newline);
289            docs.push(t.force_break());
290        }
291
292        let body = concat(docs);
293
294        Document::str("{")
295            .append(Document::Newline.append(body).nest(INDENT_WIDTH))
296            .append(Document::Newline)
297            .append("}")
298            .force_break()
299    }
300
301    pub(super) fn let_(
302        &mut self,
303        binding: &'a Binding,
304        value: &'a Expression,
305        mutable: bool,
306        else_block: Option<&'a Expression>,
307    ) -> Document<'a> {
308        let keyword = if mutable { "let mut " } else { "let " };
309
310        let base = Document::str(keyword)
311            .append(self.binding(binding))
312            .append(" = ")
313            .append(self.expression(value));
314
315        if let Some(else_expression) = else_block {
316            base.append(" else ").append(self.as_block(else_expression))
317        } else {
318            base
319        }
320    }
321
322    pub(super) fn return_(&mut self, expression: &'a Expression) -> Document<'a> {
323        if matches!(expression, Expression::Unit { .. }) {
324            Document::str("return")
325        } else {
326            Document::str("return ").append(self.expression(expression))
327        }
328    }
329
330    pub(super) fn if_(
331        &mut self,
332        condition: &'a Expression,
333        consequence: &'a Expression,
334        alternative: &'a Expression,
335    ) -> Document<'a> {
336        let if_doc = Document::str("if ")
337            .append(self.expression(condition))
338            .append(" ")
339            .append(self.as_inline_block(consequence));
340
341        match alternative {
342            Expression::Unit { .. } => if_doc,
343            Expression::If { .. } | Expression::IfLet { .. } => {
344                if_doc.append(" else ").append(self.expression(alternative))
345            }
346            _ => if_doc
347                .append(" else ")
348                .append(self.as_inline_block(alternative)),
349        }
350        .group()
351    }
352
353    pub(super) fn if_let(
354        &mut self,
355        pattern: &'a Pattern,
356        scrutinee: &'a Expression,
357        consequence: &'a Expression,
358        alternative: &'a Expression,
359    ) -> Document<'a> {
360        let if_let_doc = Document::str("if let ")
361            .append(self.pattern(pattern))
362            .append(" = ")
363            .append(self.expression(scrutinee))
364            .append(" ")
365            .append(self.as_inline_block(consequence));
366
367        match alternative {
368            Expression::Unit { .. } => if_let_doc,
369            Expression::If { .. } | Expression::IfLet { .. } => if_let_doc
370                .append(" else ")
371                .append(self.expression(alternative)),
372            _ => if_let_doc
373                .append(" else ")
374                .append(self.as_inline_block(alternative)),
375        }
376        .group()
377    }
378
379    pub(super) fn as_block(&mut self, expression: &'a Expression) -> Document<'a> {
380        match expression {
381            Expression::Block { items, span, .. } => self.block(items, span),
382            Expression::NoOp => Document::Sequence(vec![]),
383            _ => Document::str("{ ")
384                .append(self.expression(expression))
385                .append(" }"),
386        }
387    }
388
389    /// Like as_block, but allows single-expression blocks to stay inline.
390    /// Used for if/else branches where `{ expression }` should stay on one line
391    /// when the containing group fits, and expand to multi-line when it doesn't.
392    pub(super) fn as_inline_block(&mut self, expression: &'a Expression) -> Document<'a> {
393        match expression {
394            Expression::Block { items, span, .. } => {
395                if items.len() == 1 && !self.comments.has_comments_in_range(*span) {
396                    let expression = self.expression(&items[0]);
397                    return Document::str("{")
398                        .append(strict_break("", " ").append(expression).nest(INDENT_WIDTH))
399                        .append(strict_break("", " "))
400                        .append("}");
401                }
402                self.block(items, span)
403            }
404            Expression::NoOp => Document::Sequence(vec![]),
405            _ => {
406                let expression = self.expression(expression);
407                Document::str("{")
408                    .append(strict_break("", " ").append(expression).nest(INDENT_WIDTH))
409                    .append(strict_break("", " "))
410                    .append("}")
411            }
412        }
413    }
414
415    pub(super) fn match_arm_entries(&mut self, arms: &'a [MatchArm]) -> Vec<SiblingEntry<'a>> {
416        let mut entries: Vec<SiblingEntry<'a>> = Vec::with_capacity(arms.len());
417        for arm in arms {
418            let start = arm.pattern.get_span().byte_offset;
419            self.push_sibling_entry(&mut entries, start, |s| {
420                let pattern = s.pattern(&arm.pattern);
421                let expression = s.expression(&arm.expression);
422                let pattern_with_guard = if let Some(guard) = &arm.guard {
423                    pattern.append(" if ").append(s.expression(guard))
424                } else {
425                    pattern
426                };
427                pattern_with_guard
428                    .append(" => ")
429                    .append(expression)
430                    .append(",")
431            });
432        }
433        entries
434    }
435
436    pub(super) fn match_(
437        &mut self,
438        subject: &'a Expression,
439        arms: &'a [MatchArm],
440        span: &Span,
441    ) -> Document<'a> {
442        let entries = self.match_arm_entries(arms);
443
444        let header = Document::str("match ").append(self.expression(subject));
445        let body = self.join_sibling_body(entries, span.end());
446        Self::braced_body(header, body)
447    }
448
449    pub(super) fn loop_(&mut self, body: &'a Expression) -> Document<'a> {
450        Document::str("loop ").append(self.as_block(body))
451    }
452
453    pub(super) fn while_(
454        &mut self,
455        condition: &'a Expression,
456        body: &'a Expression,
457    ) -> Document<'a> {
458        Document::str("while ")
459            .append(self.expression(condition))
460            .append(" ")
461            .append(self.as_block(body))
462    }
463
464    pub(super) fn while_let(
465        &mut self,
466        pattern: &'a Pattern,
467        scrutinee: &'a Expression,
468        body: &'a Expression,
469    ) -> Document<'a> {
470        Document::str("while let ")
471            .append(self.pattern(pattern))
472            .append(" = ")
473            .append(self.expression(scrutinee))
474            .append(" ")
475            .append(self.as_block(body))
476    }
477
478    pub(super) fn for_(
479        &mut self,
480        binding: &'a Binding,
481        iterable: &'a Expression,
482        body: &'a Expression,
483    ) -> Document<'a> {
484        Document::str("for ")
485            .append(self.binding(binding))
486            .append(" in ")
487            .append(self.expression(iterable))
488            .append(" ")
489            .append(self.as_block(body))
490    }
491
492    pub(super) fn binary_operator(
493        &mut self,
494        operator: &BinaryOperator,
495        left_operand: &'a Expression,
496        right_operand: &'a Expression,
497    ) -> Document<'a> {
498        use BinaryOperator::*;
499
500        if matches!(operator, Pipeline) {
501            return self.pipeline(left_operand, right_operand);
502        }
503
504        let operator_string = match operator {
505            Addition => "+",
506            Subtraction => "-",
507            Multiplication => "*",
508            Division => "/",
509            Remainder => "%",
510            LessThan => "<",
511            LessThanOrEqual => "<=",
512            GreaterThan => ">",
513            GreaterThanOrEqual => ">=",
514            Equal => "==",
515            NotEqual => "!=",
516            And => "&&",
517            Or => "||",
518            Pipeline => unreachable!(),
519        };
520
521        self.expression(left_operand)
522            .append(" ")
523            .append(operator_string)
524            .append(strict_break("", " "))
525            .append(self.expression(right_operand))
526            .group()
527    }
528
529    pub(super) fn pipeline(&mut self, left: &'a Expression, right: &'a Expression) -> Document<'a> {
530        let mut segments = vec![right];
531        let mut current = left;
532
533        while let Expression::Binary {
534            operator: BinaryOperator::Pipeline,
535            left: l,
536            right: r,
537            ..
538        } = current
539        {
540            segments.push(r);
541            current = l;
542        }
543        segments.push(current);
544        segments.reverse();
545
546        if segments.len() == 2 {
547            return self
548                .expression(segments[0])
549                .append(flex_break("", " "))
550                .append("|> ")
551                .append(self.expression(segments[1]))
552                .nest_if_broken(INDENT_WIDTH)
553                .group();
554        }
555
556        let docs: Vec<_> = segments
557            .iter()
558            .enumerate()
559            .map(|(i, seg)| {
560                if i == 0 {
561                    self.expression(seg)
562                } else {
563                    Document::Newline.append("|> ").append(self.expression(seg))
564                }
565            })
566            .collect();
567
568        concat(docs).nest(INDENT_WIDTH)
569    }
570
571    pub(super) fn unary_operator(
572        &mut self,
573        operator: &UnaryOperator,
574        expression: &'a Expression,
575    ) -> Document<'a> {
576        match operator {
577            UnaryOperator::Negative => Document::str("-").append(self.expression(expression)),
578            UnaryOperator::Not => Document::str("!").append(self.expression(expression)),
579            UnaryOperator::Deref => self.expression(expression).append(".*"),
580        }
581    }
582
583    pub(super) fn call(
584        &mut self,
585        callee: &'a Expression,
586        args: &'a [Expression],
587        spread: &'a Option<Expression>,
588        type_args: &'a [Annotation],
589    ) -> Document<'a> {
590        if let Expression::DotAccess {
591            expression: inner,
592            member,
593            span,
594            ..
595        } = callee
596        {
597            let (root, mut chain_segments) = collect_method_chain(inner);
598            let member_start = span.byte_offset + span.byte_length - member.len() as u32;
599            chain_segments.push(MethodChainSegment {
600                member,
601                member_start,
602                args,
603                spread,
604                type_args,
605            });
606            if chain_segments.len() >= 2 {
607                return self.format_method_chain(root, &chain_segments);
608            }
609            // Single-segment chain: probe-format the root to drain any inner-receiver
610            // comments, then check if comments remain before the member. If so, there
611            // are genuine inter-segment comments and we should use chain formatting.
612            let snapshot = self.comments.cursor_snapshot();
613            let root_doc = self.expression(root);
614            let has_inter_segment_comments = self
615                .comments
616                .has_comments_before(chain_segments[0].member_start);
617            if has_inter_segment_comments {
618                return self.format_method_chain_with_root(root_doc, &chain_segments);
619            }
620            self.comments.restore_cursor(snapshot);
621        }
622
623        let head = self
624            .expression(callee)
625            .append(Self::format_type_args(type_args));
626        self.format_call_with_head(head, args, spread)
627    }
628
629    pub(super) fn format_type_args(type_args: &'a [Annotation]) -> Document<'a> {
630        if type_args.is_empty() {
631            Document::Sequence(vec![])
632        } else {
633            let types: Vec<_> = type_args.iter().map(Self::annotation).collect();
634            Document::str("<")
635                .append(join(types, Document::str(", ")))
636                .append(">")
637        }
638    }
639
640    pub(super) fn format_call_with_head(
641        &mut self,
642        head: Document<'a>,
643        args: &'a [Expression],
644        spread: &'a Option<Expression>,
645    ) -> Document<'a> {
646        if args.is_empty() && spread.is_none() {
647            return head.append("()");
648        }
649
650        if let Some(spread_expr) = spread {
651            if args.is_empty() {
652                let spread_doc = self.expression(spread_expr).append(Document::str("..."));
653                return head
654                    .append("(")
655                    .append(spread_doc.group().next_break_fits(true))
656                    .append(")")
657                    .next_break_fits(false)
658                    .group();
659            }
660            let mut entries = self.call_arg_entries(args);
661            let spread_start = spread_expr.get_span().byte_offset;
662            let spread_leading = self.split_for_rest(&mut entries, spread_start);
663            let spread_doc = self.expression(spread_expr).append(Document::str("..."));
664            let (body, close_sep) =
665                Self::join_pattern_entries(entries, Some((spread_leading, spread_doc)), "");
666            return head
667                .append("(")
668                .append(strict_break("", ""))
669                .append(body)
670                .nest(INDENT_WIDTH)
671                .append(close_sep)
672                .append(")")
673                .next_break_fits(false)
674                .group();
675        }
676
677        let Some((last, init)) = args
678            .split_last()
679            .filter(|(last, _)| is_inlinable_arg(last, args.len()))
680        else {
681            let entries = self.call_arg_entries(args);
682            let (body, close_sep) = Self::join_pattern_entries(entries, None, "");
683            return head
684                .append("(")
685                .append(strict_break("", ""))
686                .append(body)
687                .nest(INDENT_WIDTH)
688                .append(close_sep)
689                .append(")")
690                .group();
691        };
692
693        if init.is_empty() {
694            let last_doc = self.expression(last).group().next_break_fits(true);
695            head.append("(")
696                .append(last_doc)
697                .append(")")
698                .next_break_fits(false)
699                .group()
700        } else {
701            let mut entries = self.call_arg_entries(init);
702            let last_start = last.get_span().byte_offset;
703            let last_leading = self.split_for_rest(&mut entries, last_start);
704            let last_doc = self.expression(last).group().next_break_fits(true);
705            let (body, close_sep) =
706                Self::join_pattern_entries(entries, Some((last_leading, last_doc)), "");
707            head.append("(")
708                .append(strict_break("", ""))
709                .append(body)
710                .nest(INDENT_WIDTH)
711                .append(close_sep)
712                .append(")")
713                .next_break_fits(false)
714                .group()
715        }
716    }
717
718    pub(super) fn call_arg_entries(&mut self, args: &'a [Expression]) -> Vec<PatternEntry<'a>> {
719        let mut entries: Vec<PatternEntry<'a>> = Vec::with_capacity(args.len());
720        for arg in args {
721            self.push_pattern_entry(&mut entries, arg.get_span().byte_offset, |s| {
722                s.expression(arg)
723            });
724        }
725        entries
726    }
727
728    fn format_method_chain(
729        &mut self,
730        root: &'a Expression,
731        segments: &[MethodChainSegment<'a>],
732    ) -> Document<'a> {
733        let root_doc = self.expression(root);
734        self.format_method_chain_with_root(root_doc, segments)
735    }
736
737    fn format_method_chain_with_root(
738        &mut self,
739        root_doc: Document<'a>,
740        segments: &[MethodChainSegment<'a>],
741    ) -> Document<'a> {
742        let segment_docs: Vec<Document<'a>> = segments
743            .iter()
744            .map(|seg| {
745                let comments = self.comments.take_comments_before(seg.member_start);
746                let head = Document::str(".")
747                    .append(seg.member)
748                    .append(Self::format_type_args(seg.type_args));
749                let call_doc = strict_break("", "")
750                    .append(self.format_call_with_head(head, seg.args, seg.spread));
751                match comments {
752                    Some(c) => strict_break("", "")
753                        .append(c)
754                        .force_break()
755                        .append(call_doc),
756                    None => call_doc,
757                }
758            })
759            .collect();
760
761        root_doc
762            .append(concat(segment_docs).nest_if_broken(INDENT_WIDTH))
763            .group()
764    }
765
766    pub(super) fn dot_access(
767        &mut self,
768        expression: &'a Expression,
769        member: &'a str,
770    ) -> Document<'a> {
771        self.expression(expression).append(".").append(member)
772    }
773
774    pub(super) fn indexed_access(
775        &mut self,
776        expression: &'a Expression,
777        index: &'a Expression,
778        from_colon_syntax: bool,
779    ) -> Document<'a> {
780        let body = if from_colon_syntax && let Expression::Range { start, end, .. } = index {
781            let start_doc = match start {
782                Some(s) => self.expression(s),
783                None => Document::str(""),
784            };
785            let end_doc = match end {
786                Some(e) => self.expression(e),
787                None => Document::str(""),
788            };
789            start_doc.append(":").append(end_doc)
790        } else {
791            self.expression(index)
792        };
793        self.expression(expression)
794            .append("[")
795            .append(body)
796            .append("]")
797    }
798
799    pub(super) fn tuple(&mut self, elements: &'a [Expression]) -> Document<'a> {
800        if elements.is_empty() {
801            return Document::str("()");
802        }
803
804        let elements_docs: Vec<_> = elements.iter().map(|e| self.expression(e)).collect();
805        let elements_doc = join(elements_docs, strict_break(",", ", "));
806
807        Document::str("(")
808            .append(strict_break("", ""))
809            .append(elements_doc)
810            .nest(INDENT_WIDTH)
811            .append(strict_break(",", ""))
812            .append(")")
813            .group()
814    }
815
816    pub(super) fn struct_call(
817        &mut self,
818        name: &'a str,
819        fields: &'a [StructFieldAssignment],
820        spread: &'a StructSpread,
821    ) -> Document<'a> {
822        let mut entries: Vec<PatternEntry<'a>> = Vec::with_capacity(fields.len());
823        for f in fields {
824            self.push_pattern_entry(&mut entries, f.name_span.byte_offset, |s| {
825                if let Expression::Identifier { value, .. } = &*f.value
826                    && value == &f.name
827                {
828                    Document::string(f.name.to_string())
829                } else {
830                    Document::string(f.name.to_string())
831                        .append(": ")
832                        .append(s.expression(&f.value))
833                }
834            });
835        }
836
837        let rest_info = match spread {
838            StructSpread::None => None,
839            StructSpread::From(spread_expression) => {
840                let dots_pos = spread_expression.get_span().byte_offset.saturating_sub(2);
841                let leading = self.split_for_rest(&mut entries, dots_pos);
842                Some((
843                    leading,
844                    Document::str("..").append(self.expression(spread_expression)),
845                ))
846            }
847            StructSpread::ZeroFill { span } => {
848                let leading = self.split_for_rest(&mut entries, span.byte_offset);
849                Some((leading, Document::str("..")))
850            }
851        };
852
853        if entries.is_empty() && rest_info.is_none() {
854            return Document::str(name).append(" {}");
855        }
856
857        let (body, close_sep) = Self::join_pattern_entries(entries, rest_info, " ");
858
859        Document::str(name)
860            .append(" {")
861            .append(strict_break(" ", " "))
862            .append(body)
863            .nest(INDENT_WIDTH)
864            .append(close_sep)
865            .append("}")
866            .group()
867    }
868
869    pub(super) fn assignment(
870        &mut self,
871        target: &'a Expression,
872        value: &'a Expression,
873        compound_operator: Option<BinaryOperator>,
874    ) -> Document<'a> {
875        if let Some(op) = compound_operator
876            && let Some(op_str) = match op {
877                BinaryOperator::Addition => Some("+="),
878                BinaryOperator::Subtraction => Some("-="),
879                BinaryOperator::Multiplication => Some("*="),
880                BinaryOperator::Division => Some("/="),
881                BinaryOperator::Remainder => Some("%="),
882                _ => None,
883            }
884            && let Expression::Binary { right, .. } = value
885        {
886            return self
887                .expression(target)
888                .append(" ")
889                .append(op_str)
890                .append(" ")
891                .append(self.expression(right));
892        }
893
894        self.expression(target)
895            .append(" = ")
896            .append(self.expression(value))
897    }
898
899    pub(super) fn lambda(
900        &mut self,
901        params: &'a [Binding],
902        return_annotation: &'a Annotation,
903        body: &'a Expression,
904        _span: &'a Span,
905    ) -> Document<'a> {
906        let params_docs: Vec<_> = params.iter().map(|p| self.binding(p)).collect();
907
908        let params_doc = if params_docs.is_empty() {
909            Document::str("||")
910        } else {
911            Document::str("|")
912                .append(strict_break("", ""))
913                .append(join(params_docs, strict_break(",", ", ")))
914                .nest(INDENT_WIDTH)
915                .append(strict_break(",", ""))
916                .append("|")
917                .group()
918        };
919
920        let return_doc = if return_annotation.is_unknown() {
921            Document::Sequence(vec![])
922        } else {
923            Document::str(" -> ").append(Self::annotation(return_annotation))
924        };
925
926        let body_doc = self.expression(body);
927
928        params_doc.append(return_doc).append(" ").append(body_doc)
929    }
930
931    pub(super) fn task(&mut self, expression: &'a Expression) -> Document<'a> {
932        Document::str("task ").append(self.expression(expression))
933    }
934
935    pub(super) fn defer_(&mut self, expression: &'a Expression) -> Document<'a> {
936        Document::str("defer ").append(self.expression(expression))
937    }
938
939    pub(super) fn try_block(&mut self, items: &'a [Expression], span: &Span) -> Document<'a> {
940        Document::str("try ").append(self.block(items, span))
941    }
942
943    pub(super) fn recover_block(&mut self, items: &'a [Expression], span: &Span) -> Document<'a> {
944        Document::str("recover ").append(self.block(items, span))
945    }
946
947    pub(super) fn range(
948        &mut self,
949        start: &'a Option<Box<Expression>>,
950        end: &'a Option<Box<Expression>>,
951        inclusive: bool,
952    ) -> Document<'a> {
953        let start_doc = match start {
954            Some(e) => self.expression(e),
955            None => Document::Sequence(vec![]),
956        };
957        let end_doc = match end {
958            Some(e) => self.expression(e),
959            None => Document::Sequence(vec![]),
960        };
961        let op = if inclusive { "..=" } else { ".." };
962        start_doc.append(op).append(end_doc)
963    }
964
965    pub(super) fn cast(
966        &mut self,
967        expression: &'a Expression,
968        target_type: &'a Annotation,
969    ) -> Document<'a> {
970        self.expression(expression)
971            .append(" as ")
972            .append(Self::annotation(target_type))
973    }
974
975    pub(super) fn select(&mut self, arms: &'a [SelectArm], span: &Span) -> Document<'a> {
976        let mut entries: Vec<SiblingEntry<'a>> = Vec::with_capacity(arms.len());
977        for (i, arm) in arms.iter().enumerate() {
978            let start = Self::select_arm_start(arm);
979            let upper_bound = arms
980                .get(i + 1)
981                .map(Self::select_arm_start)
982                .unwrap_or_else(|| span.end());
983            self.push_sibling_entry(&mut entries, start, |s| s.select_arm_body(arm, upper_bound));
984        }
985        let body = self.join_sibling_body(entries, span.end());
986        Self::braced_body(Document::str("select"), body)
987    }
988
989    pub(super) fn select_arm_start(arm: &'a SelectArm) -> u32 {
990        match &arm.pattern {
991            SelectArmPattern::Receive { binding, .. } => binding.get_span().byte_offset,
992            SelectArmPattern::Send {
993                send_expression, ..
994            } => send_expression.get_span().byte_offset,
995            SelectArmPattern::MatchReceive {
996                receive_expression, ..
997            } => receive_expression.get_span().byte_offset,
998            SelectArmPattern::WildCard { body } => body.get_span().byte_offset,
999        }
1000    }
1001
1002    pub(super) fn select_arm_body(&mut self, arm: &'a SelectArm, upper_bound: u32) -> Document<'a> {
1003        match &arm.pattern {
1004            SelectArmPattern::Receive {
1005                binding,
1006                receive_expression,
1007                body,
1008                ..
1009            } => Document::str("let ")
1010                .append(self.pattern(binding))
1011                .append(" = ")
1012                .append(self.expression(receive_expression))
1013                .append(" => ")
1014                .append(self.expression(body))
1015                .append(","),
1016            SelectArmPattern::Send {
1017                send_expression,
1018                body,
1019            } => self
1020                .expression(send_expression)
1021                .append(" => ")
1022                .append(self.expression(body))
1023                .append(","),
1024            SelectArmPattern::MatchReceive {
1025                receive_expression,
1026                arms,
1027            } => {
1028                let header = Document::str("match ").append(self.expression(receive_expression));
1029                let last_arm_end = arms
1030                    .last()
1031                    .map(|a| a.expression.get_span().end())
1032                    .unwrap_or(0);
1033                // MatchReceive lacks a body span; find the inner `}` in source.
1034                let body_end = self
1035                    .comments
1036                    .next_byte_at(b'}', last_arm_end, upper_bound)
1037                    .unwrap_or(last_arm_end);
1038                let entries = self.match_arm_entries(arms);
1039                let body = self.join_sibling_body(entries, body_end);
1040                Self::braced_body(header, body).append(",")
1041            }
1042            SelectArmPattern::WildCard { body } => Document::str("_")
1043                .append(" => ")
1044                .append(self.expression(body))
1045                .append(","),
1046        }
1047    }
1048
1049    pub(super) fn propagate_(&mut self, expression: &'a Expression) -> Document<'a> {
1050        self.expression(expression).append("?")
1051    }
1052
1053    pub(super) fn ref_(&mut self, expression: &'a Expression) -> Document<'a> {
1054        Document::str("&").append(self.expression(expression))
1055    }
1056
1057    pub(super) fn raw_go(text: &'a str) -> Document<'a> {
1058        Document::str("@rawgo(\"")
1059            .append(Document::str(text))
1060            .append("\")")
1061    }
1062}
1063
1064struct MethodChainSegment<'a> {
1065    member: &'a str,
1066    member_start: u32,
1067    args: &'a [Expression],
1068    spread: &'a Option<Expression>,
1069    type_args: &'a [Annotation],
1070}
1071
1072fn collect_method_chain(expression: &Expression) -> (&Expression, Vec<MethodChainSegment<'_>>) {
1073    let mut segments = Vec::new();
1074    let mut current = expression;
1075
1076    while let Expression::Call {
1077        expression,
1078        args,
1079        spread,
1080        type_args,
1081        ..
1082    } = current
1083    {
1084        let Expression::DotAccess {
1085            expression: inner,
1086            member,
1087            span,
1088            ..
1089        } = expression.as_ref()
1090        else {
1091            break;
1092        };
1093        let member_start = span.byte_offset + span.byte_length - member.len() as u32;
1094        segments.push(MethodChainSegment {
1095            member,
1096            member_start,
1097            args,
1098            spread,
1099            type_args,
1100        });
1101        current = inner;
1102    }
1103
1104    segments.reverse();
1105    (current, segments)
1106}
1107
1108fn is_inlinable_arg(expression: &Expression, arity: usize) -> bool {
1109    matches!(
1110        expression,
1111        Expression::Lambda { .. }
1112            | Expression::Block { .. }
1113            | Expression::Match { .. }
1114            | Expression::Tuple { .. }
1115            | Expression::Literal {
1116                literal: Literal::Slice(_),
1117                ..
1118            }
1119    ) || matches!(expression, Expression::Call { .. } if arity == 1)
1120}