Repository navigation
Enumerable#take/#drop/#each_slice/#each_cons convert their count, so a negative size raises and a fractional one truncates (fixes #137) - #138
Open
jiro-by-nori[bot] wants to merge 1 commit into
Conversation
The four prelude methods used the count or size argument as it arrived — `n <= 0`
and then `r.length == n` — so a negative count answered instead of raising, a
fractional one never matched `length`, a non-number raised from the comparison
rather than from a conversion, and a count CRuby cannot convert raised nothing:
`[1,2].take(-1)` answered `[]`, `take(1.5)` answered `[1,2]`, `take(nil)` raised
`NoMethodError` for `<=`, and `take(2**63)`/`take(Float::NAN)` answered where
CRuby raises `RangeError`. `each_slice`/`each_cons` had the same shape, and
declined a size that is not positive where CRuby raises.
`__num2long` is `rb_num2long`: an Integer, a Float truncated toward zero, or an
object's `to_int`, then a C `long` range check. The four methods validate the
converted count and raise CRuby's own message for each — "attempt to take
negative size", "attempt to drop negative size", "invalid slice size", "invalid
size". `Enumerable#first(n)` delegates to `take`, so it inherits the conversion.
Every message is CRuby 4.0.5's wording: nil says "no implicit conversion from
nil to integer" (lowercase, and "from" where every other object says "of X into
Integer"), a `to_int` that answers a non-Integer names the answer, an Integer
beyond the range is "bignum too big to convert into 'long'", and NaN/±Infinity
are "float NaN"/"float Inf"/"float -Inf out of range of integer". A finite Float
whose truncation is outside the range still declines: CRuby prints that value at
`%.10g`, which this model cannot render.
The regression case pins every conversion and validation on Array, Hash, Range
and a user Enumerable.
Regressions: `difftest/corpus/regressions/enumerable-count-conversion.{rb,json}`
pins each conversion and validation message, on Array, Hash, Range and a user
Enumerable (closes #137).
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.
Authored by Jiro, an autonomous agent (Nori sessions), using the GitHub App.
Enumerable#take,#drop,#each_sliceand#each_consused their count orsize argument as it arrived —
n <= 0and thenr.length == n— so each ofthem answered where CRuby raises, and raised where CRuby answers. The count is
NUM2LONGin CRuby: an Integer, a Float truncated toward zero, or an object'sto_int, then a Clongrange check, then the method's own validation.__num2longin the prelude is that conversion, with CRuby's four messages.nilsaysno implicit conversion from nil to integer(lowercase, and "from"where every other object says "of X into Integer"), a
to_intthat answers anon-Integer names the answer (
can't convert N to Integer (N#to_int gives NilClass)), an Integer beyond the range isbignum too big to convert into 'long', and NaN/±Infinity arefloat NaN/float Inf/float -Inf out of range of integer.takeanddropconvert first, then raiseattempt to take negative size/attempt to drop negative size.Enumerable#first(n)delegates totake, soit inherits both.
each_sliceandeach_consconvert their size and raiseinvalid slice size/
invalid size, which also closes the two gates that stood where CRubyraises.
longrange still declines:CRuby prints that value at
%.10g(float 9.223372037e+18 out of range of integer), a format this model cannot render, so it declines rather than guessthe digits.
The generated
PreludeJson.lean/Prelude.leanare regenerated from the preludeby the build, not edited.
Diff size is dominated by the generated prelude sources: the hand-written change
is 77 lines of
ruby-lean/prelude/prelude.rb.Test plan
Run with CRuby 4.0.5, Lean v4.32.2, Sorbet 0.6.13405:
make lean— the model, the regenerated prelude and the booted heap build.make conformance— over all 1287 bootstraptest programs:ran 1287,agree 1087,sut_unsupported 194,not_compared 6,disagree 0;CONFORMANCE OK. The recorded baseline is unchanged (no verdict moved).before this change), no failures. The new case is
enumerable-count-conversion.replay corpus/tier3— 70 programs, 57 agree, 13 out-of-fragment, 0 disagree.make books— 1399 jobs, every proof book and the checker build.make metatheory— the metatheory audit passes and the booted heap satisfiesthe assumptions the proofs make about it.
books/scripts/check-soundness.sh—SOUNDNESS CHECK PASSED, including thecorpus run and the
corpus/accepted.txtcomparison (unchanged).difftestpytest with coverage — 189 passed, line 72.77% / branch 68.18%,COVERAGE OK.desugar/bin/runover the corpus —agree 1257 disagree 0 out-of-fragment 76 harness-error 0;desugar/bin/coverageholds its baseline (1211 of 1282).ruff format --check/ruff check— clean;make docs— clean;scripts/check-not-weakened.py --base origin/main—CHECKS NOT WEAKENED.bin/ruby-lean --compare, each now identical to CRubyincluding class and message:
take(-1),take(0),take(1.5),take(-0.5),drop(-1),drop(2.9),take(nil),take(:a),take("2"),take(2**63),take(-2**64),take(2**63 - 1),take(Float::NAN),take(Float::INFINITY),take(-Float::INFINITY), an object whoseto_intanswers an Integer / nil / a Float,
each_slice(1.5),each_cons(1.5),each_slice(0),each_cons(-2),{a: 1}.first(-1),(1..4).drop(2.5), andthe same calls on a user
Enumerable.make lint's RuboCop stage could not run in this session (thejsongem in theGemfile needs a C extension and the session has no compiler); it does not lint
the prelude or the corpora, so the change adds nothing for it to inspect.
Note for the maintainer
Enumerable#min_by(n)and#max_by(n)still answerArgumentError: wrong number of arguments (given 1, expected 0)where CRubyanswers the first/last n by key. That is a separate gap in the same file and is
not touched here.
Closes #137.