rubocop: Lint/RedundantSafeNavigation doesn't account for ActiveSupport core-ext
I just updated to RuboCop 0.93.0. Lint/RedundantSafeNavigation is suggesting an incorrect action.
mileage&.to_s(:delimited)
^^^^^^^^^^^^^^^^^^
RuboCop seems to assume that the & is unnecessary, because nil responds to to_s. The problem is, to_s(:delimited) is a core-ext on Numeric that ActiveSupport adds.
https://github.com/rails/rails/blob/7905bdfd8b2ae50319cd7a9a74ee1f8c865d648d/activesupport/lib/active_support/core_ext/numeric/conversions.rb#L122-L123
>> 123456.to_s(:delimited) #=> "123,456"
>> nil.to_s(:delimited)
Traceback (most recent call last):
2: from (irb):2
1: from (irb):2:in `to_s'
ArgumentError (wrong number of arguments (given 1, expected 0))
Expected behavior
Don’t flag these this line.
Actual behavior
It flagged this line.
Steps to reproduce the problem
This code shouldn’t get flagged.
val = 123_456
val&.to_s(:delimited)
val = nil
val&.to_s(:delimited)
RuboCop version
Include the output of rubocop -V or bundle exec rubocop -V if using Bundler. Here’s an example:
$ [bundle exec] rubocop -V
0.93.0 (using Parser 2.7.2.0, rubocop-ast 0.7.1, running on ruby 2.5.1 x86_64-linux)
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 4
- Comments: 15 (4 by maintainers)
I’m sorry I missed that cop in passing, I would have given my 2 cents: this cop makes no sense to me except in narrow condition: within a condition and with a predicate of
ObjectBesides the
respond_to?, only case I can think of:This example requires the
if(orunless/while/until)I can’t think of other circumstances where this cop could intervene in a meaningful fashion.
FWIW, the current bug report is not dependent on ActiveSupport.
foo&.to_sis perfectly acceptable code and has no correct equivalent (sinceniland""are very different objects in Ruby).My conclusion: let’s delete this cop, or make an allow list (
respond_to?+is_a?+instance_of?) + restrict to conditions and make a bug release ASAP.just my 2c: I love blindly trusting autocorrect. This autocorrect would have caused a bunch of regressions in our code because
niland""(empty string) are different enough.p.s. I love rubocop and I think you are all the coolest.
The more I think about it, the more I don’t understand the purpose of this cop. I don’t understand why it’s safe to assume that
NilClassresponding to a method you call, means you want that method. Even if we were comparing that it’s truly the same method (and not one that happens to have the same name), it seems like a bold leap to say that the&is unnecessary.Does anyone have a real use-case for this cop that isn’t
fooandbar?The comments https://github.com/rubocop-hq/rubocop/blob/db37beaf077ab68880b00f37949ebbfb8852bfed/lib/rubocop/cop/lint/redundant_safe_navigation.rb#L6-L23 and PR https://github.com/rubocop-hq/rubocop/issues/8668 don’t really mention a case outside of
respond_to?.A few more examples where this cop would erroneously flag:
The cop I would like (that I don’t know is possible) is detecting when
&is used on a receiver that can never be nil.@marcandre
I don’t think you have anything to be sorry for. At first glance it looked like a good idea. When it first marked offenses, I thought it was right. But after giving it more thought, I think there’s just too many pitfalls.
I agree. Though, as I mentioned above, it’s 2.19x faster to call
nil&.respond_to?thannil.respond_to?. I feel this cop might encourage people to write less performant code, which I know performance and readability can be tradeoffs at times, I just don’t think the one less&is worth the performance hit. What you guys do is your call, but at this point, I’ll personally just disable it if it doesn’t get removed soon (I haven’t actually accepted the 0.93.0 update PR on my app yet, still running the previous version).Thanks for all you guys do.
I just reread https://github.com/rubocop-hq/rubocop/issues/8668.
What’s interesting as well, is
next unless attrs && attrs.respond_to?(:[])may have actually been a performance optimization. Callingrespond_to?takes time. So, if the value is falsey, the original code would have skipped therespond_to?call all together.The original code was twice as fast.
I guess my point is, “safe” is a bit debatable. Even with the example given
nil&.respond_to?(:to_s)is not the same asnil.respond_to?(:to_s). The first returns nil, the second returns true.I hope I don’t sound ungrateful here. I appreciate this gem. I try to avoid turning off any cops, as I genuinely find them helpful. I’m just trying to understand what some real world examples of this working are.
Basically the goal is anything from Object and BasicObject shouldn’t have a
&in front of it?I think a solution to this could be to see if nil responds to the method, with an arity of the same amount of arguments that are being handed in. Since I’m handing in 1 argument, but
nil.to_sonly allows for 0 arguments, it’s definitely not a safe alternative.Things can get tricky though, because what if
nilDID respond with 1 argument, but:delimitedwasn’t an allowed option forNilClass#to_s(or maybe returned a different value)?The other thing at play here is, what if I don’t want an empty string? What if I either want a stringified number that contains a delimiter or nil? The
&means that I don’t callto_son anilat all – whethernilresponds toto_sis irrelevant, because I only want toto_snon-nils. It feels like RuboCop would be pushing me intoval.to_s.presence, which is a pointless string allocation andpresent?regex check.I’m not sure what to do about this this. I like the intention of this cop for sure. I’ve seen times in our code where people add an unnecessary
&(on methods that don’t returnnilever, for example). I just don’t know how to implement it in a way that guarantees removing the&won’t change the output.