Keep :: and ?. through decompile instead of collapsing both to a dot - #67
Merged
Conversation
memberExpression accepts three separators -- DOT, DOUBLECOLUMN and nullSafeOperator -- and none of them reached the AST. DOUBLECOLUMN appeared nowhere in the Java source at all. CFFullVarExpression hardcoded "." between members, so: a::b -> a.b a?.b -> a.b a?.b() -> a.b() ?. is the one that matters. a?.b yields null where a.b throws, so the round trip changed what the code does, and safe navigation is far more common in real code than static references. :: turns a static member reference into an ordinary property access on a variable that may not exist. An existing fixture was pinning the wrong output. acf2016/safenav.cfc parses if(xyz?.bar) and its recorded decompile read if(xyz.bar) -- the operator was being dropped and the expectation had been recorded from the broken result. That section is corrected here by hand, since AutoReplaceFailedTestResults does not cover decompile. The operator is recorded on CFFullVarExpression keyed by the member's character offset, not by its index. Members are gathered through aggregateResult, where one source construct does not reliably yield one element -- a[1].b puts three expressions in the list with a single dot between them -- so index alignment would drift. Source offsets do not. Only the two non-default operators are stored; an absent entry still means a dot, so nothing changes for ordinary member access. Decompile's existing logic for whether to emit a separator is untouched; only which separator it writes. Round trips verified across chains, calls and array members: a[1]?.b, a?.b?.c, a::b::c, a.b?.c.d, a?.b[1].c, a?.b().c. 325 tests, ./gradlew build, differential harness unchanged at 1 with nothing newly broken, CFLint's 675 against a clean build. Both fixtures fail with cfml.parsing/src/main stashed.
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.
memberExpressionaccepts three separators —DOT,DOUBLECOLUMNandnullSafeOperator— and none reached the AST.DOUBLECOLUMNappeared nowhere in the Java source at all.CFFullVarExpressionhardcoded.between members:Why this is more than cosmetic
?.is the one that matters.a?.byields null wherea.bthrows, so the round trip changed what the code does — and safe navigation is far more common in real code than static references.::turns a static member reference into an ordinary property access on a variable that may not exist.This started as an aside while verifying cfmleditor/CFLint#50 — I noticed
Some::myVardecompiling toSome.myVarand flagged it in #64 as needing confirmation rather than asserting it was a bug. Confirming it turned up the?.case, which I had not expected and which is the more serious half.An existing fixture was pinning the wrong output
acf2016/safenav.cfcparsesif(xyz?.bar). Its recorded decompile read:The operator was being dropped, and the expectation had been recorded from the broken result — so the suite was actively asserting the bug. Corrected here by hand, since
AutoReplaceFailedTestResultsdoes not cover the decompile section.That fixture is named
safenav. The one test in the corpus specifically about safe navigation was locking in the behaviour that defeats it.Keyed by source offset, not by index
The operator is recorded on
CFFullVarExpressionagainst the member's character offset rather than its position in the list. Members are gathered throughaggregateResult, where one source construct does not reliably yield one element —a[1].bputs three expressions in the list with a single dot between them — so index alignment drifts. Source offsets do not.Only the two non-default operators are stored, so an absent entry still means a dot and nothing changes for ordinary member access.
Decompile's existing logic for whether to emit a separator is untouched; only which separator it writes.Verification
Round trips, including the array-member cases the offset keying exists to survive:
./gradlew buildcfml.parsing/src/mainstashed,safenavincludedNot covered
multipartIdentifieralso admitsDOUBLECOLUMNand is used for catch types,for..inkeys and type specs. I did not touch it —::is unlikely there and I have no failing case. Worth a look if one turns up.Generated by Claude Code