[ruby-core:108175] [Ruby master Bug#18625] ruby2_keywords does not unmark the hash if the receiving method has a *rest parameter
From:
"Eregon (Benoit Daloze)" <noreply@...>
Date:
2022-04-05 09:52:36 UTC
List:
ruby-core #108175
Issue #18625 has been updated by Eregon (Benoit Daloze).
I merged https://github.com/ruby/ruby/pull/5684, which is Jeremy's PR + a needed update of rspec-mocks + [a NEWS entry](https://github.com/eregon/ruby/blob/3d0aac928f7592838deff2cb84b57f0678c348cc/NEWS.md).
This is the only way to really assess the impact on gems/apps depending on this bug, and to let gems/apps to find and add the missing `ruby2_keywords` (asking gems or even rails to use a branch of ruby is a lot of overhead).
Because this was a bug, and there is no other way to fix it, and this fix will help migration to other forms of delegation, I think it is very unlikely we would revert this change, but it remains a possibility until the 3.2 release.
A good technique to find the potentially-missing `ruby2_keywords` is to run the test suite, for where it fails find the last method which must receive keyword arguments,
use `puts nil, caller, nil` there, and check each method on the call chain which must delegate keywords is correctly marked as `ruby2_keywords`.
----------------------------------------
Bug #18625: ruby2_keywords does not unmark the hash if the receiving method has a *rest parameter
https://bugs.ruby-lang.org/issues/18625#change-97148
* Author: Eregon (Benoit Daloze)
* Status: Open
* Priority: Normal
* Backport: 2.6: UNKNOWN, 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN
----------------------------------------
The code below shows the inconsistency.
In all cases the `marked` Hash is copied at call sites using `some_call(*args)`, however for the case of `splat` it keeps the ruby2_keywords flag to true, and not false as expected.
This can be observed in user code and will hurt migration from `ruby2_keywords` to other ways of delegation (`(...)` and `(*args, **kwargs)`).
I believe this is another manifestation of #16466.
```ruby
ruby2_keywords def foo(*args)
args
end
def single(arg)
arg
end
def splat(*args)
args.last
end
def kwargs(**kw)
kw
end
h = { a: 1 }
args = foo(**h)
marked = args.last
Hash.ruby2_keywords_hash?(marked) # => true
after_usage = single(*args)
after_usage == h # => true
after_usage.equal?(marked) # => false
p Hash.ruby2_keywords_hash?(after_usage) # => false
after_usage = splat(*args)
after_usage == h # => true
after_usage.equal?(marked) # => true, BUG, should be false
p Hash.ruby2_keywords_hash?(after_usage) # => true, BUG, should be false
after_usage = kwargs(*args)
after_usage == h # => true
after_usage.equal?(marked) # => false
p Hash.ruby2_keywords_hash?(after_usage) # => false
Hash.ruby2_keywords_hash?(marked) # => true
```
I'm implementing Ruby 3 kwargs in TruffleRuby and this came up as an inconsistency in specs.
In TruffleRuby it's also basically not possible to implement this behavior, because at a splat call site where we check for a last Hash argument marked as ruby2_keywords, we have no idea of which method will be called yet, and so cannot differentiate behavior based on that.
cc @jeremyevans0 @mame
--
https://bugs.ruby-lang.org/
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>