Skip to content

Please just change something. Weird combination of dead code and inferred None #10634

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

Open
Akuli opened this issue Jun 11, 2021 · 6 comments
Open
Labels
feature topic-configuration Configuration files and flags topic-reachability Detecting unreachable code

Comments

@Akuli
Copy link
Contributor

Akuli commented Jun 11, 2021

Feature

foo = None

def bar() -> str:
    global foo
    if foo is not None:
        print("Hello")
    else:
        foo = "bar"
        return "lol"

This program type-checks, even though it's obviously wrong. It's not a bug, it's a feature:

  • Inferred type of foo is None, even though Optional[str] was intended.
  • Because foo has type None, the if foo is not None: part is eliminated as dead code, and therefore the missing return isn't detected.
  • After setting foo = "bar", reveal_type(foo) shows str. Perhaps the assignment should be an error?

Pitch

A friend submitted a PR like this and thought it was fine.

@Akuli
Copy link
Contributor Author

Akuli commented Jun 13, 2021

I set warn_unreachable = True and discovered other similar bugs in existing code. Can we enable warn_unreachable by default?

@scaramallion
Copy link

Having warn_unreachable as the default would flag errors in code that performs checking along the lines of:

def foo(val: str) -> None:
    if not isinstance(val, str):
        raise TypeError("Bad!")

@Akuli
Copy link
Contributor Author

Akuli commented Jun 15, 2021

I think warn_unreachable should be included in --strict, and I don't see this as a bad thing in a project that uses --strict. A --strict project doesn't need checks like that because of the type checker. If a function like this is really needed, then it would be better to annotate val as object or Any.

@sobolevn
Copy link
Member

related #11223

@AlexWaygood AlexWaygood added topic-configuration Configuration files and flags topic-reachability Detecting unreachable code labels Mar 30, 2022
@pranavrajpal
Copy link
Contributor

I think the problem here is that mypy is inferring a partial None for foo, and then inconsistently using that fact when type checking bar.

I suppose mypy could try rechecking bar repeatedly until all partial types stopped changing using something similar to what we do for type checking loops, but I think the better fix is just disallowing partial types that can't be inferred completely using only the scope they were defined in by enabling --local-partial-types.

With --local-partial-types enabled, the output I'm getting is:

foo = None # E: Need type annotation for "foo"

def bar() -> str: # E: Missing return statement
    global foo
    if foo is not None:
        print("Hello")
    else:
        foo = "bar"
        return "lol"

which is a lot closer to what I'd expect.

Merging #10169 would make --local-partial-types the default, which should fix this issue.

@JukkaL
Copy link
Collaborator

JukkaL commented May 13, 2022

I agree that --local-partial-types is the right fix. Making it the default is blocked for now since the switch can be quite disruptive for users. Improving the error messages could help. For example, we could suggest --no-local-partial-types or similar in a note. We could also perhaps allow disabling most of the new errors selectively, while still otherwise supporting local partial types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature topic-configuration Configuration files and flags topic-reachability Detecting unreachable code
Projects
None yet
Development

No branches or pull requests

6 participants