Repository navigation
deps: parser v0.5.0 — the COND half of the do...end-block family - #683
Conversation
MRI's lexer does COND_PUSH(0) at `(`, `[`, `{` and tSTRING_DBEG, and at
local_push. v0.4.0 cleared only the CMDARG flag in those places, so a
`do...end` block was still refused inside a `while`/`until`/`for`
condition once it sat behind one of those openers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Follow-ups found while measuring this bump. None of them is a regression of Parser, still refused where MRI 4.0.5 accepts
Parser, accepted where MRI 4.0.5 refuses
This is what makes VM:
|
| file | examples MRI reaches through our own shim |
|---|---|
core/signal/trap_spec.rb |
36 |
core/exception/signal_exception_spec.rb |
12 |
core/kernel/exit_spec.rb |
12 |
core/process/exit_spec.rb |
8 |
core/mutex/lock_spec.rb |
4 |
core/exception/interrupt_spec.rb |
3 |
core/exception/signm_spec.rb |
1 |
core/exception/signo_spec.rb |
1 |
≈77 examples behind one defect. Entry points: internal/vm/process.go:306
(Process.kill) and internal/vm/signal.go (Signal.trap).
core/string/unpack/carret_spec.rb produces nothing for MRI under our shim
either, so that one is a shim gap, not ours.
🤖 Generated with Claude Code
github.com/go-ruby-parser/parserv0.4.0 → v0.5.0.go.mod/go.sumonly.What v0.5.0 adds
The COND half of the
do...end-block family. MRI's lexer doesCOND_PUSH(0)at(,[,{andtSTRING_DBEG, and atlocal_push;v0.4.0 cleared only the CMDARG flag in those places. So a
do...endblock wasstill refused inside a
while/until/forcondition as soon as it satbehind one of those openers, or behind a
def/class/module/class << selfin that position.
Witness behaviour, verified against local MRI 4.0.5
Every shape below was run three ways —
ruby,rbgobuilt frommain(parser v0.4.0),
rbgobuilt from this branch (v0.5.0) — as a whole program,not read off a diff.
while (y do end); endwhile [y do end].first; endwhile "#{y do end}".empty?; enduntil (y do end); endwhile def f; y do end; end; break; endwhile ({k: y do end})[:k]; endwhile (class C; y do end; end); break; endwhile (module M; y do end; end); break; endwhile (class << o; y do end; end) && false; endwhile (def o.g; y do end; end) && false; endfor v in ((y do end) || [1]); break; endwhile (begin; y do end; end); endwhile (case 1 when 1 then y do end end); endwhile (if true then y do end end); endwhile <<~X.empty?+#{y do end}heredocwhile (y do end; y do end); endwhile (def f; y do end; end) && y do break end27 shapes REFUSED → accepted, 0 accepted → REFUSED.
The last row is the hard-scope restoration: after a
defbody inside thecondition the loop's own
domust become a loopdoagain, and it does.while (i < 2) do i += 1 endandfor v in ([1,2]) do ... endstill workunchanged, so the loop's own
dowas not eaten.Binding, not merely parsing
For a shape with two candidate receivers,
&bwas captured in each receiverand the block traced to the one that actually received it:
MRI 4.0.5 prints
[true, false]; this branch prints[true, false]. Thedo...endblock binds to the outer commandfoo, not to the innerbar, asMRI's precedence requires.
maincannot parse the program at all.The other direction — shapes MRI REFUSES must stay refused
while y do end; endwhile begin; y do end; end; enduntil y do end; endfor v in y do end; endwhile y y do end; endTwo shapes became reachable over-acceptances, and both reduce to a defect
that already exists on
main: rbgo accepts a paren-less command call as acollection element, which MRI refuses in any position.
v0.5.0 does not introduce that; it makes the
do...endvariant of it(
while [foo bar do end].first; end,while ({k: foo bar do end})[:k]; end)reachable where the block kept it out before. Filed as a follow-up against the
parser; it is not a regression of this bump.
Measurement
ruby/spec ratchet (pinned corpus
87b1631)Local, macOS/arm64,
TIMEOUT=25, 2-way (a parallel sweep is notreproducible), the two sweeps back to back under comparable load
(1-minute load average 17→40 for
main, 44→21 for the branch — three siblingagents were working this machine):
main(2aa1c94)Zero examples moved. One row differed:
core/exception/signo_spec.rbreported
FILEFAILformainandOK pass=0for the branch. Run ten moretimes (five per binary) it produced a result once out of five for
mainandzero times out of five for the branch:
Process.kill(:TERM, Process.pid)kills the rbgo process (
rc=143) instead of raisingSignalException, so theshim never gets to print its tally. A pre-existing flake worth 0 examples
either way.
CI agrees independently: the ratchet reads 22772 / 2197 files on
mainat2aa1c94 and 22771 / 2196 files on this branch — the same ±1 signal-spec
flake, from the opposite side.
Second corpus: MRI 4.0.5's own stdlib (728 files under
rubylibdir)Parsed with
parser.Parseat v0.4.0 and at v0.5.0, 728 inputs and 728 outputrows both times:
No file moved in either direction. The same five files fail with the same
five messages before and after.
Gates
CGO_ENABLED=0 GOWORK=off go test ./...— full suite, 8 packagesok, 0FAIL.gofmt -l internal/ cmd/— empty.-coverpkg=$(go list ./internal/... | paste -sd, -), same profileon a detached worktree at
origin/main: the below-100% function list isbyte-identical between
mainand this branch — 16 functions, 99.9% totalon this host. No function dropped to 0.0% that was not already there
(
bigdecimal_bind.go:35 Inspectanddate_bind.go:44 Inspectread 0.0% onboth). Run without
-race, which needs cgo this host cannot provide.internal/vm/prelude_frozen_gen.go: regenerated withgo run ./cmd/freeze-preludefrom the repo root. It came out byte-identical,so the file was deleted and regenerated from scratch to prove the
generator ran — same md5
bb193eb1e7b30babc8a4f7c0d710742f, 384852 bytes.The compiler's lowering did not change.
go build ./...andGOOS=wasip1 GOARCH=wasm go build ./...both clean.GOOS=js GOARCH=wasmis broken onmainalready (GOOS=js GOARCH=wasm has not compiled for 3 days, and the test that catches it is behind an env var no workflow sets #682) and is not touchedhere.
replacedirective ingo.mod, checked before the push and again beforethis description.
Honest summary
This bump is a parser-conformance win with no ruby/spec yield: 26 near-miss
shapes that MRI accepts and
mainrefused now parse and bind correctly, andneither the pinned ruby/spec corpus nor MRI's own stdlib contains a single
occurrence of any of them. It is worth landing as debt paid down, not as a
score move.
🤖 Generated with Claude Code