Repository navigation
Range#count counts matches with a block instead of aliasing #size - #118
Open
jiro-by-nori[bot] wants to merge 1 commit into
Open
jiro-by-nori[bot] wants to merge 1 commit into
jiro-by-nori[bot] wants to merge 1 commit into
Conversation
`Range#count` was `alias count size`, so it answered the *size* where CRuby's
`Enumerable#count` counts. The block form was a silent wrong answer --
`(1..5).count { |x| x > 2 }` answered 5 and CRuby answers 3 -- and the item
form was rejected with `size`'s arity error (`given 1, expected 0`) instead of
counting. `Array#count` goes through `Enumerable#count` and is right, so the two
collections disagreed with each other as well as with CRuby.
CRuby's `enum_count` defers to `size` only for the bare form, and counts
matches otherwise. `count` is now its own method: with no argument and no block
it answers `size` (which stays O(1), and is what `enum_count` asks for anyway),
a block counts through `each`, and an item argument keeps the named
`Enumerable#count with an argument` gate that `Enumerable#count` already gives
every other collection, so a Range does not answer where its neighbours refuse.
`Range#each` now refuses a begin CRuby has no iteration story for: it raises
`TypeError: can't iterate from Float` instead of leaking its own `succ`
`NoMethodError`, which is what `(1.0..3.0).each` and, with this change, the
block form of `count` used to answer.
`difftest/corpus/regressions/range-count-block` pins the family against CRuby
4.0.5: the bare, exclusive and empty forms, the block form (some, none, all,
truthy), `size`'s unchanged arity, and the Float refusal from both `each` and
`count`. Conformance is unchanged (ran 1287, agree 1087, sut_unsupported 194):
no bootstraptest or corpus program counts a range.
Co-authored-by: Nori <contact@tilework.tech>
jiro-by-nori
Bot
force-pushed
the
jiro/fix-115-range-count
branch
from
November 9, 2026 23:55
f040fdc to
f9225ee
Compare
2 tasks
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.
Summary
Fixes #115.
Range#countwasalias count size, so a Range answered the size whereCRuby's
Enumerable#countcounts: the block form silently answered the wrongnumber (
(1..5).count { |x| x > 2 }→5, CRuby →3), and the item form wasrejected with
size's arity error (given 1, expected 0) instead of counting.Array#countgoes throughEnumerable#countand is right, so the twocollections disagreed with each other as well as with CRuby.
Written by Jiro, an autonomous coding agent; the code and this description are
its work.
What changed
ruby-lean/prelude/prelude.rb(plus the regeneratedPrelude.leanandPreludeJson.lean; no other generated file moves):Range#countis its own method. CRuby'senum_countdefers tosizeonly for the bare form and counts matches otherwise, so:
size, unchanged and still O(1) (sizeis whatenum_countasks for anyway);each;Enumerable#count with an argument, the same oneEnumerable#countalready gives every other collection (an
Arrayincluded), rather than ananswer only some receivers give.
Range#eachrefuses a begin CRuby refuses. Iteration issucc, and CRubyhas no iteration story for a
Floatbegin:(1.0..3.0).eachisTypeError: can't iterate from Float. The model leaked its ownsuccNoMethodError,which the block form of
countnow reaches, soeachraises CRuby'sTypeErrorfor a begin that is notInteger,StringorSymbol. Abeginless/endless range is still the existing named gate, and a
Stringrange still gates on
String#succ.difftest/corpus/regressions/range-count-block(.rb+.json) pins 13 shapesof the family against CRuby 4.0.5: bare, exclusive and empty ranges, the block
form (some / none / all / truthy / one element),
size's unchanged arity, andthe
Floatrefusal from botheachandcount.Test plan
Every check was run on the final tree, on this machine (Lean
leanprover/lean4:v4.32.2, CRuby4.0.5).bin/ruby-lean --compare-style probedifftest replay(lean SUT)agreedifftest run --tier regressions221 held,1 gated(the pre-existing open case),0 failuresThe table from #115, after the change:
(1..5).count55(1...5).count44(1..0).count00(1..5).count { |x| x > 2 }33(1..5).count { |x| x }55(1..5).size(1)ArgumentError (given 1, expected 0)(1..5).count(2)Enumerable#count with an argument1(1..5).count(2) { }Enumerable#count with an argument1(with a "block not used" warning)(1.0..3.0).each { }TypeError: can't iterate from Float(1.0..3.0).count { |x| x > 1 }TypeError: can't iterate from Floatmake checktargets, each run to completion:make prereqs,make ci-sync— OKmake lint— ruff, RuboCop (43 files), shellcheck, actionlint: cleanmake gen-check— every generated source fresh (Prelude.leanregeneratedin this commit)
make lean,make books(1399 jobs),make metatheory— every theorem restson
propext,Classical.choice,Quot.soundonly, and the booted heapsatisfies the assumptions the proofs make about it
make soundness—SOUNDNESS CHECK PASSEDmake comparator—OK: the comparator acceptsmake desugar-unit(917 runs),make emitter-unit(367 runs),make desugar-test(76/76 rules),make desugar-coverage,make difftest-test(189 passed) — every coverage baseline holdsmake conformance—CONFORMANCE OK; ran 1287, agree 1087,sut_unsupported 194, not_compared 6, disagree 0 — unchanged, so no baseline
file moved (nothing in the harvested corpus counts a range)
make feature-loading,make book-checks(72/72 inputs),make docsmake check-not-weakened—CHECKS NOT WEAKENEDNo
sorry,native_decide, axiom, lint suppression or test skip was added.