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

Variable loses type inference after passing through dictionary of known type #9369

Closed
GPHemsley opened this issue Aug 28, 2020 · 8 comments
Closed
Labels
bug mypy got something wrong priority-2-low

Comments

@GPHemsley
Copy link

GPHemsley commented Aug 28, 2020

🐛 Bug Report

(Possibly related to #6755?)

When a variable is declared to be of a type including Optional, mypy correctly infers that it is not None when passing through an assert indicating as such. However, if mapped through a dictionary with a known type (either by inference or declaration), mypy loses that inference and reverts to the original type definition.

To Reproduce

from typing import (
	Dict,
	Optional,
)

def check1(x: Optional[str]) -> str:
	assert x  # x: str

	return x  # x: str

def check2(x: Optional[str]) -> str:
	assert x  # x: str

	x_map: Dict[str, str] = {
		"x": "y"
	}
	x = x_map.get(x, x)  # x: Optional[str] ??

	return x  # x: Optional[str] ??

def check3(x: Optional[str]) -> str:
	assert x  # x: str

	x_map: Dict[str, str] = {
		"x": "y"
	}
	x = x_map.get(x, x)  # x: Optional[str] ??

	assert x  # x: str

	return x  # x: str

Expected Behavior

The type of x should remain inferred as str in check2, because both possible return values from x_map.get(x, x) are known to be str.

Actual Behavior

20: error: Incompatible return value type (got "Optional[str]", expected "str")

Your Environment

  • Mypy version used: mypy 0.782
  • Mypy command-line flags: mypy --strict
  • Python version used: Python 3.6.11
@GPHemsley GPHemsley added the bug mypy got something wrong label Aug 28, 2020
@gvanrossum
Copy link
Member

Please look up the definition of dict.get in typeshed. Maybe there’s an explanation there?

@JelleZijlstra
Copy link
Member

I believe that's Mapping.get: https://github.com/python/typeshed/blob/master/stdlib/3/typing.pyi#L428. It should pick the second overload, so I'm not sure why mypy puts the Optional back in.

@gvanrossum
Copy link
Member

Perhaps this is because the type solver takes the target type (i.e. the full, declared type of x) as input, if present. In this case that's Optional[str].

The error goes away when I change the code to this:

    y = x_map.get(x, x)
    return y

A shorter, self-contained repro is:

from typing import *

T = TypeVar("T")
S = TypeVar("S")

def get(a: S, b: Union[S, T]) -> Union[S, T]: ...

def f(x: Optional[str]) -> None:
    assert x
    reveal_type(x)  # str
    y = get(x, x)
    reveal_type(y)  # str
    x = get(x, x)
    reveal_type(x)  # Optional[str]

It is the last line that is surprising.

FYI pyright has the same results. Pyre says the inferred type is str on the last line.

I'm labeling this as a bug, low priority (since there's an easy override).

@adigitoleo

This comment has been minimized.

@JelleZijlstra
Copy link
Member

@adigitoleo dict.update() returns None, not a dict. Your code is incorrect.

reveal_type() still exists and should work; it may not get executed in code mypy thinks is dead.

Optional[str] means an argument that may be None, not one that may be omitted or has a default. In practice those two concepts often overlap, but conceptually they are distinct.

@adigitoleo
Copy link

@JelleZijlstra Thanks for all the info! I'll hide my comment so it's not confused with this issue.

@hauntsaninja
Copy link
Collaborator

Looks like your mypy-play link passes as of mypy 1.0.0. Fixed by #14151

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-2-low
Projects
None yet
Development

No branches or pull requests

5 participants