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