Skip to content
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

strict_keyword_argument_matching warning when ruby turns keyword arguments into a positional hash #593

Open
seandilda opened this issue Dec 13, 2022 · 7 comments

Comments

@seandilda
Copy link

seandilda commented Dec 13, 2022

The current implementation of strict_keyword_argument_matching causes a deprecation warning with the following code (tested with mocha 2.0.2 on ruby 3.1.3):

class Example
  def foo(options_hash); end
end

example = Example.new
example.expects(:foo).with({ bar: 'bar' }).twice
# This first call works as expected
example.foo({ bar: 'bar' })
# This second call is equivilant to the first, but triggers a deprecation warning
example.foo(bar: 'bar')

According to https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ is perfectly valid in ruby 3+

Note that Ruby 3.0 doesn’t behave differently when calling a method which doesn’t accept keyword arguments with keyword arguments. For instance, the following case is not going to be deprecated and will keep working in Ruby 3.0. The keyword arguments are still treated as a positional Hash argument.

def foo(kwargs = {})
 kwargs
end

foo(k: 1) #=> {:k=>1}

This is because this style is used very frequently, and there is no ambiguity in how the argument should be treated. Prohibiting this conversion would result in additional incompatibility for little benefit.

I'd like to request that the implementation of strict_keyword_argument_matching be adjusted to allow this case.
The current implementation is requiring the test case and application code match functionally and stylistically. I believe the test framework should allow the application code to change stylistically without requiring a change in the test case.

@floehopper
Copy link
Member

@seandilda

Thanks for opening this issue. Can you clarify things a bit further...

  1. Are you saying that you don't think a deprecation warning should be displayed in this case?

  2. Or are you saying that you want the matching to work in both examples even when strict keyword matching is enabled?

Or both 1 & 2?

@seandilda
Copy link
Author

Thank you for the quick response.

I'm asking for both 1 & 2.

@floehopper
Copy link
Member

@seandilda Thanks for explaining. I'm sorry I haven't got back to you sooner - I haven't had a lot of spare time recently. However, I have done some more thinking about it and explored some possible solutions. Unfortunately I don't think it's something I can fix quickly - it's going to take me some time to work out the best approach. I hope to be able to devote a chunk of time to it over the next couple of weeks. Thanks for your patience.

@floehopper
Copy link
Member

floehopper commented Dec 27, 2022

There's a bit of ambiguity in the "What is deprecated" section of the release notes which @seandilda linked to. The "The spec of keyword arguments is changed towards 3.0" section of "NEWS for Ruby 2.7.0" and/or the "Arguments" section of "Calling Methods" might be more useful.

Anyway I think we're going to need to work out whether the stubbed method accepts keyword arguments or not. The example that @seandilda has given is when the user is using a partial mock and in that case we ought to be able to inspect the original method to determine whether it accepts keyword arguments. While it might be possible to do something similar for full mocks, I suspect it will be harder and (more significantly) there may be some ambiguity. So I'm going to say that's out-of-scope for now.

There's not currently any precedent in Mocha for behaviour which depends on the original method signature other than checking whether the method exists or checking whether the method is public. Having said that some relevant work has been done in relation to #149 regarding checking that the arguments used in the invocation of the stubbed method are supported by the original method. It would be worth looking through that work before attempting to fix this issue.

In order to determine whether a method accepts keyword arguments, I think we need to check that Method#parameters does not return any tuples where the first element is :keyreq, :key, or :keyrest.

@floehopper
Copy link
Member

While it might be possible to do something similar for full mocks, I suspect it will be harder and (more significantly) there may be some ambiguity. So I'm going to say that's out-of-scope for now.

I've done a bit more work on #532 and I'm now hopeful that we might be able to use the "responder" functionality to work out whether the stubbed method accepts keyword arguments or not. This would mean the solution would work for full mocks that have a responder as well as for partial mocks.

floehopper added a commit that referenced this issue Dec 31, 2022
When a method doesn't accept keyword arguments.

In this scenario keyword or Hash-type arguments are assigned as a single
Hash to the last argument without any warnings and strict keyword
matching should not have any effect.

This is an exploratory spike on fixing #593.

* This has highlighted a significant problem with partial mocks in #532.
  The method obtained from the responder is the stub method defined by
  Mocha and not the original. This effectively makes it useless!

* I'm not sure the method_accepts_keyword_arguments? belongs on
  Invocation, but that's the most convenient place for now. It feels as
  if we need to have a bit of a sort out of where various things live
  and perhaps introduce some new classes to make things clearer.

* We might want to think ahead a bit at what we want to do in #149 to
  decide the best way to go about this.

* I'm not sure it's sensible to re-use the Equals matcher; we could
  instead parameterize PositionalOrKeywordHash, although the logic in
  there is already quite complex. Conversely if this is a good approach,
  it might make more sense to do something similar when creating a hash
  matcher for a non-last parameter to further simplify the code.

* I haven't yet introduced any acceptance tests for this and I suspect
  there might be some edge cases yet to come out of the woodwork. In
  particular, I think it's worth exhaustively working through the
  various references mentioned in this comment [1].

[1]: #593 (comment)
tagliala added a commit to tagliala/inherited_resources that referenced this issue Oct 29, 2023
Deprecation warnings are incorrect because there is a misunderstanding
around the Ruby keyword arguments change in 3.0.

Ref: freerange/mocha#593

Close activeadmin#876
tagliala added a commit to activeadmin/inherited_resources that referenced this issue Oct 29, 2023
* Add warning gem

* Silence Mocha warnings

Deprecation warnings are incorrect because there is a misunderstanding
around the Ruby keyword arguments change in 3.0.

Ref: freerange/mocha#593

Close #876
@sarahsehr
Copy link

Hi @floehopper!

I was wondering if there is an update on this issue? We're running into this in hundreds of places, and I'm a little worried we'll be "fixing" these false positives only to have to correct them again.

Thanks!

@floehopper
Copy link
Member

Hi @sarahsehr. It looks like I ran into a bit of a blocker here and ran out of steam. Without financial support it's hard for me to find bigger chunks of time to work on issues like this. Having said that, thanks for telling me about the problem it's causing for you - that gives me some motivation in itself - I'll do my best to find some time in between client projects to work on it, but I can't guarantee anything.

techguru321 added a commit to techguru321/inherited_resources that referenced this issue Dec 23, 2023
* Add warning gem

* Silence Mocha warnings

Deprecation warnings are incorrect because there is a misunderstanding
around the Ruby keyword arguments change in 3.0.

Ref: freerange/mocha#593

Close #876
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants