Repository navigation
Range#each refuses a begin it cannot step, and compares with <=> (fixes #144) - #145
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
…(issue #144) `Range#each` stepped with `succ` and compared with `<`/`<=`. CRuby's `range_each` does neither. It first asks whether the begin responds to `succ` and raises `TypeError: can't iterate from <Class>` when it does not, before yielding anything — the model called `succ` and let that raise, so `(1.0..2.0).each { |x| p x }` printed `1.0` and *then* answered `NoMethodError`, and `map`/`to_a` answered `NoMethodError` where CRuby answers `TypeError`. Those are wrong answers in the exception family the validator claims to exclude, not gates, which is the line #27 settled. For a begin it can step, CRuby's comparison is `<=>`, tested before each step, with the end value yielded once after the loop. So an object that defines `succ` and `<=>` iterates without the `<`/`<=` that `Comparable` would add, and a `succ` that answers the receiver itself is yielded once and never stepped again — where the old `<= 0` test answered `NoMethodError` on `<` and a `<= 0` loop would not terminate. An Integer begin keeps the native `<`/`<=` loop: it answers the same and costs a step rather than three. `respond_to?` is CRuby's name table, so `("a".."c")` and `(:a..:b)` keep gating on the unmodeled `String#succ`/`Symbol#succ` instead of becoming a new `TypeError`, and the beginless/endless gate is unchanged. `difftest/corpus/regressions/range-each-non-succ-begin.rb` pins all three behaviours; it disagreed before the change and agrees after. Validation: `make check` passes (build, every book, the axiom audits, the checker corpus, difftest over bootstraptest at the recorded baseline, the regressions and tier-3 tiers, the unit tests and their coverage, the docs). `make check-not-weakened` reports CHECKS NOT WEAKENED. The bootstraptest numbers are unchanged (ran 1287, agree 1087, sut_unsupported 194, disagree 0), so no baseline moved. Authored by Jiro, an autonomous agent (Nori sessions), on behalf of @sam-xif.
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.
Fixes #144.
What was wrong
Range#eachstepped withsuccand compared with</<=. CRuby'srange_eachdoes neither, so three separate answers came out wrong:(1.0..2.0).each { |x| p x }1.0, thenNoMethodError: undefined method 'succ' for an instance of FloatTypeError: can't iterate from Float, nothing printed(1.0..2.0).map { |x| x }NoMethodError(succon Float)TypeError: can't iterate from Float(obj..obj).each { },objdefiningsuccand<=>but no<NoMethodError: undefined method '<=' for an instance of ...NoMethodErroris inside the family the validator claims to exclude, so theseare wrong answers rather than gates (the line #27 settled) — and the first one
yields
1.0before failing, so it has already produced output.What changed
ruby-lean/prelude/prelude.rb,Range#each:rb_respond_to(beg, succ)and raisesTypeError: can't iterate from <Class>otherwise.
respond_to?in the prelude is CRuby's name table, so a beginwhose
succCRuby has but the model does not model (String,Symbol)keeps gating on the unmodeled
succinstead of turning into a new wrongTypeError. The beginless/endless gate is unchanged:(1..).eachreally isinfinite, and a faithful infinite loop is indistinguishable from a stuck
model.
<=>for a non-Integer begin, tested before each step, withthe end value yielded once after the loop. That is the shape CRuby's
range_eachhas: an object needssuccand<=>only, and asuccthatanswers the receiver itself is yielded once and not stepped again.
</<=loop — same answers, a steprather than three.
ruby-lean/RubyCore/Generated/{Prelude,PreludeJson}.leanare regenerated(
make gen, checked bymake gen-check).Test plan
New regression case
difftest/corpus/regressions/range-each-non-succ-begin.rb(sidecar
status: fixed), which pins:TypeErrorfor aFloatbegin on the block,mapandto_aforms, andthat nothing is yielded first;
Floatend;succand<=>only iterates (inclusive one visit,exclusive none, then three visits), matching CRuby byte-for-byte;
succreturningselfwith<=>0 yields one element and stops.How I validated it:
status: openand the oldprelude,
cd difftest && uv run difftest run --tier regressions --sut leanreported
still_open: 1with the diffstdout: '[TypeError, can't iterate from Float]...' vs '1.0\n[NoMethodError, ...]'and
exception: None vs ('NoMethodError', "undefined method '<=' ...").223 ran, 222 agree, 1 sut_unsupportedand
heldfor the new case, with no other outcome changed (the onesut_unsupportedis the pre-existingsorbet-hash-gate.rb, gated).make check: ALL CHECKS PASSED — build, every proof book, the axiomaudits, the checker corpus, difftest over bootstraptest, the regressions and
tier-3 corpora, the unit tests and their coverage, the docs. The bootstraptest
watermark is unchanged (
ran 1287, agree 1087, sut_unsupported 194, not_compared 6, disagree 0), so no baseline moved.make check-not-weakened: CHECKS NOT WEAKENED.("a".."c")and(:a..:b)still gate rather than raising the new
TypeError, and that(0..10).step(2)/each_slicegates are untouched.Note
Authored by Jiro, an autonomous agent (Nori sessions), on behalf of
@sam-xif — including the code and this description.