Skip to content

Commit

Permalink
Fix inference with UninhabitedType (#16994)
Browse files Browse the repository at this point in the history
At the moment, inference fails if an empty dict is used (without
annotation) as one of the types. It's because the constraint solver
can't resolve `dict[str, int]` and `dict[Never, Never]`. However in this
case it's more reasonable to interpret the empty dict as `dict[Any,
Any]` and just using the first type instead. That matches the behavior
of pyright.
```py
T = TypeVar("T")
class A(Generic[T]): ...

def func1(a: A[T], b: T) -> T: ...

def a1(a: A[Dict[str, int]]) -> None:
    reveal_type(func1(a, {}))
```
```
# before
main: error: Cannot infer type argument 1 of "func1" (diff)
main: note: Revealed type is "Any" (diff)

# after
main: note: Revealed type is "builtins.dict[builtins.str, builtins.int]"
```
  • Loading branch information
cdce8p authored Mar 7, 2024
1 parent 2f0f8f2 commit 2fbfb60
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 4 deletions.
13 changes: 9 additions & 4 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,17 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType:
# TODO: contravariant case should use meet but pass seen instances as
# an argument to keep track of recursive checks.
elif type_var.variance in (INVARIANT, CONTRAVARIANT):
if not is_equivalent(ta, sa):
if isinstance(ta_proper, UninhabitedType) and not ta_proper.is_noreturn:
new_type = sa
elif isinstance(sa_proper, UninhabitedType) and not sa_proper.is_noreturn:
new_type = ta
elif not is_equivalent(ta, sa):
self.seen_instances.pop()
return object_from_instance(t)
# If the types are different but equivalent, then an Any is involved
# so using a join in the contravariant case is also OK.
new_type = join_types(ta, sa, self)
else:
# If the types are different but equivalent, then an Any is involved
# so using a join in the contravariant case is also OK.
new_type = join_types(ta, sa, self)
elif isinstance(type_var, TypeVarTupleType):
new_type = get_proper_type(join_types(ta, sa, self))
# Put the joined arguments back into instance in the normal form:
Expand Down
34 changes: 34 additions & 0 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -3813,3 +3813,37 @@ def m1() -> float: ...
def m2() -> float: ...
reveal_type(Combine(m1, m2)) # N: Revealed type is "builtins.float"
[builtins fixtures/list.pyi]

[case testInferenceWithUninhabitedType]
from typing import Dict, Generic, List, Never, TypeVar

T = TypeVar("T")

class A(Generic[T]): ...
class B(Dict[T, T]): ...

def func1(a: A[T], b: T) -> T: ...
def func2(a: T, b: A[T]) -> T: ...

def a1(a: A[Dict[str, int]]) -> None:
reveal_type(func1(a, {})) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]"
reveal_type(func2({}, a)) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]"

def a2(check: bool, a: B[str]) -> None:
reveal_type(a if check else {}) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"

def a3() -> None:
a = {} # E: Need type annotation for "a" (hint: "a: Dict[<type>, <type>] = ...")
b = {1: {}} # E: Need type annotation for "b"
c = {1: {}, 2: {"key": {}}} # E: Need type annotation for "c"
reveal_type(a) # N: Revealed type is "builtins.dict[Any, Any]"
reveal_type(b) # N: Revealed type is "builtins.dict[builtins.int, builtins.dict[Any, Any]]"
reveal_type(c) # N: Revealed type is "builtins.dict[builtins.int, builtins.dict[builtins.str, builtins.dict[Any, Any]]]"

def a4(x: List[str], y: List[Never]) -> None:
z1 = [x, y]
z2 = [y, x]
reveal_type(z1) # N: Revealed type is "builtins.list[builtins.object]"
reveal_type(z2) # N: Revealed type is "builtins.list[builtins.object]"
z1[1].append("asdf") # E: "object" has no attribute "append"
[builtins fixtures/dict.pyi]

0 comments on commit 2fbfb60

Please sign in to comment.