1use crate::ast::{
14 Alt, Binding, ChoiceDecl, Decl, DoStmt, Equation, Expr, FieldAssign, FixityDecl,
15 GuardQualifier, InterfaceInstanceBodyItem, Module, Pat, PatFieldAssign, Span, TemplateBodyDecl,
16 Type, TypeAnnotation,
17};
18use crate::lexer::{Trivia, TriviaKind};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum AstSpanError {
23 InvalidSpan {
25 start: usize,
27 end: usize,
29 },
30 OverlappingSpans {
32 start: usize,
34 end: usize,
36 parent_start: usize,
38 parent_end: usize,
40 },
41 OverlappingTile {
43 start: usize,
45 end: usize,
47 previous_end: usize,
49 },
50 UncoveredBytes {
52 start: usize,
54 end: usize,
56 text: String,
58 },
59 UncoveredTail {
61 start: usize,
63 text: String,
65 },
66 ReconstructionMismatch {
68 reconstructed_len: usize,
70 source_len: usize,
72 },
73 IntervalStartAfterEnd {
75 start: usize,
77 end: usize,
79 },
80 IntervalExceedsSource {
82 start: usize,
84 end: usize,
86 source_len: usize,
88 },
89 IntervalNotUtf8Boundary {
91 start: usize,
93 end: usize,
95 },
96}
97
98impl std::fmt::Display for AstSpanError {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 Self::InvalidSpan { start, end } => write!(f, "invalid span [{start}, {end})"),
102 Self::OverlappingSpans {
103 start,
104 end,
105 parent_start,
106 parent_end,
107 } => write!(
108 f,
109 "span [{start}, {end}) overlaps [{parent_start}, {parent_end}) without nesting"
110 ),
111 Self::OverlappingTile {
112 start,
113 end,
114 previous_end,
115 } => write!(
116 f,
117 "span/trivia interval [{start}, {end}) overlaps previous tile ending at {previous_end}"
118 ),
119 Self::UncoveredBytes { start, end, text } => write!(
120 f,
121 "bytes {start}..{end} not covered by any node or trivia span: {text:?}"
122 ),
123 Self::UncoveredTail { start, text } => {
124 write!(f, "bytes {start}.. lost at EOF: {text:?}")
125 }
126 Self::ReconstructionMismatch {
127 reconstructed_len,
128 source_len,
129 } => write!(
130 f,
131 "reconstruction differs from source ({reconstructed_len} vs {source_len} bytes)"
132 ),
133 Self::IntervalStartAfterEnd { start, end } => {
134 write!(f, "span/trivia interval [{start}, {end}) has start after end")
135 }
136 Self::IntervalExceedsSource {
137 start,
138 end,
139 source_len,
140 } => write!(
141 f,
142 "span/trivia interval [{start}, {end}) exceeds source length {source_len}"
143 ),
144 Self::IntervalNotUtf8Boundary { start, end } => write!(
145 f,
146 "span/trivia interval [{start}, {end}) does not align with UTF-8 boundaries"
147 ),
148 }
149 }
150}
151
152impl std::error::Error for AstSpanError {}
153
154#[must_use = "check and handle render failures"]
169pub fn render_from_ast(
170 source: &str,
171 module: &Module,
172 trivia: &[Trivia],
173) -> Result<String, AstSpanError> {
174 check_nesting(module)?;
175 tile(source, module, trivia)
176}
177
178fn check_nesting(module: &Module) -> Result<(), AstSpanError> {
182 let mut spans: Vec<Span> = Vec::new();
183 collect_module(module, &mut spans);
184 for span in &spans {
185 if !span.is_valid() {
186 return Err(AstSpanError::InvalidSpan {
187 start: span.start_usize(),
188 end: span.end_usize(),
189 });
190 }
191 }
192 spans.retain(|s| !s.is_empty());
193 spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
195
196 let mut stack: Vec<Span> = Vec::new();
197 for span in spans {
198 while stack.last().is_some_and(|top| top.end <= span.start) {
200 stack.pop();
201 }
202 if let Some(parent) = stack.last() {
206 if !parent.contains(&span) {
207 return Err(AstSpanError::OverlappingSpans {
208 start: span.start_usize(),
209 end: span.end_usize(),
210 parent_start: parent.start_usize(),
211 parent_end: parent.end_usize(),
212 });
213 }
214 }
215 stack.push(span);
216 }
217 Ok(())
218}
219
220fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, AstSpanError> {
226 let mut content: Vec<Span> = Vec::new();
227 collect_module(module, &mut content);
228 let container = module.span;
229 content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
230
231 let mut items: Vec<(usize, usize)> = content
232 .iter()
233 .map(|s| (s.start_usize(), s.end_usize()))
234 .collect();
235 items.extend(
238 trivia
239 .iter()
240 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
241 .map(|t| (t.start, t.end)),
242 );
243 items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
247
248 let mut out = String::with_capacity(source.len());
249 let mut prev = 0usize;
250 for (start, end) in items {
251 validate_interval(source, start, end)?;
252 if start < prev {
253 if end <= prev {
257 continue;
258 }
259 return Err(AstSpanError::OverlappingTile {
260 start,
261 end,
262 previous_end: prev,
263 });
264 }
265 let gap = &source[prev..start];
266 if !gap.chars().all(char::is_whitespace) {
267 return Err(AstSpanError::UncoveredBytes {
268 start: prev,
269 end: start,
270 text: gap.to_string(),
271 });
272 }
273 out.push_str(gap);
274 out.push_str(&source[start..end]);
275 prev = end;
276 }
277 let tail = &source[prev..];
278 if !tail.chars().all(char::is_whitespace) {
279 return Err(AstSpanError::UncoveredTail {
280 start: prev,
281 text: tail.to_string(),
282 });
283 }
284 out.push_str(tail);
285
286 if out != source {
287 return Err(AstSpanError::ReconstructionMismatch {
288 reconstructed_len: out.len(),
289 source_len: source.len(),
290 });
291 }
292 Ok(out)
293}
294
295const fn validate_interval(source: &str, start: usize, end: usize) -> Result<(), AstSpanError> {
296 if start > end {
297 return Err(AstSpanError::IntervalStartAfterEnd { start, end });
298 }
299 if end > source.len() {
300 return Err(AstSpanError::IntervalExceedsSource {
301 start,
302 end,
303 source_len: source.len(),
304 });
305 }
306 if !source.is_char_boundary(start) || !source.is_char_boundary(end) {
307 return Err(AstSpanError::IntervalNotUtf8Boundary { start, end });
308 }
309 Ok(())
310}
311
312fn collect_module(module: &Module, spans: &mut Vec<Span>) {
315 spans.push(module.span);
316 if !module.header.is_empty() {
317 spans.push(module.header);
318 }
319 for import in &module.imports {
320 spans.push(import.span);
321 if let Some(label) = &import.package_label {
322 spans.push(label.span);
323 }
324 }
325 for decl in &module.decls {
326 collect_decl(decl, spans);
327 }
328}
329
330fn collect_decl(decl: &Decl, spans: &mut Vec<Span>) {
331 match decl {
332 Decl::Template(template) => {
333 spans.push(template.span);
334 for field in &template.fields {
335 spans.push(field.span);
336 if let TypeAnnotation::Present(ty) = &field.ty {
337 collect_type(ty, spans);
338 }
339 }
340 for body_decl in &template.body {
341 collect_tbody(body_decl, spans);
342 }
343 }
344 Decl::Interface(interface) => {
345 spans.push(interface.span);
346 for method in &interface.methods {
347 spans.push(method.span);
348 if let TypeAnnotation::Present(ty) = &method.ty {
349 collect_type(ty, spans);
350 }
351 }
352 for choice in &interface.choices {
353 collect_choice(choice, spans);
354 }
355 }
356 Decl::Function(function) => {
357 spans.push(function.span);
360 for equation in &function.equations {
361 collect_eq(equation, spans);
362 }
363 if let Some(sig) = function.sig_span {
364 spans.push(sig);
365 }
366 if let TypeAnnotation::Present(ty) = &function.ty {
367 collect_type(ty, spans);
368 }
369 }
370 Decl::TypeDef { span, .. }
371 | Decl::Unknown { span, .. }
372 | Decl::Fixity(FixityDecl { span, .. })
373 | Decl::UnsupportedSyntax { span, .. } => spans.push(*span),
374 }
375}
376
377fn collect_tbody(template_body_decl: &TemplateBodyDecl, spans: &mut Vec<Span>) {
378 match template_body_decl {
379 TemplateBodyDecl::Signatory { parties, span, .. }
380 | TemplateBodyDecl::Observer { parties, span, .. } => {
381 spans.push(*span);
382 for party in parties {
383 collect_expr(party, spans);
384 }
385 }
386 TemplateBodyDecl::Ensure { expr, span, .. }
387 | TemplateBodyDecl::Maintainer { expr, span, .. } => {
388 spans.push(*span);
389 collect_expr(expr, spans);
390 }
391 TemplateBodyDecl::Key { expr, span, ty, .. } => {
392 spans.push(*span);
393 collect_expr(expr, spans);
394 if let TypeAnnotation::Present(ty) = ty {
395 collect_type(ty, spans);
396 }
397 }
398 TemplateBodyDecl::Choice(choice) => collect_choice(choice, spans),
399 TemplateBodyDecl::InterfaceInstance(interface_instance) => {
400 spans.push(interface_instance.span);
401 for item in &interface_instance.items {
402 match item {
403 InterfaceInstanceBodyItem::View { expr, span, .. } => {
404 spans.push(*span);
405 collect_expr(expr, spans);
406 }
407 InterfaceInstanceBodyItem::Method(method) => collect_binding(method, spans),
408 }
409 }
410 }
411 TemplateBodyDecl::Other { span, .. } => spans.push(*span),
412 }
413}
414
415fn collect_choice(choice: &ChoiceDecl, spans: &mut Vec<Span>) {
416 spans.push(choice.span);
417 for param in &choice.params {
418 spans.push(param.span);
419 if let TypeAnnotation::Present(ty) = ¶m.ty {
420 collect_type(ty, spans);
421 }
422 }
423 for controller in &choice.controllers {
424 collect_expr(controller, spans);
425 }
426 for observer in &choice.observers {
427 collect_expr(observer, spans);
428 }
429 for authority in &choice.authority_exprs {
430 collect_expr(authority, spans);
431 }
432 if let Some(body) = &choice.body {
433 collect_expr(body, spans);
434 }
435 if let TypeAnnotation::Present(return_ty) = &choice.return_ty {
436 collect_type(return_ty, spans);
437 }
438}
439
440fn collect_type(ty: &Type, spans: &mut Vec<Span>) {
441 spans.push(ty.span());
442 match ty {
443 Type::Con { .. } | Type::Var(_, _) | Type::Unit(_) | Type::Lit { .. } => {}
444 Type::App(head, args, _) => {
445 collect_type(head, spans);
446 for arg in args {
447 collect_type(arg, spans);
448 }
449 }
450 Type::List(inner, _) | Type::Constrained(inner, _) => collect_type(inner, spans),
451 Type::Tuple(elems, _) => {
452 for elem in elems {
453 collect_type(elem, spans);
454 }
455 }
456 Type::Fun(lhs, rhs, _) => {
457 collect_type(lhs, spans);
458 collect_type(rhs, spans);
459 }
460 }
461}
462
463fn collect_eq(equation: &Equation, spans: &mut Vec<Span>) {
464 spans.push(equation.span);
465 for param in &equation.params {
466 collect_pat(param, spans);
467 }
468 collect_expr(&equation.body, spans);
469 for (guard, body) in &equation.guards {
470 collect_expr(guard, spans);
471 collect_expr(body, spans);
472 }
473 for where_binding in &equation.where_bindings {
474 collect_binding(where_binding, spans);
475 }
476}
477
478fn collect_binding(binding: &Binding, spans: &mut Vec<Span>) {
479 spans.push(binding.span);
480 collect_pat(&binding.pat, spans);
481 for param in &binding.params {
482 collect_pat(param, spans);
483 }
484 collect_expr(&binding.expr, spans);
485}
486
487fn collect_pat(pattern: &Pat, spans: &mut Vec<Span>) {
488 spans.push(pattern.span());
489 match pattern {
490 Pat::Con { args, .. } => {
491 for arg in args {
492 collect_pat(arg, spans);
493 }
494 }
495 Pat::Record { fields, .. } => {
496 for field in fields {
497 spans.push(field.span());
498 if let PatFieldAssign::Assign { pat, .. } = field {
499 collect_pat(pat, spans);
500 }
501 }
502 }
503 Pat::Tuple { items, .. } | Pat::List { items, .. } => {
504 for item in items {
505 collect_pat(item, spans);
506 }
507 }
508 Pat::As { pat, .. } => collect_pat(pat, spans),
509 Pat::Var { .. } | Pat::Wild { .. } | Pat::Lit { .. } | Pat::Other { .. } => {}
510 }
511}
512
513fn collect_expr(expr: &Expr, spans: &mut Vec<Span>) {
514 spans.push(expr.span());
515 match expr {
516 Expr::App { func, args, .. } => {
517 collect_expr(func, spans);
518 for arg in args {
519 collect_expr(arg, spans);
520 }
521 }
522 Expr::BinOp { lhs, rhs, .. } => {
523 collect_expr(lhs, spans);
524 collect_expr(rhs, spans);
525 }
526 Expr::Neg { expr, .. } => collect_expr(expr, spans),
527 Expr::Lambda { params, body, .. } => {
528 for param in params {
529 collect_pat(param, spans);
530 }
531 collect_expr(body, spans);
532 }
533 Expr::If {
534 cond,
535 then_branch,
536 else_branch,
537 ..
538 } => {
539 collect_expr(cond, spans);
540 collect_expr(then_branch, spans);
541 collect_expr(else_branch, spans);
542 }
543 Expr::Case {
544 scrutinee, alts, ..
545 } => {
546 collect_expr(scrutinee, spans);
547 for alt in alts {
548 collect_alt(alt, spans);
549 }
550 }
551 Expr::Do { stmts, .. } => {
552 for stmt in stmts {
553 collect_dostmt(stmt, spans);
554 }
555 }
556 Expr::LetIn { bindings, body, .. } => {
557 for binding in bindings {
558 collect_binding(binding, spans);
559 }
560 collect_expr(body, spans);
561 }
562 Expr::Record { base, fields, .. } => {
563 collect_expr(base, spans);
564 for field in fields {
565 collect_field_assign(field, spans);
566 }
567 }
568 Expr::Tuple { items, .. } | Expr::List { items, .. } => {
569 for item in items {
570 collect_expr(item, spans);
571 }
572 }
573 Expr::Try { body, handlers, .. } => {
574 collect_expr(body, spans);
575 for handler in handlers {
576 collect_alt(handler, spans);
577 }
578 }
579 Expr::LeftSection { operand, .. } | Expr::RightSection { operand, .. } => {
580 collect_expr(operand, spans);
581 }
582 Expr::Var { .. }
583 | Expr::Con { .. }
584 | Expr::Lit { .. }
585 | Expr::OperatorRef { .. }
586 | Expr::Error { .. } => {}
587 }
588}
589
590fn collect_guard_qualifier(guard: &GuardQualifier, spans: &mut Vec<Span>) {
591 spans.push(guard.span());
592 match guard {
593 GuardQualifier::Bool { expr, .. } => collect_expr(expr, spans),
594 GuardQualifier::Pattern { pat, expr, .. } => {
595 collect_pat(pat, spans);
596 collect_expr(expr, spans);
597 }
598 }
599}
600
601fn collect_alt(alt: &Alt, spans: &mut Vec<Span>) {
602 spans.push(alt.span);
603 collect_pat(&alt.pat, spans);
604 for branch in &alt.branches {
605 spans.push(branch.span);
606 for guard in &branch.guards {
607 collect_guard_qualifier(guard, spans);
608 }
609 collect_expr(&branch.body, spans);
610 }
611 for where_binding in &alt.where_bindings {
612 collect_binding(where_binding, spans);
613 }
614}
615
616fn collect_field_assign(field_assign: &FieldAssign, spans: &mut Vec<Span>) {
617 spans.push(field_assign.span());
618 if let FieldAssign::Assign { value, .. } = field_assign {
619 collect_expr(value, spans);
620 }
621}
622
623fn collect_dostmt(do_stmt: &DoStmt, spans: &mut Vec<Span>) {
624 match do_stmt {
625 DoStmt::Bind {
626 pat, expr, span, ..
627 } => {
628 spans.push(*span);
629 collect_pat(pat, spans);
630 collect_expr(expr, spans);
631 }
632 DoStmt::Let { bindings, span, .. } => {
633 spans.push(*span);
634 for binding in bindings {
635 collect_binding(binding, spans);
636 }
637 }
638 DoStmt::Expr { expr, span, .. } => {
639 spans.push(*span);
640 collect_expr(expr, spans);
641 }
642 }
643}
644
645#[cfg(test)]
647#[allow(clippy::unwrap_used)]
648mod tests {
649 use super::*;
650 use crate::lexer::{Pos, Trivia};
651
652 #[test]
653 fn tile_reports_overlapping_intervals() {
654 let source = "module M where\n-- comment\n";
655 let module = Module {
656 name: "M".into(),
657 pos: Pos { line: 1, column: 1 },
658 span: Span::from_usize(0, source.len()),
659 header: Span::from_usize(0, "module M where".len()),
660 imports: Vec::new(),
661 decls: Vec::new(),
662 };
663 let trivia = vec![Trivia {
664 kind: TriviaKind::LineComment,
665 text: "-- comment".to_string(),
666 pos: Pos { line: 2, column: 1 },
667 start: "module M wher".len(),
668 end: "module M where\n-- comment".len(),
669 }];
670
671 let err = tile(source, &module, &trivia).unwrap_err();
672 assert!(
673 err.to_string().contains("overlaps previous tile"),
674 "overlap should fail loudly, got: {err}"
675 );
676 }
677
678 #[test]
679 fn validate_interval_reports_malformed_bounds() {
680 assert_eq!(
681 validate_interval("abc", 2, 1),
682 Err(AstSpanError::IntervalStartAfterEnd { start: 2, end: 1 })
683 );
684 assert_eq!(
685 validate_interval("abc", 0, 4),
686 Err(AstSpanError::IntervalExceedsSource {
687 start: 0,
688 end: 4,
689 source_len: 3
690 })
691 );
692 assert_eq!(
693 validate_interval("é", 1, 2),
694 Err(AstSpanError::IntervalNotUtf8Boundary { start: 1, end: 2 })
695 );
696 }
697
698 #[test]
699 fn tile_reports_uncovered_non_whitespace_bytes() {
700 let source = "module M where\nmissing\n";
701 let module = Module {
702 name: "M".into(),
703 pos: Pos { line: 1, column: 1 },
704 span: Span::from_usize(0, source.len()),
705 header: Span::from_usize(0, "module M where".len()),
706 imports: Vec::new(),
707 decls: Vec::new(),
708 };
709
710 assert_eq!(
711 tile(source, &module, &[]),
712 Err(AstSpanError::UncoveredTail {
713 start: "module M where".len(),
714 text: "\nmissing\n".to_string(),
715 })
716 );
717 }
718
719 #[test]
720 fn render_from_ast_roundtrips_header_only_module() {
721 let source = "module M where\n";
722 let module = Module {
723 name: "M".into(),
724 pos: Pos { line: 1, column: 1 },
725 span: Span::from_usize(0, source.len()),
726 header: Span::from_usize(0, "module M where".len()),
727 imports: Vec::new(),
728 decls: Vec::new(),
729 };
730
731 assert_eq!(render_from_ast(source, &module, &[]).as_deref(), Ok(source));
732 }
733}