calls_out: keywords, annotations and tcl SQL are not calls (#3359) - #3403
Merged
Merged
Conversation
Contract C2/C1/C7 (docs/calls_out_rule_contract.md, classes K/A/S): - _calls_out_ignore keyword additions for 24 languages (no built-ins touched, _CALLS_OUT_GLOBAL_IGNORE untouched) - CALLS_OUT_C_STYLE_NO_ANNOTATION ((?<!@)) for java/kotlin/swift/dart/ groovy/scala; python/ts decorator factories stay calls - per-language ignore compared exactly for case-sensitive languages, casefolded only for identifier_case: insensitive ones - tcl: upper-case SQL keywords excluded from command position - cobol: END-PERFORM/END-CALL no longer read the next statement as a callee; inline PERFORM VARYING/UNTIL/WITH names no paragraph - agc: TC Q is the return idiom Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3359) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…3359) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
… too (#3359) tree-sitter parses php isset/empty/unset/die/exit and C# nameof as call nodes; the contract says they are keywords (C2), and the engine now drops them. Exclude them from the tree-sitter side so the gate does not read following the contract as lost recall. Baseline regenerated: precision up in go (90.3 -> 99.6), php (88.1 -> 96.3), csharp (89.5 -> 93.8), python, rust, java, cpp, typescript; recall unchanged except php 98.3 -> 98.2 (the excluded names left the matched set). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resolve detector.py: keep #3360's nested-declaration check and run it for both CALLS_OUT_C_STYLE and CALLS_OUT_C_STYLE_NO_ANNOTATION, so the java/kotlin/swift/dart/groovy/scala family gets C5 too. Pinned by test_nested_declaration_check_covers_the_annotation_free_pattern. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 #3359
Precision fix for
calls_out, per the #3327 contract (docs/calls_out_rule_contract.md, classes K, A and S). A keyword followed by(is not a call (C2). A metadata annotation is not a call (C1). String content is not a call (C7).What changed
_calls_out_ignoreentries for 24 languages: ada, cpp, csharp, dart, embedded_python, go, groovy, java, kotlin, livecode, lua, perl, php, powershell, python, rust, scala, scheme, solidity, swift, typescript and zig, plus cobol and agc. Only keywords were added. No built-ins were removed, and_CALLS_OUT_GLOBAL_IGNOREis unchanged (#3327 follow-up: built-ins are calls -- trim _CALLS_OUT_GLOBAL_IGNORE to keywords (contract C2) #3361 owns those).detector.pynow casefolds the per-language set only for languages declaredidentifier_case: insensitive. Every other language compares names exactly. Without this, go'stypekeyword swallowed the real callv.Type(),thisswallowed C#'sfactory.This(), and rust'sletswallowedExpr::Let(.CALLS_OUT_C_STYLE_NO_ANNOTATION((?<!@)), for java, kotlin, swift, dart, groovy and scala.detector.pytreats it the same asCALLS_OUT_C_STYLE, so qualifiers are still captured (#3265 step 2: capture the call qualifier (utils.parse, obj.save, Class::method) #3329). Python, TypeScript and JavaScript keep the plain pattern, because a decorator factory like@retry(3)really is a call.setandupdateare real Tcl commands.END-PERFORMandEND-CALLend in the verb. Because of that,\btreated the next statement's first word (MOVE,IF,END-IF, ...) as the callee, so the anchor is now(?<![\w-]). InlinePERFORM VARYING/UNTIL/WITH TESTnames no paragraph, so those words are ignored.TC Qis the return idiom (a transfer through the Q register), not a call.test_calls_out_contract_3327.pystrict xfails for #3327 follow-up: calls_out keyword and annotation captures (contract C1/C2) #3359 (annotation, gofunc) are replaced by a new pin file,tests/extraction/languages/test_calls_out_keywords_3359.py(33 cases). Each case pairs the dropped non-call with a real call in the same body, so a recall loss would fail the test.Census (language-crucible v1.4.0 + keyword-rosetta, every function, before vs after)
4,342 names removed and 18 added, across 25 languages. The set of sliced functions did not change.
Largest removals: php 1,000 (
foreach237,isset210,empty152,elseif114,array111,unset79,fn/use44), livecode 893 (private250,public99,command95, ...), go 445 (func436), ada 316 (is269), csharp 223, zig 172, rust 163, python 147, scheme 146, solidity 101, scala 86, powershell 80, groovy 77, cobol 77, perl 70, agc 61 (Q), lua 57, typescript 47, dart 42, cpp 35, tcl 35 (SQL), java 34, swift 26, kotlin 6, embedded_python 3.For every removed name, every occurrence in that function was a keyword, a special form, an annotation (
@X() or SQL string text. Ambiguous candidates were checked by reading the crucible source, which is why these are not ignored:match:$this->match(is a real method call.do/then: every crucible hit is$dbh->do(or->then(. The contract table lists perldoas K, but the crucible shows it is a real call.default: a macports command.qx(: it runs a shell command, and the rosetta oracle plants it as a call.typewas dropped from the go set in favour of exact compare, becausev.Type()is a common real call.The 18 added names are real calls that were recovered:
Warn(17):$self->Warn(in exiftool. It had been filtered because the old casefolded compare matched it againstwarn.740-WRITE-CUSTOUT-FILE(1):END-PERFORM PERFORM 740-...used to capture the secondPERFORMas the callee.These are the only recall movements, and both are side effects of the precision fixes.
Golden master
crucible_check.py --update --yesran against the pinned v1.4.0 corpus (language-crucible-worktrees/v1.4.0-agent), and thenrebase_rebless.py --executeran on top of #3394/#3396/#3399. Both fixtures report no drift, so the bless diff is 0 lines.calls_out_tois not part of the audit JSON, which is why nothing moved there.Verification
pytest tests/(after the #3327 follow-up: nested declarations captured as calls (contract C5) #3360 merge): 10,661 passed, 318 skipped, 10 xfailed, 3 xpassed. The 3 xpasses are pre-existing jcl ones.audit_check.py: ruff, mypy, dead-key and ast-accuracy all clear.ruff formatis clean.rosetta_audit.py(51 languages): with the companion rebless, 0 regressions. Against rosetta main, 7 languages move, all incalls_out_toand all intended: csharpnameof, groovy/javaSuppressWarnings, kotlinwhen/Suppress, phpisset/unset, schemeguard, and solidityreturns(13 cells).check_calls_out_truth.py --allon the companion branch: 51 PASS. The remaining WARNs are pre-existing periphery gaps in languages this PR does not touch.call-graph-accuracy gate (#3332)
The gate compares engine callees against tree-sitter call nodes. Tree-sitter parses php
isset/empty/unset/die/exitand C#nameofas call nodes, but the #3327 contract says they are keywords. So the gate read this PR as a recall drop (php 98.3 -> 92.5, csharp 97.2 -> 95.8).The tool's own docstring says both sides follow the contract.
CONTRACT_NON_CALLSnow removes those names from the tree-sitter side, and the baseline was regenerated against v1.4.0. Results:func), php 88.1 -> 96.3, csharp 89.5 -> 93.8, python 92.1 -> 93.8, java 96.9 -> 99.1, rust 84.9 -> 86.7, cpp 87.4 -> 87.7, typescript 80.7 -> 81.2.Built-ins are deliberately not excluded, since they are calls (#3361).
After merging #3360 the baseline was regenerated again. Current precision: python 96.2, typescript 82.6, ruby 91.3, rust 87.1, csharp 93.9, php 96.3, java 99.1, go 99.6. Recall is unchanged in every language.
Merge with #3360 (nested declarations, C5)
This branch has
origin/mainmerged in, not rebased, so nothing was force-pushed. The only conflict was indetector.py:if invocation_pattern is CALLS_OUT_C_STYLE:to add its nested-declaration-header check.CALLS_OUT_C_STYLE_NO_ANNOTATION.The resolution keeps #3360's body and uses the two-pattern condition. That matters: had the merge kept either side's version alone, the #3360 check would have stopped running for java, kotlin, swift, dart, groovy and scala, and nested
inner(headers would have come back as callees.test_nested_declaration_check_covers_the_annotation_free_pattern(kotlin, java, scala) pins this, and it fails 3/3 if the condition is narrowed back toCALLS_OUT_C_STYLEalone.Census against the new main (which includes #3360): 4,341 names removed and 18 added, the same classes as below. Nothing beyond this PR's own keyword/annotation/SQL removals moved, because main already applied the nested-declaration check to those six languages and this merge keeps it.
Golden masters: #3402 (the split layout) has not merged, so the merge took main's monolithic fixtures.
crucible_check.py --update --yesagainst v1.4.0 reports no drift in either mode. A hand-rungolden_diffon the zero-dep audit also gives 0 differences.Deferred (named in the contract doc's new "#3359 resolution" section)
letbinding lists((x 1)): the same((shape is also acondclause or a curried call, so fixing it needs more than a regex tweak.@a.b.C(, kotlin@file:JvmName().Fn(trait sugar and#[cfg(not(...))]predicates.PERFORM <data-name> TIMES.Cross-repo
docs/calls_out_truth.json. It drops phpisset/unsetand schemeguardfrom the periphery lists and adds the new keyword/annotation traps toforbidden_everywhere.bias-history.ymlregenerates on its own. Label:rosetta:rebless-owed.🤖 Generated with Claude Code