mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Mypy infers `x.get("a") or x.get("b", "c")` to `str | None`, even when `x` is `dict[str, str]`

Open hfcredidio opened this issue 2 years ago • 4 comments

Bug Report

Mypy wrongly infers the type of the expression x.get("a") or x.get("b", "c") to be str | None, even when x is explicitly a dict[str, str].

To Reproduce

def works(x: dict[str, str]) -> str:
    a = x.get("a")
    b = x.get("b", "c")
    return a or b

def also_works(x: dict[str, str]) -> str: return x.get("a", x.get("b", "c"))
def also_fine(x: dict[str, str]) -> str: return x.get("a") or x.get("b") or "c"
def breaks(x: dict[str, str]) -> str: return x.get("a") or x.get("b", "c")

https://mypy-play.net/?mypy=latest&python=3.11&gist=33335d3d1d19e5858beb76ed65ce8470

I realise the examples are not logically equivalent, but they should all evaluate to strings

Expected Behavior

No errors.

Actual Behavior

Mypy returns main.py:8: error: Incompatible return value type (got "str | None", expected "str") [return-value]

Your Environment

  • Mypy version used: 1.4.0
  • Mypy command-line flags: N/A
  • Mypy configuration options from mypy.ini (and other config files): N/A
  • Python version used: 3.11

hfcredidio avatar Jun 27 '23 13:06 hfcredidio

Very strange error. If I had to guess, I'd say it has to do with the slightly weird signature of .get(): https://github.com/python/typeshed/pull/10294

You could try defining your own .get() as in the linked PR and see whether the problem persists.

tmke8 avatar Jun 27 '23 20:06 tmke8

@AlexWaygood topic-type-context?

ikonst avatar Jun 27 '23 21:06 ikonst

Ahh, never mind :(

def f4(x: dict[str, str]) -> str:
    q = x.get("a") or x.get("b", "c")  # N: Revealed type is "Union[builtins.str, None]"
    reveal_type(q)
    return q  # E: Incompatible return value type (got "str | None", expected "str")  [return-value]

ikonst avatar Jun 27 '23 21:06 ikonst

It does feel like a type context issue, but I think @tmke8 may be right and this is python/typeshed#10293. Unfortunately, as the typeshed PR indicates, that issue may not be easy to fix.

JelleZijlstra avatar Jun 27 '23 21:06 JelleZijlstra

It does feel like a type context issue, but I think @tmke8 may be right and this is python/typeshed#10293. Unfortunately, as the typeshed PR indicates, that issue may not be easy to fix.

(I just merged a different typeshed PR that also hopefully fixes python/typeshed#10293, but had a much less horrifying report from mypy_primer.)

AlexWaygood avatar Jul 26 '23 19:07 AlexWaygood

I confirmed that with https://github.com/python/typeshed/issues/10293, the issue is resolved, so no mypy changes will be necessary here. The fix might not make it into mypy v1.5, but it will definitely be included in mypy v1.6 if not.

With mypy v1.4.1:

>mypy -c "x: dict[str, str] = {}; reveal_type(x.get('a') or x.get('b', 'c'))" --custom-typeshed-dir .
<string>:1: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file

With mypy v1.4.1 and --custom-typeshed-dir pointing to an up-to-date clone of typeshed:

>mypy -c "x: dict[str, str] = {}; reveal_type(x.get('a') or x.get('b', 'c'))" --custom-typeshed-dir .
<string>:1: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file

AlexWaygood avatar Jul 28 '23 15:07 AlexWaygood