1pub use crate::lexer::{Identifier, ModuleName, Operator, Pos};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub struct Span {
22 pub start: usize,
23 pub end: usize,
24}
25
26impl Span {
27 #[must_use]
28 pub const fn new(start: usize, end: usize) -> Self {
29 Self { start, end }
30 }
31
32 #[must_use]
34 pub const fn is_valid(&self) -> bool {
35 self.start <= self.end
36 }
37
38 #[must_use]
40 pub const fn is_empty(&self) -> bool {
41 self.start == self.end
42 }
43
44 #[must_use]
46 pub const fn contains(&self, other: &Self) -> bool {
47 self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum LitKind {
54 Int,
55 Decimal,
56 Text,
57 Char,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum SectionSide {
67 Right,
69 Left,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[non_exhaustive]
76pub enum ImportStyle {
77 Qualified,
79 Unqualified,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct FieldAssign {
85 pub name: Identifier,
86 pub value: Option<Expr>,
89 pub pos: Pos,
90 pub span: Span,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct Alt {
95 pub pat: Pat,
96 pub body: Expr,
97 pub pos: Pos,
98 pub span: Span,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct Binding {
103 pub pat: Pat,
105 pub params: Vec<Pat>,
107 pub expr: Expr,
108 pub pos: Pos,
109 pub span: Span,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
113#[non_exhaustive]
114pub enum Pat {
115 Var {
116 name: Identifier,
117 pos: Pos,
118 span: Span,
119 },
120 Wild {
121 pos: Pos,
122 span: Span,
123 },
124 Con {
125 qualifier: Option<ModuleName>,
126 name: Identifier,
127 args: Vec<Self>,
128 pos: Pos,
129 span: Span,
130 },
131 Tuple {
132 items: Vec<Self>,
133 pos: Pos,
134 span: Span,
135 },
136 List {
137 items: Vec<Self>,
138 pos: Pos,
139 span: Span,
140 },
141 Lit {
142 kind: LitKind,
143 text: String,
144 pos: Pos,
145 span: Span,
146 },
147 As {
149 name: Identifier,
150 pat: Box<Self>,
151 pos: Pos,
152 span: Span,
153 },
154 Other {
156 raw: String,
157 pos: Pos,
158 span: Span,
159 },
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163#[non_exhaustive]
164pub enum Expr {
165 Var {
167 qualifier: Option<ModuleName>,
168 name: Identifier,
169 pos: Pos,
170 span: Span,
171 },
172 Con {
174 qualifier: Option<ModuleName>,
175 name: Identifier,
176 pos: Pos,
177 span: Span,
178 },
179 Lit {
180 kind: LitKind,
181 text: String,
182 pos: Pos,
183 span: Span,
184 },
185 App {
187 func: Box<Self>,
188 args: Vec<Self>,
189 pos: Pos,
190 span: Span,
191 },
192 BinOp {
194 op: Operator,
195 lhs: Box<Self>,
196 rhs: Box<Self>,
197 pos: Pos,
198 span: Span,
199 },
200 Neg {
202 expr: Box<Self>,
203 pos: Pos,
204 span: Span,
205 },
206 Lambda {
207 params: Vec<Pat>,
208 body: Box<Self>,
209 pos: Pos,
210 span: Span,
211 },
212 If {
213 cond: Box<Self>,
214 then_branch: Box<Self>,
215 else_branch: Box<Self>,
216 pos: Pos,
217 span: Span,
218 },
219 Case {
220 scrutinee: Box<Self>,
221 alts: Vec<Alt>,
222 pos: Pos,
223 span: Span,
224 },
225 Do {
226 stmts: Vec<DoStmt>,
227 pos: Pos,
228 span: Span,
229 },
230 LetIn {
231 bindings: Vec<Binding>,
232 body: Box<Self>,
233 pos: Pos,
234 span: Span,
235 },
236 Record {
239 base: Box<Self>,
240 fields: Vec<FieldAssign>,
241 pos: Pos,
242 span: Span,
243 },
244 Tuple {
245 items: Vec<Self>,
246 pos: Pos,
247 span: Span,
248 },
249 List {
250 items: Vec<Self>,
251 pos: Pos,
252 span: Span,
253 },
254 Try {
256 body: Box<Self>,
257 handlers: Vec<Alt>,
258 pos: Pos,
259 span: Span,
260 },
261 Section {
263 op: Operator,
264 operand: Option<Box<Self>>,
265 side: SectionSide,
266 pos: Pos,
267 span: Span,
268 },
269 Error { raw: String, pos: Pos, span: Span },
272}
273
274#[derive(Debug, Clone, PartialEq, Eq)]
275#[non_exhaustive]
276pub enum DoStmt {
277 Bind {
279 pat: Pat,
280 expr: Expr,
281 pos: Pos,
282 span: Span,
283 },
284 Let {
286 bindings: Vec<Binding>,
287 pos: Pos,
288 span: Span,
289 },
290 Expr { expr: Expr, pos: Pos, span: Span },
292}
293
294#[derive(Debug, Clone)]
302#[non_exhaustive]
303pub enum Type {
304 Con {
306 qualifier: Option<ModuleName>,
307 name: Identifier,
308 span: Span,
309 },
310 App(Box<Self>, Vec<Self>, Span),
315 List(Box<Self>, Span),
317 Tuple(Vec<Self>, Span),
319 Fun(Box<Self>, Box<Self>, Span),
321 Var(Identifier, Span),
323 Unit(Span),
325 Constrained(Box<Self>, Span),
328}
329
330impl PartialEq for Type {
335 fn eq(&self, other: &Self) -> bool {
336 match (self, other) {
337 (
338 Self::Con {
339 qualifier: aq,
340 name: an,
341 ..
342 },
343 Self::Con {
344 qualifier: bq,
345 name: bn,
346 ..
347 },
348 ) => aq == bq && an == bn,
349 (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
350 (Self::List(a, _), Self::List(b, _))
351 | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
352 (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
353 (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
354 (Self::Var(a, _), Self::Var(b, _)) => a == b,
355 (Self::Unit(_), Self::Unit(_)) => true,
356 _ => false,
357 }
358 }
359}
360
361impl Eq for Type {}
362
363impl Type {
364 #[must_use]
365 pub const fn span(&self) -> Span {
366 match self {
367 Self::Con { span, .. }
368 | Self::App(_, _, span)
369 | Self::List(_, span)
370 | Self::Tuple(_, span)
371 | Self::Fun(_, _, span)
372 | Self::Var(_, span)
373 | Self::Unit(span)
374 | Self::Constrained(_, span) => *span,
375 }
376 }
377
378 pub(crate) const fn with_span(mut self, span: Span) -> Self {
379 match &mut self {
380 Self::Con { span: s, .. }
381 | Self::App(_, _, s)
382 | Self::List(_, s)
383 | Self::Tuple(_, s)
384 | Self::Fun(_, _, s)
385 | Self::Var(_, s)
386 | Self::Unit(s)
387 | Self::Constrained(_, s) => *s = span,
388 }
389 self
390 }
391}
392
393#[derive(Debug, Clone, PartialEq, Eq)]
394pub struct FieldDecl {
395 pub name: Identifier,
396 pub ty: Option<Type>,
399 pub pos: Pos,
400 pub span: Span,
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub enum Consuming {
405 Consuming,
406 NonConsuming,
407 PreConsuming,
408 PostConsuming,
409}
410
411#[derive(Debug, Clone, PartialEq, Eq)]
412pub struct ChoiceDecl {
413 pub name: Identifier,
414 pub consuming: Consuming,
415 pub return_ty: Option<Type>,
418 pub params: Vec<FieldDecl>,
419 pub controllers: Vec<Expr>,
421 pub observers: Vec<Expr>,
423 pub body: Option<Expr>,
424 pub pos: Pos,
425 pub span: Span,
426}
427
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub enum TemplateBodyDecl {
430 Signatory {
431 parties: Vec<Expr>,
432 pos: Pos,
433 span: Span,
434 },
435 Observer {
436 parties: Vec<Expr>,
437 pos: Pos,
438 span: Span,
439 },
440 Ensure {
441 expr: Expr,
442 pos: Pos,
443 span: Span,
444 },
445 Key {
446 expr: Expr,
447 ty: Option<Type>,
449 pos: Pos,
450 span: Span,
451 },
452 Maintainer {
453 expr: Expr,
454 pos: Pos,
455 span: Span,
456 },
457 Choice(ChoiceDecl),
458 InterfaceInstance(InterfaceInstanceDecl),
459 Other {
461 raw: String,
462 pos: Pos,
463 span: Span,
464 },
465}
466
467#[derive(Debug, Clone, PartialEq, Eq)]
468pub struct InterfaceInstanceDecl {
469 pub interface_name: ModuleName,
471 pub for_template: ModuleName,
474 pub methods: Vec<Binding>,
476 pub pos: Pos,
477 pub span: Span,
478}
479
480#[derive(Debug, Clone, PartialEq, Eq)]
481pub struct TemplateDecl {
482 pub name: Identifier,
483 pub fields: Vec<FieldDecl>,
484 pub body: Vec<TemplateBodyDecl>,
485 pub pos: Pos,
486 pub span: Span,
487}
488
489#[derive(Debug, Clone, PartialEq, Eq)]
490pub struct InterfaceDecl {
491 pub name: Identifier,
492 pub requires: Vec<ModuleName>,
494 pub viewtype: Option<ModuleName>,
495 pub methods: Vec<FieldDecl>,
497 pub choices: Vec<ChoiceDecl>,
498 pub pos: Pos,
499 pub span: Span,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct Equation {
504 pub params: Vec<Pat>,
505 pub body: Expr,
506 pub guards: Vec<(Expr, Expr)>,
509 pub where_bindings: Vec<Binding>,
511 pub pos: Pos,
512 pub span: Span,
513}
514
515#[derive(Debug, Clone, PartialEq, Eq)]
516pub struct FunctionDecl {
517 pub name: Identifier,
518 pub ty: Option<Type>,
519 pub equations: Vec<Equation>,
520 pub pos: Pos,
521 pub span: Span,
525 pub sig_span: Option<Span>,
527}
528
529#[derive(Debug, Clone, PartialEq, Eq)]
530pub struct ImportDecl {
531 pub module_name: ModuleName,
532 pub style: ImportStyle,
533 pub alias: Option<ModuleName>,
534 pub pos: Pos,
535 pub span: Span,
536}
537
538#[derive(Debug, Clone, PartialEq, Eq)]
539#[non_exhaustive]
540pub enum Decl {
541 Template(TemplateDecl),
542 Interface(InterfaceDecl),
543 Function(FunctionDecl),
544 TypeDef {
546 keyword: String,
547 name: Identifier,
548 pos: Pos,
549 span: Span,
550 },
551 Unknown {
553 raw: String,
554 pos: Pos,
555 span: Span,
556 },
557}
558
559#[derive(Debug, Clone, PartialEq, Eq)]
560pub struct Module {
561 pub name: ModuleName,
562 pub pos: Pos,
563 pub span: Span,
565 pub header: Span,
569 pub imports: Vec<ImportDecl>,
570 pub decls: Vec<Decl>,
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
579#[non_exhaustive]
580pub enum DiagnosticCategory {
581 SkippedDecl,
583 Malformed,
586 UnsupportedSyntax,
589 RecursionLimit,
592 Lex,
594}
595
596impl DiagnosticCategory {
597 #[must_use]
599 pub const fn as_str(self) -> &'static str {
600 match self {
601 Self::SkippedDecl => "skipped-declaration",
602 Self::Malformed => "malformed",
603 Self::UnsupportedSyntax => "unsupported-syntax",
604 Self::RecursionLimit => "recursion-limit",
605 Self::Lex => "lexical-error",
606 }
607 }
608}
609
610#[derive(Debug, Clone, PartialEq, Eq)]
612pub struct ParseDiagnostic {
613 pub message: String,
614 pub pos: Pos,
615 pub span: Span,
619 pub category: DiagnosticCategory,
620}
621
622impl Expr {
623 #[must_use]
624 pub const fn pos(&self) -> Pos {
625 match self {
626 Self::Var { pos, .. }
627 | Self::Con { pos, .. }
628 | Self::Lit { pos, .. }
629 | Self::App { pos, .. }
630 | Self::BinOp { pos, .. }
631 | Self::Neg { pos, .. }
632 | Self::Lambda { pos, .. }
633 | Self::If { pos, .. }
634 | Self::Case { pos, .. }
635 | Self::Do { pos, .. }
636 | Self::LetIn { pos, .. }
637 | Self::Record { pos, .. }
638 | Self::Tuple { pos, .. }
639 | Self::List { pos, .. }
640 | Self::Try { pos, .. }
641 | Self::Section { pos, .. }
642 | Self::Error { pos, .. } => *pos,
643 }
644 }
645
646 #[must_use]
648 pub const fn span(&self) -> Span {
649 match self {
650 Self::Var { span, .. }
651 | Self::Con { span, .. }
652 | Self::Lit { span, .. }
653 | Self::App { span, .. }
654 | Self::BinOp { span, .. }
655 | Self::Neg { span, .. }
656 | Self::Lambda { span, .. }
657 | Self::If { span, .. }
658 | Self::Case { span, .. }
659 | Self::Do { span, .. }
660 | Self::LetIn { span, .. }
661 | Self::Record { span, .. }
662 | Self::Tuple { span, .. }
663 | Self::List { span, .. }
664 | Self::Try { span, .. }
665 | Self::Section { span, .. }
666 | Self::Error { span, .. } => *span,
667 }
668 }
669
670 #[must_use]
680 pub fn render(&self) -> String {
681 match self {
682 Self::Var {
683 qualifier, name, ..
684 }
685 | Self::Con {
686 qualifier, name, ..
687 } => qualifier
688 .as_ref()
689 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
690 Self::Lit { kind, text, .. } => match kind {
691 LitKind::Text => format!("{text:?}"),
692 LitKind::Char => format!("'{text}'"),
693 _ => text.clone(),
694 },
695 Self::App { func, args, .. } => {
696 let mut s = func.render_atomic();
697 for a in args {
698 s.push(' ');
699 s.push_str(&a.render_atomic());
700 }
701 s
702 }
703 Self::BinOp { op, lhs, rhs, .. } => {
704 if *op == "." {
705 format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
707 } else {
708 format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
709 }
710 }
711 Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
712 Self::Lambda { params, body, .. } => {
713 let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
714 format!("\\{} -> {}", ps.join(" "), body.render())
715 }
716 Self::If {
717 cond,
718 then_branch,
719 else_branch,
720 ..
721 } => format!(
722 "if {} then {} else {}",
723 cond.render(),
724 then_branch.render(),
725 else_branch.render()
726 ),
727 Self::Case {
728 scrutinee, alts, ..
729 } => {
730 let arms: Vec<String> = alts
731 .iter()
732 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
733 .collect();
734 format!("case {} of {}", scrutinee.render(), arms.join("; "))
735 }
736 Self::Do { stmts, .. } => {
737 let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
738 format!("do {}", body.join("; "))
739 }
740 Self::LetIn { bindings, body, .. } => {
741 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
742 format!("let {} in {}", bs.join("; "), body.render())
743 }
744 Self::Record { base, fields, .. } => {
745 let fs: Vec<String> = fields
746 .iter()
747 .map(|f| {
748 f.value.as_ref().map_or_else(
749 || f.name.to_string(),
750 |v| format!("{} = {}", f.name, v.render()),
751 )
752 })
753 .collect();
754 format!("{} with {}", base.render_atomic(), fs.join("; "))
755 }
756 Self::Tuple { items, .. } => {
757 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
758 format!("({})", xs.join(", "))
759 }
760 Self::List { items, .. } => {
761 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
762 format!("[{}]", xs.join(", "))
763 }
764 Self::Try { body, handlers, .. } => {
765 let hs: Vec<String> = handlers
766 .iter()
767 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
768 .collect();
769 format!("try {} catch {}", body.render(), hs.join("; "))
770 }
771 Self::Section {
772 op, operand, side, ..
773 } => match (operand, side) {
774 (Some(e), SectionSide::Left) => format!("({} {})", e.render(), op),
775 (Some(e), SectionSide::Right) => format!("({} {})", op, e.render()),
776 (None, _) => format!("({op})"),
777 },
778 Self::Error { raw, .. } => raw.clone(),
779 }
780 }
781
782 fn render_atomic(&self) -> String {
785 match self {
786 Self::Var { .. }
787 | Self::Con { .. }
788 | Self::Lit { .. }
789 | Self::Tuple { .. }
790 | Self::List { .. }
791 | Self::Section { .. }
792 | Self::Error { .. } => self.render(),
793 _ => format!("({})", self.render()),
794 }
795 }
796
797 #[must_use]
800 pub fn application_head(&self) -> &Self {
801 match self {
802 Self::App { func, .. } => func.application_head(),
803 _ => self,
804 }
805 }
806
807 #[must_use]
811 pub fn application_args(&self) -> &[Self] {
812 match self {
813 Self::App { args, .. } => args,
814 _ => &[],
815 }
816 }
817}
818
819fn render_do_stmt(s: &DoStmt) -> String {
820 match s {
821 DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
822 DoStmt::Let { bindings, .. } => {
823 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
824 format!("let {}", bs.join("; "))
825 }
826 DoStmt::Expr { expr, .. } => expr.render(),
827 }
828}
829
830fn render_binding(b: &Binding) -> String {
831 let mut s = b.pat.render();
832 for p in &b.params {
833 s.push(' ');
834 s.push_str(&p.render());
835 }
836 format!("{} = {}", s, b.expr.render())
837}
838
839impl Pat {
840 #[must_use]
841 pub const fn pos(&self) -> Pos {
842 match self {
843 Self::Var { pos, .. }
844 | Self::Wild { pos, .. }
845 | Self::Con { pos, .. }
846 | Self::Tuple { pos, .. }
847 | Self::List { pos, .. }
848 | Self::Lit { pos, .. }
849 | Self::As { pos, .. }
850 | Self::Other { pos, .. } => *pos,
851 }
852 }
853
854 #[must_use]
856 pub const fn span(&self) -> Span {
857 match self {
858 Self::Var { span, .. }
859 | Self::Wild { span, .. }
860 | Self::Con { span, .. }
861 | Self::Tuple { span, .. }
862 | Self::List { span, .. }
863 | Self::Lit { span, .. }
864 | Self::As { span, .. }
865 | Self::Other { span, .. } => *span,
866 }
867 }
868
869 #[must_use]
873 pub fn render(&self) -> String {
874 match self {
875 Self::Var { name, .. } => name.to_string(),
876 Self::Wild { .. } => "_".to_string(),
877 Self::Con {
878 qualifier,
879 name,
880 args,
881 ..
882 } => {
883 let head = qualifier
884 .as_ref()
885 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
886 if args.is_empty() {
887 head
888 } else {
889 let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
890 format!("({} {})", head, parts.join(" "))
891 }
892 }
893 Self::Tuple { items, .. } => {
894 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
895 format!("({})", xs.join(", "))
896 }
897 Self::List { items, .. } => {
898 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
899 format!("[{}]", xs.join(", "))
900 }
901 Self::Lit { kind, text, .. } => match kind {
902 LitKind::Text => format!("{text:?}"),
903 LitKind::Char => format!("'{text}'"),
904 _ => text.clone(),
905 },
906 Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
907 Self::Other { raw, .. } => raw.clone(),
908 }
909 }
910}
911
912impl std::fmt::Display for Expr {
913 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914 f.write_str(&self.render())
915 }
916}
917
918impl std::fmt::Display for Pat {
919 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
920 f.write_str(&self.render())
921 }
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927
928 fn pos() -> Pos {
929 Pos { line: 1, column: 1 }
930 }
931
932 fn span(start: usize, end: usize) -> Span {
933 Span::new(start, end)
934 }
935
936 #[test]
937 fn span_distinguishes_empty_from_invalid() {
938 assert!(span(3, 3).is_valid());
939 assert!(span(3, 3).is_empty());
940
941 assert!(!span(4, 3).is_valid());
942 assert!(!span(4, 3).is_empty());
943 }
944
945 #[test]
946 fn contains_rejects_invalid_spans() {
947 let parent = span(1, 10);
948
949 assert!(parent.contains(&span(3, 7)));
950 assert!(!parent.contains(&span(7, 3)));
951 assert!(!span(10, 1).contains(&span(3, 7)));
952 }
953
954 #[test]
955 fn expr_render_keeps_normalized_application_and_projection_shape() {
956 let projection = Expr::BinOp {
957 op: ".".into(),
958 lhs: Box::new(Expr::Var {
959 qualifier: None,
960 name: "this".into(),
961 pos: pos(),
962 span: span(0, 4),
963 }),
964 rhs: Box::new(Expr::Var {
965 qualifier: None,
966 name: "note".into(),
967 pos: pos(),
968 span: span(5, 9),
969 }),
970 pos: pos(),
971 span: span(0, 9),
972 };
973
974 let expr = Expr::App {
975 func: Box::new(Expr::Var {
976 qualifier: None,
977 name: "length".into(),
978 pos: pos(),
979 span: span(0, 6),
980 }),
981 args: vec![projection],
982 pos: pos(),
983 span: span(0, 16),
984 };
985
986 assert_eq!(expr.render(), "length (this.note)");
987 }
988
989 #[test]
990 fn section_render_depends_on_section_side() {
991 let expr_left = Expr::Section {
992 op: "+".into(),
993 operand: Some(Box::new(Expr::Var {
994 qualifier: None,
995 name: "x".into(),
996 pos: pos(),
997 span: span(0, 1),
998 })),
999 side: SectionSide::Left,
1000 pos: pos(),
1001 span: span(0, 4),
1002 };
1003 let expr_right = Expr::Section {
1004 op: "+".into(),
1005 operand: Some(Box::new(Expr::Lit {
1006 kind: LitKind::Int,
1007 text: "1".to_string(),
1008 pos: pos(),
1009 span: span(0, 1),
1010 })),
1011 side: SectionSide::Right,
1012 pos: pos(),
1013 span: span(0, 4),
1014 };
1015
1016 assert_eq!(expr_left.render(), "(x +)");
1017 assert_eq!(expr_right.render(), "(+ 1)");
1018 }
1019
1020 #[test]
1021 fn pat_render_preserves_collection_shape() {
1022 let pat = Pat::Tuple {
1023 items: vec![
1024 Pat::Var {
1025 name: "owner".into(),
1026 pos: pos(),
1027 span: span(1, 6),
1028 },
1029 Pat::List {
1030 items: Vec::new(),
1031 pos: pos(),
1032 span: span(8, 10),
1033 },
1034 ],
1035 pos: pos(),
1036 span: span(0, 11),
1037 };
1038
1039 assert_eq!(pat.render(), "(owner, [])");
1040 }
1041}