Skip to content

Commit 6eb1e92

Browse files
committed
Merge pull request #898 from jhance/cov-contra
Fixes for covariance/contravariance + #734
2 parents 6d9351a + 5fc466b commit 6eb1e92

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

mypy/checker.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
LITERAL_TYPE, BreakStmt, ContinueStmt, ComparisonExpr, StarExpr,
2020
YieldFromExpr, YieldFromStmt, NamedTupleExpr, SetComprehension,
2121
DictionaryComprehension, ComplexExpr, EllipsisExpr, TypeAliasExpr,
22-
RefExpr, YieldExpr
22+
RefExpr, YieldExpr, CONTRAVARIANT, COVARIANT
2323
)
2424
from mypy.nodes import function_type, method_type, method_type_with_fallback
2525
from mypy import nodes
2626
from mypy.types import (
2727
Type, AnyType, CallableType, Void, FunctionLike, Overloaded, TupleType,
28-
Instance, NoneTyp, UnboundType, ErrorType, TypeTranslator, strip_type, UnionType
28+
Instance, NoneTyp, UnboundType, ErrorType, TypeTranslator, strip_type,
29+
UnionType, TypeVarType,
2930
)
3031
from mypy.sametypes import is_same_type
3132
from mypy.messages import MessageBuilder
@@ -505,12 +506,25 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
505506
elif name == '__getattr__':
506507
self.check_getattr_method(typ, defn)
507508

509+
# Refuse contravariant return type variable
510+
if isinstance(typ.ret_type, TypeVarType):
511+
if typ.ret_type.variance == CONTRAVARIANT:
512+
self.fail(messages.RETURN_TYPE_CANNOT_BE_CONTRAVARIANT,
513+
typ.ret_type)
514+
508515
# Push return type.
509516
self.return_types.append(typ.ret_type)
510517

511518
# Store argument types.
512519
for i in range(len(typ.arg_types)):
513520
arg_type = typ.arg_types[i]
521+
522+
# Refuse covariant parameter type variables
523+
if isinstance(arg_type, TypeVarType):
524+
if arg_type.variance == COVARIANT:
525+
self.fail(messages.FUNCTION_PARAMETER_CANNOT_BE_COVARIANT,
526+
arg_type)
527+
514528
if typ.arg_kinds[i] == nodes.ARG_STAR:
515529
# builtins.tuple[T] is typing.Tuple[T, ...]
516530
arg_type = self.named_generic_type('builtins.tuple',

mypy/messages.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
FORMAT_REQUIRES_MAPPING = 'Format requires a mapping'
6767
GENERIC_TYPE_NOT_VALID_AS_EXPRESSION = \
6868
"Generic type not valid as an expression any more (use '# type:' comment instead)"
69+
RETURN_TYPE_CANNOT_BE_CONTRAVARIANT = "Cannot use a contravariant type variable as return type"
70+
FUNCTION_PARAMETER_CANNOT_BE_COVARIANT = "Cannot use a covariant type variable as a parameter"
6971

7072

7173
class MessageBuilder:

mypy/test/data/check-functions.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,3 +902,43 @@ def f():
902902
"""
903903
return 1
904904
f() + ''
905+
[case testRejectCovariantArgument]
906+
from typing import TypeVar, Generic
907+
908+
t = TypeVar('t', covariant=True)
909+
class A(Generic[t]):
910+
def foo(self, x: t) -> None:
911+
return None
912+
[builtins fixtures/bool.py]
913+
[out]
914+
main: note: In member "foo" of class "A":
915+
main:5: error: Cannot use a covariant type variable as a parameter
916+
917+
[case testRejectContravariantReturnType]
918+
from typing import TypeVar, Generic
919+
920+
t = TypeVar('t', contravariant=True)
921+
class A(Generic[t]):
922+
def foo(self) -> t:
923+
return None
924+
[builtins fixtures/bool.py]
925+
[out]
926+
main: note: In member "foo" of class "A":
927+
main:5: error: Cannot use a contravariant type variable as return type
928+
929+
[case testAcceptCovariantReturnType]
930+
from typing import TypeVar, Generic
931+
932+
t = TypeVar('t', covariant=True)
933+
class A(Generic[t]):
934+
def foo(self) -> t:
935+
return None
936+
[builtins fixtures/bool.py]
937+
[case testAcceptContravariantArgument]
938+
from typing import TypeVar, Generic
939+
940+
t = TypeVar('t', contravariant=True)
941+
class A(Generic[t]):
942+
def foo(self, x: t) -> None:
943+
return None
944+
[builtins fixtures/bool.py]

mypy/typeanal.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
7777
if len(t.args) > 0:
7878
self.fail('Type variable "{}" used with arguments'.format(
7979
t.name), t)
80-
values = cast(TypeVarExpr, sym.node).values
81-
return TypeVarType(t.name, sym.tvar_id, values,
80+
tvar_expr = cast(TypeVarExpr, sym.node)
81+
return TypeVarType(t.name, sym.tvar_id, tvar_expr.values,
8282
self.builtin_type('builtins.object'),
83+
tvar_expr.variance,
8384
t.line)
8485
elif fullname == 'builtins.None':
8586
return Void()

0 commit comments

Comments
 (0)