Skip to content

Commit 13928fe

Browse files
rwbartonGuido van Rossum
authored and
Guido van Rossum
committed
Allow any more general type for override of a method (#1524)
This is a prerequisite for fixing #1261 because overrides with generic function type variables currently work by accident: the type variables have the same ids in the original method and the overriding method, so is_subtype on the argument type returns True. This also allows an overriding method to generalize a specific type in the original method to a type variable, as demonstrated in one of the tests (testOverrideGenericMethodInNonGenericClassGeneralize). is_subtype already determines correctly whether the override is valid, so all the hard work here is in providing a more specific error message when possible.
1 parent b24de77 commit 13928fe

File tree

3 files changed

+96
-39
lines changed

3 files changed

+96
-39
lines changed

mypy/checker.py

+47-35
Original file line numberDiff line numberDiff line change
@@ -988,43 +988,55 @@ def check_override(self, override: FunctionLike, original: FunctionLike,
988988
only used for generating error messages.
989989
supertype: The name of the supertype.
990990
"""
991-
if (isinstance(override, Overloaded) or
992-
isinstance(original, Overloaded) or
993-
len(cast(CallableType, override).arg_types) !=
994-
len(cast(CallableType, original).arg_types) or
995-
cast(CallableType, override).min_args !=
996-
cast(CallableType, original).min_args):
997-
# Use boolean variable to clarify code.
998-
fail = False
999-
if not is_subtype(override, original):
1000-
fail = True
1001-
elif (not isinstance(original, Overloaded) and
1002-
isinstance(override, Overloaded) and
1003-
name in nodes.reverse_op_methods.keys()):
1004-
# Operator method overrides cannot introduce overloading, as
1005-
# this could be unsafe with reverse operator methods.
1006-
fail = True
1007-
if fail:
991+
# Use boolean variable to clarify code.
992+
fail = False
993+
if not is_subtype(override, original):
994+
fail = True
995+
elif (not isinstance(original, Overloaded) and
996+
isinstance(override, Overloaded) and
997+
name in nodes.reverse_op_methods.keys()):
998+
# Operator method overrides cannot introduce overloading, as
999+
# this could be unsafe with reverse operator methods.
1000+
fail = True
1001+
1002+
if fail:
1003+
emitted_msg = False
1004+
if (isinstance(override, CallableType) and
1005+
isinstance(original, CallableType) and
1006+
len(override.arg_types) == len(original.arg_types) and
1007+
override.min_args == original.min_args):
1008+
# Give more detailed messages for the common case of both
1009+
# signatures having the same number of arguments and no
1010+
# overloads.
1011+
1012+
# override might have its own generic function type
1013+
# variables. If an argument or return type of override
1014+
# does not have the correct subtyping relationship
1015+
# with the original type even after these variables
1016+
# are erased, then it is definitely an incompatiblity.
1017+
1018+
override_ids = override.type_var_ids()
1019+
1020+
def erase_override(t: Type) -> Type:
1021+
return erase_typevars(t, ids_to_erase=override_ids)
1022+
1023+
for i in range(len(override.arg_types)):
1024+
if not is_subtype(original.arg_types[i],
1025+
erase_override(override.arg_types[i])):
1026+
self.msg.argument_incompatible_with_supertype(
1027+
i + 1, name, name_in_super, supertype, node)
1028+
emitted_msg = True
1029+
1030+
if not is_subtype(erase_override(override.ret_type),
1031+
original.ret_type):
1032+
self.msg.return_type_incompatible_with_supertype(
1033+
name, name_in_super, supertype, node)
1034+
emitted_msg = True
1035+
1036+
if not emitted_msg:
1037+
# Fall back to generic incompatibility message.
10081038
self.msg.signature_incompatible_with_supertype(
10091039
name, name_in_super, supertype, node)
1010-
return
1011-
else:
1012-
# Give more detailed messages for the common case of both
1013-
# signatures having the same number of arguments and no
1014-
# overloads.
1015-
1016-
coverride = cast(CallableType, override)
1017-
coriginal = cast(CallableType, original)
1018-
1019-
for i in range(len(coverride.arg_types)):
1020-
if not is_subtype(coriginal.arg_types[i],
1021-
coverride.arg_types[i]):
1022-
self.msg.argument_incompatible_with_supertype(
1023-
i + 1, name, name_in_super, supertype, node)
1024-
1025-
if not is_subtype(coverride.ret_type, coriginal.ret_type):
1026-
self.msg.return_type_incompatible_with_supertype(
1027-
name, name_in_super, supertype, node)
10281040

10291041
def visit_class_def(self, defn: ClassDef) -> Type:
10301042
"""Type check a class definition."""

mypy/erasetype.py

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional, Container
2+
13
from mypy.types import (
24
Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp,
35
Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType,
@@ -96,13 +98,20 @@ def visit_instance(self, t: Instance) -> Type:
9698
return Instance(t.type, [], t.line)
9799

98100

99-
def erase_typevars(t: Type) -> Type:
100-
"""Replace all type variables in a type with any."""
101-
return t.accept(TypeVarEraser())
101+
def erase_typevars(t: Type, ids_to_erase: Optional[Container[int]] = None) -> Type:
102+
"""Replace all type variables in a type with any,
103+
or just the ones in the provided collection.
104+
"""
105+
return t.accept(TypeVarEraser(ids_to_erase))
102106

103107

104108
class TypeVarEraser(TypeTranslator):
105109
"""Implementation of type erasure"""
106110

111+
def __init__(self, ids_to_erase: Optional[Container[int]]) -> None:
112+
self.ids_to_erase = ids_to_erase
113+
107114
def visit_type_var(self, t: TypeVarType) -> Type:
115+
if self.ids_to_erase is not None and t.id not in self.ids_to_erase:
116+
return t
108117
return AnyType()

mypy/test/data/check-generic-subtyping.test

+37-1
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,45 @@ class A:
235235
class B(A):
236236
def f(self, x: S, y: T) -> None: pass
237237
class C(A):
238-
def f(self, x: T, y: T) -> None: pass # E: Argument 2 of "f" incompatible with supertype "A"
238+
# Okay, because T = object allows any type for the arguments.
239+
def f(self, x: T, y: T) -> None: pass
240+
241+
[case testOverrideGenericMethodInNonGenericClassLists]
242+
from typing import TypeVar, List
243+
244+
T = TypeVar('T')
245+
S = TypeVar('S')
246+
247+
class A:
248+
def f(self, x: List[T], y: List[S]) -> None: pass
249+
class B(A):
250+
def f(self, x: List[S], y: List[T]) -> None: pass
251+
class C(A):
252+
def f(self, x: List[T], y: List[T]) -> None: pass # E: Signature of "f" incompatible with supertype "A"
253+
[builtins fixtures/list.py]
254+
[out]
255+
main: note: In class "C":
256+
257+
[case testOverrideGenericMethodInNonGenericClassGeneralize]
258+
from typing import TypeVar
259+
260+
T = TypeVar('T')
261+
T1 = TypeVar('T1', bound=str)
262+
S = TypeVar('S')
263+
264+
class A:
265+
def f(self, x: int, y: S) -> None: pass
266+
class B(A):
267+
def f(self, x: T, y: S) -> None: pass
268+
class C(A):
269+
def f(self, x: T, y: str) -> None: pass
270+
class D(A):
271+
def f(self, x: T1, y: S) -> None: pass # TODO: This error could be more specific.
239272
[out]
240273
main: note: In class "C":
274+
main:12: error: Argument 2 of "f" incompatible with supertype "A"
275+
main: note: In class "D":
276+
main:14: error: Signature of "f" incompatible with supertype "A"
241277

242278

243279
-- Inheritance from generic types with implicit dynamic supertype

0 commit comments

Comments
 (0)