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

Incorrent "Missing return statement" in presence of bad combination of decorated function call, definition order and NoReturn #8129

Closed
jstasiak opened this issue Dec 10, 2019 · 2 comments · Fixed by #13575
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal

Comments

@jstasiak
Copy link
Contributor

Code to reproduce:

from typing import Callable, NoReturn, TypeVar


def no_return() -> NoReturn:
    pass


def use_decorated_before_defined() -> int:
    decorated()
    if bool():
        return 0
    else:
        no_return()


T = TypeVar('T')


def wrapper(function: T) -> T:
    return function


def decorator() -> Callable[[T], T]:
    return wrapper


@decorator()
def decorated() -> None: pass


def use_decorated_after_defined() -> int:
    decorated()
    if bool():
        return 0
    else:
        no_return()


def use_wrapped_before_defined() -> int:
    wrapped()
    if bool():
        return 0
    else:
        no_return()


@wrapper
def wrapped() -> None: pass

I tested this both with mypy 0.750 and one from the current master branch (526a218), running on CPython 3.7.5. The command line and the output:

% python -m mypy repro.py
repro.py:8: error: Missing return statement
Found 1 error in 1 file (checked 1 source file)

I'd expect no errors here. After some digging in mypy source code I believe use_decorated_before_defined can't be fully processed when it's type-checked (at least the first time it's entered) because of a) the call to decorated being looked at before decorated is defined and b) decorated being decorated (pardon my repetition) with a result of another function call which I suspect creates a difficult environment for mypy to deduce things and it defers further processing of use_decorated_before_defined (or at least part of it).

Then, when the call to no_return is encountered its type is detected as Any because of the processing being deferred per above, hence the error.

I left two similar variants of the function (use_decorated_after_defined and use_wrapped_before_defined) to help narrow things down as those don't trigger the error.

@msullivan
Copy link
Collaborator

Yeah, this seems legit and somewhat hairy. I suspect you are right and the issue is that the node is being deferred.

I think that this can be fixed by adding a check for if the node is deferred, though that feels ugly.

@LEdoian
Copy link

LEdoian commented Aug 12, 2022

Shouldn't the title of this issue be made more generic, since issue #8401 is a duplicate of this, but does match the title?

I have an issue similar to #8401 (NoReturn being confused by code being run before definition) but am not sure whether to post here or create separate issue.

Michael0x2a added a commit to Michael0x2a/mypy that referenced this issue Sep 1, 2022
This diff:

- Fixes python#8129
- Fixes python#13043
- Fixes python#13167

For more concise repros of these various issues, see the modified
test files. But in short, there were two broad categories of errors:

1.  Within the deferred pass, we tend to infer 'Any' for the types of
    different variables instead of the actual type. This interacts
    badly with our unreachable and disallow-any checks and causes
    spurious errors.

    Arguably, the better way of handling this error is to only collect
    errors during the final pass. I briefly experimented with this
    approach, but was unable to find a clean, efficient, and
    non-disruptive way of implementing this. So, I settled for
    sprinkling in a few more `not self.current_node_deferred` checks.

2.  The 'self.msg.disallowed_any_type(...)` call is normally guarded
    behind a `not self.chk.current_node_deferred` check. However, we
    were bypassing this check for `except` block assignments because
    we were deliberately setting that flag to False to work around some
    bug. For more context, see python#2290.

    It appears we no longer need this patch anymore. I'm not entirely
    sure why, but I'm guessing we tightened and fixed the underlying
    problem with deferred passes some time during the past half-decade.
Michael0x2a added a commit to Michael0x2a/mypy that referenced this issue Sep 2, 2022
This diff:

- Fixes python#8129
- Fixes python#13043
- Fixes python#13167

For more concise repros of these various issues, see the modified
test files. But in short, there were two broad categories of errors:

1.  Within the deferred pass, we tend to infer 'Any' for the types of
    different variables instead of the actual type. This interacts
    badly with our unreachable and disallow-any checks and causes
    spurious errors.

    Arguably, the better way of handling this error is to only collect
    errors during the final pass. I briefly experimented with this
    approach, but was unable to find a clean, efficient, and
    non-disruptive way of implementing this. So, I settled for
    sprinkling in a few more `not self.current_node_deferred` checks.

2.  The 'self.msg.disallowed_any_type(...)` call is normally guarded
    behind a `not self.chk.current_node_deferred` check. However, we
    were bypassing this check for `except` block assignments because
    we were deliberately setting that flag to False to work around some
    bug. For more context, see python#2290.

    It appears we no longer need this patch anymore. I'm not entirely
    sure why, but I'm guessing we tightened and fixed the underlying
    problem with deferred passes some time during the past half-decade.
hauntsaninja pushed a commit that referenced this issue Sep 2, 2022
…#13575)

This diff:

- Fixes #8129
- Fixes #13043
- Fixes #13167

For more concise repros of these various issues, see the modified test files. But in short, there were two broad categories of errors:

1.  Within the deferred pass, we tend to infer 'Any' for the types of different variables instead of the actual type. This interacts badly with our unreachable and disallow-any checks and causes spurious errors.

    Arguably, the better way of handling this error is to only collect errors during the final pass. I briefly experimented with this
    approach, but was unable to find a clean, efficient, and non-disruptive way of implementing this. So, I settled for sprinkling in a few more `not self.current_node_deferred` checks.

2.  The `self.msg.disallowed_any_type(...)` call is normally guarded behind a `not self.chk.current_node_deferred` check. However, we were bypassing this check for `except` block assignments because we were deliberately setting that flag to False to work around some bug. For more context, see #2290.

    It appears we no longer need this patch anymore. I'm not entirely sure why, but I'm guessing we tightened and fixed the underlying problem with deferred passes some time during the past half-decade.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong false-positive mypy gave an error on correct code priority-1-normal
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants