-
-
Notifications
You must be signed in to change notification settings - Fork 158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Work out whether any changes are needed for positional/keyword args in Ruby 3.0 #446
Comments
I'm having trouble matching keyword arguments precisely in Ruby 3. EDIT: removed the original example in favour of #446 (comment), which I think explains this better. I don't think #475 will resolve this, since the
|
@wasabigeek Thanks for reporting this. I'm on holiday at the moment, so I might not be able to look at this properly for a while. Just to make sure I understand, can you confirm that the main issue is that the stubbed version of If that is the case, then I suspect we'll need to make use of |
More precisely, I'm trying to set a Sorry I wasn't clearer before, finding it hard to explain this 🙇 Perhaps another example: require "minitest/autorun"
require 'mocha/minitest'
class Example
def foo(arg, kwarg:)
end
end
class Test < Minitest::Test
def test_arguments
example = Example.new
arg = "test1"
hash = { kwarg: "test2" }
example.expects(:foo).with(arg, **hash).twice
# This is the correct form, splatting the hash as kwargs.
# This passes.
example.foo(arg, **hash)
# This is an incorrect form, as in Ruby 3 the hash is not interpreted as kwargs (see example error later).
# However, this also passes! So this code might have made it into production.
# I would have expected this to have been caught by the `with` expectation.
example.foo(arg, hash)
# To illustrate the actual error
example.unstub(:foo)
example.foo(arg, hash) # ArgumentError: wrong number of arguments (given 2, expected 1; required keyword: kwarg)
end
end TIL class Invocation
def initialize(mock, method_name, *arguments, &block)
# both the following would give the same `arguments`, when in the first, it could have been a positional hash argument
# Invocation.new(mock, method_name, "arg1", { kwarg: "test" })
# Invocation.new(mock, method_name, "arg1", kwarg: "test")
...
end
end We could try converting references for def example(_arg, options = {})
p options
end
def example_method_missing(*args, **kwargs)
p [args, kwargs]
end
# This matches the signature exactly, and prints the correct options.
example("", {test: 2}) # {:test=>2}
# if we use method missing with this style, it interprets the hash as a positional argument, as expected.
example_method_missing("", {test: 2}) # [["", {:test=>2}], {}]
# However, Ruby 3 still allows this, where the trailing kwargs gets treated as a positional param:
example("", test: 2) # {:test=>2}
# In this scenario, the trailing kwargs get interpreted as kwargs, instead of a positional param, which is different from above:
example_method_missing("", test: 2) # [[""], {:test=>2}] I've an ugly patch that enables more precise keyword matching by swapping |
@floehopper wondering if you've any thoughts on how to address this? Would love to help where possible. Thought: we could replace |
@wasabigeek Hi. Thanks for your patience on this. I'm afraid I haven't got any time right now, but it might be worth you having a look at the changes in #475 to see if they help. Let me know and I'll try to find some time to think about this more carefully soon! 🤞 |
Actually, #149 which I've been working recently on might be relevant too. |
Thanks @floehopper! I took a look, but I don't think the issue would be fully addressed. The particular concern I have is that keyword matching is imprecise and may cause a false negative (test passes when it fails in real life). From what I recall, there are two parts to the problem:
For (1), I think # First Case
example('a', bar: 'b')
# => nil
# Second Case
example('a', { bar: 'b' })
# in `example': wrong number of arguments (given 2, expected 1; required keyword: bar) (ArgumentError) Above, if mocking |
This is a somewhat related issue in RSpec rspec/rspec-support#522 (comment) with an attempted fix rspec/rspec-support#537. EDIT: Looking at rspec/rspec-mocks#1394, I think it's possible we can tweak |
@floehopper I've attempted a proof of concept for checking keyword args more strictly via |
@wasabigeek Thanks so much for continuing to work on this. I've just had some time to read this issue through more carefully and I think I'm slowly getting my head around it. I'll have a more careful look at #534 now. Can I just double-check the latter is only intended to address point (2) in your earlier comment, i.e. keyword matching using |
Yes, it only addresses (2)! At first, I thought (1) might be a prerequisite, but it seems it's not. |
Tracking some todos now that Ruby 1.9 support is dropped:
The above follows the template laid out in this spike: #544 |
@wasabigeek This makes sense to me, although I'm starting to think I might like to combine all these changes into a single PR at least when I come to merge all the changes. I'll give it some more thought... |
I'm happy to combine it! That was a thought that went through my head. I'll work on that this week. |
When the strict keyword argument option is enabled, an expectation expecting keyword arguments (via Expectation#with) will no longer match an invocation passing a positional Hash argument. Without this option, positional hash and keyword arguments are treated the same during comparison, which can lead to false negatives in Ruby >= v3.0 (see examples below). For more details on keyword arguments in Ruby v3, refer to this article [1]. Note that Hash matchers such as has_value or has_key will still treat positional hash and keyword arguments the same, so false negatives are still possible when they are used. Closes #446. See also #535 & #544 for discussions relating to this change. [1]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0
When the strict keyword argument option is enabled, an expectation expecting keyword arguments (via Expectation#with) will no longer match an invocation passing a positional Hash argument. Without this option, positional hash and keyword arguments are treated the same during comparison, which can lead to false negatives in Ruby >= v3.0 (see examples below). For more details on keyword arguments in Ruby v3, refer to this article [1]. Note that Hash matchers such as has_value or has_key will still treat positional hash and keyword arguments the same, so false negatives are still possible when they are used. Closes #446. See also #535 & #544 for discussions relating to this change. [1]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0
When the strict keyword argument option is enabled, an expectation expecting keyword arguments (via Expectation#with) will no longer match an invocation passing a positional Hash argument. Without this option, positional hash and keyword arguments are treated the same during comparison, which can lead to false negatives in Ruby >= v3.0 (see examples below). For more details on keyword arguments in Ruby v3, refer to this article [1]. Note that Hash matchers such as has_value or has_key will still treat positional hash and keyword arguments the same, so false negatives are still possible when they are used. Closes #446. See also #535 & #544 for discussions relating to this change. [1]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0
When the strict keyword argument option is enabled, an expectation expecting keyword arguments (via Expectation#with) will no longer match an invocation passing a positional Hash argument. Without this option, positional hash and keyword arguments are treated the same during comparison, which can lead to false negatives in Ruby >= v3.0 (see examples below). For more details on keyword arguments in Ruby v3, refer to this article [1]. Note that Hash matchers such as has_value or has_key will still treat positional hash and keyword arguments the same, so false negatives are still possible when they are used. Closes #446. See also #535 & #544 for discussions relating to this change. [1]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0
Closes #562. This introduces a new `strict_keyword_argument_matching` configuration option. This option is only available in Ruby >= v2.7 and is disabled by default to enable gradual adoption. When the strict keyword argument option is enabled, an expectation expecting keyword arguments (via `Expectation#with`) will no longer match an invocation passing a positional Hash argument. Without this option, positional hash and keyword arguments are treated the same during comparison, which can lead to false negatives in Ruby >= v3.0 (see examples below). * Loose keyword argument matching (default) class Example def foo(a, bar:); end end example = Example.new example.expects(:foo).with('a', bar: 'b') example.foo('a', { bar: 'b' }) # This passes the test, but would result in an ArgumentError in practice * Strict keyword argument matching Mocha.configure do |c| c.strict_keyword_argument_matching = true end class Example def foo(a, bar:); end end example = Example.new example.expects(:foo).with('a', bar: 'b') example.foo('a', { bar: 'b' }) # This now fails as expected For more details on keyword arguments in Ruby v3, refer to this article [1]. Note that Hash matchers such as `has_value` or `has_key` will still treat positional hash and keyword arguments the same, so false negatives are still possible when they are used. Closes #446. See also #535 & #544 for discussions relating to this change. [1]: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0 Co-authored-by: Nicholas Koh <[email protected]>
See: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
The text was updated successfully, but these errors were encountered: