Skip to content

Commit

Permalink
Try assuming covariance during inference
Browse files Browse the repository at this point in the history
  • Loading branch information
ilevkivskyi committed Jul 2, 2024
1 parent 55a0812 commit 35a49bf
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 20 deletions.
5 changes: 2 additions & 3 deletions mypy/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
ARG_STAR,
ARG_STAR2,
CONTRAVARIANT,
COVARIANT,
ArgKind,
TypeInfo,
)
Expand Down Expand Up @@ -808,7 +807,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]:
# Include constraints from both directions if invariant.
if tvar.variance != CONTRAVARIANT:
res.extend(infer_constraints(mapped_arg, instance_arg, self.direction))
if tvar.variance != COVARIANT:
else:
res.extend(
infer_constraints(mapped_arg, instance_arg, neg_op(self.direction))
)
Expand Down Expand Up @@ -872,7 +871,7 @@ def visit_instance(self, template: Instance) -> list[Constraint]:
# Include constraints from both directions if invariant.
if tvar.variance != CONTRAVARIANT:
res.extend(infer_constraints(template_arg, mapped_arg, self.direction))
if tvar.variance != COVARIANT:
else:
res.extend(
infer_constraints(template_arg, mapped_arg, neg_op(self.direction))
)
Expand Down
3 changes: 3 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -3177,6 +3177,7 @@ def append_invariance_notes(
arg_type.type.fullname == "builtins.list"
and expected_type.type.fullname == "builtins.list"
and is_subtype(arg_type.args[0], expected_type.args[0])
and not isinstance(get_proper_type(arg_type.args[0]), UninhabitedType)
):
invariant_type = "List"
covariant_suggestion = 'Consider using "Sequence" instead, which is covariant'
Expand All @@ -3185,6 +3186,8 @@ def append_invariance_notes(
and expected_type.type.fullname == "builtins.dict"
and is_same_type(arg_type.args[0], expected_type.args[0])
and is_subtype(arg_type.args[1], expected_type.args[1])
and not isinstance(get_proper_type(arg_type.args[0]), UninhabitedType)
and not isinstance(get_proper_type(arg_type.args[1]), UninhabitedType)
):
invariant_type = "Dict"
covariant_suggestion = (
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-dataclasses.test
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@ class A(Generic[T]):
return self.z # E: Incompatible return value type (got "List[T]", expected "T")

reveal_type(A) # N: Revealed type is "def [T] (x: T`1, y: T`1, z: builtins.list[T`1]) -> __main__.A[T`1]"
A(1, 2, ["a", "b"]) # E: Cannot infer type argument 1 of "A"
reveal_type(A(1, 2, ["a", "b"])) # N: Revealed type is "__main__.A[builtins.object]"
a = A(1, 2, [1, 2])
reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]"
reveal_type(a.x) # N: Revealed type is "builtins.int"
Expand Down
6 changes: 3 additions & 3 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ def func2(x: SameNode[T]) -> SameNode[T]:
return x
reveal_type(func2) # N: Revealed type is "def [T] (x: __main__.Node[T`-1, T`-1]) -> __main__.Node[T`-1, T`-1]"

func2(Node(1, 'x')) # E: Cannot infer type argument 1 of "func2"
reveal_type(func2(Node(1, 'x'))) # N: Revealed type is "__main__.Node[builtins.object, builtins.object]"
y = func2(Node('x', 'x'))
reveal_type(y) # N: Revealed type is "__main__.Node[builtins.str, builtins.str]"

Expand Down Expand Up @@ -888,7 +888,7 @@ def fun2(v: Vec[T], scale: T) -> Vec[T]:

reveal_type(fun1([(1, 1)])) # N: Revealed type is "builtins.int"
fun1(1) # E: Argument 1 to "fun1" has incompatible type "int"; expected "List[Tuple[bool, bool]]"
fun1([(1, 'x')]) # E: Cannot infer type argument 1 of "fun1"
fun1([(1, 'x')]) # E: Value of type variable "T" of "fun1" cannot be "object"

reveal_type(fun2([(1, 1)], 1)) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.int]]"
fun2([('x', 'x')], 'x') # E: Value of type variable "T" of "fun2" cannot be "str"
Expand All @@ -909,7 +909,7 @@ def f(x: Node[T, T]) -> TupledNode[T]:
return Node(x.x, (x.x, x.x))

f(1) # E: Argument 1 to "f" has incompatible type "int"; expected "Node[Never, Never]"
f(Node(1, 'x')) # E: Cannot infer type argument 1 of "f"
reveal_type(f(Node(1, 'x'))) # N: Revealed type is "a.Node[builtins.object, Tuple[builtins.object, builtins.object]]"
reveal_type(Node('x', 'x')) # N: Revealed type is "a.Node[builtins.str, builtins.str]"

[file a.py]
Expand Down
7 changes: 6 additions & 1 deletion test-data/unit/check-inference-context.test
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,12 @@ class D(C): ...

def f(x: List[T], y: List[T]) -> List[T]: ...

f([C()], [D()]) # E: Cannot infer type argument 1 of "f"
reveal_type(f([C()], [D()])) # N: Revealed type is "builtins.list[__main__.C]"
lc: List[C]
ld: List[D]
f(lc, ld) # E: Argument 2 to "f" has incompatible type "List[D]"; expected "List[C]" \
# N: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance \
# N: Consider using "Sequence" instead, which is covariant
[builtins fixtures/list.pyi]

[case testInferTypeVariableFromTwoGenericTypes3]
Expand Down
8 changes: 4 additions & 4 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,8 @@ class A(Generic[T]): pass
class B: pass


f(ao, ab) # E: Cannot infer type argument 1 of "f"
f(ab, ao) # E: Cannot infer type argument 1 of "f"
f(ao, ab) # E: Argument 2 to "f" has incompatible type "A[B]"; expected "A[object]"
f(ab, ao) # E: Argument 1 to "f" has incompatible type "A[B]"; expected "A[object]"
f(ao, ao)
f(ab, ab)

Expand Down Expand Up @@ -3738,8 +3738,8 @@ reveal_type(f(x, [])) # N: Revealed type is "builtins.str"
reveal_type(f(["yes"], [])) # N: Revealed type is "builtins.str"

empty: List[NoReturn]
f(x, empty) # E: Cannot infer type argument 1 of "f"
f(["no"], empty) # E: Cannot infer type argument 1 of "f"
f(x, empty) # E: Argument 2 to "f" has incompatible type "List[Never]"; expected "List[str]"
f(["no"], empty) # E: Argument 2 to "f" has incompatible type "List[Never]"; expected "List[str]"
[builtins fixtures/list.pyi]

[case testInferenceWorksWithEmptyCollectionsUnion]
Expand Down
10 changes: 5 additions & 5 deletions test-data/unit/check-overloading.test
Original file line number Diff line number Diff line change
Expand Up @@ -3341,7 +3341,7 @@ reveal_type(obj.f(A())) # N: Revealed type is "__main__.C"
reveal_type(obj.f(B())) # N: Revealed type is "__main__.B"
reveal_type(obj.f(x)) # N: Revealed type is "Union[__main__.C, __main__.B]"

[case testOverloadingInferUnionReturnWithFunctionTypevarReturn]
[case testOverloadingInferUnionReturnWithFunctionTypeVarReturn]
from typing import overload, Union, TypeVar, Generic

T = TypeVar('T')
Expand Down Expand Up @@ -3370,10 +3370,11 @@ def wrapper() -> None:
obj2: Union[W1[A], W2[B]]

reveal_type(foo(obj2)) # N: Revealed type is "Union[__main__.A, __main__.B]"
bar(obj2) # E: Cannot infer type argument 1 of "bar"
bar(obj2) # E: Argument 1 to "bar" has incompatible type "Union[W1[A], W2[B]]"; expected "Union[W1[object], W2[object]]"

b1_overload: A = foo(obj2) # E: Incompatible types in assignment (expression has type "Union[A, B]", variable has type "A")
b1_union: A = bar(obj2) # E: Cannot infer type argument 1 of "bar"
b1_union: A = bar(obj2) # E: Incompatible types in assignment (expression has type "object", variable has type "A") \
# E: Argument 1 to "bar" has incompatible type "Union[W1[A], W2[B]]"; expected "Union[W1[object], W2[object]]"

[case testOverloadingInferUnionReturnWithObjectTypevarReturn]
from typing import overload, Union, TypeVar, Generic
Expand Down Expand Up @@ -3496,8 +3497,7 @@ def t_is_same_bound(arg1: T1, arg2: S) -> Tuple[T1, S]:
# The arguments in the tuple are swapped
x3: Union[List[S], List[Tuple[S, T1]]]
y3: S
Dummy[T1]().foo(x3, y3) # E: Cannot infer type argument 1 of "foo" of "Dummy" \
# E: Argument 1 to "foo" of "Dummy" has incompatible type "Union[List[S], List[Tuple[S, T1]]]"; expected "List[Tuple[T1, Any]]"
Dummy[T1]().foo(x3, y3) # E: Argument 1 to "foo" of "Dummy" has incompatible type "Union[List[S], List[Tuple[S, T1]]]"; expected "List[Tuple[T1, object]]"

x4: Union[List[int], List[Tuple[C, int]]]
y4: int
Expand Down
5 changes: 2 additions & 3 deletions test-data/unit/check-plugin-attrs.test
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,8 @@ reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]"
reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int]"
reveal_type(a.y) # N: Revealed type is "builtins.int"

A(['str'], 7) # E: Cannot infer type argument 1 of "A"
A([1], '2') # E: Cannot infer type argument 1 of "A"

reveal_type(A(['str'], 7)) # N: Revealed type is "__main__.A[builtins.object]"
reveal_type(A([1], '2')) # N: Revealed type is "__main__.A[builtins.object]"
[builtins fixtures/list.pyi]

[case testAttrsGenericWithConverter]
Expand Down

0 comments on commit 35a49bf

Please sign in to comment.