Skip to content

Range#min/#max with a comparison block say "unbounded" where MRI says "endless" and "beginless" — and no spec asserts either #854

Open

Description

@tannevaled

min/max with a custom comparison block say "unbounded" where MRI says two different words

Measured on main:

$ rbgo -e 'begin; (1..).min { |a,b| a<=>b }; rescue => e; p e.message; end'
"cannot get the minimum of unbounded range with custom comparison method"

MRI 4.0.9, range.c:

1649:  rb_raise(rb_eRangeError, "cannot get the minimum of endless range with custom comparison method");
1762:  rb_raise(rb_eRangeError, "cannot get the maximum of beginless range with custom comparison method");

So upstream uses opposite words for the two, and each is the end the method
actually needs: min with a block must walk from the beginning to the end, so
an endless range defeats it; max must walk back from the end, so a
beginless one does. rbgo has a single format string with a %s for min/max
and the word unbounded for both:

// internal/vm/builtins.go:11903
raise("RangeError", "cannot get the %s of unbounded range with custom comparison method", which)

unbounded is arguably the better word — it is true of both cases, where MRI's
pair reads as if someone wrote each message where they needed it. But this
project's standard is MRI's bytes, and the surrounding messages all match
exactly (cannot get the last element of endless range, cannot convert endless range to an array, and #853 adds cannot convert endless range to a set).

⛔ Why this is not a one-word fix, and why I have not done it

The two words are not interchangeable between the two methods, so a change
has to split the single format string and get the asymmetry the right way round.
And:

$ grep -rn "minimum of endless range\|minimum of unbounded range" core/
(nothing)

No spec in language/ or core/ asserts either message, so nothing would
catch inverting them. A change with no test and an easy-to-invert asymmetry is
exactly the shape that lands backwards and stays that way.

What it needs, in order: a Go test pinning both messages against the two MRI
lines above (quoted, with their file and line), then the split. That is small but
it is its own change — I kept it out of #853, which is a hang fix measured
against a control, because mixing an unverifiable cosmetic edit into a crash fix
makes the whole thing harder to trust.

Found while auditing the endless-range family for #853: every other guard in it
matches MRI exactly, which is what made this one visible.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions