DevMeeting-2023-06-08
https://bugs.ruby-lang.org/issues/19599
DateTime and location
- 2023/06/08 (Thu) 13:00-17:00 JST @ Online
Next Date
- Next: 2023/07/13 (Thu) 13:00-17:00 JST @ Online
Announce
About release timeframe
Ordinary tickets
[Feature #18368] Range#step semantics change for non-Numeric ranges (zverok)
- Implementation is provided, but no movement is seen in months. Can we agree (or provide the adjustments) on the final spec and the implementation?
Discussion:
# current
(1..-1).step(1) { p _1 } #=> no output
(1..-1).step(-1) { p _1 } #=> step can't be negative
(1..-1).step(2.minutes) { p _1 } #=> no output
(1..-1).step(-1.seconds) { p _1 } #=> step can't be negative
(1..-1).step(1i) { p _1 } #=> comparison of Complex with 0 failed
# after zverok's patch
(1..-1).step(1) { p _1 } #=> no output
(1..-1).step(-1) { p _1 } #=> 1 0 -1
(1..-1).step(2.minutes) { p _1 } #=> no output
(1..-1).step(-1.seconds) { p _1 } #=> no output <- NG
(1..-1).step(1i) { p _1 } #=> no output
p (1..-1).step(-1.seconds) #=> #<Enumerator: 1..-1:step(-1 seconds)>
(1.seconds..-1.seconds).step(-1.seconds) { p _1 } #=> no output
class Range
def my_step(n)
a, b = self.begin, self.end
case n <=> 0
when -1
while a >= b # TODO: we need to care "exclude end"
yield a
a += n
end
when 0
raise "step can't be 0"
when 1
while a <= b # TODO: we need to care "exclude end"
yield a
a += n
end
end
end
end
(1..-1).step(-1) { p _1 } #=> 1, 0, -1
(1..2).step(-1) { p _1 } #=> current: can't be negative, new: no output <- matz: the change accepted
- knu: I think making
(1..-1).step(-1.seconds) { p _1 }not raising exception is a step backward if it silently ends not yielding anything. The step value should be compared with zero using the<=>operator to see if it is consistent with the range direction (ascending/descending) and start a loop if it is. - knu: I’m not absolutely sure if it’s OK to introduce comparison with zero because it could close the door to allowing step over a non-Numeric range.
Conclusion:
- matz: I would like to confirm if the OP’s expectation is the same as the avobe code of my_step
- matz: The proposed changes are all accepted, but
- matz: At least,
(1..-1).step(-1.seconds) { p _1 }should display1, 0 seconds, -1 seconds - matz: I think
Integer#coerceshould call#to_intif any. We need a separated ticket to discuss this change. I want to ask the OP to create that
[Bug #19681] The final classpath of partially named modules is sometimes inconsistent once permanently named (byroot)
- Please see the code snippet in the ticket description.
- The current implementation iterate over
RCLASS_CONST_TBLand assign the first name it find. - But that table is unordered, so if the module was aliased, the result can be surprising.
Preliminary discussion:
m = Module.new
class m::C; end
p m::C.name # => "#<Module:0x000000010789fbe0>::C"
m::D = m::C
p m::D.name # => "#<Module:0x000000010789fbe0>::C"
M = m
p M::C.name # => "M::D" # actual (depending on the order of unordered hash)
p M::C.name # => "M::C" # expected
Discussion:
- samuel: Use ordered hash? Resolve in the order they were defined?
Conclusion:
- matz: I choose the option 2 (undefined behavior): https://bugs.ruby-lang.org/issues/19681#note-26
- samuel: Even if you choose option 2, can we implement option 1?
- matz: If it is worth doing that, it is possible, but I don’t see the use case yet. I will ask it in the ticket
[Bug #11704] Refinements only get “used” once in loop (jeremyevans0)
- Is it expected that if a refinement is activated for a block, it remains activated if the block is re-entered?
- This is how CRuby works, but may be confusing to users expecting more dynamic behavior.
- This would seem difficult to change in CRuby, though JRuby (and maybe TruffleRuby) already operates the more dynamic way.
Preliminary discussion:
module C
refine Array do
def foo
p :C
end
end
end
module D
refine Array do
def foo
p :D
end
end
end
2.times do
module X
using C
[].foo
end
module Y
using D
[].foo
end
end
#=> :C, :D, :C, :D
2.times do
tap do
using C
[].foo
end
tap do
using D
[].foo
end
end
#=> expected: :C, :D, :C, :D
#=> actual: :C, :D, :D, :D
[].foo #=> :D
Discussion:
- It is expected behaviour that using in a block affects the outer file or module scope. (shugo)
Conclusion:
- matz: rejected
[Bug #18622] const_get still looks in Object, while lexical constant lookup no longer does (jeremyevans0)
- Should we change
Module#const_getto raiseNameErrorinstead of looking intoObject, for modules and subclasses ofObject? - If so, should we deprecate the behavior in Ruby 3.3 and change the behavior in Ruby 3.4?
Preliminary discussion:
module A
Foo = :foo
end
module B
end
p B.const_get("A::Foo") # => :foo
p B::A::Foo # => const_get.rb:9:in `<main>': uninitialized constant B::A (NameError)
module B
p A::Foo #=> :foo
end
Discussion:
- mame: I think no use case is explained
- samuel: Isn’t “
const_getstill looks inObject, while lexical constant lookup no longer does” the use case, i.e. making theMyConstantandself.const_get(:MyConstant)behave the same? - mame: no one write
self.const_get(:MyConstant)casually. And we confirmed some code fragments that depends on the current behavior. We need more explicit explanation if we change it - samuel: Makes sense. I actually use
const_getquite frequently and didn’t realise the semantics were different.
Conclusion:
- matz: I will reply
[Bug #15428] Refactor Proc#>> and #<< (jeremyevans0)
- Last discussed at August 2021 developer meeting, no decision made.
- Calling
to_procif the passed object does not respond tocallshould be backwards compatible. - Do we want to change the behavior of
Proc#>>andProc#<<to encourage their usage with symbols? - Alternatively, should
Proc#>>andProc#<<raise error if called with argument not supporting#call(to avoidNoMethodErrorlater when you call the resultingProc)?
Discussion:
JSON.method(:parse) >> :symbolize_keys
#=> current: callable object is expected (TypeError)
#=> expected: same as: -> x { JSON.parse(x).symbolize_keys }
- mame: Why not write the proc plainly?
-> x { JSON.parse(x).symbolize_keys }is much simpler - samuel: I agree, I don’t think we should encourage this style.
Conclusion:
- matz: My current opinion is “reject”, but I will read the ticket later.
[Bug #19702] Promote racc as bundled gems (hsbt)
- Does anyone have objection this?
Discussion:
- no one was against the proposal
Conclusion:
- matz: go ahead
[Feature #19057] Hide implementation of rb_io_t. (ioquatix)
- So far the consensus is to hide the entire implementation (
struct rb_iomembers) and fix dependencies.
Preliminary discussion:
- It is blocking us improving
IO#closeand will simplify other changes toIOimplementation. -
Exposing the implementation details makes it tricky for TruffleRuby / JRuby.
- Is deprecation acceptable?
- PR: https://github.com/ruby/ruby/pull/7916
- Can we remove completely before 3.3 is released?
- We already fixed
io-console,io-wait,pty,stringio, etc.
Discussion:
-
naruse: Please list up the affected gems. Unless you prove the impact is small, we need a migration period (can’t remove in 3.3)
-
Gem code search results: https://gist.github.com/ioquatix/c0422bde98def1f9878c096d0035a840
Conclusion:
- it seems okay to merge the deprecations.
[Bug #19717] ConditionVariable#signal is not fair when the wakeup is consistently spurious. (ioquatix)
- Can we discuss whether we should prevent spurious wakeup?
- No PR yet.. can we discuss best approach?
Preliminary discussion:
-
samuel: Reproduction https://github.com/ioquatix/ruby-condition-variable-timeout
-
samuel: alternative (joke) implementation of
wakeup_onewhich also solves the problem:
// thread_sync.c
static void
wakeup_one_random(struct ccan_list_head *head)
{
struct sync_waiter *cur = 0, *next;
size_t size = 0;
ccan_list_for_each_safe(head, cur, next, node) {
size++;
}
if (size == 0) return;
size_t index = rand() % size;
ccan_list_for_each_safe(head, cur, next, node) {
if (index-- == 0) {
ccan_list_del_init(&cur->node);
if (cur->th->status != THREAD_KILLED) {
if (cur->th->scheduler != Qnil && cur->fiber) {
rb_fiber_scheduler_unblock(cur->th->scheduler, cur->self, rb_fiberptr_self(cur->fiber));
}
else {
rb_threadptr_interrupt(cur->th);
cur->th->status = THREAD_RUNNABLE;
}
}
return;
}
}
}
// ...
static VALUE
rb_condvar_signal(VALUE self)
{
struct rb_condvar *cv = condvar_ptr(self);
wakeup_one_random(&cv->waitq);
return self;
}
workers = 3.times do
Thread.new do
# (0) All workers try to get the resource. Worker 0 proceeds and 1, 2 are waiting.
# (2) (worker-1) is woken up
POOL.with_resource do # (3) Try to lock but can't.
Logger.debug('Sleep with resource #1')
sleep(0.001) # simulates a DB call
end # (1) Worker 0 `ConditionVariable#signal`
POOL.with_resource do # (1) Immediately re-acquire the mutex here.
Logger.debug('Sleep with resource #2')
sleep(0.001) # simulates a DB call
end
end
sleep(0.001)
end
Discussion:
*
Conclusion:
- samuel: I’m going to confirm whether this issue is to do with Ruby’s implementation of
wakeup_oneor whether the same issue occurs with pthread (C implementation).
[Bug #18818] Thread waitq does not retain referenced objects, can lead to use after free.
- Should we mark waitq and similar structures?
- PR: https://github.com/ruby/ruby/pull/5979
Preliminary discussion:
*
Discussion:
*
Conclusion:
- ko1: My preference is for the clean up code, because marking of
waitqcan be expensive. - We hope that this issue can’t affect dead threads.
- We know it can affect dead fibers, so they should be changed to clean up if possible.
[Feature #19520] Support for Module.new(name) and Class.new(superclass, name).
- Any thoughts on moving forward with this proposal?
- PR: https://github.com/ruby/ruby/pull/7376
Preliminary discussion:
-
What about marking it as experimental?
- Examples:
- Rails: https://github.com/rails/rails/blob/55c3066da325703ff7a9524dbdc479b860db3970/activerecord/lib/active_record/relation/delegation.rb#L36-L39
- Rails: https://github.com/rails/rails/blob/55c3066da325703ff7a9524dbdc479b860db3970/activemodel/lib/active_model/attribute_methods.rb#L535
- Rails: https://github.com/rails/rails/blob/55c3066da325703ff7a9524dbdc479b860db3970/activerecord/lib/active_record/associations.rb#L2059https://github.com/rails/rails/blob/55c3066da325703ff7a9524dbdc479b860db3970/activerecord/lib/active_record/associations.rb#L2059
- RSpec: https://github.com/rspec/rspec-core/blob/d722da4a175f0347e4be1ba16c0eb763de48f07c/lib/rspec/core/example_group.rb#L842-L848
- Lots of other examples if you go looking for them.
- We can always make it a no-op later if it turns out to be a bad idea?
Discussion:
- My recommended proposal:
Class.new(superclass, name)
Module.new(name)
- Alternative: https://bugs.ruby-lang.org/issues/19521
c = Class.new do
self.name = "fake name"
end
c = Class.new
c.name = "fake name"
- names could be randomly changed?
- Callbacks like
inherited- between creating the class, invoking callbacks, and setting the name, strange temporary names can be created.
c = Class.new
c.set_anonymous_name "Array"
Conclusion:
- matz: will reply to the issues, “19520” rejected, “19521” with improved naming.
[Feature #19712] IO#reopen removes singleton class (nobu)
-
As well
#reopenremoves the singleton class, even when the logical class is the same. This can be surprising at times. - An example:
io = File.open(__FILE__) io.define_singleton_method(:foo) {} io.reopen(File.open(__FILE__)) io.foo # NoMethodError -
While it’s hard to trivially solve this with keeping the intended functionality, could it be considered to make edge cases more visible?
- Examples:
IO#reopenissues a warning message when the receiver’s singleton class has been updated (but keep removing it as of current state)IO#reopenis limited on instances with a default singleton class, and issues an error when called on an instance with an updated singleton class- make
IO#reopencarry over the singleton class only if the recipient and argument’s class are the same, and yield an error otherwise (different classes)
Preliminary discussion:
- samuel: This is the line of code responsible for copying the class (but not singleton class): https://github.com/ruby/ruby/blob/533368ccbd9b0abd1633f1d7ea4dc737d75f38e6/io.c#L8348
- It’s not commonly used: https://github.com/search?q=repo%3Aruby%2Fruby%20RBASIC_SET_CLASS&type=code
- Should
reopenshould change the class?
- akr: How about adding a keyword argument to disable changing the class? Such as:
io1.reopen(io2, retain_class:true)
Discussion:
f1 = File.open("a")
p f1.class #=> File
f2 = TCPSocket.open("localhost", 8080)
p f2.class #=> TCPSocket
f1.reopen(f2)
p f1.class #=> TCPSocket
- Assigning to
$stdoutcould be better if that’s the desired behaviour:
$stdout = "foo"
# $stdout must have write method, String given (TypeError)
class MyFakeOut
def write(s)
STDOUT.write(s)
end
end
=> :write
irb(main):014:0> $stdout = MyFakeOut.new
# Accepted but doesn't work in irb.
- samuel: Changing the interface should never be possible IMHO.
class BorkedIO < IO
IO.instance_methods(false).each do |method|
self.undef_method(method)
end
end
$stdout.reopen(BorkedIO.new(1))
$stdout.write(...) # NoMethodError
# Does it make sense?
- samuel: It also doesn’t work for singleton methods defined on the argument to
reopen:
f = File.open("foo.txt", "w+")
# #<File:foo.txt>
r, w = IO.pipe
# [#<IO:fd 10>, #<IO:fd 11>]
def f.bar
self.write("bar")
end
w.reopen(f)
w.bar
# undefined method `bar' for #<File:foo.txt> (NoMethodError)
Conclusion:
- matz: prefer documentation update to describe reopen changes the class and can drop singleton.
[Feature #19718] Extend -0 option (nobu)
- Perl’s
-0option is extended to accept a hexadecimal Unicode codepoint. -0=separator-0:CODEPOINT,...
Discussion:
- no one sees its use case
Conclusion:
- matz: rejected
[Feature #19719] Universal Parser
Discussion:
- https://github.com/ruby/ruby/compare/master…yui-knk:ruby:universal-parser-2
Conclusion:
- matz: accept the conditional compilation by UNIVERSAL_PARSER
- matz: also accept the direction described in https://bugs.ruby-lang.org/issues/19719