Skip to content

Commit

Permalink
Update pass_positional_hash documentation to add some caveats
Browse files Browse the repository at this point in the history
It is not a good idea to use pass_positional_hash if modifying
the keyword arguments or appending positional arguments, as that
can result in different behavior without warning on Ruby 3 if
the target method does not accept keyword arguments.  Update
tests to show the issue.
  • Loading branch information
jeremyevans committed Sep 10, 2019
1 parent 32c7e0b commit 97eeab3
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 3 deletions.
27 changes: 27 additions & 0 deletions test/ruby/test_keyword.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,33 @@ def arg1o(a, **o)
assert_raise(NameError) { c.send(:pass_positional_hash, "a5e36ccec4f5080a1d5e63f8") }
assert_raise(NameError) { c.send(:pass_positional_hash, :quux) }

c.class_eval do
pass_positional_hash def f(meth, *args, **kw)
send(meth, *args, a: 1, **kw)
end
pass_positional_hash def g(meth, *args, **kw)
kw[:a] = 1
send(meth, *args, **kw)
end
pass_positional_hash def h(meth, *args, **kw)
send(meth, *args, 2, **kw)
end
end
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
assert_equal([[1], {b: 1, a: 1}], o.f(:bar, 1, {b: 1}))
end
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
assert_equal([[1], {b: 1, a: 1}], o.g(:bar, 1, {b: 1}))
end
assert_warn(/The last argument is used as the keyword parameter.* for `bar'/m) do
assert_equal([[1, 2], {b: 1}], o.h(:bar, 1, {b: 1}))
end

# Different behavior in Ruby 3 without warning! (pass_positional_hash should not have been used)
assert_equal([1, {b: 1, a: 1}], o.f(:baz, 1, {b: 1})) # Ruby 3: [1, {b: 1}, {a: 1}]
assert_equal([1, {b: 1, a: 1}], o.g(:baz, 1, {b: 1})) # Ruby 3: [1, {b: 1}, {a: 1}]
assert_equal([1, 2, {b: 1}], o.h(:baz, 1, {b: 1})) # Ruby 3: [1, {b: 1}, 2]

c.freeze
assert_raise(FrozenError) { c.send(:pass_positional_hash, :baz) }
end
Expand Down
11 changes: 8 additions & 3 deletions vm_method.c
Original file line number Diff line number Diff line change
Expand Up @@ -1757,10 +1757,15 @@ rb_mod_private(int argc, VALUE *argv, VALUE module)
*
* This should only be used for methods that delegate keywords to another
* method, suppressing a warning when the target method does not accept
* keyword arguments the final argument is a hash.
* keyword arguments and the final positional argument is a hash.
*
* Will only be present in 2.7, will be removed in 3.0, so always check that
* the module responds to this method before calling it.
* +pass_positional_hash+ should only called with a method if keyword arguments are
* not modified inside the method before splatting, and additional arguments are
* not appended to the array of positional arguments. Otherwise the behavior may
* differ in Ruby 3 and will not be warned about.
*
* +pass_positional_hash+ is only be present in 2.7, it will be removed in 3.0. You
* should always check that the module responds to this method before calling it.
*
* module Mod
* def foo(meth, *args, **kw, &block)
Expand Down

0 comments on commit 97eeab3

Please sign in to comment.