Closed sultur closed 12 months ago
Thank you for the kind words :)
So, I think the issue here is that a function call (rather than a class instantiation) is being passed to the raise
statement, which is not invalid per se, but does strike me as non-idiomatic (FWIW). It's totally fine to call raise MyCustomError
on custom errors, they just have to be classes, per the Python docs:
If an exception class is passed, it will be implicitly instantiated by calling its constructor with no arguments.
In other words, what we'd like to do here is guard against triggering RSE102
on raise foo()
where foo
is a function, not a class.
I agree, the raising from a function example is a bit odd :).
I encountered this in a situation closer to the following example (perhaps a bit less odd? Still raising from a function call though):
class CustomError(Exception):
def __init__(self, msg: str):
self.msg = msg
@staticmethod
def timeout():
return CustomError("Operation timed out!")
raise CustomError.timeout()
These kinds of instantiations of exceptions of course don't trigger RSE102 when they are called with arguments.
But if you have a way of distinguishing function calls from class instantiations with no arguments, then that would of course be the perfect solution!
This is being worked on in #5536.
Great work! Thank you for the quick response and fix. Looking forward to this in upcoming releases :)
Still occurs to me in 0.0.285. Is that expected?
I think the current version only works for definitions and raises
that occur in the same file. Fixing this for multi-file references will be part of a larger project. Can you post a repro for your use-case?
I think the current version only works for definitions and
raises
that occur in the same file. Fixing this for multi-file references will be part of a larger project. Can you post a repro for your use-case?
Indeed, that's the problem.
custom.py
class CustomError(Exception):
def __init__(self, msg: str):
self.msg = msg
@staticmethod
def timeout():
return CustomError("Operation timed out!")
bug.py
from backend.ruff_bug.custom import CustomError
raise CustomError().timeout()
Auto-fix will remove the method call:
bug.py
from ruff_bug.custom import CustomError
raise CustomError().timeout
Raising ctypes.WinError()
also triggers this. There is a code snippet here for example.
Wow, that's a function? That's a rough API. Anyway, we can special-case it.
An example from the python standard library: concurrent.futures.Future.exception()
returns an exception raised by the called function, or None
if no exception was raised.
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as executor:
future = executor.submit(float, "a")
if future.exception():
raise future.exception() # RSE102 Unnecessary parentheses on raised exception
@charliermarsh future.exception()
example given above is part of the standard library and should not be linted.
Can we get a fix, please?
Fixed here: https://github.com/astral-sh/ruff/pull/10206
Hello, thank you for ruff, it's fantastic! I encountered this small bug regarding rule RSE102.
Ruff version:
ruff 0.0.274
Ruff settings: All rules turned on (ignoring a few specific ones, not relevant here).Minimal code snippet:
Commands to reproduce:
Resulting file after autofix:
Issue: The line
raise return_error()
is diagnosed as violating RSE102 (Unecessary parentheses on raised exception), despite the parentheses being necessary. This gets autofixed asraise return_error
, which raises aTypeError
(as the function isn't an exception) instead of aValueError
.Solution idea (I took a quick glance at the changes in https://github.com/astral-sh/ruff/pull/2596, but I'm not well versed enough in Rust to fully understand them.): When autofixing, instead of removing any empty parentheses at the end of
raise ...
lines, only attempt to remove them when they come directly after the name of a built-in exception (e.g.ValueError
,TypeError
,(asyncio.)?CancelledError
, etc.).So lines like
can be autofixed into
but lines like
would require manual fixing.
Maybe you have some more clever solution to this than matching a long list of built-in exceptions, but I hope this helps though!