diff --git a/mypy/checker.py b/mypy/checker.py index 6ff1f3923b0d..445861d153f5 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -34,6 +34,7 @@ from mypy.sametypes import is_same_type from mypy.messages import MessageBuilder import mypy.checkexpr +from mypy.checkmember import map_type_from_supertype from mypy import defaults from mypy import messages from mypy.subtypes import ( @@ -2326,36 +2327,6 @@ def method_type(self, func: FuncBase) -> FunctionLike: return method_type_with_fallback(func, self.named_type('builtins.function')) -def map_type_from_supertype(typ: Type, sub_info: TypeInfo, - super_info: TypeInfo) -> Type: - """Map type variables in a type defined in a supertype context to be valid - in the subtype context. Assume that the result is unique; if more than - one type is possible, return one of the alternatives. - - For example, assume - - . class D(Generic[S]) ... - . class C(D[E[T]], Generic[T]) ... - - Now S in the context of D would be mapped to E[T] in the context of C. - """ - # Create the type of self in subtype, of form t[a1, ...]. - inst_type = self_type(sub_info) - if isinstance(inst_type, TupleType): - inst_type = inst_type.fallback - # Map the type of self to supertype. This gets us a description of the - # supertype type variables in terms of subtype variables, i.e. t[t1, ...] - # so that any type variables in tN are to be interpreted in subtype - # context. - inst_type = map_instance_to_supertype(inst_type, super_info) - # Finally expand the type variables in type with those in the previously - # constructed type. Note that both type and inst_type may have type - # variables, but in type they are interpreterd in supertype context while - # in inst_type they are interpreted in subtype context. This works even if - # the names of type variables in supertype and subtype overlap. - return expand_type_by_instance(typ, inst_type) - - def find_isinstance_check(node: Node, type_map: Dict[Node, Type], weak: bool=False) \ diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a67363c1caca..21b71f37b08c 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -359,6 +359,19 @@ def type_object_type(info: TypeInfo, builtin_type: Callable[[str], Instance]) -> def type_object_type_from_function(init_or_new: FuncBase, info: TypeInfo, fallback: Instance) -> FunctionLike: signature = method_type_with_fallback(init_or_new, fallback) + + # The __init__ method might come from a generic superclass + # (init_or_new.info) with type variables that do not map + # identically to the type variables of the class being constructed + # (info). For example + # + # class A(Generic[T]): def __init__(self, x: T) -> None: pass + # class B(A[List[T]], Generic[T]): pass + # + # We need to first map B's __init__ to the type (List[T]) -> None. + signature = cast(FunctionLike, + map_type_from_supertype(signature, info, init_or_new.info)) + if isinstance(signature, CallableType): return class_callable(signature, info, fallback) else: @@ -416,3 +429,33 @@ def translate_variables(self, else: items.append(v) return items + + +def map_type_from_supertype(typ: Type, sub_info: TypeInfo, + super_info: TypeInfo) -> Type: + """Map type variables in a type defined in a supertype context to be valid + in the subtype context. Assume that the result is unique; if more than + one type is possible, return one of the alternatives. + + For example, assume + + . class D(Generic[S]) ... + . class C(D[E[T]], Generic[T]) ... + + Now S in the context of D would be mapped to E[T] in the context of C. + """ + # Create the type of self in subtype, of form t[a1, ...]. + inst_type = self_type(sub_info) + if isinstance(inst_type, TupleType): + inst_type = inst_type.fallback + # Map the type of self to supertype. This gets us a description of the + # supertype type variables in terms of subtype variables, i.e. t[t1, ...] + # so that any type variables in tN are to be interpreted in subtype + # context. + inst_type = map_instance_to_supertype(inst_type, super_info) + # Finally expand the type variables in type with those in the previously + # constructed type. Note that both type and inst_type may have type + # variables, but in type they are interpreterd in supertype context while + # in inst_type they are interpreted in subtype context. This works even if + # the names of type variables in supertype and subtype overlap. + return expand_type_by_instance(typ, inst_type) diff --git a/mypy/test/data/check-generic-subtyping.test b/mypy/test/data/check-generic-subtyping.test index 8613923df390..e66481fecf17 100644 --- a/mypy/test/data/check-generic-subtyping.test +++ b/mypy/test/data/check-generic-subtyping.test @@ -352,6 +352,41 @@ class A(B[S], Generic[T, S]): main: note: In member "g" of class "A": +-- Type of inherited constructor +-- ----------------------------- + + +[case testInheritedConstructor] +from typing import TypeVar, Generic +T = TypeVar('T') +class A(Generic[T]): + def __init__(self, x: T) -> None: pass +class B(A[T], Generic[T]): pass +class C(A[int]): pass +class D(A[A[T]], Generic[T]): pass +B(1) +C(1) +C('a') # E: Argument 1 to "C" has incompatible type "str"; expected "int" +D(A(1)) +D(1) # E: Argument 1 to "D" has incompatible type "int"; expected A[None] + + +[case testInheritedConstructor2] +from typing import TypeVar, Generic +T = TypeVar('T') +U = TypeVar('U') +Z = TypeVar('Z') +class A(Generic[T, U]): + def __init__(self, x: T, y: U, z: Z) -> None: pass +class B(A[int, T], Generic[T]): pass +class C(B[A[T, str]], Generic[T, U]): pass +# C[T, U] <: B[A[T, str]] <: A[int, A[T, str]] +C(1, A(1, 'a', 0), 'z') +C(1, A('1', 'a', 0), 'z') +C('1', A(1, 'a', 0), 'z') # E: Argument 1 to "C" has incompatible type "str"; expected "int" +C(1, A(1, 1, 0), 'z') # E: Argument 2 to "A" has incompatible type "int"; expected "str" + + -- Subtyping with a generic abstract base class -- --------------------------------------------