From 0e2632477069193e1a83981db7a2c0d19c2b4b87 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 04:38:32 +0000 Subject: [PATCH] Let final and abstract be used as variable names Every other modifier keyword already worked as an ordinary name -- public, private, package, remote, component, interface, static -- because the identifier rule lists them. FINAL and ABSTRACT were missing, so `final = 3;` and `abstract = 4;` were syntax errors. Same shape as the three already recorded in CLAUDE.md, and the fix is the same: add them to identifier alongside default, var, to, include, new. The one that needed checking is the pair FINAL now serves. `final x = 1;` is Lucee's immutable local, added in #34, so FINAL both starts a declaration and is a name. Both parse and, more to the point, build different trees: `final = 3;` decompiles as an assignment to a variable called final, `final x = 1;` as a declaration. `var final = 1;` and `final var y = 2;` are covered too, and the fixture pins all four together so a later change cannot collapse them. Differential harness 8 disagreements to 6 -- both corpus cases for this, and nothing newly broken. Verified end to end -- 313 cfparser tests, ./gradlew build, CFLint's 675. Closes #46 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35 --- .../src/main/antlr4/cfml/CFSCRIPTParser.g4 | 2 + .../expressions/final_abstract_names_46.cfc | 14 + .../final_abstract_names_46.expected.txt | 247 ++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.cfc create mode 100644 cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.expected.txt diff --git a/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4 b/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4 index cd5cbe6..354c786 100644 --- a/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4 +++ b/cfml.parsing/src/main/antlr4/cfml/CFSCRIPTParser.g4 @@ -684,6 +684,8 @@ identifier | TO | DEFAULT // default is a cfscript keyword that's always allowed as a var name | INSTANCEOF // ColdBox's Matcher and TestBox's Assertion both declare function instanceOf() + | FINAL // modifiers, and ordinary names -- `final = 3;` is a variable called final + | ABSTRACT | CT // two-letter operator abbreviations; far too likely as ordinary names | NCT | INCLUDE diff --git a/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.cfc b/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.cfc new file mode 100644 index 0000000..328af97 --- /dev/null +++ b/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.cfc @@ -0,0 +1,14 @@ +// final and abstract are modifiers, and also ordinary names. +final = 3; +abstract = 4; +combined = final + abstract; +var final = 1; +// The declaration forms have to stay distinguishable from the assignments above. +final limit = 10; +final var total = 0; +// The other modifier keywords, which already worked. +public = 1; +private = 2; +package = 5; +remote = 6; +static = 7; diff --git a/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.expected.txt b/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.expected.txt new file mode 100644 index 0000000..f55644d --- /dev/null +++ b/cfml.parsing/src/test/resources/cfml/tests/expressions/final_abstract_names_46.expected.txt @@ -0,0 +1,247 @@ +/*===TOKENS===*/ +Hidden:LINE_COMMENT +FINAL +'=' <=> +INTEGER_LITERAL <3> +';' <;> +Hidden:NEWLINE <> +ABSTRACT +'=' <=> +INTEGER_LITERAL <4> +';' <;> +Hidden:NEWLINE <> +IDENTIFIER +'=' <=> +FINAL +'+' <+> +ABSTRACT +';' <;> +Hidden:NEWLINE <> +VAR +FINAL +'=' <=> +INTEGER_LITERAL <1> +';' <;> +Hidden:NEWLINE <> +Hidden:LINE_COMMENT +FINAL +IDENTIFIER +'=' <=> +INTEGER_LITERAL <10> +';' <;> +Hidden:NEWLINE <> +FINAL +VAR +IDENTIFIER +'=' <=> +INTEGER_LITERAL <0> +';' <;> +Hidden:NEWLINE <> +Hidden:LINE_COMMENT +PUBLIC +'=' <=> +INTEGER_LITERAL <1> +';' <;> +Hidden:NEWLINE <> +PRIVATE +'=' <=> +INTEGER_LITERAL <2> +';' <;> +Hidden:NEWLINE <> +PACKAGE +'=' <=> +INTEGER_LITERAL <5> +';' <;> +Hidden:NEWLINE <> +REMOTE +'=' <=> +INTEGER_LITERAL <6> +';' <;> +Hidden:NEWLINE <> +STATIC +'=' <=> +INTEGER_LITERAL <7> +';' <;> +Hidden:NEWLINE <> +/*===TREE===*/ +(scriptBlock + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier final)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 3)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier abstract)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 4)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier combined)))) + ) + = + (startExpression + (baseExpression + (baseExpression (unaryExpression (memberExpression (identifier final)))) + + + (baseExpression (unaryExpression (memberExpression (identifier abstract)))) + ) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (localAssignmentExpression + var + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier final)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 1)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (localAssignmentExpression + final + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier limit)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 10)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (localAssignmentExpression + final + var + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier total)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 0)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier public)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 1)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier private)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 2)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier package)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 5)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier remote)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 6)))) + ) + ) + (endOfStatement ;) + ) + ) + (element + (statement + (assignmentExpression + (startExpression + (baseExpression (unaryExpression (memberExpression (identifier static)))) + ) + = + (startExpression + (baseExpression (unaryExpression (primaryExpression (literalExpression 7)))) + ) + ) + (endOfStatement ;) + ) + ) +) +/*======*/ +/*===DECOMPILE===*/ +{ +final = 3; +abstract = 4; +combined = final + abstract; + var final = 1; + final limit = 10; + final total = 0; +public = 1; +private = 2; +package = 5; +remote = 6; +static = 7; + +} +/*======*/ \ No newline at end of file