Skip to content

docs: the Known limitations section listed nothing that is still a limitation - #47

Merged
tannevaled merged 2 commits into
mainfrom
readme-claims-measured
Oct 4, 2026
Merged

tannevaled merged 2 commits into
mainfrom
readme-claims-measured

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

What

The Known limitations section listed nothing that is still a limitation.
Measured at v0.11.0 on 2026-10-04:

the section said measured
BEGIN { } — "does not parse" parses
END { } — "does not parse" parses
CRuby stdlib: 46 of 47 47 of 47
the one refusal: mkmf.rb parses

mkmf.rb tripped on def self::log_open — the def self::name form — which
parses now, and that is what took the stdlib figure to 47 of 47.

The section was also dated "as of v0.4.0", seven minors back.

Why this is worse than a stale number

A section naming limitations that no longer exist sends a reader round a bug
that is fixed: they avoid BEGIN/END for nothing, or go looking for the
mkmf.rb refusal. A number that is merely out of date is read as approximate;
a named limitation is read as a fact.

The claims are now executable

  • TestReadmeClaimsStillHold parses every construct the section names, so
    a future change that breaks one fails the suite rather than quietly
    contradicting the page.
  • TestUnderPermissiveNotOver pins the design claim with its discriminating
    case: MRI refuses BEGIN inside a method body, and so must this. A test that
    only checked things we accept could not tell under- from over-permissive.
  • TestCRubyStdlibTopLevelParses runs the sweep that produced the 47-of-47
    figure, and skips when no CRuby tree is present. CI has none, which is
    exactly why the README states that figure as a dated local measurement
    rather than as a gate — with the command to reproduce it.

Before writing "none"

I probed 23 further constructs against ruby -c — flip-flops, redo,
__END__/DATA, heredoc-with-method-call, _1, it, endless methods,
rightward assignment, one-line pattern match, hash shorthand, all three
anonymous forwards, refinements, ?a, imaginary/rational literals, special
globals — and found no gap and no over-acceptance, including the one MRI
rejects.

The section says in as many words that twenty-three hand-picked constructs are
not a completeness proof, because they are not.

Also

ast.go's Lines doc said "a position field on all 55 node types" and
the file defines 56. A count of something the file itself defines rots on
the next addition and tells the reader nothing, so it is gone rather than
corrected — I had reread that comment the day before, while adding Block.Line,
and did not check the number.

Green

go test ./... -count=1 exit 0 (4 packages), gofmt clean, and the parser
package stays at 100.0% of statements.

🤖 Generated with Claude Code

tannevaled and others added 2 commits October 4, 2026 17:57
…mitation

Every claim in it was obsolete, measured at v0.11.0 on 2026-10-04:

    BEGIN { }              listed as failing   parses
    END { }                listed as failing   parses
    CRuby stdlib           46 of 47            47 of 47
    the one refusal        mkmf.rb             parses

mkmf.rb tripped on `def self::log_open` -- the `def self::name` form -- which
parses now, and that is what took the stdlib figure to 47.

A section naming limitations that no longer exist is worse than a stale number:
a reader avoids BEGIN and END for nothing, or goes round a bug that is fixed.
The section was also dated "as of v0.4.0", seven minors back.

So the page's claims are now EXECUTABLE. TestReadmeClaimsStillHold parses each
construct the section names; TestUnderPermissiveNotOver pins the design claim
with its discriminating case (MRI refuses BEGIN inside a method body, and so
must this); TestCRubyStdlibTopLevelParses runs the sweep that produced the
47-of-47 figure, and SKIPS when no CRuby tree is present -- CI has none, which
is why the figure is stated as a dated local measurement rather than a gate.

Before writing "none", I probed 23 further constructs against `ruby -c` --
flip-flops, redo, __END__/DATA, heredoc-with-method-call, _1, it, endless
methods, rightward assignment, one-line pattern match, hash shorthand, all
three anonymous forwards, refinements, ?a, imaginary/rational literals -- and
found no gap and no over-acceptance. The section says that twenty-three
hand-picked constructs are not a completeness proof, because they are not.

