Skip to main content

daml_parser/
ast_span.rs

1//! AST losslessness oracle for daml-fmt.
2//!
3//! Every AST node carries a byte `Span` (see `ast::Span`). This module proves
4//! those spans are faithful: it collects every node's span, checks they form a
5//! *laminar family* (each child contained in its parent, siblings ordered and
6//! disjoint — `ast::Span` invariants), and reconstructs the file by tiling the
7//! span forest with the verbatim source bytes that fall between sibling spans.
8//!
9//! If the spans nest correctly and the module span covers the file, the
10//! reconstruction is byte-identical to the source. A parser that dropped a
11//! token's bytes from every node span, or produced an overlap, fails here.
12
13use crate::ast::*;
14use crate::lexer::{Trivia, TriviaKind};
15
16/// Reconstruct `source` from the AST's byte spans plus the lexer's `trivia`.
17///
18/// The AST-level mirror of `lexer::render_lossless`: it checks the spans nest
19/// (V2), then tiles the file from every *content* node span merged with the
20/// non-blank trivia spans (V1/V3). `Ok(reconstruction)` is byte-identical to
21/// `source`; `Err` names the first nesting violation or the first run of bytes
22/// no span covers (content the AST dropped).
23///
24/// Obtain `trivia` from [`crate::lexer::lex_with_trivia`].
25pub fn render_from_ast(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
26    check_nesting(module)?;
27    tile(source, module, trivia)
28}
29
30/// V2 — every child span is contained in its parent, and sibling spans are
31/// ordered and disjoint. Validates the whole node set (module container
32/// included) as a laminar family via a containment stack.
33fn check_nesting(module: &Module) -> Result<(), String> {
34    let mut spans: Vec<Span> = Vec::new();
35    collect_module(module, &mut spans);
36    for span in &spans {
37        if !span.is_valid() {
38            return Err(format!("invalid span [{}, {})", span.start, span.end));
39        }
40    }
41    spans.retain(|s| !s.is_empty());
42    // Outer-first: earlier start, then later end.
43    spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
44
45    let mut stack: Vec<Span> = Vec::new();
46    for span in spans {
47        // Pop ancestors that end at/before this span starts (its left siblings).
48        while stack.last().is_some_and(|top| top.end <= span.start) {
49            stack.pop();
50        }
51        // Whatever remains on top must contain this span; otherwise the two
52        // overlap without nesting (a sibling that starts before the previous
53        // one ended, or a child that spills past its parent).
54        if let Some(parent) = stack.last() {
55            if !parent.contains(&span) {
56                return Err(format!(
57                    "span [{}, {}) overlaps [{}, {}) without nesting",
58                    span.start, span.end, parent.start, parent.end
59                ));
60            }
61        }
62        stack.push(span);
63    }
64    Ok(())
65}
66
67/// V1/V3 — tile the file from content spans + non-blank trivia and reconstruct.
68/// "Content" excludes the whole-module container (which would cover everything
69/// trivially) but includes the `module … where` header. Any gap between spans
70/// must be whitespace-only; a non-whitespace gap is a real token no node claims,
71/// i.e. content the AST dropped.
72fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
73    let mut content: Vec<Span> = Vec::new();
74    collect_module(module, &mut content);
75    let container = module.span;
76    content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
77
78    let mut items: Vec<(usize, usize)> = content.iter().map(|s| (s.start, s.end)).collect();
79    // Blank-line trivia carry no bytes; comment/CPP trivia fill the gaps that
80    // are legitimately not AST nodes.
81    items.extend(
82        trivia
83            .iter()
84            .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
85            .map(|t| (t.start, t.end)),
86    );
87    // Outer-first, like `check_nesting`: when intervals share a start, emit the
88    // broader parent before contained children so child spans can be skipped as
89    // already-covered tiles.
90    items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
91
92    let mut out = String::with_capacity(source.len());
93    let mut prev = 0usize;
94    for (start, end) in items {
95        validate_interval(source, start, end)?;
96        if start < prev {
97            // Nested AST child spans and contained trivia are already covered
98            // by their parent tile. A partial overlap that extends past `prev`
99            // cannot be tiled losslessly and means the interval set is invalid.
100            if end <= prev {
101                continue;
102            }
103            return Err(format!(
104                "span/trivia interval [{start}, {end}) overlaps previous tile ending at {prev}"
105            ));
106        }
107        let gap = &source[prev..start];
108        if !gap.chars().all(char::is_whitespace) {
109            return Err(format!(
110                "bytes {prev}..{start} not covered by any node or trivia span: {gap:?}"
111            ));
112        }
113        out.push_str(gap);
114        out.push_str(&source[start..end]);
115        prev = end;
116    }
117    let tail = &source[prev..];
118    if !tail.chars().all(char::is_whitespace) {
119        return Err(format!("bytes {prev}.. lost at EOF: {tail:?}"));
120    }
121    out.push_str(tail);
122
123    if out != source {
124        return Err(format!(
125            "reconstruction differs from source ({} vs {} bytes)",
126            out.len(),
127            source.len()
128        ));
129    }
130    Ok(out)
131}
132
133fn validate_interval(source: &str, start: usize, end: usize) -> Result<(), String> {
134    if start > end {
135        return Err(format!(
136            "span/trivia interval [{start}, {end}) has start after end"
137        ));
138    }
139    if end > source.len() {
140        return Err(format!(
141            "span/trivia interval [{start}, {end}) exceeds source length {}",
142            source.len()
143        ));
144    }
145    if !source.is_char_boundary(start) || !source.is_char_boundary(end) {
146        return Err(format!(
147            "span/trivia interval [{start}, {end}) does not align with UTF-8 boundaries"
148        ));
149    }
150    Ok(())
151}
152
153// ----- span collection ---------------------------------------------------
154
155fn collect_module(module: &Module, spans: &mut Vec<Span>) {
156    spans.push(module.span);
157    if !module.header.is_empty() {
158        spans.push(module.header);
159    }
160    for import in &module.imports {
161        spans.push(import.span);
162    }
163    for decl in &module.decls {
164        collect_decl(decl, spans);
165    }
166}
167
168fn collect_decl(decl: &Decl, spans: &mut Vec<Span>) {
169    match decl {
170        Decl::Template(template) => {
171            spans.push(template.span);
172            for field in &template.fields {
173                spans.push(field.span);
174            }
175            for body_decl in &template.body {
176                collect_tbody(body_decl, spans);
177            }
178        }
179        Decl::Interface(interface) => {
180            spans.push(interface.span);
181            for method in &interface.methods {
182                spans.push(method.span);
183            }
184            for choice in &interface.choices {
185                collect_choice(choice, spans);
186            }
187        }
188        Decl::Function(function) => {
189            // `function.span` is the equations' extent (contiguous); the signature,
190            // which may sit apart, is a separate sibling span.
191            spans.push(function.span);
192            for equation in &function.equations {
193                collect_eq(equation, spans);
194            }
195            if let Some(sig) = function.sig_span {
196                spans.push(sig);
197            }
198        }
199        Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => spans.push(*span),
200    }
201}
202
203fn collect_tbody(template_body_decl: &TemplateBodyDecl, spans: &mut Vec<Span>) {
204    match template_body_decl {
205        TemplateBodyDecl::Signatory { parties, span, .. }
206        | TemplateBodyDecl::Observer { parties, span, .. } => {
207            spans.push(*span);
208            for party in parties {
209                collect_expr(party, spans);
210            }
211        }
212        TemplateBodyDecl::Ensure { expr, span, .. }
213        | TemplateBodyDecl::Maintainer { expr, span, .. }
214        | TemplateBodyDecl::Key { expr, span, .. } => {
215            spans.push(*span);
216            collect_expr(expr, spans);
217        }
218        TemplateBodyDecl::Choice(choice) => collect_choice(choice, spans),
219        TemplateBodyDecl::InterfaceInstance(interface_instance) => {
220            spans.push(interface_instance.span);
221            for method in &interface_instance.methods {
222                collect_binding(method, spans);
223            }
224        }
225        TemplateBodyDecl::Other { span, .. } => spans.push(*span),
226    }
227}
228
229fn collect_choice(choice: &ChoiceDecl, spans: &mut Vec<Span>) {
230    spans.push(choice.span);
231    for param in &choice.params {
232        spans.push(param.span);
233    }
234    for controller in &choice.controllers {
235        collect_expr(controller, spans);
236    }
237    for observer in &choice.observers {
238        collect_expr(observer, spans);
239    }
240    if let Some(body) = &choice.body {
241        collect_expr(body, spans);
242    }
243}
244
245fn collect_eq(equation: &Equation, spans: &mut Vec<Span>) {
246    spans.push(equation.span);
247    for param in &equation.params {
248        collect_pat(param, spans);
249    }
250    collect_expr(&equation.body, spans);
251    for (guard, body) in &equation.guards {
252        collect_expr(guard, spans);
253        collect_expr(body, spans);
254    }
255    for where_binding in &equation.where_bindings {
256        collect_binding(where_binding, spans);
257    }
258}
259
260fn collect_binding(binding: &Binding, spans: &mut Vec<Span>) {
261    spans.push(binding.span);
262    collect_pat(&binding.pat, spans);
263    for param in &binding.params {
264        collect_pat(param, spans);
265    }
266    collect_expr(&binding.expr, spans);
267}
268
269fn collect_pat(pattern: &Pat, spans: &mut Vec<Span>) {
270    spans.push(pattern.span());
271    match pattern {
272        Pat::Con { args, .. } => {
273            for arg in args {
274                collect_pat(arg, spans);
275            }
276        }
277        Pat::Tuple { items, .. } | Pat::List { items, .. } => {
278            for item in items {
279                collect_pat(item, spans);
280            }
281        }
282        Pat::As { pat, .. } => collect_pat(pat, spans),
283        Pat::Var { .. } | Pat::Wild { .. } | Pat::Lit { .. } | Pat::Other { .. } => {}
284    }
285}
286
287fn collect_expr(expr: &Expr, spans: &mut Vec<Span>) {
288    spans.push(expr.span());
289    match expr {
290        Expr::App { func, args, .. } => {
291            collect_expr(func, spans);
292            for arg in args {
293                collect_expr(arg, spans);
294            }
295        }
296        Expr::BinOp { lhs, rhs, .. } => {
297            collect_expr(lhs, spans);
298            collect_expr(rhs, spans);
299        }
300        Expr::Neg { expr, .. } => collect_expr(expr, spans),
301        Expr::Lambda { params, body, .. } => {
302            for param in params {
303                collect_pat(param, spans);
304            }
305            collect_expr(body, spans);
306        }
307        Expr::If {
308            cond,
309            then_branch,
310            else_branch,
311            ..
312        } => {
313            collect_expr(cond, spans);
314            collect_expr(then_branch, spans);
315            collect_expr(else_branch, spans);
316        }
317        Expr::Case {
318            scrutinee, alts, ..
319        } => {
320            collect_expr(scrutinee, spans);
321            for alt in alts {
322                collect_alt(alt, spans);
323            }
324        }
325        Expr::Do { stmts, .. } => {
326            for stmt in stmts {
327                collect_dostmt(stmt, spans);
328            }
329        }
330        Expr::LetIn { bindings, body, .. } => {
331            for binding in bindings {
332                collect_binding(binding, spans);
333            }
334            collect_expr(body, spans);
335        }
336        Expr::Record { base, fields, .. } => {
337            collect_expr(base, spans);
338            for field in fields {
339                collect_field_assign(field, spans);
340            }
341        }
342        Expr::Tuple { items, .. } | Expr::List { items, .. } => {
343            for item in items {
344                collect_expr(item, spans);
345            }
346        }
347        Expr::Try { body, handlers, .. } => {
348            collect_expr(body, spans);
349            for handler in handlers {
350                collect_alt(handler, spans);
351            }
352        }
353        Expr::Section {
354            operand: Some(operand),
355            ..
356        } => collect_expr(operand, spans),
357        Expr::Var { .. }
358        | Expr::Con { .. }
359        | Expr::Lit { .. }
360        | Expr::Section { operand: None, .. }
361        | Expr::Error { .. } => {}
362    }
363}
364
365fn collect_alt(alt: &Alt, spans: &mut Vec<Span>) {
366    spans.push(alt.span);
367    collect_pat(&alt.pat, spans);
368    collect_expr(&alt.body, spans);
369}
370
371fn collect_field_assign(field_assign: &FieldAssign, spans: &mut Vec<Span>) {
372    spans.push(field_assign.span);
373    if let Some(value) = &field_assign.value {
374        collect_expr(value, spans);
375    }
376}
377
378fn collect_dostmt(do_stmt: &DoStmt, spans: &mut Vec<Span>) {
379    match do_stmt {
380        DoStmt::Bind {
381            pat, expr, span, ..
382        } => {
383            spans.push(*span);
384            collect_pat(pat, spans);
385            collect_expr(expr, spans);
386        }
387        DoStmt::Let { bindings, span, .. } => {
388            spans.push(*span);
389            for binding in bindings {
390                collect_binding(binding, spans);
391            }
392        }
393        DoStmt::Expr { expr, span, .. } => {
394            spans.push(*span);
395            collect_expr(expr, spans);
396        }
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403    use crate::lexer::{Pos, Trivia};
404
405    #[test]
406    fn tile_reports_overlapping_intervals() {
407        let source = "module M where\n-- comment\n";
408        let module = Module {
409            name: "M".to_string(),
410            pos: Pos { line: 1, column: 1 },
411            span: Span::new(0, source.len()),
412            header: Span::new(0, "module M where".len()),
413            imports: Vec::new(),
414            decls: Vec::new(),
415        };
416        let trivia = vec![Trivia {
417            kind: TriviaKind::LineComment,
418            text: "-- comment".to_string(),
419            pos: Pos { line: 2, column: 1 },
420            start: "module M wher".len(),
421            end: "module M where\n-- comment".len(),
422        }];
423
424        let err = tile(source, &module, &trivia).unwrap_err();
425        assert!(
426            err.contains("overlaps previous tile"),
427            "overlap should fail loudly, got: {err}"
428        );
429    }
430}