Skip to content

fix(parser): allow range as a function in expression position - #49

Open
akesling wants to merge 1 commit into
sunsided:mainfrom
akesling:fix/range-function-in-expression-position
Open

akesling wants to merge 1 commit into
sunsided:mainfrom
akesling:fix/range-function-in-expression-position

Conversation

@akesling

@akesling akesling commented Jul 7, 2026

Copy link
Copy Markdown

Summary

range(...) — a standard openCypher list function (UNWIND range(1, n) is idiomatic) — fails to parse in expression position with unexpected token 'range'. This adds the one missing dispatch entry so range parses as an ordinary function (and as a variable name), while staying reserved in the schema index-type position.

Root cause

range is registered in the keyword table (parser/lexer.rs: "RANGE" => KW_RANGE) because it is reserved in the schema index-type position (CREATE RANGE INDEX …). But the primary-expression dispatch in parser/grammar/expr.rs only routes a specific set of function-name keywords — KW_COUNT | KW_TYPE | KW_KEY — through the name-or-call machinery. KW_RANGE was omitted, so in expression position it falls through to the error arm.

In openCypher, range is not a reserved word or keyword at all — it is an ordinary function name / identifier (grammar/openCypher.bnf lists the reserved and non-reserved words; RANGE appears in neither, and function calls go through the generic function-invocation production). decypher reserving KW_RANGE is an implementation choice for the CREATE RANGE INDEX admin syntax; that reservation should stay scoped to the index grammar (already handled in parse_create_clause / parse_create_index) and not leak into expression position — exactly how type, count, and key are already treated here.

Fix

Add KW_RANGE to that existing arm. It reuses the same code path that already makes type(r) work as both a call and a name — no lexer change, no new AST node, and the schema CREATE RANGE INDEX path (which peeks for KW_RANGE before expression parsing) is untouched.

Before / after

query before after
RETURN range(0, 3) parse error parses (FunctionCall "range", 2 args)
UNWIND range(1, 5) AS x RETURN x parse error parses
WITH 1 AS range RETURN range parse error parses
CREATE RANGE INDEX FOR (n:Person) ON (n.name) parses parses (unchanged)

Tests

  • tests/ast_shape.rsrange(0, 3) and range(0, 10, 2) parse as a FunctionCall named range with two/three arguments; range remains usable as a variable name.
  • tests/smoke.rsRETURN range(…), UNWIND range(…), and a regression guard that CREATE RANGE INDEX … still parses.

All new tests fail on main and pass with the fix; the full test suite is green and cargo fmt / clippy are clean.

Conformance

range() has a dedicated openCypher TCK feature, expressions/list/List11.feature ("List11 — Create a list from a range"), and is exercised across many others — e.g. clauses/unwind/Unwind1.feature, clauses/return-skip-limit/ReturnSkipLimit1.feature, and expressions/list/List2.feature (slicing). None of these can be attempted while range(...) fails to parse; they are natural downstream coverage for this fix.

References

`range` is in the keyword table (reserved for the schema index-type
position, `CREATE RANGE INDEX …`) but was omitted from the primary-
expression arm that already accepts the other function-name keywords
(`count`, `type`, `key`). So `range(0, 10)` failed to parse
("unexpected token `range`") even though `range()` is a standard
openCypher list function, and a bare `range` could not be referenced in
expression position either.

`range` is a *contextual* keyword — special only where the index grammar
looks for it (handled separately in `parse_create_clause` /
`parse_create_index`), and an ordinary function/identifier everywhere
else. Adding `KW_RANGE` to the existing name-or-call arm routes it
through the same machinery as `type`/`count`/`key`: `range(start, end
[, step])` now parses as a FUNCTION_INVOCATION and a bare `range` stays a
valid variable name. No lexer change, no new AST, and the
`CREATE RANGE INDEX …` path is unaffected.

Tests (tests/ast_shape.rs, tests/smoke.rs): `range(0, 3)` and
`range(0, 10, 2)` parse as a FunctionCall with the right arity; `range`
remains a valid variable name; `RETURN range(…)` and `UNWIND range(…)`
parse; and a regression guard that `CREATE RANGE INDEX …` still parses.
@kilo-code-bot

kilo-code-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • src/parser/grammar/expr.rs — 1 changed line: adds KW_RANGE to the primary-expression dispatch arm that already handles count, type, and key as contextual keywords.
  • tests/ast_shape.rs — 3 new tests verifying range() parses as a FunctionCall with correct arity and that range remains a valid variable name.
  • tests/smoke.rs — 3 new integration tests covering range() in RETURN, UNWIND, and a regression guard for CREATE RANGE INDEX.

Notes

The fix is minimal and correct: range was already registered in is_keyword_as_name and looks_like_qualified_call, but was omitted from the parse_atom match arm that routes keyword tokens through the function-invocation / variable-name machinery. Adding KW_RANGE there aligns it with the existing treatment of count, type, and key as contextual keywords.

Test coverage is thorough—function calls with 2 and 3 arguments, bare variable usage, placement in RETURN and UNWIND, and a regression guard confirming the schema CREATE RANGE INDEX path is unaffected. No security, logic, or edge-case concerns identified.


Reviewed by kimi-k2.6 · Input: 72.7K · Output: 5.6K · Cached: 888.8K

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants