Skip to content

Commit 0b59a10

Browse files
authored
Fix the logic in active self-type calculation for current scope (#7235)
Fixes #5846 The fix for crash is kind of straightforward. Previously `active_self_type()` always returned `None` at method scope (sic!) While working on this I discovered couple other problems: * Area around checking LSP is generally problematic (see also #5425 that I tried to fix earlier but failed). In particular, the are two ways to get current `TypeInfo`, one is from `lvalue.node.info` or `defn.info`, another is using `active_self_type()` (this is rather abusing it IMO). I don't try to fix this here (i.e. switch to always using one way) because this is a relatively large refactoring. * Currently in type checker we defer nested functions instead of top-level ones. I believe this is not by design and is rather caused by absence of docstrings and unintuitive method names in `CheckerScope` class. Namely, there "top function" stays for "top of stack function", not "top-level function". I don't try to fix this here either because this is conceptually a big change.
1 parent b405e05 commit 0b59a10

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

mypy/checker.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,8 +877,10 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
877877
# Store argument types.
878878
for i in range(len(typ.arg_types)):
879879
arg_type = typ.arg_types[i]
880-
881-
ref_type = self.scope.active_self_type() # type: Optional[Type]
880+
with self.scope.push_function(defn):
881+
# We temporary push the definition to get the self type as
882+
# visible from *inside* of this function/method.
883+
ref_type = self.scope.active_self_type() # type: Optional[Type]
882884
if (isinstance(defn, FuncDef) and ref_type is not None and i == 0
883885
and not defn.is_static
884886
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]):
@@ -4456,6 +4458,7 @@ def active_class(self) -> Optional[TypeInfo]:
44564458
return None
44574459

44584460
def enclosing_class(self) -> Optional[TypeInfo]:
4461+
"""Is there a class *directly* enclosing this function?"""
44594462
top = self.top_function()
44604463
assert top, "This method must be called from inside a function"
44614464
index = self.stack.index(top)
@@ -4466,7 +4469,14 @@ def enclosing_class(self) -> Optional[TypeInfo]:
44664469
return None
44674470

44684471
def active_self_type(self) -> Optional[Union[Instance, TupleType]]:
4472+
"""An instance or tuple type representing the current class.
4473+
4474+
This returns None unless we are in class body or in a method.
4475+
In particular, inside a function nested in method this returns None.
4476+
"""
44694477
info = self.active_class()
4478+
if not info and self.top_function():
4479+
info = self.enclosing_class()
44704480
if info:
44714481
return fill_typevars(info)
44724482
return None

test-data/unit/check-classes.test

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6077,3 +6077,68 @@ TX = TypeVar('TX', bound='X')
60776077
class X:
60786078
def __new__(cls, x: TX) -> TX: # E: "__new__" must return a class instance (got "TX")
60796079
pass
6080+
6081+
[case testGenericOverride]
6082+
from typing import Generic, TypeVar, Any
6083+
6084+
T = TypeVar('T')
6085+
6086+
class B(Generic[T]):
6087+
x: T
6088+
6089+
class C(B):
6090+
def __init__(self) -> None:
6091+
self.x: Any
6092+
6093+
[case testGenericOverridePreciseInvalid]
6094+
from typing import Generic, TypeVar, Any
6095+
6096+
T = TypeVar('T')
6097+
6098+
class B(Generic[T]):
6099+
x: T
6100+
6101+
class C(B[str]):
6102+
def __init__(self) -> None:
6103+
self.x: int # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str")
6104+
6105+
[case testGenericOverridePreciseValid]
6106+
from typing import Generic, TypeVar
6107+
6108+
T = TypeVar('T')
6109+
6110+
class B(Generic[T]):
6111+
x: T
6112+
6113+
class C(B[float]):
6114+
def __init__(self) -> None:
6115+
self.x: int # We currently allow covariant overriding.
6116+
6117+
[case testGenericOverrideGeneric]
6118+
from typing import Generic, TypeVar, List
6119+
6120+
T = TypeVar('T')
6121+
6122+
class B(Generic[T]):
6123+
x: T
6124+
6125+
class C(B[T]):
6126+
def __init__(self) -> None:
6127+
self.x: List[T] # E: Incompatible types in assignment (expression has type "List[T]", base class "B" defined the type as "T")
6128+
[builtins fixtures/list.pyi]
6129+
6130+
[case testGenericOverrideGenericChained]
6131+
from typing import Generic, TypeVar, Tuple
6132+
6133+
T = TypeVar('T')
6134+
S = TypeVar('S')
6135+
6136+
class A(Generic[T]):
6137+
x: T
6138+
6139+
class B(A[Tuple[T, S]]): ...
6140+
6141+
class C(B[int, T]):
6142+
def __init__(self) -> None:
6143+
# TODO: error message could be better.
6144+
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]")

0 commit comments

Comments
 (0)