Skip to content
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

warn_no_return = False: handle implicit "return None" (not ignoring return type) #7511

Closed
blueyed opened this issue Sep 13, 2019 · 23 comments · Fixed by #13219
Closed

warn_no_return = False: handle implicit "return None" (not ignoring return type) #7511

blueyed opened this issue Sep 13, 2019 · 23 comments · Fixed by #13219

Comments

@blueyed
Copy link
Contributor

blueyed commented Sep 13, 2019

warn_no_return is on by default, but does not take into account that None is returned explicitly.

I think it should maybe only warn if the return type does not include None (i.e. uses typing.Optional).

Given t_mypy.py:

import typing


def no_return_optional(arg) -> typing.Optional[str]:
    if arg:
        return "false"


def no_return(arg) -> str:
    if arg:
        return "false"


a1 = no_return_optional(True)
a2 = no_return_optional(False)
reveal_type(a1)
reveal_type(a2)

b1 = no_return(True)
b2 = no_return(False)
reveal_type(b1)
reveal_type(b2)

mypy --warn-no-return shows:

t_mypy.py:4: error: Missing return statement
t_mypy.py:9: error: Missing return statement
t_mypy.py:16: note: Revealed type is 'Union[builtins.str, None]'
t_mypy.py:17: note: Revealed type is 'Union[builtins.str, None]'
t_mypy.py:21: note: Revealed type is 'builtins.str'
t_mypy.py:22: note: Revealed type is 'builtins.str'
Found 2 errors in 1 file (checked 1 source file)

mypy --no-warn-no-return does not show any error, but should warn/error about the missing return statement / wrong return value for no_return (which is annotated to return str, but might return None).

mypy 0.730+dev.28423cd78062b81365ec9aaa591b4e823af1606d

@JukkaL
Copy link
Collaborator

JukkaL commented Sep 16, 2019

This is by design. warn_no_return excepts an explicit return statement even in functions that return an optional type. The assumption is that relying on the implicit None return is often an error (or at least a code smell).

When the check is disabled, mypy effectively behaves as if there Python had no implicit None return. I can see how this can be surprising, and I may be persuaded that this is not the best way to implement this.

At the very least we could make the documentation more detailed.

@blueyed blueyed changed the title warn_no_return: do not warn with Optional return type warn_no_return = False: handle implicit "return None" (not ignoring return type) Nov 26, 2019
@blueyed
Copy link
Contributor Author

blueyed commented Nov 26, 2019

When the check is disabled, mypy effectively behaves as if there Python had
no implicit None return. I can see how this can be surprising, and I may be
persuaded that this is not the best way to implement this.

The main problem I see here is that you are forced to use the default
(warn_no_return = True), since otherwise you are not getting an error with:

def f(a) -> int:
    if a:
        return 1

Disabling the warning is useful/necessary to not change code unnecessarily
when adding type annotations, but currently you are forced to add return None at the end always despite of the setting to not have errors go silent
(btw: return by itself results in "Return value expected").

Therefore I think it would be great if the behavior in this regard could be
changed (as if return None was used), since otherwise disabling the warning
also hides real errors silently.
This would affect the non-default part of the option.

While I'd still prefer to have Optional[…] not cause a warning without an explicit return None by default, I think the above is more important for now, and have changed/clarified the issue title in this regard.

Docs for reference: https://mypy.readthedocs.io/en/latest/command_line.html#cmdoption-mypy-no-warn-no-return

@ilevkivskyi
Copy link
Member

FWIW, I am fine with the current behaviour -- "explicit is better than implicit". The error message and the docs however can be improved.

@blueyed
Copy link
Contributor Author

blueyed commented Nov 26, 2019

@ilevkivskyi
Don't you agree that mypy not showing an error with the above example (with warn_no_return = False) is wrong / hiding the error?

@ilevkivskyi
Copy link
Member

I don't see a problem if someone opts-in to unsafety and gets an unsafety. (Also this is my final opinion here, I don't have time to write an essay about why/what/etc.)

@blueyed
Copy link
Contributor Author

blueyed commented Nov 29, 2019

