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            BitwiseAnd => "&",
511            BitwiseOr => "|",
512            BitwiseXor => "^",
513            BitwiseAndNot => "&^",
514            ShiftLeft => "<<",
515            ShiftRight => ">>",
516            LessThan => "<",
517            LessThanOrEqual => "<=",
518            GreaterThan => ">",
519            GreaterThanOrEqual => ">=",
520            Equal => "==",
521            NotEqual => "!=",
522            And => "&&",
523            Or => "||",
524            Pipeline => unreachable!(),
525        };
526
527        self.expression(left_operand)
528            .append(" ")
529            .append(operator_string)
530            .append(strict_break("", " "))
531            .append(self.expression(right_operand))
532            .group()
533    }
534
535    pub(super) fn pipeline(&mut self, left: &'a Expression, right: &'a Expression) -> Document<'a> {
536        let mut segments = vec![right];
537        let mut current = left;
538
539        while let Expression::Binary {
540            operator: BinaryOperator::Pipeline,
541            left: l,
542            right: r,
543            ..
544        } = current
545        {
546            segments.push(r);
547            current = l;
548        }
549        segments.push(current);
550        segments.reverse();
551
552        if segments.len() == 2 {
553            return self
554                .expression(segments[0])
555                .append(flex_break("", " "))
556                .append("|> ")
557                .append(self.expression(segments[1]))
558                .nest_if_broken(INDENT_WIDTH)
559                .group();
560        }
561
562        let docs: Vec<_> = segments
563            .iter()
564            .enumerate()
565            .map(|(i, seg)| {
566                if i == 0 {
567                    self.expression(seg)
568                } else {
569                    Document::Newline.append("|> ").append(self.expression(seg))
570                }
571            })
572            .collect();
573
574        concat(docs).nest(INDENT_WIDTH)
575    }
576
577    pub(super) fn unary_operator(
578        &mut self,
579        operator: &UnaryOperator,
580        expression: &'a Expression,
581    ) -> Document<'a> {
582        match operator {
583            UnaryOperator::Negative => Document::str("-").append(self.expression(expression)),
584            UnaryOperator::Not => Document::str("!").append(self.expression(expression)),
585            UnaryOperator::BitwiseNot => Document::str("^").append(self.expression(expression)),
586            UnaryOperator::Deref => self.expression(expression).append(".*"),
587        }
588    }
589
590    pub(super) fn call(
591        &mut self,
592        callee: &'a Expression,
593        args: &'a [Expression],
594        spread: &'a Option<Expression>,
595        type_args: &'a [Annotation],
596    ) -> Document<'a> {
597        if let Expression::DotAccess {
598            expression: inner,
599            member,
600            span,
601            ..
602        } = callee
603        {
604            let (root, mut chain_segments) = collect_method_chain(inner);
605            let member_start = span.byte_offset + span.byte_length - member.len() as u32;
606            chain_segments.push(MethodChainSegment {
607                member,
608                member_start,
609                args,
610                spread,
611                type_args,
612            });
613            if chain_segments.len() >= 2 {
614                return self.format_method_chain(root, &chain_segments);
615            }
616            // Single-segment chain: probe-format the root to drain any inner-receiver
617            // comments, then check if comments remain before the member. If so, there
618            // are genuine inter-segment comments and we should use chain formatting.
619            let snapshot = self.comments.cursor_snapshot();
620            let root_doc = self.expression(root);
621            let has_inter_segment_comments = self
622                .comments
623                .has_comments_before(chain_segments[0].member_start);
624            if has_inter_segment_comments {
625                return self.format_method_chain_with_root(root_doc, &chain_segments);
626            }
627            self.comments.restore_cursor(snapshot);
628        }
629
630        let head = self
631            .expression(callee)
632            .append(Self::format_type_args(type_args));
633        self.format_call_with_head(head, args, spread)
634    }
635
636    pub(super) fn format_type_args(type_args: &'a [Annotation]) -> Document<'a> {
637        if type_args.is_empty() {
638            Document::Sequence(vec![])
639        } else {
640            let types: Vec<_> = type_args.iter().map(Self::annotation).collect();
641            Document::str("<")
642                .append(join(types, Document::str(", ")))
643                .append(">")
644        }
645    }
646
647    pub(super) fn format_call_with_head(
648        &mut self,
649        head: Document<'a>,
650        args: &'a [Expression],
651        spread: &'a Option<Expression>,
652    ) -> Document<'a> {
653        if args.is_empty() && spread.is_none() {
654            return head.append("()");
655        }
656
657        if let Some(spread_expr) = spread {
658            if args.is_empty() {
659                let spread_doc = self.expression(spread_expr).append(Document::str("..."));
660                return head
661                    .append("(")
662                    .append(spread_doc.group().next_break_fits(true))
663                    .append(")")
664                    .next_break_fits(false)
665                    .group();
666            }
667            let mut entries = self.call_arg_entries(args);
668            let spread_start = spread_expr.get_span().byte_offset;
669            let spread_leading = self.split_for_rest(&mut entries, spread_start);
670            let spread_doc = self.expression(spread_expr).append(Document::str("..."));
671            let (body, close_sep) =
672                Self::join_pattern_entries(entries, Some((spread_leading, spread_doc)), "");
673            return head
674                .append("(")
675                .append(strict_break("", ""))
676                .append(body)
677                .nest(INDENT_WIDTH)
678                .append(close_sep)
679                .append(")")
680                .next_break_fits(false)
681                .group();
682        }
683
684        let Some((last, init)) = args
685            .split_last()
686            .filter(|(last, _)| is_inlinable_arg(last, args.len()))
687        else {
688            let entries = self.call_arg_entries(args);
689            let (body, close_sep) = Self::join_pattern_entries(entries, None, "");
690            return head
691                .append("(")
692                .append(strict_break("", ""))
693                .append(body)
694                .nest(INDENT_WIDTH)
695                .append(close_sep)
696                .append(")")
697                .group();
698        };
699
700        if init.is_empty() {
701            let last_doc = self.expression(last).group().next_break_fits(true);
702            head.append("(")
703                .append(last_doc)
704                .append(")")
705                .next_break_fits(false)
706                .group()
707        } else {
708            let mut entries = self.call_arg_entries(init);
709            let last_start = last.get_span().byte_offset;
710            let last_leading = self.split_for_rest(&mut entries, last_start);
711            let last_doc = self.expression(last).group().next_break_fits(true);
712            let (body, close_sep) =
713                Self::join_pattern_entries(entries, Some((last_leading, last_doc)), "");
714            head.append("(")
715                .append(strict_break("", ""))
716                .append(body)
717                .nest(INDENT_WIDTH)
718                .append(close_sep)
719                .append(")")
720                .next_break_fits(false)
721                .group()
722        }
723    }
724
725    pub(super) fn call_arg_entries(&mut self, args: &'a [Expression]) -> Vec<PatternEntry<'a>> {
726        let mut entries: Vec<PatternEntry<'a>> = Vec::with_capacity(args.len());
727        for arg in args {
728            self.push_pattern_entry(&mut entries, arg.get_span().byte_offset, |s| {
729                s.expression(arg)
730            });
731        }
732        entries
733    }
734
735    fn format_method_chain(
736        &mut self,
737        root: &'a Expression,
738        segments: &[MethodChainSegment<'a>],
739    ) -> Document<'a> {
740        let root_doc = self.expression(root);
741        self.format_method_chain_with_root(root_doc, segments)
742    }
743
744    fn format_method_chain_with_root(
745        &mut self,
746        root_doc: Document<'a>,
747        segments: &[MethodChainSegment<'a>],
748    ) -> Document<'a> {
749        let segment_docs: Vec<Document<'a>> = segments
750            .iter()
751            .map(|seg| {
752                let comments = self.comments.take_comments_before(seg.member_start);
753                let head = Document::str(".")
754                    .append(seg.member)
755                    .append(Self::format_type_args(seg.type_args));
756                let call_doc = strict_break("", "")
757                    .append(self.format_call_with_head(head, seg.args, seg.spread));
758                match comments {
759                    Some(c) => strict_break("", "")
760                        .append(c)
761                        .force_break()
762                        .append(call_doc),
763                    None => call_doc,
764                }
765            })
766            .collect();
767
768        root_doc
769            .append(concat(segment_docs).nest_if_broken(INDENT_WIDTH))
770            .group()
771    }
772
773    pub(super) fn dot_access(
774        &mut self,
775        expression: &'a Expression,
776        member: &'a str,
777    ) -> Document<'a> {
778        self.expression(expression).append(".").append(member)
779    }
780
781    pub(super) fn indexed_access(
782        &mut self,
783        expression: &'a Expression,
784        index: &'a Expression,
785        from_colon_syntax: bool,
786    ) -> Document<'a> {
787        let body = if from_colon_syntax && let Expression::Range { start, end, .. } = index {
788            let start_doc = match start {
789                Some(s) => self.expression(s),
790                None => Document::str(""),
791            };
792            let end_doc = match end {
793                Some(e) => self.expression(e),
794                None => Document::str(""),
795            };
796            start_doc.append(":").append(end_doc)
797        } else {
798            self.expression(index)
799        };
800        self.expression(expression)
801            .append("[")
802            .append(body)
803            .append("]")
804    }
805
806    pub(super) fn tuple(&mut self, elements: &'a [Expression]) -> Document<'a> {
807        if elements.is_empty() {
808            return Document::str("()");
809        }
810
811        let elements_docs: Vec<_> = elements.iter().map(|e| self.expression(e)).collect();
812        let elements_doc = join(elements_docs, strict_break(",", ", "));
813
814        Document::str("(")
815            .append(strict_break("", ""))
816            .append(elements_doc)
817            .nest(INDENT_WIDTH)
818            .append(strict_break(",", ""))
819            .append(")")
820            .group()
821    }
822
823    pub(super) fn struct_call(
824        &mut self,
825        name: &'a str,
826        fields: &'a [StructFieldAssignment],
827        spread: &'a StructSpread,
828    ) -> Document<'a> {
829        let mut entries: Vec<PatternEntry<'a>> = Vec::with_capacity(fields.len());
830        for f in fields {
831            self.push_pattern_entry(&mut entries, f.name_span.byte_offset, |s| {
832                if let Expression::Identifier { value, .. } = &*f.value
833                    && value == &f.name
834                {
835                    Document::string(f.name.to_string())
836                } else {
837                    Document::string(f.name.to_string())
838                        .append(": ")
839                        .append(s.expression(&f.value))
840                }
841            });
842        }
843
844        let rest_info = match spread {
845            StructSpread::None => None,
846            StructSpread::From(spread_expression) => {
847                let dots_pos = spread_expression.get_span().byte_offset.saturating_sub(2);
848                let leading = self.split_for_rest(&mut entries, dots_pos);
849                Some((
850                    leading,
851                    Document::str("..").append(self.expression(spread_expression)),
852                ))
853            }
854            StructSpread::ZeroFill { span } => {
855                let leading = self.split_for_rest(&mut entries, span.byte_offset);
856                Some((leading, Document::str("..")))
857            }
858        };
859
860        if entries.is_empty() && rest_info.is_none() {
861            return Document::str(name).append(" {}");
862        }
863
864        let (body, close_sep) = Self::join_pattern_entries(entries, rest_info, " ");
865
866        Document::str(name)
867            .append(" {")
868            .append(strict_break(" ", " "))
869            .append(body)
870            .nest(INDENT_WIDTH)
871            .append(close_sep)
872            .append("}")
873            .group()
874    }
875
876    pub(super) fn assignment(
877        &mut self,
878        target: &'a Expression,
879        value: &'a Expression,
880        compound_operator: Option<BinaryOperator>,
881    ) -> Document<'a> {
882        if let Some(op) = compound_operator
883            && let Some(op_str) = match op {
884                BinaryOperator::Addition => Some("+="),
885                BinaryOperator::Subtraction => Some("-="),
886                BinaryOperator::Multiplication => Some("*="),
887                BinaryOperator::Division => Some("/="),
888                BinaryOperator::Remainder => Some("%="),
889                _ => None,
890            }
891            && let Expression::Binary { right, .. } = value
892        {
893            return self
894                .expression(target)
895                .append(" ")
896                .append(op_str)
897                .append(" ")
898                .append(self.expression(right));
899        }
900
901        self.expression(target)
902            .append(" = ")
903            .append(self.expression(value))
904    }
905
906    pub(super) fn lambda(
907        &mut self,
908        params: &'a [Binding],
909        return_annotation: &'a Annotation,
910        body: &'a Expression,
911        _span: &'a Span,
912    ) -> Document<'a> {
913        let params_docs: Vec<_> = params.iter().map(|p| self.binding(p)).collect();
914
915        let params_doc = if params_docs.is_empty() {
916            Document::str("||")
917        } else {
918            Document::str("|")
919                .append(strict_break("", ""))
920                .append(join(params_docs, strict_break(",", ", ")))
921                .nest(INDENT_WIDTH)
922                .append(strict_break(",", ""))
923                .append("|")
924                .group()
925        };
926
927        let return_doc = if return_annotation.is_unknown() {
928            Document::Sequence(vec![])
929        } else {
930            Document::str(" -> ").append(Self::annotation(return_annotation))
931        };
932
933        let body_doc = self.expression(body);
934
935        params_doc.append(return_doc).append(" ").append(body_doc)
936    }
937
938    pub(super) fn task(&mut self, expression: &'a Expression) -> Document<'a> {
939        Document::str("task ").append(self.expression(expression))
940    }
941
942    pub(super) fn defer_(&mut self, expression: &'a Expression) -> Document<'a> {
943        Document::str("defer ").append(self.expression(expression))
944    }
945
946    pub(super) fn try_block(&mut self, items: &'a [Expression], span: &Span) -> Document<'a> {
947        Document::str("try ").append(self.block(items, span))
948    }
949
950    pub(super) fn recover_block(&mut self, items: &'a [Expression], span: &Span) -> Document<'a> {
951        Document::str("recover ").append(self.block(items, span))
952    }
953
954    pub(super) fn range(
955        &mut self,
956        start: &'a Option<Box<Expression>>,
957        end: &'a Option<Box<Expression>>,
958        inclusive: bool,
959    ) -> Document<'a> {
960        let start_doc = match start {
961            Some(e) => self.expression(e),
962            None => Document::Sequence(vec![]),
963        };
964        let end_doc = match end {
965            Some(e) => self.expression(e),
966            None => Document::Sequence(vec![]),
967        };
968        let op = if inclusive { "..=" } else { ".." };
969        start_doc.append(op).append(end_doc)
970    }
971
972    pub(super) fn cast(
973        &mut self,
974        expression: &'a Expression,
975        target_type: &'a Annotation,
976    ) -> Document<'a> {
977        self.expression(expression)
978            .append(" as ")
979            .append(Self::annotation(target_type))
980    }
981
982    pub(super) fn select(&mut self, arms: &'a [SelectArm], span: &Span) -> Document<'a> {
983        let mut entries: Vec<SiblingEntry<'a>> = Vec::with_capacity(arms.len());
984        for (i, arm) in arms.iter().enumerate() {
985            let start = Self::select_arm_start(arm);
986            let upper_bound = arms
987                .get(i + 1)
988                .map(Self::select_arm_start)
989                .unwrap_or_else(|| span.end());
990            self.push_sibling_entry(&mut entries, start, |s| s.select_arm_body(arm, upper_bound));
991        }
992        let body = self.join_sibling_body(entries, span.end());
993        Self::braced_body(Document::str("select"), body)
994    }
995
996    pub(super) fn select_arm_start(arm: &'a SelectArm) -> u32 {
997        match &arm.pattern {
998            SelectArmPattern::Receive { binding, .. } => binding.get_span().byte_offset,
999            SelectArmPattern::Send {
1000                send_expression, ..
1001            } => send_expression.get_span().byte_offset,
1002            SelectArmPattern::MatchReceive {
1003                receive_expression, ..
1004            } => receive_expression.get_span().byte_offset,
1005            SelectArmPattern::WildCard { body } => body.get_span().byte_offset,
1006        }
1007    }
1008
1009    pub(super) fn select_arm_body(&mut self, arm: &'a SelectArm, upper_bound: u32) -> Document<'a> {
1010        match &arm.pattern {
1011            SelectArmPattern::Receive {
1012                binding,
1013                receive_expression,
1014                body,
1015                ..
1016            } => Document::str("let ")
1017                .append(self.pattern(binding))
1018                .append(" = ")
1019                .append(self.expression(receive_expression))
1020                .append(" => ")
1021                .append(self.expression(body))
1022                .append(","),
1023            SelectArmPattern::Send {
1024                send_expression,
1025                body,
1026            } => self
1027                .expression(send_expression)
1028                .append(" => ")
1029                .append(self.expression(body))
1030                .append(","),
1031            SelectArmPattern::MatchReceive {
1032                receive_expression,
1033                arms,
1034            } => {
1035                let header = Document::str("match ").append(self.expression(receive_expression));
1036                let last_arm_end = arms
1037                    .last()
1038                    .map(|a| a.expression.get_span().end())
1039                    .unwrap_or(0);
1040                // MatchReceive lacks a body span; find the inner `}` in source.
1041                let body_end = self
1042                    .comments
1043                    .next_byte_at(b'}', last_arm_end, upper_bound)
1044                    .unwrap_or(last_arm_end);
1045                let entries = self.match_arm_entries(arms);
1046                let body = self.join_sibling_body(entries, body_end);
1047                Self::braced_body(header, body).append(",")
1048            }
1049            SelectArmPattern::WildCard { body } => Document::str("_")
1050                .append(" => ")
1051                .append(self.expression(body))
1052                .append(","),
1053        }
1054    }
1055
1056    pub(super) fn propagate_(&mut self, expression: &'a Expression) -> Document<'a> {
1057        self.expression(expression).append("?")
1058    }
1059
1060    pub(super) fn ref_(&mut self, expression: &'a Expression) -> Document<'a> {
1061        Document::str("&").append(self.expression(expression))
1062    }
1063
1064    pub(super) fn raw_go(text: &'a str) -> Document<'a> {
1065        Document::str("@rawgo(\"")
1066            .append(Document::str(text))
1067            .append("\")")
1068    }
1069}
1070
1071struct MethodChainSegment<'a> {
1072    member: &'a str,
1073    member_start: u32,
1074    args: &'a [Expression],
1075    spread: &'a Option<Expression>,
1076    type_args: &'a [Annotation],
1077}
1078
1079fn collect_method_chain(expression: &Expression) -> (&Expression, Vec<MethodChainSegment<'_>>) {
1080    let mut segments = Vec::new();
1081    let mut current = expression;
1082
1083    while let Expression::Call {
1084        expression,
1085        args,
1086        spread,
1087        type_args,
1088        ..
1089    } = current
1090    {
1091        let Expression::DotAccess {
1092            expression: inner,
1093            member,
1094            span,
1095            ..
1096        } = expression.as_ref()
1097        else {
1098            break;
1099        };
1100        let member_start = span.byte_offset + span.byte_length - member.len() as u32;
1101        segments.push(MethodChainSegment {
1102            member,
1103            member_start,
1104            args,
1105            spread,
1106            type_args,
1107        });
1108        current = inner;
1109    }
1110
1111    segments.reverse();
1112    (current, segments)
1113}
1114
1115fn is_inlinable_arg(expression: &Expression, arity: usize) -> bool {
1116    matches!(
1117        expression,
1118        Expression::Lambda { .. }
1119            | Expression::Block { .. }
1120            | Expression::Match { .. }
1121            | Expression::Tuple { .. }
1122            | Expression::Literal {
1123                literal: Literal::Slice(_),
1124                ..
1125            }
1126    ) || matches!(expression, Expression::Call { .. } if arity == 1)
1127}