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.
33pub fn 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 [{}, {}) overlaps previous tile ending at {}",
99                start, end, prev
100            ));
101        }
102        let gap = &source[prev..start];
103        if !gap.chars().all(char::is_whitespace) {
104            return Err(format!(
105                "bytes {}..{} not covered by any node or trivia span: {:?}",
106                prev, start, gap
107            ));
108        }
109        out.push_str(gap);
110        out.push_str(&source[start..end]);
111        prev = end;
112    }
113    let tail = &source[prev..];
114    if !tail.chars().all(char::is_whitespace) {
115        return Err(format!("bytes {}.. lost at EOF: {:?}", prev, tail));
116    }
117    out.push_str(tail);
118
119    if out != source {
120        return Err(format!(
121            "reconstruction differs from source ({} vs {} bytes)",
122            out.len(),
123            source.len()
124        ));
125    }
126    Ok(out)
127}
128
129// ----- span collection ---------------------------------------------------
130
131fn collect_module(m: &Module, out: &mut Vec<Span>) {
132    out.push(m.span);
133    if !m.header.is_empty() {
134        out.push(m.header);
135    }
136    for imp in &m.imports {
137        out.push(imp.span);
138    }
139    for d in &m.decls {
140        collect_decl(d, out);
141    }
142}
143
144fn collect_decl(d: &Decl, out: &mut Vec<Span>) {
145    match d {
146        Decl::Template(t) => {
147            out.push(t.span);
148            for f in &t.fields {
149                out.push(f.span);
150            }
151            for b in &t.body {
152                collect_tbody(b, out);
153            }
154        }
155        Decl::Interface(i) => {
156            out.push(i.span);
157            for m in &i.methods {
158                out.push(m.span);
159            }
160            for c in &i.choices {
161                collect_choice(c, out);
162            }
163        }
164        Decl::Function(f) => {
165            // `f.span` is the equations' extent (contiguous); the signature,
166            // which may sit apart, is a separate sibling span.
167            out.push(f.span);
168            for eq in &f.equations {
169                collect_eq(eq, out);
170            }
171            if let Some(sig) = f.sig_span {
172                out.push(sig);
173            }
174        }
175        Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => out.push(*span),
176    }
177}
178
179fn collect_tbody(b: &TemplateBodyDecl, out: &mut Vec<Span>) {
180    match b {
181        TemplateBodyDecl::Signatory { parties, span, .. }
182        | TemplateBodyDecl::Observer { parties, span, .. } => {
183            out.push(*span);
184            for e in parties {
185                collect_expr(e, out);
186            }
187        }
188        TemplateBodyDecl::Ensure { expr, span, .. }
189        | TemplateBodyDecl::Maintainer { expr, span, .. }
190        | TemplateBodyDecl::Key { expr, span, .. } => {
191            out.push(*span);
192            collect_expr(expr, out);
193        }
194        TemplateBodyDecl::Choice(c) => collect_choice(c, out),
195        TemplateBodyDecl::InterfaceInstance(ii) => {
196            out.push(ii.span);
197            for m in &ii.methods {
198                collect_binding(m, out);
199            }
200        }
201        TemplateBodyDecl::Other { span, .. } => out.push(*span),
202    }
203}
204
205fn collect_choice(c: &ChoiceDecl, out: &mut Vec<Span>) {
206    out.push(c.span);
207    for p in &c.params {
208        out.push(p.span);
209    }
210    for e in &c.controllers {
211        collect_expr(e, out);
212    }
213    for e in &c.observers {
214        collect_expr(e, out);
215    }
216    if let Some(b) = &c.body {
217        collect_expr(b, out);
218    }
219}
220
221fn collect_eq(eq: &Equation, out: &mut Vec<Span>) {
222    out.push(eq.span);
223    for p in &eq.params {
224        collect_pat(p, out);
225    }
226    collect_expr(&eq.body, out);
227    for (g, b) in &eq.guards {
228        collect_expr(g, out);
229        collect_expr(b, out);
230    }
231    for wb in &eq.where_bindings {
232        collect_binding(wb, out);
233    }
234}
235
236fn collect_binding(b: &Binding, out: &mut Vec<Span>) {
237    out.push(b.span);
238    collect_pat(&b.pat, out);
239    for p in &b.params {
240        collect_pat(p, out);
241    }
242    collect_expr(&b.expr, out);
243}
244
245fn collect_pat(p: &Pat, out: &mut Vec<Span>) {
246    out.push(p.span());
247    match p {
248        Pat::Con { args, .. } => {
249            for a in args {
250                collect_pat(a, out);
251            }
252        }
253        Pat::Tuple { items, .. } | Pat::List { items, .. } => {
254            for it in items {
255                collect_pat(it, out);
256            }
257        }
258        Pat::As { pat, .. } => collect_pat(pat, out),
259        _ => {}
260    }
261}
262
263fn collect_expr(e: &Expr, out: &mut Vec<Span>) {
264    out.push(e.span());
265    match e {
266        Expr::App { func, args, .. } => {
267            collect_expr(func, out);
268            for a in args {
269                collect_expr(a, out);
270            }
271        }
272        Expr::BinOp { lhs, rhs, .. } => {
273            collect_expr(lhs, out);
274            collect_expr(rhs, out);
275        }
276        Expr::Neg { expr, .. } => collect_expr(expr, out),
277        Expr::Lambda { params, body, .. } => {
278            for p in params {
279                collect_pat(p, out);
280            }
281            collect_expr(body, out);
282        }
283        Expr::If {
284            cond,
285            then_branch,
286            else_branch,
287            ..
288        } => {
289            collect_expr(cond, out);
290            collect_expr(then_branch, out);
291            collect_expr(else_branch, out);
292        }
293        Expr::Case {
294            scrutinee, alts, ..
295        } => {
296            collect_expr(scrutinee, out);
297            for a in alts {
298                collect_alt(a, out);
299            }
300        }
301        Expr::Do { stmts, .. } => {
302            for s in stmts {
303                collect_dostmt(s, out);
304            }
305        }
306        Expr::LetIn { bindings, body, .. } => {
307            for b in bindings {
308                collect_binding(b, out);
309            }
310            collect_expr(body, out);
311        }
312        Expr::Record { base, fields, .. } => {
313            collect_expr(base, out);
314            for f in fields {
315                collect_field_assign(f, out);
316            }
317        }
318        Expr::Tuple { items, .. } | Expr::List { items, .. } => {
319            for it in items {
320                collect_expr(it, out);
321            }
322        }
323        Expr::Try { body, handlers, .. } => {
324            collect_expr(body, out);
325            for h in handlers {
326                collect_alt(h, out);
327            }
328        }
329        Expr::Section {
330            operand: Some(o), ..
331        } => collect_expr(o, out),
332        _ => {}
333    }
334}
335
336fn collect_alt(a: &Alt, out: &mut Vec<Span>) {
337    out.push(a.span);
338    collect_pat(&a.pat, out);
339    collect_expr(&a.body, out);
340}
341
342fn collect_field_assign(f: &FieldAssign, out: &mut Vec<Span>) {
343    out.push(f.span);
344    if let Some(v) = &f.value {
345        collect_expr(v, out);
346    }
347}
348
349fn collect_dostmt(s: &DoStmt, out: &mut Vec<Span>) {
350    match s {
351        DoStmt::Bind {
352            pat, expr, span, ..
353        } => {
354            out.push(*span);
355            collect_pat(pat, out);
356            collect_expr(expr, out);
357        }
358        DoStmt::Let { bindings, span, .. } => {
359            out.push(*span);
360            for b in bindings {
361                collect_binding(b, out);
362            }
363        }
364        DoStmt::Expr { expr, span, .. } => {
365            out.push(*span);
366            collect_expr(expr, out);
367        }
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374    use crate::lexer::{Pos, Trivia};
375
376    #[test]
377    fn tile_reports_overlapping_intervals() {
378        let source = "module M where\n-- comment\n";
379        let module = Module {
380            name: "M".to_string(),
381            pos: Pos { line: 1, column: 1 },
382            span: Span::new(0, source.len()),
383            header: Span::new(0, "module M where".len()),
384            imports: Vec::new(),
385            decls: Vec::new(),
386        };
387        let trivia = vec![Trivia {
388            kind: TriviaKind::LineComment,
389            text: "-- comment".to_string(),
390            pos: Pos { line: 2, column: 1 },
391            start: "module M wher".len(),
392            end: "module M where\n-- comment".len(),
393        }];
394
395        let err = tile(source, &module, &trivia).unwrap_err();
396        assert!(
397            err.contains("overlaps previous tile"),
398            "overlap should fail loudly, got: {err}"
399        );
400    }
401}