Project

General

Profile

Bug #18625

Updated by Eregon (Benoit Daloze) over 2 years ago

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

Back