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