diff --git a/mypy/checker.py b/mypy/checker.py index dc744c6d3467..7a131abb37be 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -877,8 +877,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str]) # Store argument types. for i in range(len(typ.arg_types)): arg_type = typ.arg_types[i] - - ref_type = self.scope.active_self_type() # type: Optional[Type] + with self.scope.push_function(defn): + # We temporary push the definition to get the self type as + # visible from *inside* of this function/method. + ref_type = self.scope.active_self_type() # type: Optional[Type] if (isinstance(defn, FuncDef) and ref_type is not None and i == 0 and not defn.is_static and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]): @@ -4456,6 +4458,7 @@ def active_class(self) -> Optional[TypeInfo]: return None def enclosing_class(self) -> Optional[TypeInfo]: + """Is there a class *directly* enclosing this function?""" top = self.top_function() assert top, "This method must be called from inside a function" index = self.stack.index(top) @@ -4466,7 +4469,14 @@ def enclosing_class(self) -> Optional[TypeInfo]: return None def active_self_type(self) -> Optional[Union[Instance, TupleType]]: + """An instance or tuple type representing the current class. + + This returns None unless we are in class body or in a method. + In particular, inside a function nested in method this returns None. + """ info = self.active_class() + if not info and self.top_function(): + info = self.enclosing_class() if info: return fill_typevars(info) return None diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9a113ace0df1..bfe057e1de02 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -6077,3 +6077,68 @@ TX = TypeVar('TX', bound='X') class X: def __new__(cls, x: TX) -> TX: # E: "__new__" must return a class instance (got "TX") pass + +[case testGenericOverride] +from typing import Generic, TypeVar, Any + +T = TypeVar('T') + +class B(Generic[T]): + x: T + +class C(B): + def __init__(self) -> None: + self.x: Any + +[case testGenericOverridePreciseInvalid] +from typing import Generic, TypeVar, Any + +T = TypeVar('T') + +class B(Generic[T]): + x: T + +class C(B[str]): + def __init__(self) -> None: + self.x: int # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str") + +[case testGenericOverridePreciseValid] +from typing import Generic, TypeVar + +T = TypeVar('T') + +class B(Generic[T]): + x: T + +class C(B[float]): + def __init__(self) -> None: + self.x: int # We currently allow covariant overriding. + +[case testGenericOverrideGeneric] +from typing import Generic, TypeVar, List + +T = TypeVar('T') + +class B(Generic[T]): + x: T + +class C(B[T]): + def __init__(self) -> None: + self.x: List[T] # E: Incompatible types in assignment (expression has type "List[T]", base class "B" defined the type as "T") +[builtins fixtures/list.pyi] + +[case testGenericOverrideGenericChained] +from typing import Generic, TypeVar, Tuple + +T = TypeVar('T') +S = TypeVar('S') + +class A(Generic[T]): + x: T + +class B(A[Tuple[T, S]]): ... + +class C(B[int, T]): + def __init__(self) -> None: + # TODO: error message could be better. + self.x: Tuple[str, T] # E: Incompatible types in assignment (expression has type "Tuple[str, T]", base class "A" defined the type as "Tuple[int, T]")