Conversation
`threadName = new Query():function( result, error ) { … };`. That
completes the eleven forms in Lucee's Function Listeners recipe; the
other ten landed in #96 and #97.
The +591 this issue was parked on had expired. It was measured when
new_expression could complete on the bare keyword `new`; requiring its
arguments — shipped separately — removed that, and the same widening
then measured +14. A cost taken before a related change is not evidence
about after it.
What actually blocked it was precedence, not size. With the target
widened, `new Foo()` before a contested `:` is either a listener target
or a ternary consequence, and only an open `?` tells them apart. The rule
shipped in #87 sits at prec.right('call', …), and 'call' binds tighter
than 'ternary', so the listener reading won inside `c ? new Foo() : obj`
and that ternary stopped parsing — the failure every earlier attempt hit,
including the +260 variant that restricted the listener side to a
function literal.
So the `new` target is a second arm below 'ternary', with the conflict
[$.expression, $.function_listener_expression] that tree-sitter generate
asks for. Where a `?` is open the ternary wins; where none is, nothing
competes for the colon. The call-target arm keeps 'call' and is
untouched.
+53 parse states (5315 -> 5368). Corpus 640 -> 638 error nodes across
121 -> 120 files, FunctionListener.cfc going to zero and nothing
regressing. `npm run treediff` reports zero changed trees in both
grammars.
The conflict was benchmarked, as a declared conflict must be: cfscript
+1.0% against untouched controls spanning 4.2 points (cfml +2.5%,
cfquery -1.7%), so inside the noise floor. That is what the prefix
predicts — the conflict is live on a new_expression followed by `:` and
nowhere else.
Both ternary controls, `c ? new Foo() : obj` and `isNull(o) ? new() : o`,
are pinned by a corpus test alongside the construct, because they are
what every earlier attempt broke. Probe
cfscript/function_listener_new.cfc flips to pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117EvdX3EgLgDevVNrWZ5hr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #98 — the residual half, after #102 shipped the
arguments-required change.threadName = new Query():function( result, error ) { … };parses. That completes the eleven forms in Lucee's Function Listeners recipe; the other ten landed in #96 and #97.The number this issue was parked on had expired
#98 records the widening at +591 states and parks the issue on that. That figure was measured when
new_expressioncould complete on the bare keywordnew— and #102 removed exactly that by requiring itsarguments. Re-measured on today's base, the same widening costs +14.The transferable part: a cost taken before a related change is not evidence about after it. Nothing about this feature got cheaper; the measurement simply stopped describing the grammar it was taken from.
What actually blocked it was precedence, not size
With the target widened,
new Foo()before a contested:is either a listener target or a ternary consequence, and only an open?tells them apart — which is what GLR carries. The rule shipped in #87 sits atprec.right('call', …), and'call'binds tighter than'ternary', so the listener reading won insidec ? new Foo() : objand that ternary stopped parsing. Every earlier attempt failed this way, including the +260 variant that restricted the listener side to a function literal.The fix is a second arm below
'ternary', plus the conflict[$.expression, $.function_listener_expression]thattree-sitter generateasks for. Where a?is open the ternary wins; where none is, nothing competes for the colon. The call-target arm keeps'call'and is untouched.new Query():function( r, e ) { … }ERRORfunction_listener_expressionwith anew_expressiontargetnew org.lucee.cfml.Query():callbackERRORc ? new Foo() : objisNull(o) ? new() : oQuery():function( … )(call target)x = new.foo,var new = 1,obj.new()a[ f() : g() ]slice,case f():Verification
npm testnpm run probecfscript/function_listener_new.cfcflips topassnpm run lintFunctionListener.cfcto zero, nothing regressednpm run treediffvsmasterSTATE_COUNTnpm run benchThe benchmark is the gate
CLAUDE.mdrequires for a declared conflict, and the controls are the point: the subject moved less than two grammars that did not change at all, so the cost is inside the noise floor. That matches what the prefix predicts — this conflict is live on anew_expressionfollowed by:and nowhere else, unlike theidentifier (shapes that cost 2.8×.Also in the diff
function listener on a new target (#98)pinning both ternary controls beside the construct, because they are what every earlier attempt broke.LIMITATIONS.mdanddocs/FAILING-PATTERNS.mdrewritten from rejection to record, keeping the expired-measurement lesson and the still-valid reasonsnewis not reserved (395.new(call sites across 160 corpus files; Lucee's reserved-word list isnull,true,false).CHANGELOG.mdunder## [Unreleased], and the in-flight entry indocs/TODO.md.🤖 Generated with Claude Code
https://claude.ai/code/session_0117EvdX3EgLgDevVNrWZ5hr
Generated by Claude Code