Project

General

Profile

Bug #16560

Proc autosplats first argument if called with one argument and empty keyword splat

Added by Dan0042 (Daniel DeLorme) 6 months ago. Updated 4 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:96990]

Description

While working on understanding the vm_args.c code via refactoring, I found the following:

b = proc{ |a,b=0| [a,b] }
h = {k:42}
b.call([1,2,3],**h) #=> [[1, 2, 3], {:k=>42}]
h = {} 
b.call([1,2,3],**h) #=> [1, 2]           (in 2.7)
                    #=> [[1, 2, 3], {}]  (in 2.6)

Since the result is different from 2.6 I think this is a bug, especially since the result in 2.7 is so different based on being an empty or non-empty splat.

In my refactoring branch I'm solving this by moving the args_check_block_arg0 check before the ignore_keyword_hash_p check, but that doesn't look so easy in master.

Updated by jeremyevans0 (Jeremy Evans) 6 months ago

  • Subject changed from Empty splat is ignored, resulting in lone arg splatting to Proc autosplats first argument if called with one argument and empty keyword splat

Thanks for the report. This is probably due to the removal of the empty keyword splats. In general, passing empty keyword splat to a method should be the same as not passing any keywords (in the master branch), and that is how it currently works:

b.call([1,2,3])     # [1, 2]
b.call([1,2,3],**h) # [1, 2]

However, if you have b.call([1,2,3],**h), having autosplat behavior change based on the contents of h is definitely not going to be what the user wants. So I agree this is a bug (regression) and it should be fixed. I'll try to work on a fix today.

Updated by jeremyevans0 (Jeremy Evans) 6 months ago

  • Backport changed from 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: UNKNOWN to 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: REQUIRED

I have submitted a pull request to fix this: https://github.com/ruby/ruby/pull/2861

Note that behavior still changes slightly compared to 2.6:

h = {} 
b.call([1,2,3],**h) #=> [[1, 2, 3], 0]   (with patch)
                    #=> [[1, 2, 3], {}]  (in 2.6)

This is because the splat argument is still removed, so only a single argument is provided, and the second parameter uses the default argument. This is the expected behavior in 2.7+.

Updated by Dan0042 (Daniel DeLorme) 6 months ago

Thanks for the quick fix, and your hard work as usual.
Although imho that would be the expected behavior in 3.0, and in 2.7 it should be a warning with the same behavior as 2.6.

#4

Updated by jeremyevans (Jeremy Evans) 6 months ago

  • Status changed from Open to Closed

Applied in changeset git|c1d8829ef515ee51fadeadd7dd022b5c47a71cdd.


Do not autosplat when calling proc with empty keyword splat

With the removal of the splatted argument when using an empty
keyword splat, the autosplat code considered an empty keyword
splat the same as no argument at all. However, that results
in autosplat behavior changing dependent on the content of
the splatted hash, which is not what anyone would expect or
want. This change always skips an autosplat if keywords were
provided.

Fixes [Bug #16560]

Updated by jeremyevans0 (Jeremy Evans) 6 months ago

Dan0042 (Daniel DeLorme) wrote:

Although imho that would be the expected behavior in 3.0, and in 2.7 it should be a warning with the same behavior as 2.6.

No, in 2.7 empty keyword splats are removed in most cases without warning, that is unrelated to proc autosplatting:

def a(b, c=0)
  [b, c]
end
h = {}
a([1, 2], **h)
# => [[1, 2], {}] # 2.6
# => [[1, 2], 0]  # 2.7

The cases where they aren't removed in 2.7:

  • Empty keyword splat is needed for mandatory positional argument (issues a warning)
  • ruby2_keywords methods so that a final positional hash before keywords is not treated as keywords by target method

Updated by naruse (Yui NARUSE) 4 months ago

  • Backport changed from 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: REQUIRED to 2.5: UNKNOWN, 2.6: UNKNOWN, 2.7: DONE

ruby_2_7 e74d2a42b274844ed020ed121ee2f11c626c5fec.

Also available in: Atom PDF