Ok, fair enough - I do not want to waste anyone's time of course.
But just to be clear - the option is not named ignore_errors_with_no_return, i.e. it is not clear but surprising that it behaves that way.
I also think that mypy ignoring the fact that Python has implicit returns is a design decision to be revisited, and that should be done before adding a warning to the docs then probably.

@ofek
Copy link

ofek commented Jun 1, 2020

I also think that mypy ignoring the fact that Python has implicit returns is a design decision to be revisited

I agree wholeheartedly. From #3974 (comment):

This is a style issue

Indeed, Mypy is overreaching here and enforcing a stylistic choice at the cost of correct language semantics.

This is the only reason I'm not using Mypy 😢

@JukkaL
Copy link
Collaborator

JukkaL commented Jun 8, 2020

The most likely way around this that I can see is adding support for enabling/disabling individual error codes, and giving the relevant error message a specific error code that can be disabled (which would hide all these messages). Now anybody who doesn't like this error can disable the relevant error code.

I'm happy to accept a PR that implements enabling/disabling error codes (we should first discuss how this would be configured in a separate issue).

@ofek
Copy link

ofek commented Jun 9, 2020

@JukkaL Thanks! I opened #8975

@WhyNotHugo
Copy link

There's actually a few scenarios relevant here:

Scenario 1

def f1(a) -> int:
    if a:
        return 1
  • Currently, this fails with error: Missing return statement.
  • This makes sense, the function is missing a return.

Scenario 2

def f2(a) -> Optional[int]:
    if a:
        return 1
  • Currently, this fails with: error: Missing return statement.
  • This code is fine, it explicitly states that it returns None.

Scenario 3

def f3(a) -> Optional[int]:
    if a:
        return 1
    return None
  • This one is doubly explicit, and passes fine.

Scenario 4

def f4(a) -> None:
    pass
  • This looks like scenario 2 (None returned implicitly).
  • However, this one passes checks fine.

@gvanrossum
Copy link
Member

  • Scenario 4 is because of a mypy exception for functions with a one-line body. These are used as stubs and we found usability issues around this in real-world code. Arguably this behavior should not be the default (except for stub files), and we could introduce a flag to enable it.

  • The error you get in Scenario 2 enforces a PEP 8 rule that is dear to my heart -- if you return a value, you must explicitly return a value on all code paths. Even if that value has an Optional type. The quickest way to avoid the error is to add return None at the end. If you don't like that, maybe use --no-warn-no-return.

@WhyNotHugo
Copy link

My issue here is that I have to be explicit twice. I'm more of the DRY mentality:

Initial code:

def f2(a) -> int:
    if a:
        return 1

Be explicit about the return value possibly being None:

def f2(a) -> Optional[int]:
    if a:
        return 1

Be explicit about the return value possibly being None again:

def f2(a) -> Optional[int]:
    if a:
        return 1
    return None

It's very annoying that 1/3 of my function is now just redundant code that does nothing. It just hurts readability. It was obvious from the start it could return None. I can respect having to be explicit about it by adding Optional. Having to be explicit about it twice it just noise.

@gvanrossum
Copy link
Member

Whatever.

@ofek
Copy link

ofek commented Sep 9, 2020

Note there is now support for enabling/disabling error codes #9172 which will be released soon #9290

I'm not sure what this code would be though

@dimaqq
Copy link

dimaqq commented Nov 30, 2020

While I agree in general with the Zen of Python,
I feel that warning in this particular case represents feature creep for mypy.

Specifically, mypy is a type checker, and should (in my opinion) allow whatever silly, broken or ugly code someone may write, as long as types are correct. It's a job for other tools to enforce PEP-8 or local style conventions.

@PedanticHacker
Copy link

I once read a beautiful code styling principle on some forum. It goes like this:
I hate code. I want as little of it as possible.

Reading this affected my code styling in a very profound way since then.

@svet-b
Copy link

svet-b commented Jul 5, 2021

