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}
347
348impl PartialEq for Type {
353 fn eq(&self, other: &Self) -> bool {
354 match (self, other) {
355 (
356 Self::Con {
357 qualifier: aq,
358 name: an,
359 ..
360 },
361 Self::Con {
362 qualifier: bq,
363 name: bn,
364 ..
365 },
366 ) => aq == bq && an == bn,
367 (Self::App(ah, aa, _), Self::App(bh, ba, _)) => ah == bh && aa == ba,
368 (Self::List(a, _), Self::List(b, _))
369 | (Self::Constrained(a, _), Self::Constrained(b, _)) => a == b,
370 (Self::Tuple(a, _), Self::Tuple(b, _)) => a == b,
371 (Self::Fun(al, ar, _), Self::Fun(bl, br, _)) => al == bl && ar == br,
372 (Self::Var(a, _), Self::Var(b, _)) => a == b,
373 (Self::Unit(_), Self::Unit(_)) => true,
374 _ => false,
375 }
376 }
377}
378
379impl Eq for Type {}
380
381impl Type {
382 #[must_use]
383 pub const fn span(&self) -> Span {
384 match self {
385 Self::Con { span, .. }
386 | Self::App(_, _, span)
387 | Self::List(_, span)
388 | Self::Tuple(_, span)
389 | Self::Fun(_, _, span)
390 | Self::Var(_, span)
391 | Self::Unit(span)
392 | Self::Constrained(_, span) => *span,
393 }
394 }
395
396 pub(crate) const fn with_span(mut self, span: Span) -> Self {
397 match &mut self {
398 Self::Con { span: s, .. }
399 | Self::App(_, _, s)
400 | Self::List(_, s)
401 | Self::Tuple(_, s)
402 | Self::Fun(_, _, s)
403 | Self::Var(_, s)
404 | Self::Unit(s)
405 | Self::Constrained(_, s) => *s = span,
406 }
407 self
408 }
409}
410
411#[derive(Debug, Clone, PartialEq, Eq)]
412pub struct FieldDecl {
413 pub name: Identifier,
414 pub ty: Option<Type>,
417 pub pos: Pos,
418 pub span: Span,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum Consuming {
423 Consuming,
424 NonConsuming,
425 PreConsuming,
426 PostConsuming,
427}
428
429#[derive(Debug, Clone, PartialEq, Eq)]
430pub struct ChoiceDecl {
431 pub name: Identifier,
432 pub consuming: Consuming,
433 pub return_ty: Option<Type>,
436 pub params: Vec<FieldDecl>,
437 pub controllers: Vec<Expr>,
439 pub observers: Vec<Expr>,
441 pub body: Option<Expr>,
442 pub pos: Pos,
443 pub span: Span,
444}
445
446#[derive(Debug, Clone, PartialEq, Eq)]
447pub enum TemplateBodyDecl {
448 Signatory {
449 parties: Vec<Expr>,
450 pos: Pos,
451 span: Span,
452 },
453 Observer {
454 parties: Vec<Expr>,
455 pos: Pos,
456 span: Span,
457 },
458 Ensure {
459 expr: Expr,
460 pos: Pos,
461 span: Span,
462 },
463 Key {
464 expr: Expr,
465 ty: Option<Type>,
467 pos: Pos,
468 span: Span,
469 },
470 Maintainer {
471 expr: Expr,
472 pos: Pos,
473 span: Span,
474 },
475 Choice(ChoiceDecl),
476 InterfaceInstance(InterfaceInstanceDecl),
477 Other {
479 raw: String,
480 pos: Pos,
481 span: Span,
482 },
483}
484
485#[derive(Debug, Clone, PartialEq, Eq)]
486pub struct InterfaceInstanceDecl {
487 pub interface_name: ModuleName,
489 pub for_template: ModuleName,
492 pub methods: Vec<Binding>,
494 pub pos: Pos,
495 pub span: Span,
496}
497
498#[derive(Debug, Clone, PartialEq, Eq)]
499pub struct TemplateDecl {
500 pub name: Identifier,
501 pub fields: Vec<FieldDecl>,
502 pub body: Vec<TemplateBodyDecl>,
503 pub pos: Pos,
504 pub span: Span,
505}
506
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub struct InterfaceDecl {
509 pub name: Identifier,
510 pub requires: Vec<ModuleName>,
512 pub viewtype: Option<ModuleName>,
513 pub methods: Vec<FieldDecl>,
515 pub choices: Vec<ChoiceDecl>,
516 pub pos: Pos,
517 pub span: Span,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq)]
521pub struct Equation {
522 pub params: Vec<Pat>,
523 pub body: Expr,
524 pub guards: Vec<(Expr, Expr)>,
527 pub where_bindings: Vec<Binding>,
529 pub pos: Pos,
530 pub span: Span,
531}
532
533#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct FunctionDecl {
535 pub name: Identifier,
536 pub ty: Option<Type>,
537 pub equations: Vec<Equation>,
538 pub pos: Pos,
539 pub span: Span,
543 pub sig_span: Option<Span>,
545}
546
547#[derive(Debug, Clone, PartialEq, Eq)]
548pub struct ImportDecl {
549 pub module_name: ModuleName,
550 pub style: ImportStyle,
551 pub alias: Option<ModuleName>,
552 pub pos: Pos,
553 pub span: Span,
554}
555
556#[derive(Debug, Clone, PartialEq, Eq)]
557#[non_exhaustive]
558pub enum Decl {
559 Template(TemplateDecl),
560 Interface(InterfaceDecl),
561 Function(FunctionDecl),
562 TypeDef {
564 keyword: String,
565 name: Identifier,
566 pos: Pos,
567 span: Span,
568 },
569 Unknown {
571 raw: String,
572 pos: Pos,
573 span: Span,
574 },
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
578pub struct Module {
579 pub name: ModuleName,
580 pub pos: Pos,
581 pub span: Span,
583 pub header: Span,
587 pub imports: Vec<ImportDecl>,
588 pub decls: Vec<Decl>,
589}
590
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597#[non_exhaustive]
598pub enum DiagnosticCategory {
599 SkippedDecl,
601 Malformed,
604 UnsupportedSyntax,
607 RecursionLimit,
610 Lex,
612}
613
614impl DiagnosticCategory {
615 #[must_use]
617 pub const fn as_str(self) -> &'static str {
618 match self {
619 Self::SkippedDecl => "skipped-declaration",
620 Self::Malformed => "malformed",
621 Self::UnsupportedSyntax => "unsupported-syntax",
622 Self::RecursionLimit => "recursion-limit",
623 Self::Lex => "lexical-error",
624 }
625 }
626}
627
628#[derive(Debug, Clone, PartialEq, Eq)]
630pub struct ParseDiagnostic {
631 pub message: String,
632 pub pos: Pos,
633 pub span: Span,
637 pub category: DiagnosticCategory,
638}
639
640impl Expr {
641 #[must_use]
642 pub const fn pos(&self) -> Pos {
643 match self {
644 Self::Var { pos, .. }
645 | Self::Con { pos, .. }
646 | Self::Lit { pos, .. }
647 | Self::App { pos, .. }
648 | Self::BinOp { pos, .. }
649 | Self::Neg { pos, .. }
650 | Self::Lambda { pos, .. }
651 | Self::If { pos, .. }
652 | Self::Case { pos, .. }
653 | Self::Do { pos, .. }
654 | Self::LetIn { pos, .. }
655 | Self::Record { pos, .. }
656 | Self::Tuple { pos, .. }
657 | Self::List { pos, .. }
658 | Self::Try { pos, .. }
659 | Self::Section { pos, .. }
660 | Self::Error { pos, .. } => *pos,
661 }
662 }
663
664 #[must_use]
666 pub const fn span(&self) -> Span {
667 match self {
668 Self::Var { span, .. }
669 | Self::Con { span, .. }
670 | Self::Lit { span, .. }
671 | Self::App { span, .. }
672 | Self::BinOp { span, .. }
673 | Self::Neg { span, .. }
674 | Self::Lambda { span, .. }
675 | Self::If { span, .. }
676 | Self::Case { span, .. }
677 | Self::Do { span, .. }
678 | Self::LetIn { span, .. }
679 | Self::Record { span, .. }
680 | Self::Tuple { span, .. }
681 | Self::List { span, .. }
682 | Self::Try { span, .. }
683 | Self::Section { span, .. }
684 | Self::Error { span, .. } => *span,
685 }
686 }
687
688 #[must_use]
698 pub fn render(&self) -> String {
699 match self {
700 Self::Var {
701 qualifier, name, ..
702 }
703 | Self::Con {
704 qualifier, name, ..
705 } => qualifier
706 .as_ref()
707 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}")),
708 Self::Lit { kind, text, .. } => match kind {
709 LitKind::Text => format!("{text:?}"),
710 LitKind::Char => format!("'{text}'"),
711 _ => text.clone(),
712 },
713 Self::App { func, args, .. } => {
714 let mut s = func.render_atomic();
715 for a in args {
716 s.push(' ');
717 s.push_str(&a.render_atomic());
718 }
719 s
720 }
721 Self::BinOp { op, lhs, rhs, .. } => {
722 if *op == "." {
723 format!("{}.{}", lhs.render_atomic(), rhs.render_atomic())
725 } else {
726 format!("{} {} {}", lhs.render_atomic(), op, rhs.render_atomic())
727 }
728 }
729 Self::Neg { expr, .. } => format!("-{}", expr.render_atomic()),
730 Self::Lambda { params, body, .. } => {
731 let ps: Vec<String> = params.iter().map(|p| p.render()).collect();
732 format!("\\{} -> {}", ps.join(" "), body.render())
733 }
734 Self::If {
735 cond,
736 then_branch,
737 else_branch,
738 ..
739 } => format!(
740 "if {} then {} else {}",
741 cond.render(),
742 then_branch.render(),
743 else_branch.render()
744 ),
745 Self::Case {
746 scrutinee, alts, ..
747 } => {
748 let arms: Vec<String> = alts
749 .iter()
750 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
751 .collect();
752 format!("case {} of {}", scrutinee.render(), arms.join("; "))
753 }
754 Self::Do { stmts, .. } => {
755 let body: Vec<String> = stmts.iter().map(render_do_stmt).collect();
756 format!("do {}", body.join("; "))
757 }
758 Self::LetIn { bindings, body, .. } => {
759 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
760 format!("let {} in {}", bs.join("; "), body.render())
761 }
762 Self::Record { base, fields, .. } => {
763 let fs: Vec<String> = fields
764 .iter()
765 .map(|f| {
766 f.value.as_ref().map_or_else(
767 || f.name.to_string(),
768 |v| format!("{} = {}", f.name, v.render()),
769 )
770 })
771 .collect();
772 format!("{} with {}", base.render_atomic(), fs.join("; "))
773 }
774 Self::Tuple { items, .. } => {
775 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
776 format!("({})", xs.join(", "))
777 }
778 Self::List { items, .. } => {
779 let xs: Vec<String> = items.iter().map(|e| e.render()).collect();
780 format!("[{}]", xs.join(", "))
781 }
782 Self::Try { body, handlers, .. } => {
783 let hs: Vec<String> = handlers
784 .iter()
785 .map(|a| format!("{} -> {}", a.pat.render(), a.body.render()))
786 .collect();
787 format!("try {} catch {}", body.render(), hs.join("; "))
788 }
789 Self::Section {
790 op, operand, side, ..
791 } => match (operand, side) {
792 (Some(e), SectionSide::Left) => format!("({} {})", e.render(), op),
793 (Some(e), SectionSide::Right) => format!("({} {})", op, e.render()),
794 (None, _) => format!("({op})"),
795 },
796 Self::Error { raw, .. } => raw.clone(),
797 }
798 }
799
800 fn render_atomic(&self) -> String {
803 match self {
804 Self::Var { .. }
805 | Self::Con { .. }
806 | Self::Lit { .. }
807 | Self::Tuple { .. }
808 | Self::List { .. }
809 | Self::Section { .. }
810 | Self::Error { .. } => self.render(),
811 _ => format!("({})", self.render()),
812 }
813 }
814
815 #[must_use]
818 pub fn application_head(&self) -> &Self {
819 match self {
820 Self::App { func, .. } => func.application_head(),
821 _ => self,
822 }
823 }
824
825 #[must_use]
829 pub fn application_args(&self) -> &[Self] {
830 match self {
831 Self::App { args, .. } => args,
832 _ => &[],
833 }
834 }
835}
836
837fn render_do_stmt(s: &DoStmt) -> String {
838 match s {
839 DoStmt::Bind { pat, expr, .. } => format!("{} <- {}", pat.render(), expr.render()),
840 DoStmt::Let { bindings, .. } => {
841 let bs: Vec<String> = bindings.iter().map(render_binding).collect();
842 format!("let {}", bs.join("; "))
843 }
844 DoStmt::Expr { expr, .. } => expr.render(),
845 }
846}
847
848fn render_binding(b: &Binding) -> String {
849 let mut s = b.pat.render();
850 for p in &b.params {
851 s.push(' ');
852 s.push_str(&p.render());
853 }
854 format!("{} = {}", s, b.expr.render())
855}
856
857impl Pat {
858 #[must_use]
859 pub const fn pos(&self) -> Pos {
860 match self {
861 Self::Var { pos, .. }
862 | Self::Wild { pos, .. }
863 | Self::Con { pos, .. }
864 | Self::Tuple { pos, .. }
865 | Self::List { pos, .. }
866 | Self::Lit { pos, .. }
867 | Self::As { pos, .. }
868 | Self::Other { pos, .. } => *pos,
869 }
870 }
871
872 #[must_use]
874 pub const fn span(&self) -> Span {
875 match self {
876 Self::Var { span, .. }
877 | Self::Wild { span, .. }
878 | Self::Con { span, .. }
879 | Self::Tuple { span, .. }
880 | Self::List { span, .. }
881 | Self::Lit { span, .. }
882 | Self::As { span, .. }
883 | Self::Other { span, .. } => *span,
884 }
885 }
886
887 #[must_use]
891 pub fn render(&self) -> String {
892 match self {
893 Self::Var { name, .. } => name.to_string(),
894 Self::Wild { .. } => "_".to_string(),
895 Self::Con {
896 qualifier,
897 name,
898 args,
899 ..
900 } => {
901 let head = qualifier
902 .as_ref()
903 .map_or_else(|| name.to_string(), |q| format!("{q}.{name}"));
904 if args.is_empty() {
905 head
906 } else {
907 let parts: Vec<String> = args.iter().map(|p| p.render()).collect();
908 format!("({} {})", head, parts.join(" "))
909 }
910 }
911 Self::Tuple { items, .. } => {
912 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
913 format!("({})", xs.join(", "))
914 }
915 Self::List { items, .. } => {
916 let xs: Vec<String> = items.iter().map(|p| p.render()).collect();
917 format!("[{}]", xs.join(", "))
918 }
919 Self::Lit { kind, text, .. } => match kind {
920 LitKind::Text => format!("{text:?}"),
921 LitKind::Char => format!("'{text}'"),
922 _ => text.clone(),
923 },
924 Self::As { name, pat, .. } => format!("{}@{}", name, pat.render()),
925 Self::Other { raw, .. } => raw.clone(),
926 }
927 }
928}
929
930impl std::fmt::Display for Expr {
931 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
932 f.write_str(&self.render())
933 }
934}
935
936impl std::fmt::Display for Pat {
937 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
938 f.write_str(&self.render())
939 }
940}
941
942#[cfg(test)]
943mod tests {
944 use super::*;
945
946 fn pos() -> Pos {
947 Pos { line: 1, column: 1 }
948 }
949
950 fn span(start: usize, end: usize) -> Span {
951 Span::new(start, end)
952 }
953
954 #[test]
955 fn span_distinguishes_empty_from_invalid() {
956 assert!(span(3, 3).is_valid());
957 assert!(span(3, 3).is_empty());
958
959 assert!(!span(4, 3).is_valid());
960 assert!(!span(4, 3).is_empty());
961 }
962
963 #[test]
964 fn contains_rejects_invalid_spans() {
965 let parent = span(1, 10);
966
967 assert!(parent.contains(&span(3, 7)));
968 assert!(!parent.contains(&span(7, 3)));
969 assert!(!span(10, 1).contains(&span(3, 7)));
970 }
971
972 #[test]
973 fn span_range_and_get_share_source_bytes_safely() {
974 let source = "foo: Int";
975
976 assert_eq!(span(0, 3).range(), 0..3);
977 assert_eq!(span(0, 3).get(source), Some("foo"));
978 assert_eq!(span(3, 7).get(source), Some(": In"));
979 assert!(span(3, 100).get(source).is_none());
980 }
981
982 #[test]
983 fn expr_render_keeps_normalized_application_and_projection_shape() {
984 let projection = Expr::BinOp {
985 op: ".".into(),
986 lhs: Box::new(Expr::Var {
987 qualifier: None,
988 name: "this".into(),
989 pos: pos(),
990 span: span(0, 4),
991 }),
992 rhs: Box::new(Expr::Var {
993 qualifier: None,
994 name: "note".into(),
995 pos: pos(),
996 span: span(5, 9),
997 }),
998 pos: pos(),
999 span: span(0, 9),
1000 };
1001
1002 let expr = Expr::App {
1003 func: Box::new(Expr::Var {
1004 qualifier: None,
1005 name: "length".into(),
1006 pos: pos(),
1007 span: span(0, 6),
1008 }),
1009 args: vec![projection],
1010 pos: pos(),
1011 span: span(0, 16),
1012 };
1013
1014 assert_eq!(expr.render(), "length (this.note)");
1015 }
1016
1017 #[test]
1018 fn section_render_depends_on_section_side() {
1019 let expr_left = Expr::Section {
1020 op: "+".into(),
1021 operand: Some(Box::new(Expr::Var {
1022 qualifier: None,
1023 name: "x".into(),
1024 pos: pos(),
1025 span: span(0, 1),
1026 })),
1027 side: SectionSide::Left,
1028 pos: pos(),
1029 span: span(0, 4),
1030 };
1031 let expr_right = Expr::Section {
1032 op: "+".into(),
1033 operand: Some(Box::new(Expr::Lit {
1034 kind: LitKind::Int,
1035 text: "1".to_string(),
1036 pos: pos(),
1037 span: span(0, 1),
1038 })),
1039 side: SectionSide::Right,
1040 pos: pos(),
1041 span: span(0, 4),
1042 };
1043
1044 assert_eq!(expr_left.render(), "(x +)");
1045 assert_eq!(expr_right.render(), "(+ 1)");
1046 }
1047
1048 #[test]
1049 fn pat_render_preserves_collection_shape() {
1050 let pat = Pat::Tuple {
1051 items: vec![
1052 Pat::Var {
1053 name: "owner".into(),
1054 pos: pos(),
1055 span: span(1, 6),
1056 },
1057 Pat::List {
1058 items: Vec::new(),
1059 pos: pos(),
1060 span: span(8, 10),
1061 },
1062 ],
1063 pos: pos(),
1064 span: span(0, 11),
1065 };
1066
1067 assert_eq!(pat.render(), "(owner, [])");
1068 }
1069}