Open go-to-mjrecent-on-TG opened 3 days ago
Thanks for bringing this up!
It appears the decision to skip the lint when the return annotation is present and not None
was made in the resolution of this issue, for compatibility with mypy
.
We should certainly update the documentation to reflect this. But if there is also some interest from the community in revisiting this decision, or perhaps even making it configurable, it'd be nice to hear more about what's most helpful here!
Ruff's current behavior seems correct to me. The return isn't useless. It's just that the returned value doesn't match the annotated type. But that's more a type checker concern than a concern of the useless return rule.
Thanks for bringing this up!
It appears the decision to skip the lint when the return annotation is present and not
None
was made in the resolution of this issue, for compatibility withmypy
.We should certainly update the documentation to reflect this. But if there is also some interest from the community in revisiting this decision, or perhaps even making it configurable, it'd be nice to hear more about what's most helpful here!
Is there currently any way of making ruff complain about such returns? If not, I would be happy if that was made possible. So revise the decision or add the other rule for this, please.
Thank you for ruff!
Ruff's current behavior seems correct to me. The return isn't useless. It's just that the returned value doesn't match the annotated type. But that's more a type checker concern than a concern of the useless return rule.
How are such returns not useless? What do you mean? So you think that this should be written in docs:
Checks for functions that end with an unnecessary return or return None, and contain no other return statements. Why is this bad?
Python implicitly assumes a None return at the end of a function, making it unnecessary to explicitly write return None. Example
def f(): print(5) return None
Use instead:
def f(): print(5)
But ruff should not flag this?:
def f() -> ...:
print()
return None
I don't mind extending the documentation.
How are such returns not useless? What do you mean?
The return in the given example is incorrect: It returns None
instead of a number. But it isn't that the return is useless. Instead, the user should either:
None
or remove it entirely. Ruff then flags the return statementTo my knowledge, there's currently no rule to catch this because catching that the returned value None
is incomaptible (not assignable to) with int
is something that type checkers do. We plan to add support for a rule that catches the incorrect return with our type checker red knot, but that's still far out.
I don't know, I'm not sure if I'm fully on board with this interpretation. From my point of view, there are two separate things happening:
return None
or return
vs the equivalent implicit return of None.I think (1) is a question of style and (2) is a question for a type checker. It makes sense to me that one would want to enforce (1) uniformly in a project as a lint rule (really either way - to always have explicit returns or always implicit returns of None - just a style preference), and then separately have a type checker complain about the values being returned from your function and what type hint you used.
Sorry for being difficult! I'm fine leaving it as is but just wanted to share this perspective.
That interpretation makes sense to me. But to me, showing both PLR1711 and a violation that the returned value doesn't match the annotated type is confusing for users using a type checker. It's provided fix is may also be incorrect, depending on the situations. That's why I think it's better to not raise the violation at all.
So I would answer yes to the question whether it should there should be an option to make ruff complain about such returns:
def f() -> {not None}:
...
return None
Because it might very well be that somebody wants this code to be valid:
# returns some int
def f() -> int: ...
exec('redefine f. example: def f(): return 0')
r = f()
assert isinstance(r, int)
print(r)
but get complains about the same code with:
def f() -> int:
...
return None
or with
def f() -> int:
"""docs in the signature function, description..."""
return None
Actually, ruff doesnt complain even about the following now (so does pylint):
def f():
"""docs in the signature function, description..."""
return None
I myself didn't know that functions with docstring and without return at the end are valid. And I can imagine how I could have ended up not knowing about it and working with some other developers who know that this is valid, use ruff, share the ruff config with their teammates, and want ruff to tell newbies that they are fine with:
def f():
"""docs in the signature function, description..."""
I think there is an underlying assumption in applying static program analysis to a highly dynamic language like Python that behavior involving exec
/eval
can't really be detected. (But someone should correct me if I'm wrong). Basically the best that can be done in these cases is detect the use of exec
/eval
and warn against it: S102, S307
I think there is an underlying assumption in applying static program analysis to a highly dynamic language like Python that behavior involving
exec
/eval
can't really be detected. (But someone should correct me if I'm wrong). Basically the best that can be done in these cases is detect the use ofexec
/eval
and warn against it: S102, S307
What do I do if I want to allow exec and eval, and allow the definition of function signatures, but also get warnings when there is some return None that is not needed there?
Yes I think that's a fair question (and orthogonal to the use of eval
/exec
). That's I think the core remaining issue here - deciding whether to not support this, support it as a configurable variant of the rule, or support it as a separate rule. Might take some further discussion and consideration. Thank you for the examples and clarifications!
Examples of 'real world' code that contains useless return, but that ruff doesn't complain about (with abstractmethod, not exec or eval):
from typing import Any
from abc import ABC, abstractmethod
class DataProcessor(ABC):
@abstractmethod
def process(self) -> Any:
...
return None
from abc import ABC, abstractmethod
class DataProcessor(ABC):
@abstractmethod
def process(self):
"""processes the data"""
return None
Today, I went to Ruff playground, enabled PLR1711 (useless-return) rule and pasted this code:
In Diagnostics tab I saw only
Everything is looking good!
. I then removed-> int
for the code to be:and saw
Useless `return` statement at end of function (PLR1711) [Ln 3, Col 5]
. Ruff complained in the same way when I annotated function to return None:All the same goes for cases with
return
instead ofreturn None
. Is this an intended behavior? If it is, I think the documentation on this rule should inform about this.I have checked how pylint handles such cases by running cell with the following in fresh google colab runtime:
I got
How can I get ruff to complain about useless return statements in cases when function is not annotated to return None? You can also help on StackOverflow