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