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
Enable typing in Cask::Artifact #14769
Conversation
Review period will end on 2023-02-23 at 17:36:21 UTC. |
@@ -73,7 +75,7 @@ def dispatch_uninstall_directive(directive_sym, **options) | |||
|
|||
args = directives[directive_sym] | |||
|
|||
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options) | |||
send("uninstall_#{directive_sym}", (args.is_a?(Enumerable) ? args : [args]), **options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This previously converted args to a splat, in order to subsequently call a method that accepts splat arguments. I've cut out the middleman, in order to enable typing, although I think it also makes for slightly more understandable code.
The methods below are all private, which ofc doesn't guarantee that changing the method signature is safe, so this proposed change deserves a critical eye.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't Hash
also an Enumerable
, i.e. would now be just args
instead of [args]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@reitermarkus Great question! When it was a Hash, it would be wrapped as an array, then splatted back into a hash arg. Since we've removed the *
, we no longer want to wrap hashes as arrays.
This might help explain things:
def foo(args, c: nil)
args.class
end
argv = 'str'
foo(*argv)
# => String
argv = ['str']
foo(*argv)
# => String
argv = {a: 1, b: 2}
foo(*argv)
# => ArgumentError: wrong number of arguments (given 2, expected 1)
foo(*[argv])
# => Hash
Ruby will convert a hash into kwargs when preceded by *
, leading to errors in methods that take a mix of positional and kwargs (iirc without the kwargs it will pass the hash as the first poisitional arg). You can try it for yourself by changing Enumerable
to Array
above, and running brew tests --only=cask/artifact/uninstall
.
(I believe this also relates to why sorbet has a hard time supporting splats, but my memory is hazy here)
I generally find this all pretty hard to wrap my head around, so I try to avoid splats regardless of Sorbet's stance 🤕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, we had e.g.
def uninstall_something(*args)
args
end
and passing a Hash
would look like
uninstall_something(*[{a: 1}]) == [{a: 1}]
while now we have
def uninstall_something(args)
args
end
and passing a Hash
, since it is Enumerable
, we effectively have
uninstall_something({a: 1}) == {a: 1}
which isn't the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so the only method we actually pass a Hash
to is uninstall_script
, which was not using a splat to begin with, so passing both *[{a: 1}]
and {a: 1}
still behave the same for that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks again @dduugg!
@@ -73,7 +75,7 @@ def dispatch_uninstall_directive(directive_sym, **options) | |||
|
|||
args = directives[directive_sym] | |||
|
|||
send("uninstall_#{directive_sym}", *(args.is_a?(Hash) ? [args] : args), **options) | |||
send("uninstall_#{directive_sym}", (args.is_a?(Enumerable) ? args : [args]), **options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so the only method we actually pass a Hash
to is uninstall_script
, which was not using a splat to begin with, so passing both *[{a: 1}]
and {a: 1}
still behave the same for that case.
Thanks again @dduugg! |
Review period ended. |
brew style
with your changes locally?brew typecheck
with your changes locally?brew tests
with your changes locally?Enables typing in
Cask::Artifact
. In this PR, i've introduced a new util module,Utils::Splat
, which allows a file to be typed by wrapping splat calls to Ruby core/std library methods. Initially, it has a single method, aProcess.kill
wrapper. It's admittedly a potentially clumsy abstraction – bringing together a bunch of methods whose only unifying factor is accepting splat arguments, but we could refactor it as necessary (though I think it will mostly be lower-level CLI-wrapped methods likesystem
).