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

mypy deduces type as Optional after dict.get(x, x) #11337

Closed
jspricke opened this issue Oct 14, 2021 · 5 comments
Closed

mypy deduces type as Optional after dict.get(x, x) #11337

jspricke opened this issue Oct 14, 2021 · 5 comments
Labels
bug mypy got something wrong topic-type-narrowing Conditional type narrowing / binder

Comments

@jspricke
Copy link

Bug Report

from typing import Optional


def foo(s: str) -> None:
    pass


def bar(s: Optional[str] = None) -> None:
    if not s:
        return

    d: dict[str, str] = {}
    s = d.get(s, s)
    foo(s)  # Argument 1 to "foo" has incompatible type "Optional[str]"; expected "str"

When calling foo s is no longer Optional and mypy gets this right when removing d.get(s, s). But it does not complain either with def bar(s: str) -> None: So from my understanding the d.get is not the problem either.

Expected Behavior

I would expect mypy to deduce that foo(s) is always called with a str.

Actual Behavior

mypy deduces Optional[str]

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.9
  • Operating system and version: Debian unstable
@jspricke jspricke added the bug mypy got something wrong label Oct 14, 2021
@sobolevn
Copy link
Member

I had a moment to dig into it. It is not releated to .get(), this happens mostly because s has declared type s: Optional[str]. For some reason s = ... restores this type.

@hauntsaninja
Copy link
Collaborator

mypy probably still uses Optional[str] as type context or something like that

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Oct 17, 2021

Not sure if it's minimally reproducing the same issue, but I found this example interesting:

from typing import Optional, TypeVar, Union

T = TypeVar("T")


def f1(x: T) -> T:
    return x


def f2(x: T) -> Union[str, T]:
    return x


def g1(x: Optional[str]) -> None:
    if x is None:
        return

    reveal_type(x)  # Revealed type is "builtins.str"
    x = f1(x)
    reveal_type(x)  # Revealed type is "builtins.str"


def g2(x: Optional[str]) -> None:
    if x is None:
        return

    reveal_type(x)  # Revealed type is "builtins.str"
    x = f2(x)
    reveal_type(x)  # Revealed type is "Union[builtins.str, None]"

EDIT:
I just made this unit test on master and it's passing, so it might be already fixed?

[case testNarrowingTypeVarOptionalUnion]
from typing import Optional, TypeVar, Union

T = TypeVar("T")


def f2(x: T) -> Union[str, T]:
    return x


def g2(x: Optional[str]) -> None:
    if x is None:
        return

    reveal_type(x)  # N: Revealed type is "builtins.str"
    x = f2(x)
    reveal_type(x)  # N: Revealed type is "builtins.str"

OP's example also passes:

[case testNarrowingTypeVarOptionalUnion2]
from typing import Optional, Dict


def foo(s: str) -> None:
    pass


def bar(s: Optional[str] = None) -> None:
    if not s:
        return

    d: Dict[str, str] = {}
    s = d.get(s, s)
    reveal_type(s)  # N: Revealed type is "builtins.str"
    foo(s)
[builtins fixtures/dict.pyi]

@MestreLion
Copy link
Contributor

MestreLion commented Jan 27, 2023

Another minimal example, not even using dict.get(s, s), but rather dict.get(x, "foo"), ie, the default case is a string literal

def foo(s: str) : ...

def bar(x: int, s: Optional[str] = None):
    d: Dict[int, str] = {1: "a", 2: "b"}
    if s is None:
        s = d.get(x, "foo")
    reveal_type(s)
    f(s)

mypy:

note: Revealed type is "Union[builtins.str, None]"
error: Argument 1 to "foo" has incompatible type "Optional[str]"; expected "str"  [arg-type]

@ichard26 ichard26 added the topic-type-narrowing Conditional type narrowing / binder label Aug 21, 2023
@ichard26
Copy link
Collaborator

All examples have been fixed testing with the latest release. A Git bisect points to #14151 as the resolving PR. Thanks everyone for reporting!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-type-narrowing Conditional type narrowing / binder
Projects
None yet
Development

No branches or pull requests

6 participants