Repository navigation
Description
Enumerable#to_set with arguments is deprecated in 4.0 and MRI says so on stderr. rbgo says nothing.
require "set"
class Sub < Set; end
[1, 2].to_set(Sub)$ /opt/homebrew/opt/ruby@4.0/bin/ruby probe.rb
probe.rb:3: warning: passing arguments to Enumerable#to_set is deprecated
$ rbgo probe.rb
(nothing)Measured through scripts/conformance/rubyspec/cmd/oracle against
ruby 4.0.7 (2026-09-15 revision 229531a6cf) +PRISM [arm64-darwin25]. Only stderr differs; stdout is byte-identical, and the resulting Set is correct in both (Sub).
It is Enumerable#to_set, not Set#to_set
Localised one call at a time rather than inferred, because the warning text names Enumerable while the obvious suspect is the method that takes the arguments:
| call | MRI warns? |
|---|---|
[1, 2].to_set(Sub) — Enumerable, with a klass |
yes |
[1, 2].to_set — Enumerable, bare |
no |
Set[1, 2].to_set(Sub) — Set, with a klass |
no |
Set[1, 2].to_set { … } — Set, with a block |
no |
Set[1, 2].to_set |
no |
So the deprecation is on the Enumerable path when arguments are passed, and Set#to_set — which also accepts a klass — does not carry it.
⛔ I have not established whether a block alone triggers it on the Enumerable path, nor whether *args without a klass does; the table above is what I measured and no more. The rule looks like "arguments passed to the Enumerable version", but one of those rows could contradict it.
Not a regression, and not from #903
rbgo has never emitted this warning. prelude.rb:169 is
Enumerable#to_set(klass = Set, *args, &block) and simply forwards. The
#903 change (a Set
subclass reporting its own class) touched Set#to_set, which is a different
method and does not warn in MRI either — the two are independent and the stdout
of all eight to_set rows agrees.
Why it is worth having
Warning[:deprecated] and -W:deprecated are how a Ruby program finds the
calls it has to change before the next release. A runtime that stays silent
tells its users they have no work to do. The same shape as the
straight-quote message migration in
#900: a message, not a
behaviour, and still a divergence a program can observe.
Scope
A rbWarn at prelude.rb:169's Go-visible counterpart, gated the way rbgo
gates other deprecations — plus whatever the two unmeasured rows above turn out
to require. Worth checking at the same time whether other Enumerable
deprecations of the 4.0 cycle are equally silent here; this one was found by
accident while probing something else, which is not a sampling method.
🤖 Generated with Claude Code
Activity
tannevaled commentedon Nov 8, 2026
The corpus already asserts this, and the example is FAILING — so this is +1 on the ratchet, not cosmetic
I filed this from a probe and wrote that finding it by accident "is not a sampling method". Correct, and the corpus had it all along:
core/enumerable/to_set_spec.rb.Measured on
mainat6898f4b4and on the branch of #910 — identical, so this is independent of both:⭐ And the guard makes it a version-move item
ruby_version_is "4.0"..."4.1"— the example exists only for 4.0.x. Below 4.0 the same description asserts the call without any warning; at 4.1 the deprecation is gone and so is the example. So this is not a permanent gap to fill at leisure: it is one of the examples the move toRUBY_VERSION4.0.9 newly admitted, the same shape as everything in the (now closed) #799, and it will stop being assertable when the corpus moves to 4.1.⛔ The
complainmatcher is NOT stubbed, whatever the README saysscripts/conformance/rubyspec/README.mdlistscomplainamong the matchers that "are stubbed, so those examples count as skipped rather than passing". That is wrong, in a direction that matters:spec_helper.rb:204-249implements a realComplainMatcherwith a$stderrcapture, and:249wirescomplain;output(:254-279) andbe_computed_by(:199) are implemented too;ruby_exe(:882) andoutput_to_fd(:280), both raisingSpecSkip.The failure above is the proof: a skipped example cannot report
expected a warning matching /…/, got "".It matters because that sentence is load-bearing —
cmd/phantom's rationale quotes it to argue "the stubbed matchers make the total too LOW". Forcomplainthe error runs the other way: those examples fail and count against the ratchet. I will correct the README separately rather than inside aSetchange.Scope, revised
Narrower than I guessed when filing. The spec asserts only that something matching
/Enumerable#to_set/reaches$stderrwhen arguments are passed to theEnumerableversion — which is the one row I had measured as warning. The two rows I flagged as unmeasured (a block alone;*argswithout a klass) are still unmeasured and the spec does not pin them, so they stay open questions rather than requirements.🤖 Generated with Claude Code
tannevaled commentedon Nov 8, 2026
⛔ It is
rb_warn, NOT a:deprecatedcategory warning — measured, and it inverts the obvious fixThe message says "deprecated", and this repository has
warnDeprecated(globals.go:306) which gates on$VERBOSEandWarning[:deprecated]. Using it would be wrong. Measured on 4.0.7:$VERBOSE=false,Warning[:deprecated]=false)Warning[:deprecated] = falseWarning[:deprecated] = true,$VERBOSE = nilWarning[:deprecated] = true,$VERBOSE = falseSo the category is not consulted at all — it warns with
Warning[:deprecated] = false— and the only gate is$VERBOSE != nil. That is plainrb_warn(error.c:466), which this repository already has asrbWarn(builtins.go:12734, documented against that exact upstream line).⛔ Why this matters for the spec:
core/enumerable/to_set_spec.rbdoes not enable the category, so awarnDeprecatedimplementation would stay silent in exactly the case the spec exercises and the example would keep failing while looking fixed. The helper whose name matches the message is the wrong one; the helper whose gating matches isrbWarn.Same shape as #906, where
respond_to?was the obvious predicate and a method-entry lookup was the correct one — the name suggested one thing and the measurement said another.The real work: the method is in Ruby, the helper is in Go
Enumerable#to_setisprelude.rb:169:rbWarnis called from Go (builtins.go:3152,:4445, …) and has no prelude-visible counterpart.Kernel#warnis not a substitute: MRI'sKernel#warnwrites unconditionally, so it would speak when$VERBOSEis nil, where the table above says MRI is silent.So the choice is between giving the prelude a
$VERBOSE-gated primitive and moving this one method's argument check into Go. Not deciding that here — the measurement is the part that was missing, and it rules out the option someone would otherwise reach for first.Still unestablished
Whether a block alone, or
*argswithout an explicit klass, also warns. The spec pins only the explicit-klass case. Noted when filing and still true.🤖 Generated with Claude Code
rubyspec README: complain/output/be_computed_by are NOT stubbed, and …
tannevaled commentedon Nov 8, 2026
The condition, measured exactly — and two constraints that rule out the obvious implementation
I had left two questions open when filing. Both are answered, and the answers change the fix.
1. Any POSITIONAL argument warns; a block alone does not
[1,2].to_set[1,2].to_set { |x| x }[1,2].to_set(Set)Setis the default[1,2].to_set(Sub)[1,2].to_set(Sub) { |x| x }The trigger is an argument was passed, not it differs from the default. So the current signature cannot express it:
klassisSetboth when omitted and whenSetis passed, and only the second warns. The condition needs*args(or a sentinel default).2. ⛔ But
*argsplus a plainsuperwould be WRONG —Range#to_setproves it(1..3).to_set(1..3).to_set(Sub){a: 1}.to_set[1,2].lazy.to_setSet[1].to_set(Sub)Set#to_setcarries no deprecation)Range#to_set(prelude.rb:1143) isdef to_set(klass = Set, *args, &block)ending insuper. WithEnumerable#to_setchanged to*args, zsuper would forwardklassas a bound positional — so(1..3).to_setwould warn, where MRI is silent. Changing one signature requires changing its delegator in the same breath.3. ⛔ And the warning's location is the USER's frame, through the delegation
(1..3).to_set(Sub)warns with the caller'sfile:line(line 8 of my probe), notRange#to_set's line in the prelude. So a prefix built fromcaller_locations(1, 1)insideEnumerable#to_setis right for a direct call and wrong for one reached viasuper— it would name the prelude.⭐
caller_locations(1, 1)itself behaves identically in rbgo and MRI (2 engines AGREED, byte for byte), so the prefix is achievable; the frame DEPTH is the part that needs care, not the API.4. It is
rb_warn, so no new Go API is neededPer the gating measured earlier (warns with
Warning[:deprecated] = false, silent with$VERBOSE = nil), this isrb_warn: "if$VERBOSEis not nil, write the prefixed message viaWarning.warn". The prelude can do that itself —$VERBOSEandWarning.warn(prelude.rb:2750) are both reachable from Ruby — so the choice I posed earlier between a new$VERBOSE-gated primitive and moving the method into Go is a false one. Neither is required.Brief
Enumerable#to_set:*args, warn when!args.empty? && !$VERBOSE.nil?, thenklass = args.empty? ? Set : args.shift.Range#to_set: stop forwarding a defaultedklass— forward*argsthrough, so an omitted klass stays omitted.core/enumerable/to_set_spec.rb, which asserts this withcomplain(/Enumerable#to_set/).Not implementing it in this pass: items 2 and 3 are the kind of frame-depth detail that wants its own change and its own probe rows rather than being appended to a
Setbranch. The measurements above are the part that was missing.🤖 Generated with Claude Code
tannevaled commentedon Nov 8, 2026
Narrowing item 3: the prefix machinery already works — measured
priorart --repo go-embedded-ruby/ruby "Enumerable to_set deprecation warning"surfaces #699 (open):That reads as a blocker for the brief above, which needs the warning to carry the caller's
file:line. It is not. Measured onmainatc604cce1, using an existingrbWarnsite (Array.new(2, 0) { |i| i }):So the prefix half of #699 is satisfied today — consistent with #706 (merged 2026-09-26) having referenced it. #699 stays open on its other claims, which I have not re-measured and am not pronouncing on.
⛔ Which means item 3 of the brief is narrower than I wrote it. It is not "the prefix mechanism needs work":
rbWarnproduces MRI's exact prefix from the Go side. The only open question is which frame the prelude asks for whenEnumerable#to_setis reached throughRange#to_set'ssuper— acaller_locationsdepth, not a missing capability.⛔ And a note on my first attempt at this measurement, because it would have been a wrong answer: I probed with
Hash.new(0) { |x| 1 }, expecting a warning. Both engines raiseArgumentErrorthere (4.0 forbids a default and a block), so I had measured an exception and not a warning at all. The second probe used a site the source shows callingrbWarn(builtins.go:3152). Picking the trigger from the code rather than from memory is what made the comparison mean anything.🤖 Generated with Claude Code