Skip to content

Remove the unreachable VAR (FINAL | STATIC) alternative - #69

Merged
ghedwards merged 1 commit into
masterfrom
claude/remove-dead-var-modifier-68
Aug 13, 2026
Merged

Remove the unreachable VAR (FINAL | STATIC) alternative#69
ghedwards merged 1 commit into
masterfrom
claude/remove-dead-var-modifier-68

Conversation

@ghedwards

Copy link
Copy Markdown

Closes #68.

 localAssignmentExpression
-  : (VAR | (FINAL | STATIC) VAR? | VAR (FINAL | STATIC)) left=startExpression ...
+  : (VAR | (FINAL | STATIC) VAR?) left=startExpression ...

The trailing form could not be reached by any input. FINAL and STATIC are both in the identifier rule — put there by #46 so code naming a variable final keeps working — so var final matches the plain VAR alternative with final as the identifier, prediction commits, and the third alternative is never considered.

Removed rather than repaired

var final already means something, and cfparser gets it right:

final = getValue();       ->  final = getValue()
var final = getValue();   ->  var final = getValue()

tree-sitter-cfml's corpus tests exactly these under a case named "keywords as variable names", with the expected tree showing left: (identifier). Making var final x = 1; parse would resolve that ambiguity against a documented, tested reading in favour of one with nothing behind it.

I could find no support for var final x = 1, var static x = 1 or static final x = 1 in:

Source Result
tree-sitter-cfml corpus contradicts it — var final = getValue() is a variable named final
tree-sitter-cfml grammar.js no final/static modifier modelled
Shipped dictionaries (cf9cf11, lucee5) no entries
CFLint's real-world test corpus no usage
cfdocs (foundeo/cfdocs) cannot answer — 806 functions, 170 tags, no language keywords at all, not even var
Adobe / Lucee docs unreachable from this environment

final x = 1; is unaffected and stays — Lucee's immutable local, and the reason FINAL is in this rule at all.

Behaviour-neutral, and verified as such

Parse and decompile output across thirteen forms is byte-identical before and after, including the three that fail:

diff before.txt after.txt
IDENTICAL — removing the alternative changed nothing

That is the proof the alternative was dead. It also means the new fixture passes with cfml.parsing/src/main stashed, which is by design rather than a weak test: it exists to pin the keyword-as-name readings against a future change to this rule, not to demonstrate a fix. Worth saying plainly, since every other grammar PR in this series used the revert-check as evidence and this one cannot.

Provenance

VAR FINAL came in with 292ba9c, whose message says final "takes VAR's shape and may combine with it" — an assertion with no corpus case behind it. VAR STATIC was mine in #65, written to mirror it, so half of what this removes I added three PRs ago.

Verification

  • 326 tests, ./gradlew build
  • Differential harness unchanged at 1, nothing newly broken case-by-case
  • CFLint 675 against a clean build

Still open

static final x = 1; remains unparsed. Unlike the above it was never declared, so it is a gap rather than dead grammar, and it has the same evidence problem. If someone can confirm it against a live Lucee or ACF, it is a small change to this same line.


Generated by Claude Code

localAssignmentExpression declared a trailing modifier form that no input
could reach. FINAL and STATIC are both in the identifier rule, put there by
#46 so code naming a variable final keeps working, so "var final" matches the
plain VAR alternative with final as the identifier before the longer one is
considered. Prediction commits there and never reaches the third alternative.

Removed rather than made reachable, because "var final" already means
something and cfparser gets it right:

  var final = getValue();   ->  var final = getValue()

tree-sitter-cfml's corpus tests exactly that under a case named "keywords as
variable names", with the expected tree showing an identifier. Making
"var final x = 1" parse would resolve that ambiguity against a documented and
tested reading in favour of one with no evidence behind it. I could find no
support for "var final x = 1", "var static x = 1" or "static final x = 1" in
the tree-sitter corpus or grammar, the shipped dictionaries, CFLint's
real-world test corpus, or cfdocs -- and cfdocs cannot answer it either way,
since it documents 806 functions and 170 tags but no language keywords at all,
not even var. Adobe and Lucee documentation was unreachable from here.

"final x = 1" is not affected and stays. It is Lucee's immutable local and the
reason FINAL is in this rule.

The change is behaviour-neutral by construction, and that is verified rather
than asserted: parse and decompile output across thirteen forms is
byte-identical before and after, including the three that fail. The new
fixture therefore passes with cfml.parsing/src/main stashed, by design -- it
exists to pin the keyword-as-name readings against a future change to this
rule, not to demonstrate a fix.

VAR STATIC was added in #65, mirroring the existing VAR FINAL, so half of what
this removes is mine.

326 tests, ./gradlew build, differential harness unchanged at 1 with nothing
newly broken, CFLint's 675 against a clean build.

Closes #68
@ghedwards
ghedwards merged commit 2023433 into master Aug 13, 2026
3 checks passed
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.

Remove the unreachable VAR (FINAL | STATIC) alternative

2 participants