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    spans.retain(|s| !s.is_empty());
37    // Outer-first: earlier start, then later end.
38    spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
39
40    let mut stack: Vec<Span> = Vec::new();
41    for span in spans {
42        // Pop ancestors that end at/before this span starts (its left siblings).
43        while stack.last().is_some_and(|top| top.end <= span.start) {
44            stack.pop();
45        }
46        // Whatever remains on top must contain this span; otherwise the two
47        // overlap without nesting (a sibling that starts before the previous
48        // one ended, or a child that spills past its parent).
49        if let Some(parent) = stack.last() {
50            if !parent.contains(&span) {
51                return Err(format!(
52                    "span [{}, {}) overlaps [{}, {}) without nesting",
53                    span.start, span.end, parent.start, parent.end
54                ));
55            }
56        }
57        stack.push(span);
58    }
59    Ok(())
60}
61
62/// V1/V3 — tile the file from content spans + non-blank trivia and reconstruct.
63/// "Content" excludes the whole-module container (which would cover everything
64/// trivially) but includes the `module … where` header. Any gap between spans
65/// must be whitespace-only; a non-whitespace gap is a real token no node claims,
66/// i.e. content the AST dropped.
67fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
68    let mut content: Vec<Span> = Vec::new();
69    collect_module(module, &mut content);
70    let container = module.span;
71    content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
72
73    let mut items: Vec<(usize, usize)> = content.iter().map(|s| (s.start, s.end)).collect();
74    // Blank-line trivia carry no bytes; comment/CPP trivia fill the gaps that
75    // are legitimately not AST nodes.
76    items.extend(
77        trivia
78            .iter()
79            .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
80            .map(|t| (t.start, t.end)),
81    );
82    // Outer-first, like `check_nesting`: when intervals share a start, emit the
83    // broader parent before contained children so child spans can be skipped as
84    // already-covered tiles.
85    items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
86
87    let mut out = String::with_capacity(source.len());
88    let mut prev = 0usize;
89    for (start, end) in items {
90        if start < prev {
91            // Nested AST child spans and contained trivia are already covered
92            // by their parent tile. A partial overlap that extends past `prev`
93            // cannot be tiled losslessly and means the interval set is invalid.
94            if end <= prev {
95                continue;
96            }
97            return Err(format!(
98                "span/trivia interval [{start}, {end}) overlaps previous tile ending at {prev}"
99            ));
100        }
101        let gap = &source[prev..start];
102        if !gap.chars().all(char::is_whitespace) {
103            return Err(format!(
104                "bytes {prev}..{start} not covered by any node or trivia span: {gap:?}"
105            ));
106        }
107        out.push_str(gap);
108        out.push_str(&source[start..end]);
109        prev = end;
110    }
111    let tail = &source[prev..];
112    if !tail.chars().all(char::is_whitespace) {
113        return Err(format!("bytes {prev}.. lost at EOF: {tail:?}"));
114    }
115    out.push_str(tail);
116
117    if out != source {
118        return Err(format!(
119            "reconstruction differs from source ({} vs {} bytes)",
120            out.len(),
121            source.len()
122        ));
123    }
124    Ok(out)
125}
126
127// ----- span collection ---------------------------------------------------
128
129fn collect_module(m: &Module, out: &mut Vec<Span>) {
130    out.push(m.span);
131    if !m.header.is_empty() {
132        out.push(m.header);
133    }
134    for imp in &m.imports {
135        out.push(imp.span);
136    }
137    for d in &m.decls {
138        collect_decl(d, out);
139    }
140}
141
142fn collect_decl(d: &Decl, out: &mut Vec<Span>) {
143    match d {
144        Decl::Template(t) => {
145            out.push(t.span);
146            for f in &t.fields {
147                out.push(f.span);
148            }
149            for b in &t.body {
150                collect_tbody(b, out);
151            }
152        }
153        Decl::Interface(i) => {
154            out.push(i.span);
155            for m in &i.methods {
156                out.push(m.span);
157            }
158            for c in &i.choices {
159                collect_choice(c, out);
160            }
161        }
162        Decl::Function(f) => {
163            // `f.span` is the equations' extent (contiguous); the signature,
164            // which may sit apart, is a separate sibling span.
165            out.push(f.span);
166            for eq in &f.equations {
167                collect_eq(eq, out);
168            }
169            if let Some(sig) = f.sig_span {
170                out.push(sig);
171            }
172        }
173        Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => out.push(*span),
174    }
175}
176
177fn collect_tbody(b: &TemplateBodyDecl, out: &mut Vec<Span>) {
178    match b {
179        TemplateBodyDecl::Signatory { parties, span, .. }
180        | TemplateBodyDecl::Observer { parties, span, .. } => {
181            out.push(*span);
182            for e in parties {
183                collect_expr(e, out);
184            }
185        }
186        TemplateBodyDecl::Ensure { expr, span, .. }
187        | TemplateBodyDecl::Maintainer { expr, span, .. }
188        | TemplateBodyDecl::Key { expr, span, .. } => {
189            out.push(*span);
190            collect_expr(expr, out);
191        }
192        TemplateBodyDecl::Choice(c) => collect_choice(c, out),
193        TemplateBodyDecl::InterfaceInstance(ii) => {
194            out.push(ii.span);
195            for m in &ii.methods {
196                collect_binding(m, out);
197            }
198        }
199        TemplateBodyDecl::Other { span, .. } => out.push(*span),
200    }
201}
202
203fn collect_choice(c: &ChoiceDecl, out: &mut Vec<Span>) {
204    out.push(c.span);
205    for p in &c.params {
206        out.push(p.span);
207    }
208    for e in &c.controllers {
209        collect_expr(e, out);
210    }
211    for e in &c.observers {
212        collect_expr(e, out);
213    }
214    if let Some(b) = &c.body {
215        collect_expr(b, out);
216    }
217}
218
219fn collect_eq(eq: &Equation, out: &mut Vec<Span>) {
220    out.push(eq.span);
221    for p in &eq.params {
222        collect_pat(p, out);
223    }
224    collect_expr(&eq.body, out);
225    for (g, b) in &eq.guards {
226        collect_expr(g, out);
227        collect_expr(b, out);
228    }
229    for wb in &eq.where_bindings {
230        collect_binding(wb, out);
231    }
232}
233
234fn collect_binding(b: &Binding, out: &mut Vec<Span>) {
235    out.push(b.span);
236    collect_pat(&b.pat, out);
237    for p in &b.params {
238        collect_pat(p, out);
239    }
240    collect_expr(&b.expr, out);
241}
242
243fn collect_pat(p: &Pat, out: &mut Vec<Span>) {
244    out.push(p.span());
245    match p {
246        Pat::Con { args, .. } => {
247            for a in args {
248                collect_pat(a, out);
249            }
250        }
251        Pat::Tuple { items, .. } | Pat::List { items, .. } => {
252            for it in items {
253                collect_pat(it, out);
254            }
255        }
256        Pat::As { pat, .. } => collect_pat(pat, out),
257        _ => {}
258    }
259}
260
261fn collect_expr(e: &Expr, out: &mut Vec<Span>) {
262    out.push(e.span());
263    match e {
264        Expr::App { func, args, .. } => {
265            collect_expr(func, out);
266            for a in args {
267                collect_expr(a, out);
268            }
269        }
270        Expr::BinOp { lhs, rhs, .. } => {
271            collect_expr(lhs, out);
272            collect_expr(rhs, out);
273        }
274        Expr::Neg { expr, .. } => collect_expr(expr, out),
275        Expr::Lambda { params, body, .. } => {
276            for p in params {
277                collect_pat(p, out);
278            }
279            collect_expr(body, out);
280        }
281        Expr::If {
282            cond,
283            then_branch,
284            else_branch,
285            ..
286        } => {
287            collect_expr(cond, out);
288            collect_expr(then_branch, out);
289            collect_expr(else_branch, out);
290        }
291        Expr::Case {
292            scrutinee, alts, ..
293        } => {
294            collect_expr(scrutinee, out);
295            for a in alts {
296                collect_alt(a, out);
297            }
298        }
299        Expr::Do { stmts, .. } => {
300            for s in stmts {
301                collect_dostmt(s, out);
302            }
303        }
304        Expr::LetIn { bindings, body, .. } => {
305            for b in bindings {
306                collect_binding(b, out);
307            }
308            collect_expr(body, out);
309        }
310        Expr::Record { base, fields, .. } => {
311            collect_expr(base, out);
312            for f in fields {
313                collect_field_assign(f, out);
314            }
315        }
316        Expr::Tuple { items, .. } | Expr::List { items, .. } => {
317            for it in items {
318                collect_expr(it, out);
319            }
320        }
321        Expr::Try { body, handlers, .. } => {
322            collect_expr(body, out);
323            for h in handlers {
324                collect_alt(h, out);
325            }
326        }
327        Expr::Section {
328            operand: Some(o), ..
329        } => collect_expr(o, out),
330        _ => {}
331    }
332}
333
334fn collect_alt(a: &Alt, out: &mut Vec<Span>) {
335    out.push(a.span);
336    collect_pat(&a.pat, out);
337    collect_expr(&a.body, out);
338}
339
340fn collect_field_assign(f: &FieldAssign, out: &mut Vec<Span>) {
341    out.push(f.span);
342    if let Some(v) = &f.value {
343        collect_expr(v, out);
344    }
345}
346
347fn collect_dostmt(s: &DoStmt, out: &mut Vec<Span>) {
348    match s {
349        DoStmt::Bind {
350            pat, expr, span, ..
351        } => {
352            out.push(*span);
353            collect_pat(pat, out);
354            collect_expr(expr, out);
355        }
356        DoStmt::Let { bindings, span, .. } => {
357            out.push(*span);
358            for b in bindings {
359                collect_binding(b, out);
360            }
361        }
362        DoStmt::Expr { expr, span, .. } => {
363            out.push(*span);
364            collect_expr(expr, out);
365        }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use crate::lexer::{Pos, Trivia};
373
374    #[test]
375    fn tile_reports_overlapping_intervals() {
376        let source = "module M where\n-- comment\n";
377        let module = Module {
378            name: "M".to_string(),
379            pos: Pos { line: 1, column: 1 },
380            span: Span::new(0, source.len()),
381            header: Span::new(0, "module M where".len()),
382            imports: Vec::new(),
383            decls: Vec::new(),
384        };
385        let trivia = vec![Trivia {
386            kind: TriviaKind::LineComment,
387            text: "-- comment".to_string(),
388            pos: Pos { line: 2, column: 1 },
389            start: "module M wher".len(),
390            end: "module M where\n-- comment".len(),
391        }];
392
393        let err = tile(source, &module, &trivia).unwrap_err();
394        assert!(
395            err.contains("overlaps previous tile"),
396            "overlap should fail loudly, got: {err}"
397        );
398    }
399}