I had a quick go at using the new return codes functionality (#8975) to get the behavior requested by OP. I.e. to accept an implicit return None for a function returning an Optional type, but throw an error if the function needs to return a type that is not Optional.

Unfortunately it seems that both situations result in the same return error code. In other words, setting --disable-error-code return simply replicates the behavior of --no-warn-no-return, which takes us back to the original issue. Has anyone else figured out a better approach here, i.e. one which which distinguishes between a valid implicit return and an invalid one? I suppose that more granular error codes need to be implemented in order for this to be resolved?

@PedanticHacker
Copy link

If your code works and you are confident about it, although mypy is nagging you about not having an explicit return statement in a function/method, just ignore mypy. What mypy is giving you are only suggestions (which you can ignore); it's not like mypy is giving you messages about critical security vulnerabilities and you must make your code mypy compliant and follow its suggestions to the teeth or else you will die on the spot. Having your code mypy-friendly is just like having your code PEP8-friendly. Who among you follow PEP8 to the teeth? Just have code that you are happy with and that it does exactly what you need it to do.

@svet-b
Copy link

svet-b commented Jul 5, 2021

@PedanticHacker that's a reasonable approach in principle, though not always implementable in practice. Specifically, we're making a push to integrate mypy checks into our CI/CD pipelines. Which means that the mypy checks need to pass, otherwise "computer says no" and the code doesn't get deployed.

Just like others who commented above, we'd love to have a way for mypy to check and enforce typing, without enforcing style choices. We experimented with adding explicit returns everywhere simply to satisfy mypy, but most members of our dev team were ultimately not keen on them. Anyway, that's neither here nor there. At this point the best solution we can hope for would be to have the above-mentioned granularity in the error codes. Based on the docs it's not currently there.

For the time being we've added --disable-error-code return to our CI tests (#YOLO). Finally, for anyone looking for a workaround of sorts - the type-checker built into Pylance (VSCode's linter, which I think is basically pyright) doesn't mind implicit returns where allowed, but does throw errors if the return type isn't Optional.

@PedanticHacker
Copy link

I understand.

@gwerbin
Copy link
Contributor

gwerbin commented Sep 10, 2021

Normally I'm in agreement with GvR on this. I find that implicitly returning None leads to confusion and extra mental burden, more often than it leads to cleanliness or elegance.

However, I can think of one exception:

import sys

def main() -> Optional[int]:
    if something_bad:
        return 1
    print('ok :)')

if __name__ == '__main__':
    sys.exit(main())

In this case, it would be nice to selectively recognize the implicit return None, without disabling type checking of the return value. Mostly for aesthetic reasons, including symmetry with the behavior of C 99 main() (you can omit the return 0).

kovidgoyal added a commit to kovidgoyal/kitty that referenced this issue Oct 26, 2021
Its a shame GvR is married to "return None"
python/mypy#7511
@0xfede7c8
Copy link

0xfede7c8 commented Jun 3, 2022

Hi,
so, is this fixed?
Is there a way of ONLY disabling implicit None returns but forcing every other return check?

I believe this must be fixed because if the semantics of the language are that function/methods which don't return anything return None by default, then my types are correct even if I avoid explicit None returns.

I understand that mypy must check all paths to make sure to comply with types, hence it is correct to warn when a path is missing a return statement. But in the None case, the return statment is there but hidden.

To me it's clearly a Mypy error (or feature creep) just because of this logic and I don't think it can be denied.

Thanks

@hauntsaninja
Copy link
Collaborator

hauntsaninja commented Jul 23, 2022

I'm surprised that I'm doing this, but here's the fix #13219 . Enjoy your --no-warn-no-returns, folks!

Julian added a commit to python-jsonschema/referencing that referenced this issue Jan 5, 2023
See python/mypy#7511 though it seems this isn't fully
fixed, as it still warns for returns with no explicit
return None...
hauntsaninja added a commit to hauntsaninja/mypy that referenced this issue Sep 13, 2023
As discussed in python#7511, mypy's
`--warn-no-return` isn't really a question of type safety. It does
however enforce a rule that is "dear to Guido's heart", and I think we
should enforce it for functions that explicitly return Any as well.

Fixes python#16095
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.