Also in ast.go: the Lines doc said "all 55 node types" and the file defines 56.
A count of something the file itself defines rots on the next addition and says
nothing the reader needs, so it is gone rather than corrected -- I had reread
that comment the day before and not checked the number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t a literal

`alias / +` was refused with "expected a method name". MRI's grammar sets
EXPR_FNAME|EXPR_FITEM for the items of both alias and undef --

    k_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem
    k_undef undef_list

-- and the lexer's atMethodName listed only `def`, `.`, `&.` and `::`, so after
`alias` a `/` opened a regexp instead of naming the division method.

Only `/` was affected, because it is the one operator character that also opens
a literal in a value position: `alias + plus`, `alias [] at`, `alias <=> cmp`
and `alias :/ :+` all parsed already. The operator as the OLD name parsed too --
it is the NEW name that sits in the state the lexer got wrong.

This is how Pathname aliases its `/`, and CRuby's own pathname.rb line 358 is
that exact line.

HOW IT WAS FOUND, which is the part worth keeping. The stdlib sweep added one
commit earlier for the README was written to "skip when no CRuby is present",
on my assumption that CI has none. CI's runner carries Ruby **3.2**, not 4.0.5:
the test ran, swept 66 files instead of 47, and refused one. A sweep restricted
to the targeted version would have missed a construct that is valid in every
Ruby and that no 4.0.5 top-level file happens to use. The README now says the
sweep runs against whatever CRuby is on the machine, and why.

atMethodName's own comment already recorded this shape once -- "the previous
form named only `def`, so all four literal openers silently carried the wrong
rule after a dot". The alias/undef omission is the same omission a second time,
which is an argument for the predicate, not against it.

Controls, because a lexer state that leaks would turn every regexp into a
division: a regexp in an assignment and as an argument, division, a regexp
AFTER an `alias / +`, division after an `undef`, and a regexp after `undef /`.
All parse, and all are in the test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit b7b53b9 into main Oct 4, 2026
11 checks passed
@tannevaled
tannevaled deleted the readme-claims-measured branch October 4, 2026 16:51
tannevaled added a commit to go-embedded-ruby/ruby that referenced this pull request Oct 4, 2026
CRuby's pathname.rb writes `alias / +`. rbgo's prelude wrote

    def /(other)
      self + other
    end

because the parser refused the alias: after `alias`, a `/` opened a regexp
instead of naming the division method. That is fixed in go-ruby-parser v0.11.1
(go-ruby-parser/parser#47), which this bumps to.

The workaround was not only a reflection difference. Three things diverged,
measured against ruby 4.0.5:

    Pathname.instance_method(:/).original_name   ruby :+     before :/
    instance_method(:/) == instance_method(:+)   ruby true   before false
    with #+ redefined, `Pathname.new("/a") / "b"`
                                                 ruby #<Pathname:/a/b>
                                                 before "HIJACKED"

The third is BEHAVIOUR: an alias keeps the body it was made from, while a
forwarding def follows whatever #+ later becomes. All three now match.

Measured: library/pathname/divide_spec.rb goes 0 -> 1, which is MRI's own
ceiling for it, and the spec is one example whose title is "is an alias of
Pathname#+" -- the corpus naming exactly this. The pathname library specs go
38 -> 39 overall with no file down. That suite is under library/, which the
ratchet does not sweep (only language/ and core/), so the gain is real but
ungated; nothing in language/ or core/ moves.

The prelude was the only site with this shape: a scan for an operator-named def
whose body just forwards to another operator found one, this one.

Closes nothing; enabled by go-ruby-parser/parser#47, which was itself found by
a README-claims test sweeping CI's Ruby 3.2 stdlib.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant