You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
detector: an orphan is a name seen once in the file, so any language that repeats the name in its own syntax reports zero orphans #2727
Found by the #2669 F.4 orphan/api census over the 46-language keyword-rosetta corpus.
detector.py L1383 is the whole definition of orphaned_logic:
eliflen(func_name) >3andtoken_counts[func_name] <=1:
orphan_count+=1# "If the function name only exists where it was defined, it's an orphan"
token_counts is over the entire code stream, including the definition's own tokens. So whether a function reads as orphaned depends on how many times the language's syntax writes the name, not on whether anything calls it:
shape
languages
orphans/file
why
name repeated by syntax
ada, haskell, livecode
0.00
Ada closes with end Probe_Globals;; Haskell writes probeGlobals :: Int -> Int above the equation; LiveCode ends the handler by name. Count is 2 before anything calls it
slice name synthetic/short
yaml, sqlite
0.00
yaml's 13 slices are all named run (3 chars, fails len > 3); sqlite's are Declarative_Block/CREATE_Statement (excluded as synthetic)
COBOL paragraphs, m4 macros, Scheme defines, Make targets: written once, invoked by other means, so every function reads as orphaned
baseline
python, go, java, +30
2.50
13 probe functions, 3 called from main
All four rows are the same 12-probe program with identical intent, so the spread is measurement, not code.
This is not cosmetic. orphaned_logic feeds state_slop_orphans (tech debt) and, through the Contextual Baseline Fix (galaxyscope.py L2228), it is converted into api — see the sibling issue on the api column.
Fix shape
Count occurrences outside the function's own [start_idx, end_idx] span instead of over the whole stream. Then:
Ada's end Probe_Globals and LiveCode's closing end stop masking a genuinely uncalled procedure (both are inside the span);
a recursive self-call is also inside the span, so a recursive-but-uncalled function correctly still reads as unused;
a call from anywhere else in the file is outside the span and still clears the orphan flag — the baseline languages keep the behaviour they have today.
Haskell needs a check that the extractor's span starts at the type signature rather than the equation; if it starts at the equation, the signature line is outside the span and haskell keeps its false negative.
Verify on language-crucible before blessing — this moves state_slop_orphans and arch_api on every language, so it is a golden-master change (B.2's lesson: confirm the real-world direction, not just the corpus's).
Ran the span check this issue asks for, on the real extractor over both corpora. It answers the Haskell question and turns up a defect in the fix shape as written.
1. The Haskell caveat is moot
The span does start at the type signature. Measured on keyword-rosetta/data/haskell/a.hs: probeGlobals spans [182,233), whose first line is probeGlobals :: Int -> Int. No extra work needed.
But haskell still will not read orphans after this fix, for a reason the issue did not anticipate: its module export list names every probe, outside every span.module A (probeGlobals, probeTest, probeSafety) where is one outside-span occurrence per probe, so all three still clear the orphan flag. The only orphan the fix finds in haskell is entry in main.hs — not exported, not called — which is correct. Corpus goes 0.00 → 0.25 orphans/file, not the 2.50 the issue's table implies. That residual is the already-ledgered api-export-names-suppress-orphan-credit shape (corpus-side), not an engine gap.
2. The fix as written loses real orphans
Count occurrences outside the function's own [start_idx, end_idx] span
That is unsafe, because the span does not reliably contain the function's own declaration. Where it does not, the declaration itself counts as an outside reference and clears the flag. Measured over the crucible (40 files/language):
language
today
fix as written
ruby
51
44
html
5
2
shell
8
3
Three languages move down — real orphans lost. Two distinct causes, both traced to concrete files:
ruby: 66 of 132 sampled functions have their declaration outside the span (spans are offset — initialize's span head lands mid-require line in base.rb).
shell: Mode-B spans start at the {. In curl/initscript.sh the K&R form puts the name on the previous line, so make_module(), db2_name(), setenv() etc. all sit outside their own span.
3. Corrected fix shape
Discount the declaration when the span does not contain the name at all — if inside == 0, the definition site is outside by construction, so exactly one outside occurrence is the declaration:
I also tried span ∪ start_line; it fixes ruby and html but not shell, because start_line points at the { line rather than the line carrying the name (a separate metadata observation, worth its own issue — it is what a reader would cite as the declaration's location).
Result: no language decreases. Crucible orphans 7397 → 8385 (+13%), the direction this issue predicts. The movers are exactly the languages named here (livecode 3 → 127, jcl 34 → 84, typescript 1728 → 2061, ada 0 → 2.50/file), plus the ones the correction recovers (ruby 51 → 59, assembly 26 → 43, matlab 3 → 6, html/shell flat instead of down).
4. What this fix does not address
The issue's third table row — abap, cobol, jcl, m4, makefile, objective-c, scheme at 3.25 orphans/file, where every function reads orphaned — is unchanged by span-scoping, in the corpus and in the crucible. Those names genuinely do not recur: COBOL PERFORM, m4 macro invocation, Make target prerequisites are invocation forms the token test cannot see at all. That is a separate no-call-graph family, not something this fix shape reaches, and the issue should not be read as closing it.
Proceeding with the corrected shape, paired with #2728 (they edit the same loop, and #2728 must not land after this — css's keyframes buckets currently sit inside the token test, so this fix would bless an artifact that #2728 then removes).
Activity
squid-protocol commentedon Sep 5, 2026
Ran the span check this issue asks for, on the real extractor over both corpora. It answers the Haskell question and turns up a defect in the fix shape as written.
1. The Haskell caveat is moot
The span does start at the type signature. Measured on
keyword-rosetta/data/haskell/a.hs:probeGlobalsspans[182,233), whose first line isprobeGlobals :: Int -> Int. No extra work needed.But haskell still will not read orphans after this fix, for a reason the issue did not anticipate: its module export list names every probe, outside every span.
module A (probeGlobals, probeTest, probeSafety) whereis one outside-span occurrence per probe, so all three still clear the orphan flag. The only orphan the fix finds in haskell isentryinmain.hs— not exported, not called — which is correct. Corpus goes 0.00 → 0.25 orphans/file, not the 2.50 the issue's table implies. That residual is the already-ledgeredapi-export-names-suppress-orphan-creditshape (corpus-side), not an engine gap.2. The fix as written loses real orphans
That is unsafe, because the span does not reliably contain the function's own declaration. Where it does not, the declaration itself counts as an outside reference and clears the flag. Measured over the crucible (40 files/language):
Three languages move down — real orphans lost. Two distinct causes, both traced to concrete files:
initialize's span head lands mid-requireline inbase.rb).{. Incurl/initscript.shthe K&R form puts the name on the previous line, somake_module(),db2_name(),setenv()etc. all sit outside their own span.3. Corrected fix shape
Discount the declaration when the span does not contain the name at all — if
inside == 0, the definition site is outside by construction, so exactly one outside occurrence is the declaration:I also tried
span ∪ start_line; it fixes ruby and html but not shell, becausestart_linepoints at the{line rather than the line carrying the name (a separate metadata observation, worth its own issue — it is what a reader would cite as the declaration's location).Result: no language decreases. Crucible orphans 7397 → 8385 (+13%), the direction this issue predicts. The movers are exactly the languages named here (
livecode3 → 127,jcl34 → 84,typescript1728 → 2061,ada0 → 2.50/file), plus the ones the correction recovers (ruby51 → 59,assembly26 → 43,matlab3 → 6,html/shellflat instead of down).4. What this fix does not address
The issue's third table row — abap, cobol, jcl, m4, makefile, objective-c, scheme at 3.25 orphans/file, where every function reads orphaned — is unchanged by span-scoping, in the corpus and in the crucible. Those names genuinely do not recur: COBOL
PERFORM, m4 macro invocation, Make target prerequisites are invocation forms the token test cannot see at all. That is a separate no-call-graph family, not something this fix shape reaches, and the issue should not be read as closing it.Proceeding with the corrected shape, paired with #2728 (they edit the same loop, and #2728 must not land after this —
css'skeyframesbuckets currently sit inside the token test, so this fix would bless an artifact that #2728 then removes).fix(core-engine): the orphan census counts references, not name repet…
fix(core-engine): four causes of orphan-census under-reporting, plus …
perf: kill ReDoS + O(N^2) splice hot paths behind v2.7.0 speed outlie…