adam_lang/ast.rs
1//! A span-carrying adam-lang structural AST, built by [`crate::AdamAstParser`] as an alternative to
2//! [`crate::AdamParser`]'s direct `adam_rs::Sheet` construction. Method bodies and cell
3//! initializers reference [`cel_parser::Expr`]/[`cel_parser::lex_lexer::Literal`] directly.
4//! Carries no resolved types, no `TypeRegistry` lookups, and never fails on semantic grounds
5//! (unknown type name, literal/type mismatch, undeclared cell, arity mismatch) — those checks
6//! are deferred to a later, separate compile-to-`Sheet` phase, mirroring
7//! [`cel_parser::AstContext`]'s design.
8
9pub use cel_parser::ExprSpan;
10use cel_parser::lex_lexer::Literal;
11
12pub use cel_parser::Comment;
13
14/// A parsed adam-lang sheet declaration, with source spans on every node.
15///
16/// Built by [`crate::AdamAstParser`]; consumed by the language server, the formatter, and the
17/// (separate, future) compile-to-`Sheet` phase.
18#[derive(Debug, Clone)]
19pub struct Sheet {
20 /// The sheet's declared name.
21 pub name: String,
22 /// The name token's span.
23 pub name_span: ExprSpan,
24 /// The sheet's items, in declaration order.
25 pub items: Vec<SheetItem>,
26 /// A leading `//`/`/* */` comment immediately preceding the `sheet` keyword (e.g. a file
27 /// header), if recovered by [`crate::trivia::attach_trivia`]. Unlike every other node's
28 /// `leading_comment`, this one has no enclosing sibling list to attach via — it covers the
29 /// gap between the start of the source and the sheet's own span.
30 pub leading_comment: Option<Comment>,
31 /// A leading `//!` doc comment immediately preceding the `sheet` keyword, if recovered by
32 /// [`crate::AdamAstParser`].
33 pub doc_comment: Option<String>,
34 /// A trailing comment immediately preceding the sheet's own closing `}`, if recovered. See
35 /// <https://github.com/stlab/cel-rs/issues/52>.
36 pub trailing_comment: Option<Comment>,
37 /// Whether a blank line preceded the sheet's own closing `}`, if recovered. See
38 /// <https://github.com/stlab/cel-rs/issues/52>.
39 pub blank_line_before_close: bool,
40 /// The span of the sheet's own opening `{`, used to recover trailing trivia when `items` is
41 /// empty. See <https://github.com/stlab/cel-rs/issues/52>.
42 pub open_brace_span: ExprSpan,
43 /// The span of the whole `sheet ... { ... }` construct.
44 pub span: ExprSpan,
45 /// Syntax errors recovered while parsing, in source order. Empty for a syntactically clean
46 /// sheet.
47 pub errors: Vec<cel_parser::ParseError>,
48}
49
50/// One top-level item inside a `sheet { ... }` body.
51#[derive(Debug, Clone)]
52pub enum SheetItem {
53 /// A `cell` declaration.
54 Cell(CellDecl),
55 /// A `relationship` declaration.
56 Relationship(RelationshipDecl),
57 /// A `conditional` declaration.
58 Conditional(ConditionalDecl),
59 /// An `out` declaration.
60 Out(OutDecl),
61 /// A `source` declaration.
62 Source(SourceDecl),
63 /// A syntax error recovered at declaration granularity; `span` covers the skipped tokens.
64 Error {
65 /// The span of the skipped, malformed item.
66 span: ExprSpan,
67 /// A leading `//`/`/* */` comment immediately preceding this item, if recovered by
68 /// [`crate::trivia::attach_trivia`]. Preserved even though the item failed to parse, so
69 /// a comment explaining a broken declaration (e.g. `// TODO: fix this`) isn't silently
70 /// dropped.
71 leading_comment: Option<Comment>,
72 /// A leading `///` doc comment immediately preceding this item, if recovered by
73 /// [`crate::AdamAstParser`] before parsing failed.
74 doc_comment: Option<String>,
75 /// A `//`/`/* */` comment on the same source line as this item's own last token, if
76 /// recovered by [`crate::trivia::attach_trivia`]. Distinct from `leading_comment`, which
77 /// precedes an item on its own line. See <https://github.com/stlab/cel-rs/issues/59>.
78 trailing_line_comment: Option<Comment>,
79 /// Whether the gap before this item contained a blank line, if recovered by
80 /// [`crate::trivia::attach_trivia`].
81 blank_line_before: bool,
82 },
83}
84
85impl SheetItem {
86 /// Returns this item's source span.
87 pub fn span(&self) -> ExprSpan {
88 match self {
89 SheetItem::Cell(c) => c.span,
90 SheetItem::Relationship(r) => r.span,
91 SheetItem::Conditional(c) => c.span,
92 SheetItem::Out(o) => o.span,
93 SheetItem::Source(s) => s.span,
94 SheetItem::Error { span, .. } => *span,
95 }
96 }
97
98 /// Sets this item's leading comment.
99 pub(crate) fn set_leading_comment(&mut self, comment: Comment) {
100 match self {
101 SheetItem::Cell(c) => c.leading_comment = Some(comment),
102 SheetItem::Relationship(r) => r.leading_comment = Some(comment),
103 SheetItem::Conditional(c) => c.leading_comment = Some(comment),
104 SheetItem::Out(o) => o.leading_comment = Some(comment),
105 SheetItem::Source(s) => s.leading_comment = Some(comment),
106 SheetItem::Error {
107 leading_comment, ..
108 } => *leading_comment = Some(comment),
109 }
110 }
111
112 /// Sets a `//`/`/* */` comment recovered on the same source line as this item's own last
113 /// token. See <https://github.com/stlab/cel-rs/issues/59>.
114 pub(crate) fn set_trailing_line_comment(&mut self, comment: Comment) {
115 match self {
116 SheetItem::Cell(c) => c.trailing_line_comment = Some(comment),
117 SheetItem::Relationship(r) => r.trailing_line_comment = Some(comment),
118 SheetItem::Conditional(c) => c.trailing_line_comment = Some(comment),
119 SheetItem::Out(o) => o.trailing_line_comment = Some(comment),
120 SheetItem::Source(s) => s.trailing_line_comment = Some(comment),
121 SheetItem::Error {
122 trailing_line_comment,
123 ..
124 } => *trailing_line_comment = Some(comment),
125 }
126 }
127
128 /// Sets whether a blank line preceded this item.
129 pub(crate) fn set_blank_line_before(&mut self, value: bool) {
130 match self {
131 SheetItem::Cell(c) => c.blank_line_before = value,
132 SheetItem::Relationship(r) => r.blank_line_before = value,
133 SheetItem::Conditional(c) => c.blank_line_before = value,
134 SheetItem::Out(o) => o.blank_line_before = value,
135 SheetItem::Source(s) => s.blank_line_before = value,
136 SheetItem::Error {
137 blank_line_before, ..
138 } => *blank_line_before = value,
139 }
140 }
141
142 /// Sets this item's doc comment and widens its span to start at `start` (the doc comment's
143 /// own first token), so `trivia::attach_trivia`'s gap scan stops before the doc comment's
144 /// source text instead of misparsing it as a plain `//` comment.
145 pub(crate) fn set_doc_comment(&mut self, text: String, start: proc_macro2::Span) {
146 match self {
147 SheetItem::Cell(c) => {
148 c.doc_comment = Some(text);
149 c.span.start = start;
150 }
151 SheetItem::Relationship(r) => {
152 r.doc_comment = Some(text);
153 r.span.start = start;
154 }
155 SheetItem::Conditional(c) => {
156 c.doc_comment = Some(text);
157 c.span.start = start;
158 }
159 SheetItem::Out(o) => {
160 o.doc_comment = Some(text);
161 o.span.start = start;
162 }
163 SheetItem::Source(s) => {
164 s.doc_comment = Some(text);
165 s.span.start = start;
166 }
167 SheetItem::Error {
168 doc_comment, span, ..
169 } => {
170 *doc_comment = Some(text);
171 span.start = start;
172 }
173 }
174 }
175}
176
177/// `type_expr = identifier | "(" [ type_expr ["," [ type_expr { "," type_expr } ]] ] ")".`
178///
179/// `()` is the empty tuple type (0 elements); `(T)` is grouping (same as bare `T` — types have
180/// no precedence to disambiguate, but staying symmetric with `cel_parser`'s expression grammar
181/// costs nothing); `(T,)` is a 1-element tuple; `(T, U, ...)` is n-element, no trailing comma.
182/// Shared verbatim with `cel_parser` (which additionally supports `[T]` array types and
183/// `Name(args)` parameterized/generic types) so both crates resolve, format, and error on
184/// exactly one type-expression grammar.
185pub use cel_parser::TypeExpr;
186
187/// `cell_decl = "cell" identifier cell_type_init [ cell_filter ] [ "require" "{" { requirement }
188/// "}" ] ";".`
189///
190/// `type_name`/`initializer` are unresolved — no `TypeRegistry` lookup, no literal validation.
191/// Exactly one of `type_name`, `initializer` may be absent, per the grammar's two
192/// `cell_type_init` forms, but this is not enforced here (an all-`None` `CellDecl` cannot be
193/// produced by [`crate::AdamAstParser`], which requires at least one of `:`/`=`).
194#[derive(Debug, Clone)]
195pub struct CellDecl {
196 /// The cell's declared name.
197 pub name: String,
198 /// The name token's span.
199 pub name_span: ExprSpan,
200 /// The `: type_expr` annotation, if present.
201 pub type_name: Option<TypeExpr>,
202 /// The `= expression` initializer, if present. Unresolved and unevaluated here — see
203 /// `crate::parser::AdamParser` for the compile-to-`Sheet` phase, which parses this with no
204 /// cell scope pushed and evaluates it eagerly, once, at parse time.
205 pub initializer: Option<cel_parser::Expr>,
206 /// The `filter` clause, if present.
207 pub filter: Option<CellFilter>,
208 /// The `require { ... }` validation block, if present.
209 pub require: Option<RequireBlock>,
210 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
211 /// [`crate::trivia::attach_trivia`].
212 pub leading_comment: Option<Comment>,
213 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
214 /// [`crate::AdamAstParser`].
215 pub doc_comment: Option<String>,
216 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
217 /// recovered by [`crate::trivia::attach_trivia`]. See
218 /// <https://github.com/stlab/cel-rs/issues/59>.
219 pub trailing_line_comment: Option<Comment>,
220 /// Whether a blank line preceded this declaration, if recovered by
221 /// [`crate::trivia::attach_trivia`].
222 pub blank_line_before: bool,
223 /// The span of the whole `cell ...;` declaration.
224 pub span: ExprSpan,
225}
226
227/// `source_decl = "source" identifier cell_type_init [ cell_filter ] [ "require" "{" {
228/// requirement } "}" ] ";".`
229///
230/// Same shape as [`CellDecl`]: a `source` cell's initializer is a one-time literal exactly like a
231/// plain `cell`'s, and it supports the same `filter` clause and `require` block.
232#[derive(Debug, Clone)]
233pub struct SourceDecl {
234 /// The source cell's declared name.
235 pub name: String,
236 /// The name token's span.
237 pub name_span: ExprSpan,
238 /// The `: type_expr` annotation, if present.
239 pub type_name: Option<TypeExpr>,
240 /// The `= expression` initializer, if present. Unresolved and unevaluated here — see
241 /// `crate::parser::AdamParser` for the compile-to-`Sheet` phase, which parses this with no
242 /// cell scope pushed and evaluates it eagerly, once, at parse time.
243 pub initializer: Option<cel_parser::Expr>,
244 /// The `filter` clause, if present.
245 pub filter: Option<CellFilter>,
246 /// The `require { ... }` validation block, if present.
247 pub require: Option<RequireBlock>,
248 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
249 /// [`crate::trivia::attach_trivia`].
250 pub leading_comment: Option<Comment>,
251 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
252 /// [`crate::AdamAstParser`].
253 pub doc_comment: Option<String>,
254 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
255 /// recovered by [`crate::trivia::attach_trivia`]. See
256 /// <https://github.com/stlab/cel-rs/issues/59>.
257 pub trailing_line_comment: Option<Comment>,
258 /// Whether a blank line preceded this declaration, if recovered by
259 /// [`crate::trivia::attach_trivia`].
260 pub blank_line_before: bool,
261 /// The span of the whole `source ...;` declaration.
262 pub span: ExprSpan,
263}
264
265/// `cell_filter = "filter" expression.`
266#[derive(Debug, Clone)]
267pub struct CellFilter {
268 /// The filter's body expression. `_` inside it denotes the candidate value being conformed;
269 /// every other identifier that names an already-declared cell is a deduced dependency.
270 pub body: cel_parser::Expr,
271 /// The span of the whole `filter ...` clause.
272 pub span: ExprSpan,
273}
274
275/// `relationship_decl = "relationship" "{" { binding } "}".`
276#[derive(Debug, Clone)]
277pub struct RelationshipDecl {
278 /// The relationship block's bindings, in declaration order.
279 pub bindings: Vec<BindingDecl>,
280 /// A leading comment immediately preceding this declaration, if recovered.
281 pub leading_comment: Option<Comment>,
282 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
283 /// [`crate::AdamAstParser`].
284 pub doc_comment: Option<String>,
285 /// A `//`/`/* */` comment on the same source line as this declaration's own closing `}`, if
286 /// recovered. Distinct from `trailing_comment`, which precedes this declaration's own
287 /// closing `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
288 pub trailing_line_comment: Option<Comment>,
289 /// Whether a blank line preceded this declaration, if recovered.
290 pub blank_line_before: bool,
291 /// A trailing comment immediately preceding this declaration's own closing `}`, if
292 /// recovered. See <https://github.com/stlab/cel-rs/issues/52>.
293 pub trailing_comment: Option<Comment>,
294 /// Whether a blank line preceded this declaration's own closing `}`, if recovered. See
295 /// <https://github.com/stlab/cel-rs/issues/52>.
296 pub blank_line_before_close: bool,
297 /// The span of this declaration's own opening `{`, used to recover trailing trivia when
298 /// `bindings` is empty. See <https://github.com/stlab/cel-rs/issues/52>.
299 pub open_brace_span: ExprSpan,
300 /// The span of the whole `relationship { ... }` declaration.
301 pub span: ExprSpan,
302}
303
304/// `binding = binding_target ":=" expression ";".`
305/// `binding_target = identifier | "(" identifier { "," identifier } [ "," ] ")".`
306///
307/// Unlike the old `method_decl` this replaces, a binding names no explicit input cell list —
308/// its inputs are whichever already-declared cells `body` references, deduced at compile time
309/// (see `crate::parser::AdamParser::parse_deduced_expr`); this untyped CST parser has no cell
310/// declarations to resolve against, so it records no input list at all, only the outputs.
311///
312/// Parenthesizing the left-hand side requests tuple destructuring, matching Rust's tuple-pattern
313/// syntax: `(a, b) := ...` and the single-element `(a,) := ...` (trailing comma mandatory,
314/// exactly as in a Rust 1-tuple pattern) both destructure `body`'s tuple result element-wise into
315/// `outputs`. A bare identifier, or a single parenthesized identifier with no comma (`(a) :=
316/// ...`, mere grouping), binds `body`'s whole result directly to that one output instead — see
317/// [`Self::destructure`].
318#[derive(Debug, Clone)]
319pub struct BindingDecl {
320 /// The binding's output cell names (the left-hand side), in declaration order.
321 pub outputs: Vec<(String, ExprSpan)>,
322 /// Whether the left-hand side requests destructuring (`(a, b) := ...` or `(a,) := ...`) as
323 /// opposed to a direct bind (`a := ...` or the equivalent grouping `(a) := ...`). Always
324 /// `true` when `outputs.len() > 1`; when `outputs.len() == 1`, distinguishes "destructure
325 /// this single-element tuple" from "bind this whole value directly" — a distinction the
326 /// parenthesized-or-not left-hand side is the only way to express, since both forms name
327 /// exactly one output.
328 pub destructure: bool,
329 /// The parsed right-hand-side expression.
330 pub body: cel_parser::Expr,
331 /// A leading comment immediately preceding this binding, if recovered by
332 /// [`crate::trivia::attach_trivia`].
333 pub leading_comment: Option<Comment>,
334 /// A `//`/`/* */` comment on the same source line as this binding's own last token, if
335 /// recovered by [`crate::trivia::attach_trivia`]. See
336 /// <https://github.com/stlab/cel-rs/issues/59>.
337 pub trailing_line_comment: Option<Comment>,
338 /// Whether a blank line preceded this binding, if recovered by
339 /// [`crate::trivia::attach_trivia`].
340 pub blank_line_before: bool,
341 /// The span of the whole `a := ...;` / `(a, b) := ...;` declaration.
342 pub span: ExprSpan,
343}
344
345/// `out_decl = "out" identifier [ ":" type_expr ] ":=" expression [ cell_filter ] [ "require"
346/// "{" { requirement } "}" ] ";".`
347///
348/// `type_expr` is unresolved here (no `TypeRegistry` lookup), matching `CellDecl`. When
349/// absent, the cell's type is inferred from `initializer`'s result type by the compile phase
350/// (`crate::parser::AdamParser`) — never here.
351#[derive(Debug, Clone)]
352pub struct OutDecl {
353 /// The declared cell's name.
354 pub name: String,
355 /// The name token's span.
356 pub name_span: ExprSpan,
357 /// The `: type_expr` annotation, if present.
358 pub type_name: Option<TypeExpr>,
359 /// The parsed initializer expression that computes this cell's value.
360 pub initializer: cel_parser::Expr,
361 /// The `filter` clause, if present.
362 pub filter: Option<CellFilter>,
363 /// The `require { ... }` validation block, if present.
364 pub require: Option<RequireBlock>,
365 /// A leading `//`/`/* */` comment immediately preceding this declaration, if recovered by
366 /// [`crate::trivia::attach_trivia`].
367 pub leading_comment: Option<Comment>,
368 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
369 /// [`crate::AdamAstParser`].
370 pub doc_comment: Option<String>,
371 /// A `//`/`/* */` comment on the same source line as this declaration's own last token, if
372 /// recovered by [`crate::trivia::attach_trivia`]. See
373 /// <https://github.com/stlab/cel-rs/issues/59>.
374 pub trailing_line_comment: Option<Comment>,
375 /// Whether a blank line preceded this declaration, if recovered by
376 /// [`crate::trivia::attach_trivia`].
377 pub blank_line_before: bool,
378 /// The span of the whole `out ... ;` declaration.
379 pub span: ExprSpan,
380}
381
382/// The optional `require { ... }` block trailing an `out` declaration's initializer.
383#[derive(Debug, Clone)]
384pub struct RequireBlock {
385 /// The block's requirements, in declaration order.
386 pub requirements: Vec<RequirementDecl>,
387 /// A trailing comment immediately preceding this block's own closing `}`, if recovered.
388 /// See <https://github.com/stlab/cel-rs/issues/52>.
389 pub trailing_comment: Option<Comment>,
390 /// Whether a blank line preceded this block's own closing `}`, if recovered. See
391 /// <https://github.com/stlab/cel-rs/issues/52>.
392 pub blank_line_before_close: bool,
393 /// The span of this block's own opening `{`, used to recover trailing trivia when
394 /// `requirements` is empty.
395 pub open_brace_span: ExprSpan,
396 /// The span of the whole `require { ... }` block.
397 pub span: ExprSpan,
398}
399
400/// `requirement = [ "@" identifier ] expression ";".`
401///
402/// `name`, when present, is a plain string label passed to `adam_rs::Sheet::add_requirement`,
403/// not a cell reference — it may coincide with a cell name declared elsewhere in the sheet but
404/// doesn't have to.
405#[derive(Debug, Clone)]
406pub struct RequirementDecl {
407 /// The requirement's declared label, if the `@identifier` marker was present.
408 pub name: Option<String>,
409 /// The `@identifier` marker's span, if present.
410 pub name_span: Option<ExprSpan>,
411 /// The parsed requirement body expression; must type-check as `bool`.
412 pub body: cel_parser::Expr,
413 /// A leading comment immediately preceding this requirement, if recovered by
414 /// [`crate::trivia::attach_trivia`].
415 pub leading_comment: Option<Comment>,
416 /// A `//`/`/* */` comment on the same source line as this requirement's own last token, if
417 /// recovered by [`crate::trivia::attach_trivia`]. See
418 /// <https://github.com/stlab/cel-rs/issues/59>.
419 pub trailing_line_comment: Option<Comment>,
420 /// Whether a blank line preceded this requirement, if recovered by
421 /// [`crate::trivia::attach_trivia`].
422 pub blank_line_before: bool,
423 /// The span of the whole `[ "@" identifier ] expr;` declaration.
424 pub span: ExprSpan,
425}
426
427/// `conditional_decl = "conditional" expression "{" { conditional_branch } "}".`
428#[derive(Debug, Clone)]
429pub struct ConditionalDecl {
430 /// The match subject: an arbitrary expression over already-declared cells (a bare
431 /// identifier, e.g. `mode`, is the degenerate single-cell case).
432 pub match_expr: cel_parser::Expr,
433 /// The named (literal `=>`) branches, in declaration order.
434 pub branches: Vec<ConditionalBranch>,
435 /// The `_ => { ... }` default branch, if present.
436 pub default: Option<DefaultBranch>,
437 /// A leading comment immediately preceding this declaration, if recovered.
438 pub leading_comment: Option<Comment>,
439 /// A leading `///` doc comment immediately preceding this declaration, if recovered by
440 /// [`crate::AdamAstParser`].
441 pub doc_comment: Option<String>,
442 /// A `//`/`/* */` comment on the same source line as this declaration's own closing `}`, if
443 /// recovered. Distinct from `trailing_comment`, which precedes this declaration's own
444 /// closing `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
445 pub trailing_line_comment: Option<Comment>,
446 /// Whether a blank line preceded this declaration, if recovered.
447 pub blank_line_before: bool,
448 /// A trailing comment immediately preceding this declaration's own closing `}`, if
449 /// recovered. See <https://github.com/stlab/cel-rs/issues/52>.
450 pub trailing_comment: Option<Comment>,
451 /// Whether a blank line preceded this declaration's own closing `}`, if recovered. See
452 /// <https://github.com/stlab/cel-rs/issues/52>.
453 pub blank_line_before_close: bool,
454 /// The span of this declaration's own opening `{`, used to recover trailing trivia when
455 /// there are no `branches`/`default`. See <https://github.com/stlab/cel-rs/issues/52>.
456 pub open_brace_span: ExprSpan,
457 /// The span of the whole `conditional ... { ... }` declaration.
458 pub span: ExprSpan,
459}
460
461/// The `_ => { ... }` default branch of a `conditional_decl`, mirroring `ConditionalBranch`'s
462/// shape (it has no match literal of its own).
463#[derive(Debug, Clone)]
464pub struct DefaultBranch {
465 /// The default branch's relationships, in declaration order.
466 pub relationships: Vec<RelationshipDecl>,
467 /// A trailing comment immediately preceding this branch's own closing `}`, if recovered.
468 /// See <https://github.com/stlab/cel-rs/issues/52>.
469 pub trailing_comment: Option<Comment>,
470 /// A `//`/`/* */` comment on the same source line as this branch's own closing `}` (between
471 /// it and the enclosing conditional's own closing `}`), if recovered. See
472 /// <https://github.com/stlab/cel-rs/issues/59>.
473 pub trailing_line_comment: Option<Comment>,
474 /// Whether a blank line preceded this branch's own closing `}`, if recovered.
475 pub blank_line_before_close: bool,
476 /// The span of this branch's own opening `{`, used to recover trailing trivia when
477 /// `relationships` is empty.
478 pub open_brace_span: ExprSpan,
479 /// The span from the `_` token through this branch's own closing `}`.
480 pub span: ExprSpan,
481}
482
483/// `conditional_branch = literal_pattern "=>" "{" { relationship_decl } "}" [ "," ].`
484/// `literal_pattern = ["-"] literal.`
485#[derive(Debug, Clone)]
486pub struct ConditionalBranch {
487 /// The branch's unresolved match literal, always stored unsigned; see [`Self::negated`]
488 /// for whether a leading `-` applies to it.
489 pub literal: Literal,
490 /// Whether the branch key is negated by a leading `-` (Rust's own `LiteralPattern` rule —
491 /// see <https://doc.rust-lang.org/reference/patterns.html#literal-patterns>). A `-`, if
492 /// present, precedes [`Self::literal_span`] and is covered by [`Self::span`]'s start, but
493 /// not by `literal_span` itself.
494 pub negated: bool,
495 /// The literal token's own span (never includes a leading `-`; see [`Self::negated`]).
496 pub literal_span: ExprSpan,
497 /// The branch's relationships, in declaration order.
498 pub relationships: Vec<RelationshipDecl>,
499 /// A leading comment immediately preceding this branch, if recovered by
500 /// [`crate::trivia::attach_trivia`].
501 pub leading_comment: Option<Comment>,
502 /// A `//`/`/* */` comment on the same source line as this branch's own closing `}`, if
503 /// recovered. Distinct from `trailing_comment`, which precedes this branch's own closing
504 /// `}` on an earlier line. See <https://github.com/stlab/cel-rs/issues/59>.
505 pub trailing_line_comment: Option<Comment>,
506 /// Whether a blank line preceded this branch, if recovered by
507 /// [`crate::trivia::attach_trivia`].
508 pub blank_line_before: bool,
509 /// A trailing comment immediately preceding this branch's own closing `}`, if recovered.
510 /// See <https://github.com/stlab/cel-rs/issues/52>.
511 pub trailing_comment: Option<Comment>,
512 /// Whether a blank line preceded this branch's own closing `}`, if recovered. See
513 /// <https://github.com/stlab/cel-rs/issues/52>.
514 pub blank_line_before_close: bool,
515 /// The span of this branch's own opening `{`, used to recover trailing trivia when
516 /// `relationships` is empty. See <https://github.com/stlab/cel-rs/issues/52>.
517 pub open_brace_span: ExprSpan,
518 /// The span from the branch's literal through its closing `}`.
519 pub span: ExprSpan,
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use proc_macro2::Span;
526
527 fn point(span: Span) -> ExprSpan {
528 ExprSpan {
529 start: span,
530 end: span,
531 }
532 }
533
534 #[test]
535 fn sheet_item_span_reads_the_cell_variant() {
536 let span = point(Span::call_site());
537 let item = SheetItem::Cell(CellDecl {
538 name: "x".to_string(),
539 name_span: span,
540 type_name: None,
541 initializer: None,
542 filter: None,
543 require: None,
544 leading_comment: None,
545 doc_comment: None,
546 trailing_line_comment: None,
547 blank_line_before: false,
548 span,
549 });
550 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
551 }
552
553 #[test]
554 fn sheet_item_span_reads_the_relationship_variant() {
555 let span = point(Span::call_site());
556 let item = SheetItem::Relationship(RelationshipDecl {
557 bindings: Vec::new(),
558 leading_comment: None,
559 doc_comment: None,
560 trailing_line_comment: None,
561 blank_line_before: false,
562 trailing_comment: None,
563 blank_line_before_close: false,
564 open_brace_span: span,
565 span,
566 });
567 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
568 }
569
570 #[test]
571 fn sheet_item_span_reads_the_conditional_variant() {
572 let span = point(Span::call_site());
573 let item = SheetItem::Conditional(ConditionalDecl {
574 match_expr: cel_parser::Expr::Ident {
575 name: "m".to_string(),
576 span,
577 },
578 branches: Vec::new(),
579 default: None,
580 leading_comment: None,
581 doc_comment: None,
582 trailing_line_comment: None,
583 blank_line_before: false,
584 trailing_comment: None,
585 blank_line_before_close: false,
586 open_brace_span: span,
587 span,
588 });
589 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
590 }
591
592 #[test]
593 fn sheet_item_span_reads_the_error_variant() {
594 let span = point(Span::call_site());
595 let item = SheetItem::Error {
596 span,
597 leading_comment: None,
598 doc_comment: None,
599 trailing_line_comment: None,
600 blank_line_before: false,
601 };
602 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
603 }
604
605 #[test]
606 fn set_leading_comment_sets_the_cell_variant() {
607 let span = point(Span::call_site());
608 let mut item = SheetItem::Cell(CellDecl {
609 name: "x".to_string(),
610 name_span: span,
611 type_name: None,
612 initializer: None,
613 filter: None,
614 require: None,
615 leading_comment: None,
616 doc_comment: None,
617 trailing_line_comment: None,
618 blank_line_before: false,
619 span,
620 });
621 item.set_leading_comment(Comment::Line("hi".to_string()));
622 match item {
623 SheetItem::Cell(c) => {
624 assert_eq!(c.leading_comment, Some(Comment::Line("hi".to_string())))
625 }
626 other => panic!("expected Cell, got {other:?}"),
627 }
628 }
629
630 #[test]
631 fn set_leading_comment_sets_the_error_variant() {
632 let span = point(Span::call_site());
633 let mut item = SheetItem::Error {
634 span,
635 leading_comment: None,
636 doc_comment: None,
637 trailing_line_comment: None,
638 blank_line_before: false,
639 };
640 item.set_leading_comment(Comment::Line("hi".to_string()));
641 match item {
642 SheetItem::Error {
643 leading_comment, ..
644 } => {
645 assert_eq!(leading_comment, Some(Comment::Line("hi".to_string())))
646 }
647 other => panic!("expected Error, got {other:?}"),
648 }
649 }
650
651 #[test]
652 fn set_blank_line_before_sets_the_cell_variant() {
653 let span = point(Span::call_site());
654 let mut item = SheetItem::Cell(CellDecl {
655 name: "x".to_string(),
656 name_span: span,
657 type_name: None,
658 initializer: None,
659 filter: None,
660 require: None,
661 leading_comment: None,
662 doc_comment: None,
663 trailing_line_comment: None,
664 blank_line_before: false,
665 span,
666 });
667 item.set_blank_line_before(true);
668 match item {
669 SheetItem::Cell(c) => assert!(c.blank_line_before),
670 other => panic!("expected Cell, got {other:?}"),
671 }
672 }
673
674 #[test]
675 fn sheet_item_span_reads_the_out_variant() {
676 let span = point(Span::call_site());
677 let item = SheetItem::Out(OutDecl {
678 name: "o".to_string(),
679 name_span: span,
680 type_name: None,
681 initializer: cel_parser::Expr::Ident {
682 name: "x".to_string(),
683 span,
684 },
685 filter: None,
686 require: None,
687 leading_comment: None,
688 doc_comment: None,
689 trailing_line_comment: None,
690 blank_line_before: false,
691 span,
692 });
693 assert_eq!(format!("{:?}", item.span()), format!("{span:?}"));
694 }
695
696 #[test]
697 fn set_leading_comment_sets_the_out_variant() {
698 let span = point(Span::call_site());
699 let mut item = SheetItem::Out(OutDecl {
700 name: "o".to_string(),
701 name_span: span,
702 type_name: None,
703 initializer: cel_parser::Expr::Ident {
704 name: "x".to_string(),
705 span,
706 },
707 filter: None,
708 require: None,
709 leading_comment: None,
710 doc_comment: None,
711 trailing_line_comment: None,
712 blank_line_before: false,
713 span,
714 });
715 item.set_leading_comment(Comment::Line("hi".to_string()));
716 match item {
717 SheetItem::Out(o) => {
718 assert_eq!(o.leading_comment, Some(Comment::Line("hi".to_string())))
719 }
720 other => panic!("expected Out, got {other:?}"),
721 }
722 }
723
724 #[test]
725 fn ast_type_expr_is_the_shared_cel_parser_type_expr() {
726 fn assert_same_type<T>(_: &T) {}
727 let span = cel_parser::ExprSpan {
728 start: Span::call_site(),
729 end: Span::call_site(),
730 };
731 let expr: TypeExpr = cel_parser::TypeExpr::Named {
732 name: "i32".to_string(),
733 args: Vec::new(),
734 span,
735 };
736 assert_same_type::<cel_parser::TypeExpr>(&expr);
737 }
738
739 #[test]
740 fn type_expr_named_span_is_its_own_span() {
741 let span = point(Span::call_site());
742 let expr = TypeExpr::Named {
743 name: "i32".to_string(),
744 args: Vec::new(),
745 span,
746 };
747 assert_eq!(format!("{:?}", expr.span()), format!("{span:?}"));
748 }
749
750 #[test]
751 fn type_expr_tuple_span_is_the_whole_parenthesized_span() {
752 let span = point(Span::call_site());
753 let expr = TypeExpr::Tuple {
754 elements: Vec::new(),
755 span,
756 };
757 assert_eq!(format!("{:?}", expr.span()), format!("{span:?}"));
758 }
759
760 #[test]
761 fn cell_decl_type_name_holds_a_nested_tuple_type_expr() {
762 let span = point(Span::call_site());
763 let cell = CellDecl {
764 name: "a".to_string(),
765 name_span: span,
766 type_name: Some(TypeExpr::Tuple {
767 elements: vec![
768 TypeExpr::Named {
769 name: "i32".to_string(),
770 args: Vec::new(),
771 span,
772 },
773 TypeExpr::Named {
774 name: "f64".to_string(),
775 args: Vec::new(),
776 span,
777 },
778 ],
779 span,
780 }),
781 initializer: None,
782 filter: None,
783 require: None,
784 leading_comment: None,
785 doc_comment: None,
786 trailing_line_comment: None,
787 blank_line_before: false,
788 span,
789 };
790 match cell.type_name {
791 Some(TypeExpr::Tuple { elements, .. }) => assert_eq!(elements.len(), 2),
792 other => panic!("expected Tuple, got {other:?}"),
793 }
794 }
795
796 #[test]
797 fn cell_decl_initializer_holds_a_parsed_expr() {
798 let span = point(Span::call_site());
799 let cell = CellDecl {
800 name: "a".to_string(),
801 name_span: span,
802 type_name: None,
803 initializer: Some(cel_parser::Expr::Ident {
804 name: "x".to_string(),
805 span,
806 }),
807 filter: None,
808 require: None,
809 leading_comment: None,
810 doc_comment: None,
811 trailing_line_comment: None,
812 blank_line_before: false,
813 span,
814 };
815 assert!(matches!(
816 cell.initializer,
817 Some(cel_parser::Expr::Ident { .. })
818 ));
819 }
820
821 #[test]
822 fn cell_decl_filter_field_holds_a_cell_filter() {
823 let span = point(Span::call_site());
824 let cell = CellDecl {
825 name: "a".to_string(),
826 name_span: span,
827 type_name: None,
828 initializer: None,
829 filter: Some(CellFilter {
830 body: cel_parser::Expr::Ident {
831 name: "_".to_string(),
832 span,
833 },
834 span,
835 }),
836 require: None,
837 leading_comment: None,
838 doc_comment: None,
839 trailing_line_comment: None,
840 blank_line_before: false,
841 span,
842 };
843 let filter = cell.filter.as_ref().expect("filter present");
844 assert!(matches!(
845 &filter.body,
846 cel_parser::Expr::Ident { name, .. } if name == "_"
847 ));
848 }
849}