Skip to content

Commit d96330b

Browse files
committed
Use class name as namespace for type variables (#12590)
This avoids confusion between type variables of two classes, which can happen at least in some edge cases. Type variables are only the same if both the numeric id and namespace match (plus meta level). Fixes #12588 (though the textual presentation of type variables doesn't take the namespace into consideration yet).
1 parent 90be28d commit d96330b

File tree

5 files changed

+55
-13
lines changed

5 files changed

+55
-13
lines changed

mypy/checkpattern.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,7 @@ def visit_mapping_pattern(self, o: MappingPattern) -> PatternType:
396396
if is_subtype(current_type, mapping) and isinstance(current_type, Instance):
397397
mapping_inst = map_instance_to_supertype(current_type, mapping.type)
398398
dict_typeinfo = self.chk.lookup_typeinfo("builtins.dict")
399-
dict_type = fill_typevars(dict_typeinfo)
400-
rest_type = expand_type_by_instance(dict_type, mapping_inst)
399+
rest_type = Instance(dict_typeinfo, mapping_inst.args)
401400
else:
402401
object_type = self.chk.named_type("builtins.object")
403402
rest_type = self.chk.named_generic_type("builtins.dict",

mypy/semanal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,8 @@ def check_decorated_function_is_method(self, decorator: str,
11161116
def visit_class_def(self, defn: ClassDef) -> None:
11171117
self.statement = defn
11181118
self.incomplete_type_stack.append(not defn.info)
1119-
with self.tvar_scope_frame(self.tvar_scope.class_frame()):
1119+
namespace = self.qualified_name(defn.name)
1120+
with self.tvar_scope_frame(self.tvar_scope.class_frame(namespace)):
11201121
self.analyze_class(defn)
11211122
self.incomplete_type_stack.pop()
11221123

mypy/tvar_scope.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from typing import Optional, Dict, Union
2-
from mypy.types import TypeVarLikeType, TypeVarType, ParamSpecType, ParamSpecFlavor
2+
from mypy.types import TypeVarLikeType, TypeVarType, ParamSpecType, ParamSpecFlavor, TypeVarId
33
from mypy.nodes import ParamSpecExpr, TypeVarExpr, TypeVarLikeExpr, SymbolTableNode
44

55

@@ -12,7 +12,8 @@ class TypeVarLikeScope:
1212
def __init__(self,
1313
parent: 'Optional[TypeVarLikeScope]' = None,
1414
is_class_scope: bool = False,
15-
prohibited: 'Optional[TypeVarLikeScope]' = None) -> None:
15+
prohibited: 'Optional[TypeVarLikeScope]' = None,
16+
namespace: str = '') -> None:
1617
"""Initializer for TypeVarLikeScope
1718
1819
Parameters:
@@ -27,6 +28,7 @@ def __init__(self,
2728
self.class_id = 0
2829
self.is_class_scope = is_class_scope
2930
self.prohibited = prohibited
31+
self.namespace = namespace
3032
if parent is not None:
3133
self.func_id = parent.func_id
3234
self.class_id = parent.class_id
@@ -51,22 +53,25 @@ def method_frame(self) -> 'TypeVarLikeScope':
5153
"""A new scope frame for binding a method"""
5254
return TypeVarLikeScope(self, False, None)
5355

54-
def class_frame(self) -> 'TypeVarLikeScope':
56+
def class_frame(self, namespace: str) -> 'TypeVarLikeScope':
5557
"""A new scope frame for binding a class. Prohibits *this* class's tvars"""
56-
return TypeVarLikeScope(self.get_function_scope(), True, self)
58+
return TypeVarLikeScope(self.get_function_scope(), True, self, namespace=namespace)
5759

5860
def bind_new(self, name: str, tvar_expr: TypeVarLikeExpr) -> TypeVarLikeType:
5961
if self.is_class_scope:
6062
self.class_id += 1
6163
i = self.class_id
64+
namespace = self.namespace
6265
else:
6366
self.func_id -= 1
6467
i = self.func_id
68+
# TODO: Consider also using namespaces for functions
69+
namespace = ''
6570
if isinstance(tvar_expr, TypeVarExpr):
6671
tvar_def: TypeVarLikeType = TypeVarType(
6772
name,
6873
tvar_expr.fullname,
69-
i,
74+
TypeVarId(i, namespace=namespace),
7075
values=tvar_expr.values,
7176
upper_bound=tvar_expr.upper_bound,
7277
variance=tvar_expr.variance,

mypy/types.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,15 @@ class TypeVarId:
416416
# Class variable used for allocating fresh ids for metavariables.
417417
next_raw_id: ClassVar[int] = 1
418418

419-
def __init__(self, raw_id: int, meta_level: int = 0) -> None:
419+
# Fullname of class (or potentially function in the future) which
420+
# declares this type variable (not the fullname of the TypeVar
421+
# definition!), or ''
422+
namespace: str
423+
424+
def __init__(self, raw_id: int, meta_level: int = 0, *, namespace: str = '') -> None:
420425
self.raw_id = raw_id
421426
self.meta_level = meta_level
427+
self.namespace = namespace
422428

423429
@staticmethod
424430
def new(meta_level: int) -> 'TypeVarId':
@@ -432,15 +438,16 @@ def __repr__(self) -> str:
432438
def __eq__(self, other: object) -> bool:
433439
if isinstance(other, TypeVarId):
434440
return (self.raw_id == other.raw_id and
435-
self.meta_level == other.meta_level)
441+
self.meta_level == other.meta_level and
442+
self.namespace == other.namespace)
436443
else:
437444
return False
438445

439446
def __ne__(self, other: object) -> bool:
440447
return not (self == other)
441448

442449
def __hash__(self) -> int:
443-
return hash((self.raw_id, self.meta_level))
450+
return hash((self.raw_id, self.meta_level, self.namespace))
444451

445452
def is_meta_var(self) -> bool:
446453
return self.meta_level > 0
@@ -514,6 +521,7 @@ def serialize(self) -> JsonDict:
514521
'name': self.name,
515522
'fullname': self.fullname,
516523
'id': self.id.raw_id,
524+
'namespace': self.id.namespace,
517525
'values': [v.serialize() for v in self.values],
518526
'upper_bound': self.upper_bound.serialize(),
519527
'variance': self.variance,
@@ -525,7 +533,7 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarType':
525533
return TypeVarType(
526534
data['name'],
527535
data['fullname'],
528-
data['id'],
536+
TypeVarId(data['id'], namespace=data['namespace']),
529537
[deserialize_type(v) for v in data['values']],
530538
deserialize_type(data['upper_bound']),
531539
data['variance'],

test-data/unit/check-selftype.test

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,11 +893,14 @@ from typing import Generic, TypeVar, Tuple
893893
T = TypeVar('T')
894894
S = TypeVar('S')
895895
U = TypeVar('U')
896+
V = TypeVar('V')
896897

897898
class C(Generic[T]):
898899
def magic(self: C[Tuple[S, U]]) -> Tuple[T, S, U]: ...
899900

900-
reveal_type(C[Tuple[int, str]]().magic()) # N: Revealed type is "Tuple[Tuple[builtins.int, builtins.str], builtins.int, builtins.str]"
901+
class D(Generic[V]):
902+
def f(self) -> None:
903+
reveal_type(C[Tuple[V, str]]().magic()) # N: Revealed type is "Tuple[Tuple[V`1, builtins.str], V`1, builtins.str]"
901904
[builtins fixtures/tuple.pyi]
902905

903906
[case testSelfTypeOnUnion]
@@ -1167,3 +1170,29 @@ def build_wrapper_non_gen(descriptor: Descriptor[int]) -> BaseWrapper[str]:
11671170
def build_sub_wrapper_non_gen(descriptor: Descriptor[int]) -> SubWrapper[str]:
11681171
return SubWrapper.create_wrapper(descriptor) # E: Argument 1 to "create_wrapper" of "BaseWrapper" has incompatible type "Descriptor[int]"; expected "Descriptor[str]"
11691172
[builtins fixtures/classmethod.pyi]
1173+
1174+
[case testSelfTypeInGenericClassUsedFromAnotherGenericClass1]
1175+
from typing import TypeVar, Generic, Iterator, List, Tuple
1176+
1177+
_T_co = TypeVar("_T_co", covariant=True)
1178+
_T1 = TypeVar("_T1")
1179+
_T2 = TypeVar("_T2")
1180+
S = TypeVar("S")
1181+
1182+
class Z(Iterator[_T_co]):
1183+
def __new__(cls,
1184+
__iter1: List[_T1],
1185+
__iter2: List[_T2]) -> Z[Tuple[_T1, _T2]]: ...
1186+
def __iter__(self: S) -> S: ...
1187+
def __next__(self) -> _T_co: ...
1188+
1189+
T = TypeVar('T')
1190+
1191+
class C(Generic[T]):
1192+
a: List[T]
1193+
b: List[str]
1194+
1195+
def f(self) -> None:
1196+
for x, y in Z(self.a, self.b):
1197+
reveal_type((x, y)) # N: Revealed type is "Tuple[T`1, builtins.str]"
1198+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)