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
scheme globals: indentation doesn't track scope — the rule is 42.7% precise, and the #2651 col-0 anchor trades 67 false positives for 32 real globals #2674
Split out of the #2660 audit (Batch B.2 of #2669). #2660 listed scheme as a candidate for the #2651^[ \t]* → ^(?![ \t]) anchor change. There is a real defect — a bad one — but the anchor is the wrong instrument, and the measurement below is the reason. Filing separately so #2660 can close on go alone.
In Scheme, indentation does not track scope — the enclosing form does. Two constructs are indented identically and mean opposite things:
(define (f x)
(definey 5) ; internal define -> a LOCAL binding. False positive.
y)
(let () ; whole-file module wrapper (a standard Chez/R6RS idiom)
(definetrack-counts #f) ; module-level binding -> a REAL global, and it is indented.
...)
Measurement
I wrote a paren-depth classifier (skipping strings, ; comments, #| |# blocks, #\( char literals) that resolves each (define …) to its nearest enclosing form and labels it module-level or internal, then joined that against what each regex actually matches, over the real language-crucible scheme sources (Chez + Racket, 1510 defines total: 619 module-level, 891 internal).
Restricted to the lines this rule's form actually targets:
The current rule is wrong more often than it is right. But the anchor is not the fix: it deletes 67 false positives and 32 real globals, cutting true positives from 53 to 21. Precision doubles, recall drops ~60%, and on a risk metric that is undercounting global state, not fixing it.
Why the recall collapse — per-file (define at column 0 vs indented:
file
col-0
indented
cpnanopass.ss (Chez; entire body inside (let () …) from line 16)
0
682
io.ss
0
285
syntax.ss
318
341
schemify.rkt
9
119
thread.rkt
83
61
Two of five real files have no column-0 defines at all. The anchor takes them to zero globals — the rule goes blind on the commonest Scheme file layout rather than getting more precise on it.
Fix shape
The discriminator is body position, not column. A (define …) is internal iff its nearest enclosing form is a body position (lambda, case-lambda, the let family, a procedure-form (define (name …) …), when/unless/cond/parameterize, …) and module-level otherwise (top level, or nested only in library / module / define-library / a depth-0 (let () …) file wrapper).
Applied to the same corpus that predicate yields 53 true globals and 0 false positives — it keeps every true positive the current rule finds and drops all 71 false ones.
This is deliberately not a regex change, and therefore not B.3 material. A flat pattern cannot see the enclosing form; this needs a paren-depth pass in the detector, i.e. the slicer layer, which #2669's stacking rule keeps out of a registry-regex PR. The classifier that produced the numbers above is ~60 lines and can seed it. Machinery precedent exists: scheme already has a func_start rule for the procedure form —
Treat the body-position predicate as a scoped design item — small, self-contained, one language, with a ready-made oracle (the classifier) and a clear pass mark (53/0 on the crucible corpus).
Until then, ledger scheme globals as a known 42.7%-precision over-count. The rosetta corpus reads the planted globals 2 and cannot see any of this, so nothing here blocks a rebless — and nothing here will be caught by one either.
Split out of the #2660 audit (Batch B.2 of #2669). #2660 listed scheme as a candidate for the #2651
^[ \t]*→^(?![ \t])anchor change. There is a real defect — a bad one — but the anchor is the wrong instrument, and the measurement below is the reason. Filing separately so #2660 can close ongoalone.Current rule
In Scheme, indentation does not track scope — the enclosing form does. Two constructs are indented identically and mean opposite things:
Measurement
I wrote a paren-depth classifier (skipping strings,
;comments,#| |#blocks,#\(char literals) that resolves each(define …)to its nearest enclosing form and labels it module-level or internal, then joined that against what each regex actually matches, over the reallanguage-cruciblescheme sources (Chez + Racket, 1510 defines total: 619 module-level, 891 internal).Restricted to the lines this rule's form actually targets:
^[ \t]*)^(?![ \t]))The current rule is wrong more often than it is right. But the anchor is not the fix: it deletes 67 false positives and 32 real globals, cutting true positives from 53 to 21. Precision doubles, recall drops ~60%, and on a risk metric that is undercounting global state, not fixing it.
Why the recall collapse — per-file
(defineat column 0 vs indented:cpnanopass.ss(Chez; entire body inside(let () …)from line 16)io.sssyntax.ssschemify.rktthread.rktTwo of five real files have no column-0 defines at all. The anchor takes them to zero globals — the rule goes blind on the commonest Scheme file layout rather than getting more precise on it.
Fix shape
The discriminator is body position, not column. A
(define …)is internal iff its nearest enclosing form is a body position (lambda,case-lambda, theletfamily, a procedure-form(define (name …) …),when/unless/cond/parameterize, …) and module-level otherwise (top level, or nested only inlibrary/module/define-library/ a depth-0(let () …)file wrapper).Applied to the same corpus that predicate yields 53 true globals and 0 false positives — it keeps every true positive the current rule finds and drops all 71 false ones.
This is deliberately not a regex change, and therefore not B.3 material. A flat pattern cannot see the enclosing form; this needs a paren-depth pass in the detector, i.e. the slicer layer, which #2669's stacking rule keeps out of a registry-regex PR. The classifier that produced the numbers above is ~60 lines and can seed it. Machinery precedent exists: scheme already has a
func_startrule for the procedure form —— so the engine already models "this define opens a procedure"; what is missing is using that to decide whether a later define sits inside its body.
Recommendation
globalsas a known 42.7%-precision over-count. The rosetta corpus reads the plantedglobals2 and cannot see any of this, so nothing here blocks a rebless — and nothing here will be caught by one either.