Skip to content

Compute type of inherited constructor correctly #1376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 1 addition & 30 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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) \
Expand Down
43 changes: 43 additions & 0 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This really deserves a comment, doesn't it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. Maybe some reasoning about why the cast is safe and a general description.


if isinstance(signature, CallableType):
return class_callable(signature, info, fallback)
else:
Expand Down Expand Up @@ -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)
35 changes: 35 additions & 0 deletions mypy/test/data/check-generic-subtyping.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
-- --------------------------------------------

Expand Down