-
Notifications
You must be signed in to change notification settings - Fork 47
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
[Discussion] Disable Style/RaiseArgs cop #155
base: main
Are you sure you want to change the base?
Conversation
This cop makes it more cumbersome to use custom exception classes that do not use a message as only argument to its constructor, e.g. ``` Class CommandFailure def initialize(command) @command = command super("`#{command}` (pid: #{command.pid}) #{termination_status}") end end raise CommandFailure.new(command_that_failed) ``` According to the cop, I should change the raise call to `raise CommandFailure, command_that_failed` While that works (accidentally), that goes against how `raise` is documented in Ruby: “With no arguments, raises the exception in $! or raises a RuntimeError if $! is nil. With a single String argument, raises a RuntimeError with the string as a message. Otherwise, the first parameter should be the name of an Exception class (or an object that returns an Exception object when sent an exception message). The optional second parameter sets the message associated with the exception, and the third parameter is an array of callback information.” (The reason why you can use exception instance is an exception instance returns itself when you call `#exception` on it.) There are several ways to work around this and appease Rubocop: - Use a keyword argument rather than a position arguments: `CommandFailure.new(command: command_that_failed)` - Introduce a second argument (for which I have no reason): `CommandFailure.new(command, nil)` - Assign the exception to a variable first, and then raise the variable: `raise command_failure` When we force people to use a workaround like this, maybe Rubocop is not the best tool to enforce this.
How about using the |
@gmalette was saying something around |
I don't know if there is a practical difference in MRI, but VMs will typically be able to optimize the |
I don't think we should stop enforcing this rule (or reverse it into the "compact" style) because I would expect any subclass of Having children of I definitely see cases when you want to raise an object that is already initialized, or was initialized in another fashion. For those I would suggest calling |
That's not an expectation that the Ruby's core lib fulfills; some of the core exceptions don't accept strings as the first positional argument.
While
... and that's not a smell? |
@chrisseaton do you know if that is the case for TruffleRuby and JRuby? |
TruffleRuby implements this logic in Ruby: def raise(exc=undefined, msg=undefined, ctx=nil)
internal_raise exc, msg, ctx, false
end
module_function :raise
def internal_raise(exc, msg, ctx, internal)
skip = false
if Primitive.undefined? exc
exc = $!
if exc
skip = true
else
exc = RuntimeError.new ''
end
elsif exc.respond_to? :exception
if Primitive.undefined? msg
exc = exc.exception
else
exc = exc.exception msg
end
raise TypeError, 'exception class/object expected' unless exc.kind_of?(Exception)
elsif exc.kind_of? String
exc = RuntimeError.exception exc
else
raise TypeError, 'exception class/object expected'
end
unless skip
exc.set_context ctx if ctx
exc.capture_backtrace!(2) unless exc.backtrace?
Primitive.exception_set_cause exc, $! unless exc.equal?($!)
end
if $DEBUG
STDERR.puts "Exception: `#{exc.class}' #{caller(2, 1)[0]} - #{exc.message}\n"
end
Primitive.vm_raise_exception exc, internal
end I think the key for this discussion is this bit: elsif exc.respond_to? :exception
if Primitive.undefined? msg
exc = exc.exception
else
exc = exc.exception msg
end
raise TypeError, 'exception class/object expected' unless exc.kind_of?(Exception)
elsif exc.kind_of? String
exc = RuntimeError.exception exc
else So we always check if it if (optionalMessage == null) {
exception = obj.callMethod(context, "exception");
} else {
exception = obj.callMethod(context, "exception", optionalMessage);
} To me, I think I would say that In practice, I see most people write |
This cop makes it more cumbersome to use custom exception classes that do not use a message as only argument to its constructor, e.g.
According to the cop, I should change the raise call to
raise CommandFailure, command_that_failed
While that works (accidentally), that goes against how
raise
is documented in Ruby:(The reason why you can use exception instance is an exception instance returns itself when you call
#exception
on it.)There are several ways to work around this and appease Rubocop:
CommandFailure.new(command: command_that_failed)
CommandFailure.new(command, nil)
raise command_failure
# rubocop:disable Style/RaiseArgs
When we force people to use a workaround like this, maybe Rubocop is not the best tool to enforce this. However, we can also decide that custom exceptions with a single argument constructor are rare enough that a workaround is acceptable.