Skip to content

Stop a call in an attribute value swallowing the tag's body - #51

Merged
ghedwards merged 1 commit into
masterfrom
claude/fix-tag-attr-call-45
Aug 13, 2026
Merged

Stop a call in an attribute value swallowing the tag's body#51
ghedwards merged 1 commit into
masterfrom
claude/fix-tag-attr-call-45

Conversation

@ghedwards

Copy link
Copy Markdown

Closes #45.

query name="q" cachedWithin=t { echo("x"); }                        // ok
query name="q" cachedWithin=createTimeSpan(0,0,0,5) { echo("x"); }  // mismatched input '<EOF>'

Not the parenthesis, as the issue guessed

I suspected prediction committing to the parenthesised-attribute branch. It's simpler: functionCall carries an optional trailing block.

functionCall : (identifier | specialWord) LEFTPAREN argumentList RIGHTPAREN
               body=compoundStatement? ;

So createTimeSpan(0,0,0,5) { echo("x"); } parses as a call with a body, taking the block the tag needed and leaving cfmlfunctionStatement staring at EOF.

That label was never read

The ctx.body handling in CFExpressionVisitor belongs to visitQualifiedFunctionCall, whose own rule keeps its body. That's why deleting this one compiles without touching a line of Java, and why no fixture's recorded tree changes. It existed only to consume input.

The forms that genuinely want a body still have their own:

cfhttp(url="a" method="GET") { }                // cfmlfunctionStatement
cfmail(to="a", from="b") { writeOutput("x"); }  // tagFunctionStatement

Both verified unchanged.

Verification


Generated by Claude Code

query name="q" cachedWithin=createTimeSpan(0,0,0,5) { ... } failed while
cachedWithin=t parsed. Not the attribute grammar: functionCall carried an
optional trailing block,

  functionCall : (identifier | specialWord) LEFTPAREN argumentList RIGHTPAREN
                 body=compoundStatement? ;

so createTimeSpan(0,0,0,5) { echo("x"); } was read as a call with a body, taking
the block the tag needed and leaving cfmlfunctionStatement at EOF.

That label was never read. The ctx.body handling in the expression visitor
belongs to visitQualifiedFunctionCall, whose own rule keeps its body, which is
why removing this one compiles untouched and why no fixture records it. It only
ever consumed input.

The forms that do want a body still have one of their own:
cfhttp(url="a" method="GET") { } through cfmlfunctionStatement,
cfmail(to="a", from="b") { } through tagFunctionStatement.

Differential harness 8 disagreements to 7 against master, nothing newly broken.
Verified end to end -- 313 cfparser tests, ./gradlew build, CFLint's 675.

Closes #45

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzpZFd4rnE1Yi2sVHAji35
@ghedwards
ghedwards merged commit faf461e 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.

A function call as a script-syntax tag attribute value fails to parse

2 participants