Summary
src/ast/build_cst.rs::build_range_literal and the ListSlice arm of build_binary_expr accumulate the start and end components of a *N..M quantifier (or list[start..end] slice) into Option<…> slots, but a second value on either side of the DOT_DOT token silently overwrites the previously stored one. There is no diagnostic and no defensive check.
Today the grammar does not actually emit two INTEGER (or two Expression) tokens on the same side of .., so the bug is unreachable from valid Cypher. It is reported as a defensive concern: any future grammar/CST refactor that does emit duplicate tokens would silently drop the first one and produce a wrong AST without surfacing the conflict.
This is the same shape as the duplicate-RETURN accumulator-overwrite bug reported separately (single-part query body), so I'm bundling them together for visibility.
Affected version
decypher 0.2.0-alpha.6.
Locations
Location 1 — src/ast/build_cst.rs, in build_range_literal():
fn build_range_literal(rl: RangeLiteral) -> Result<ast_c::RangeLiteral> {
let mut start: Option<i64> = None;
let mut end: Option<i64> = None;
let mut seen_dot_dot = false;
let mut seen_star = false;
for child in rl.syntax().children_with_tokens() {
if let Some(tok) = child.as_token() {
match tok.kind() {
SyntaxKind::STAR => seen_star = true,
SyntaxKind::DOT_DOT => seen_dot_dot = true,
SyntaxKind::INTEGER => {
if let Some(val) = parse_integer(tok.text()) {
if !seen_dot_dot {
start = Some(val); // ← silent overwrite if multiple INTEGERs before DOT_DOT
} else {
end = Some(val); // ← silent overwrite if multiple INTEGERs after DOT_DOT
}
}
}
_ => {}
}
} else if let Some(node) = child.as_node() {
for inner in node.children_with_tokens() {
if let Some(tok) = inner.as_token()
&& tok.kind() == SyntaxKind::INTEGER
&& let Some(val) = parse_integer(tok.text())
{
if !seen_dot_dot {
start = Some(val); // same overwrite, inner loop
} else {
end = Some(val);
}
}
}
}
}
...
}
Location 2 — src/ast/build_cst.rs, in build_binary_expr(), ListSlice arm (around line 1311):
let mut seen_dot_dot = false;
let mut start_expr: Option<ast_c::Expression> = None;
let mut end_expr: Option<ast_c::Expression> = None;
for child in b.syntax().children_with_tokens() {
if let Some(tok) = child.as_token() {
if tok.kind() == SyntaxKind::DOT_DOT {
seen_dot_dot = true;
}
continue;
}
if let Some(node) = child.as_node()
&& let Some(e) = Expression::cast(node.clone())
{
let built = build_expression(e)?;
if !seen_dot_dot {
start_expr = Some(built); // ← silent overwrite if multiple Expressions before DOT_DOT
} else {
end_expr = Some(built); // ← silent overwrite if multiple Expressions after DOT_DOT
}
}
}
Summary
src/ast/build_cst.rs::build_range_literaland theListSlicearm ofbuild_binary_expraccumulate thestartandendcomponents of a*N..Mquantifier (orlist[start..end]slice) intoOption<…>slots, but a second value on either side of theDOT_DOTtoken silently overwrites the previously stored one. There is no diagnostic and no defensive check.Today the grammar does not actually emit two
INTEGER(or twoExpression) tokens on the same side of.., so the bug is unreachable from valid Cypher. It is reported as a defensive concern: any future grammar/CST refactor that does emit duplicate tokens would silently drop the first one and produce a wrong AST without surfacing the conflict.This is the same shape as the duplicate-
RETURNaccumulator-overwrite bug reported separately (single-part query body), so I'm bundling them together for visibility.Affected version
decypher0.2.0-alpha.6.Locations
Location 1 —
src/ast/build_cst.rs, inbuild_range_literal():Location 2 —
src/ast/build_cst.rs, inbuild_binary_expr(), ListSlice arm (around line 1311):