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]
45 pub const fn range(&self) -> std::ops::Range<usize> {
46 self.start..self.end
47 }
48
49 #[must_use]
50 pub fn get<'a>(&self, source: &'a str) -> Option<&'a str> {
51 if self.start <= self.end
52 && self.end <= source.len()
53 && source.is_char_boundary(self.start)
54 && source.is_char_boundary(self.end)
55 {
56 Some(&source[self.start..self.end])
57 } else {
58 None
59 }
60 }
61
62 #[must_use]
64 pub const fn contains(&self, other: &Self) -> bool {
65 self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
66 }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[non_exhaustive]
71pub enum LitKind {
72 Int,
73 Decimal,
74 Text,
75 Char,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum SectionSide {
85 Right,
87 Left,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum ImportStyle {
95 Qualified,
97 Unqualified,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct FieldAssign {
103 pub name: Identifier,
104 pub value: Option<Expr>,
107 pub pos: Pos,
108 pub span: Span,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct Alt {
113 pub pat: Pat,
114 pub body: Expr,
115 pub pos: Pos,
116 pub span: Span,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Binding {
121 pub pat: Pat,
123 pub params: Vec<Pat>,
125 pub expr: Expr,
126 pub pos: Pos,
127 pub span: Span,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131#[non_exhaustive]
132pub enum Pat {
133 Var {
134 name: Identifier,
135 pos: Pos,
136 span: Span,
137 },
138 Wild {
139 pos: Pos,
140 span: Span,
141 },
142 Con {
143 qualifier: Option<ModuleName>,
144 name: Identifier,
145 args: Vec<Self>,
146 pos: Pos,
147 span: Span,
148 },
149 Tuple {
150 items: Vec<Self>,
151 pos: Pos,
152 span: Span,
153 },
154 List {
155 items: Vec<Self>,
156 pos: Pos,
157 span: Span,
158 },
159 Lit {
160 kind: LitKind,
161 text: String,
162 pos: Pos,
163 span: Span,
164 },
165 As {
167 name: Identifier,
168 pat: Box<Self>,
169 pos: Pos,
170 span: Span,
171 },
172 Other {
174 raw: String,
175 pos: Pos,
176 span: Span,
177 },
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub enum Expr {
183 Var {
185 qualifier: Option<ModuleName>,
186 name: Identifier,
187 pos: Pos,
188 span: Span,
189 },
190 Con {
192 qualifier: Option<ModuleName>,
193 name: Identifier,
194 pos: Pos,
195 span: Span,
196 },
197 Lit {
198 kind: LitKind,
199 text: String,
200 pos: Pos,
201 span: Span,
202 },
203 App {
205 func: Box<Self>,
206 args: Vec<Self>,
207 pos: Pos,
208 span: Span,
209 },
210 BinOp {
212 op: Operator,
213 lhs: Box<Self>,
214 rhs: Box<Self>,
215 pos: Pos,
216 span: Span,
217 },
218 Neg {
220 expr: Box<Self>,
221 pos: Pos,
222 span: Span,
223 },
224 Lambda {
225 params: Vec<Pat>,
226 body: Box<Self>,
227 pos: Pos,
228 span: Span,
229 },
230 If {
231 cond: Box<Self>,
232 then_branch: Box<Self>,
233 else_branch: Box<Self>,
234 pos: Pos,
235 span: Span,
236 },
237 Case {
238 scrutinee: Box<Self>,
239 alts: Vec<Alt>,
240 pos: Pos,
241 span: Span,
242 },
243 Do {
244 stmts: Vec<DoStmt>,
245 pos: Pos,
246 span: Span,
247 },
248 LetIn {
249 bindings: Vec<Binding>,
250 body: Box<Self>,
251 pos: Pos,
252 span: Span,
253 },
254 Record {
257 base: Box<Self>,
258 fields: Vec<FieldAssign>,
259 pos: Pos,
260 span: Span,
261 },
262 Tuple {
263 items: Vec<Self>,
264 pos: Pos,
265 span: Span,
266 },
267 List {
268 items: Vec<Self>,
269 pos: Pos,
270 span: Span,
271 },
272 Try {
274 body: Box<Self>,
275 handlers: Vec<Alt>,
276 pos: Pos,
277 span: Span,
278 },
279 Section {
281 op: Operator,
282 operand: Option<Box<Self>>,
283 side: SectionSide,
284 pos: Pos,
285 span: Span,
286 },
287 Error { raw: String, pos: Pos, span: Span },
290}
291
292#[derive(Debug, Clone, PartialEq, Eq)]
293#[non_exhaustive]
294pub enum DoStmt {
295 Bind {
297 pat: Pat,
298 expr: Expr,
299 pos: Pos,
300 span: Span,
301 },
302 Let {
304 bindings: Vec<Binding>,
305 pos: Pos,
306 span: Span,
307 },
308 Expr { expr: Expr, pos: Pos, span: Span },
310}
311
312#[derive(Debug, Clone)]
320#[non_exhaustive]
321pub enum Type {
322 Con {
324 qualifier: Option<ModuleName>,
325 name: Identifier,
326 span: Span,
327 },
328 App(Box<Self>, Vec<Self>, Span),
333 List(Box<Self>, Span),
335 Tuple(Vec<Self>, Span),
337 Fun(Box<Self>, Box<Self>, Span),
339 Var(Identifier, Span),
341 Unit(Span),
343 Constrained(Box<Self>, Span),
346 Lit {
349 kind: LitKind,
350 text: String,
351 span: Span,
352 },
353}
354
355impl PartialEq for Type {
360 fn eq(&self, other: &Self) -> bool {
361 match (self, other) {
362 (
363 Self::Con {
364 qualifier: aq,
365 name: an,
366 ..
367 },
368 Self::Con {
369 qualifier: bq,
370 name: bn,
371 ..
372 },
373 ) => aq == bq && an == bn,
374 (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
375 (Self::List(a, _), Self::List(b, _))
376 | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
377 (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
378 (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
379 (Self::Var(a, _), Self::Var(b, _)) => a == b,
380 (Self::Unit(_), Self::Unit(_)) => true,
381 (
382 Self::Lit {
383 kind: ak, text: at, ..
384 },
385 Self::Lit {
386 kind: bk, text: bt, ..
387 },
388 ) => ak == bk && at == bt,
389 _ => false,
390 }
391 }
392}
393
394impl Eq for Type {}
395
396impl Type {
397 #[must_use]
398 pub const fn span(&self) -> Span {
399 match self {
400 Self::Con { span, .. }
401 | Self::App(_, _, span)
402 | Self::List(_, span)
403 | Self::Tuple(_, span)
404 | Self::Fun(_, _, span)
405 | Self::Var(_, span)
406 | Self::Unit(span)
407 | Self::Constrained(_, span)
408 | Self::Lit { span, .. } => *span,
409 }
410 }
411
412 pub(crate) const fn with_span(mut self, span: Span) -> Self {
413 match &mut self {
414 Self::Con { span: s, .. }
415 | Self::App(_, _, s)
416 | Self::List(_, s)
417 | Self::Tuple(_, s)
418 | Self::Fun(_, _, s)
419 | Self::Var(_, s)
420 | Self::Unit(s)
421 | Self::Constrained(_, s)
422 | Self::Lit { span: s, .. } => *s = span,
423 }
424 self
425 }
426}
427
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct FieldDecl {
430 pub name: Identifier,
431 pub ty: Option<Type>,
434 pub pos: Pos,
435 pub span: Span,
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
439#[non_exhaustive]
440pub enum Consuming {
441 Consuming,
442 NonConsuming,
443 PreConsuming,
444 PostConsuming,
445}
446
447#[derive(Debug, Clone, PartialEq, Eq)]
448pub struct ChoiceDecl {
449 pub name: Identifier,
450 pub consuming: Consuming,
451 pub return_ty: Option<Type>,
454 pub params: Vec<FieldDecl>,
455 pub controllers: Vec<Expr>,
457 pub observers: Vec<Expr>,
459 pub body: Option<Expr>,
460 pub pos: Pos,
461 pub span: Span,
462}
463
464#[derive(Debug, Clone, PartialEq, Eq)]
465#[non_exhaustive]
466pub enum TemplateBodyDecl {
467 Signatory {
468 parties: Vec<Expr>,
469 pos: Pos,
470 span: Span,
471 },
472 Observer {
473 parties: Vec<Expr>,
474 pos: Pos,
475 span: Span,
476 },
477 Ensure {
478 expr: Expr,
479 pos: Pos,
480 span: Span,
481 },
482 Key {
483 expr: Expr,
484 ty: Option<Type>,
486 pos: Pos,
487 span: Span,
488 },
489 Maintainer {
490 expr: Expr,
491 pos: Pos,
492 span: Span,
493 },
494 Choice(ChoiceDecl),
495 InterfaceInstance(InterfaceInstanceDecl),
496 Other {
498 raw: String,
499 pos: Pos,
500 span: Span,
501 },
502}
503
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct InterfaceInstanceDecl {
506 pub interface_name: ModuleName,
508 pub for_template: Option<ModuleName>,
511 pub methods: Vec<Binding>,
513 pub pos: Pos,
514 pub span: Span,
515}
516
517#[derive(Debug, Clone, PartialEq, Eq)]
518pub struct TemplateDecl {
519 pub name: Identifier,
520 pub fields: Vec<FieldDecl>,
521 pub body: Vec<TemplateBodyDecl>,
522 pub pos: Pos,
523 pub span: Span,
524}
525
526#[derive(Debug, Clone, PartialEq, Eq)]
527pub struct InterfaceDecl {
528 pub name: Identifier,
529 pub requires: Vec<ModuleName>,
531 pub viewtype: Option<ModuleName>,
532 pub methods: Vec<FieldDecl>,
534 pub choices: Vec<ChoiceDecl>,
535 pub pos: Pos,
536 pub span: Span,
537}
538
539#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct Equation {
541 pub params: Vec<Pat>,
542 pub body: Expr,
543 pub guards: Vec<(Expr, Expr)>,
546 pub where_bindings: Vec<Binding>,
548 pub pos: Pos,
549 pub span: Span,
550}
551
552#[derive(Debug, Clone, PartialEq, Eq)]
553pub struct FunctionDecl {
554 pub name: Identifier,
555 pub ty: Option<Type>,
556 pub equations: Vec<Equation>,
557 pub pos: Pos,
558 pub span: Span,
562 pub sig_span: Option<Span>,
564}
565
566#[derive(Debug, Clone, PartialEq, Eq)]
567pub struct ImportDecl {
568 pub module_name: ModuleName,
569 pub style: ImportStyle,
570 pub alias: Option<ModuleName>,
571 pub pos: Pos,
572 pub span: Span,
573}
574
575#[derive(Debug, Clone, PartialEq, Eq)]
576#[non_exhaustive]
577pub enum Decl {
578 Template(TemplateDecl),
579 Interface(InterfaceDecl),
580 Function(FunctionDecl),
581 TypeDef {
583 keyword: String,
584 name: Identifier,
585 pos: Pos,
586 span: Span,
587 },
588 Unknown {
590 raw: String,
591 pos: Pos,
592 span: Span,
593 },
594}
595
596#[derive(Debug, Clone, PartialEq, Eq)]
597pub struct Module {
598 pub name: ModuleName,
599 pub pos: Pos,
600 pub span: Span,
602 pub header: Span,
606 pub imports: Vec<ImportDecl>,
607 pub decls: Vec<Decl>,
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
616#[non_exhaustive]
617pub enum DiagnosticCategory {
618 SkippedDecl,
620 Malformed,
623 UnsupportedSyntax,
626 RecursionLimit,
629 Lex,
631}
632
633impl DiagnosticCategory {
634 #[must_use]
636 pub const fn as_str(self) -> &'static str {
637 match self {
638 Self::SkippedDecl => "skipped-declaration",
639 Self::Malformed => "malformed",
640 Self::UnsupportedSyntax => "unsupported-syntax",
641 Self::RecursionLimit => "recursion-limit",
642 Self::Lex => "lexical-error",
643 }
644 }
645}
646
647#[derive(Debug, Clone, PartialEq, Eq)]
654pub struct ParseDiagnostic {
655 pub message: String,
656 pub pos: Pos,
657 pub span: Span,
661 pub category: DiagnosticCategory,
662}
663
664impl Expr {
665 #[must_use]
666 pub const fn pos(&self) -> Pos {
667 match self {
668 Self::Var { pos, .. }
669 | Self::Con { pos, .. }
670 | Self::Lit { pos, .. }
671 | Self::App { pos, .. }
672 | Self::BinOp { pos, .. }
673 | Self::Neg { pos, .. }
674 | Self::Lambda { pos, .. }
675 | Self::If { pos, .. }
676 | Self::Case { pos, .. }
677 | Self::Do { pos, .. }
678 | Self::LetIn { pos, .. }
679 | Self::Record { pos, .. }
680 | Self::Tuple { pos, .. }
681 | Self::List { pos, .. }
682 | Self::Try { pos, .. }
683 | Self::Section { pos, .. }
684 | Self::Error { pos, .. } => *pos,
685 }
686 }
687
688 #[must_use]
690 pub const fn span(&self) -> Span {
691 match self {
692 Self::Var { span, .. }
693 | Self::Con { span, .. }
694 | Self::Lit { span, .. }
695 | Self::App { span, .. }
696 | Self::BinOp { span, .. }
697 | Self::Neg { span, .. }
698 | Self::Lambda { span, .. }
699 | Self::If { span, .. }
700 | Self::Case { span, .. }
701 | Self::Do { span, .. }
702 | Self::LetIn { span, .. }
703 | Self::Record { span, .. }
704 | Self::Tuple { span, .. }
705 | Self::List { span, .. }
706 | Self::Try { span, .. }
707 | Self::Section { span, .. }
708 | Self::Error { span, .. } => *span,
709 }
710 }
711
712 #[must_use]
722 pub fn render(&self) -> String {
723 match self {
724 Self::Var {
725 qualifier, name, ..
726 }
727 | Self::Con {
728 qualifier, name, ..
729 } => qualifier
730 .as_ref()
731 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
732 Self::Lit { kind, text, .. } => match kind {
733 LitKind::Text => format!("{text:?}"),
734 LitKind::Char => format!("'{text}'"),
735 _ => text.clone(),
736 },
737 Self::App { func, args, .. } => {
738 let mut s = func.render_atomic();
739 for a in args {
740 s.push(' ');
741 s.push_str(&a.render_atomic());
742 }
743 s
744 }
745 Self::BinOp { op, lhs, rhs, .. } => {
746 if *op == "." {
747 format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
749 } else {
750 format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
751 }
752 }
753 Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
754 Self::Lambda { params, body, .. } => {
755 let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
756 format!("\\{} -> {}", ps.join(" "), body.render())
757 }
758 Self::If {
759 cond,
760 then_branch,
761 else_branch,
762 ..
763 } => format!(
764 "if {} then {} else {}",
765 cond.render(),
766 then_branch.render(),
767 else_branch.render()
768 ),
769 Self::Case {
770 scrutinee, alts, ..
771 } => {
772 let arms: Vec<String> = alts
773 .iter()
774 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
775 .collect();
776 format!("case {} of {}", scrutinee.render(), arms.join("; "))
777 }
778 Self::Do { stmts, .. } => {
779 let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
780 format!("do {}", body.join("; "))
781 }
782 Self::LetIn { bindings, body, .. } => {
783 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
784 format!("let {} in {}", bs.join("; "), body.render())
785 }
786 Self::Record { base, fields, .. } => {
787 let fs: Vec<String> = fields
788 .iter()
789 .map(|f| {
790 f.value.as_ref().map_or_else(
791 || f.name.to_string(),
792 |v| format!("{} = {}", f.name, v.render()),
793 )
794 })
795 .collect();
796 format!("{} with {}", base.render_atomic(), fs.join("; "))
797 }
798 Self::Tuple { items, .. } => {
799 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
800 format!("({})", xs.join(", "))
801 }
802 Self::List { items, .. } => {
803 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
804 format!("[{}]", xs.join(", "))
805 }
806 Self::Try { body, handlers, .. } => {
807 let hs: Vec<String> = handlers
808 .iter()
809 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
810 .collect();
811 format!("try {} catch {}", body.render(), hs.join("; "))
812 }
813 Self::Section {
814 op, operand, side, ..
815 } => match (operand, side) {
816 (Some(e), SectionSide::Left) => format!("({} {})", e.render(), op),
817 (Some(e), SectionSide::Right) => format!("({} {})", op, e.render()),
818 (None, _) => format!("({op})"),
819 },
820 Self::Error { raw, .. } => raw.clone(),
821 }
822 }
823
824 fn render_atomic(&self) -> String {
827 match self {
828 Self::Var { .. }
829 | Self::Con { .. }
830 | Self::Lit { .. }
831 | Self::Tuple { .. }
832 | Self::List { .. }
833 | Self::Section { .. }
834 | Self::Error { .. } => self.render(),
835 _ => format!("({})", self.render()),
836 }
837 }
838
839 #[must_use]
842 pub fn application_head(&self) -> &Self {
843 match self {
844 Self::App { func, .. } => func.application_head(),
845 _ => self,
846 }
847 }
848
849 #[must_use]
853 pub fn application_args(&self) -> &[Self] {
854 match self {
855 Self::App { args, .. } => args,
856 _ => &[],
857 }
858 }
859}
860
861fn render_do_stmt(s: &DoStmt) -> String {
862 match s {
863 DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
864 DoStmt::Let { bindings, .. } => {
865 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
866 format!("let {}", bs.join("; "))
867 }
868 DoStmt::Expr { expr, .. } => expr.render(),
869 }
870}
871
872fn render_binding(b: &Binding) -> String {
873 let mut s = b.pat.render();
874 for p in &b.params {
875 s.push(' ');
876 s.push_str(&p.render());
877 }
878 format!("{} = {}", s, b.expr.render())
879}
880
881impl Pat {
882 #[must_use]
883 pub const fn pos(&self) -> Pos {
884 match self {
885 Self::Var { pos, .. }
886 | Self::Wild { pos, .. }
887 | Self::Con { pos, .. }
888 | Self::Tuple { pos, .. }
889 | Self::List { pos, .. }
890 | Self::Lit { pos, .. }
891 | Self::As { pos, .. }
892 | Self::Other { pos, .. } => *pos,
893 }
894 }
895
896 #[must_use]
898 pub const fn span(&self) -> Span {
899 match self {
900 Self::Var { span, .. }
901 | Self::Wild { span, .. }
902 | Self::Con { span, .. }
903 | Self::Tuple { span, .. }
904 | Self::List { span, .. }
905 | Self::Lit { span, .. }
906 | Self::As { span, .. }
907 | Self::Other { span, .. } => *span,
908 }
909 }
910
911 #[must_use]
915 pub fn render(&self) -> String {
916 match self {
917 Self::Var { name, .. } => name.to_string(),
918 Self::Wild { .. } => "_".to_string(),
919 Self::Con {
920 qualifier,
921 name,
922 args,
923 ..
924 } => {
925 let head = qualifier
926 .as_ref()
927 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
928 if args.is_empty() {
929 head
930 } else {
931 let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
932 format!("({} {})", head, parts.join(" "))
933 }
934 }
935 Self::Tuple { items, .. } => {
936 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
937 format!("({})", xs.join(", "))
938 }
939 Self::List { items, .. } => {
940 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
941 format!("[{}]", xs.join(", "))
942 }
943 Self::Lit { kind, text, .. } => match kind {
944 LitKind::Text => format!("{text:?}"),
945 LitKind::Char => format!("'{text}'"),
946 _ => text.clone(),
947 },
948 Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
949 Self::Other { raw, .. } => raw.clone(),
950 }
951 }
952}
953
954impl std::fmt::Display for Expr {
955 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
956 f.write_str(&self.render())
957 }
958}
959
960impl std::fmt::Display for Pat {
961 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
962 f.write_str(&self.render())
963 }
964}
965
966#[cfg(test)]
967mod tests {
968 use super::*;
969
970 fn pos() -> Pos {
971 Pos { line: 1, column: 1 }
972 }
973
974 fn span(start: usize, end: usize) -> Span {
975 Span::new(start, end)
976 }
977
978 #[test]
979 fn span_distinguishes_empty_from_invalid() {
980 assert!(span(3, 3).is_valid());
981 assert!(span(3, 3).is_empty());
982
983 assert!(!span(4, 3).is_valid());
984 assert!(!span(4, 3).is_empty());
985 }
986
987 #[test]
988 fn contains_rejects_invalid_spans() {
989 let parent = span(1, 10);
990
991 assert!(parent.contains(&span(3, 7)));
992 assert!(!parent.contains(&span(7, 3)));
993 assert!(!span(10, 1).contains(&span(3, 7)));
994 }
995
996 #[test]
997 fn span_range_and_get_share_source_bytes_safely() {
998 let source = "foo: Int";
999
1000 assert_eq!(span(0, 3).range(), 0..3);
1001 assert_eq!(span(0, 3).get(source), Some("foo"));
1002 assert_eq!(span(3, 7).get(source), Some(": In"));
1003 assert!(span(3, 100).get(source).is_none());
1004 }
1005
1006 #[test]
1007 fn expr_render_keeps_normalized_application_and_projection_shape() {
1008 let projection = Expr::BinOp {
1009 op: ".".into(),
1010 lhs: Box::new(Expr::Var {
1011 qualifier: None,
1012 name: "this".into(),
1013 pos: pos(),
1014 span: span(0, 4),
1015 }),
1016 rhs: Box::new(Expr::Var {
1017 qualifier: None,
1018 name: "note".into(),
1019 pos: pos(),
1020 span: span(5, 9),
1021 }),
1022 pos: pos(),
1023 span: span(0, 9),
1024 };
1025
1026 let expr = Expr::App {
1027 func: Box::new(Expr::Var {
1028 qualifier: None,
1029 name: "length".into(),
1030 pos: pos(),
1031 span: span(0, 6),
1032 }),
1033 args: vec![projection],
1034 pos: pos(),
1035 span: span(0, 16),
1036 };
1037
1038 assert_eq!(expr.render(), "length (this.note)");
1039 }
1040
1041 #[test]
1042 fn section_render_depends_on_section_side() {
1043 let expr_left = Expr::Section {
1044 op: "+".into(),
1045 operand: Some(Box::new(Expr::Var {
1046 qualifier: None,
1047 name: "x".into(),
1048 pos: pos(),
1049 span: span(0, 1),
1050 })),
1051 side: SectionSide::Left,
1052 pos: pos(),
1053 span: span(0, 4),
1054 };
1055 let expr_right = Expr::Section {
1056 op: "+".into(),
1057 operand: Some(Box::new(Expr::Lit {
1058 kind: LitKind::Int,
1059 text: "1".to_string(),
1060 pos: pos(),
1061 span: span(0, 1),
1062 })),
1063 side: SectionSide::Right,
1064 pos: pos(),
1065 span: span(0, 4),
1066 };
1067
1068 assert_eq!(expr_left.render(), "(x +)");
1069 assert_eq!(expr_right.render(), "(+ 1)");
1070 }
1071
1072 #[test]
1073 fn pat_render_preserves_collection_shape() {
1074 let pat = Pat::Tuple {
1075 items: vec![
1076 Pat::Var {
1077 name: "owner".into(),
1078 pos: pos(),
1079 span: span(1, 6),
1080 },
1081 Pat::List {
1082 items: Vec::new(),
1083 pos: pos(),
1084 span: span(8, 10),
1085 },
1086 ],
1087 pos: pos(),
1088 span: span(0, 11),
1089 };
1090
1091 assert_eq!(pat.render(), "(owner, [])");
1092 }
1093}