Skip to content

Commit 20b0b9b

Browse files
authored
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 cf6a48c commit 20b0b9b

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
@@ -426,9 +426,15 @@ class TypeVarId:
426426
# Class variable used for allocating fresh ids for metavariables.
427427
next_raw_id: ClassVar[int] = 1
428428

429-
def __init__(self, raw_id: int, meta_level: int = 0) -> None:
429+
# Fullname of class (or potentially function in the future) which
430+
# declares this type variable (not the fullname of the TypeVar
431+
# definition!), or ''
432+
namespace: str
433+
434+
def __init__(self, raw_id: int, meta_level: int = 0, *, namespace: str = '') -> None:
430435
self.raw_id = raw_id
431436
self.meta_level = meta_level
437+
self.namespace = namespace
432438

433439
@staticmethod
434440
def new(meta_level: int) -> 'TypeVarId':
@@ -442,15 +448,16 @@ def __repr__(self) -> str:
442448
def __eq__(self, other: object) -> bool:
443449
if isinstance(other, TypeVarId):
444450
return (self.raw_id == other.raw_id and
445-
self.meta_level == other.meta_level)
451+
self.meta_level == other.meta_level and
452+
self.namespace == other.namespace)
446453
else:
447454
return False
448455

449456
def __ne__(self, other: object) -> bool:
450457
return not (self == other)
451458

452459
def __hash__(self) -> int:
453-
return hash((self.raw_id, self.meta_level))
460+
return hash((self.raw_id, self.meta_level, self.namespace))
454461

455462
def is_meta_var(self) -> bool:
456463
return self.meta_level > 0
@@ -524,6 +531,7 @@ def serialize(self) -> JsonDict:
524531
'name': self.name,
525532
'fullname': self.fullname,
526533
'id': self.id.raw_id,
534+
'namespace': self.id.namespace,
527535
'values': [v.serialize() for v in self.values],
528536
'upper_bound': self.upper_bound.serialize(),
529537
'variance': self.variance,
@@ -535,7 +543,7 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarType':
535543
return TypeVarType(
536544
data['name'],
537545
data['fullname'],
538-
data['id'],
546+
TypeVarId(data['id'], namespace=data['namespace']),
539547
[deserialize_type(v) for v in data['values']],
540548
deserialize_type(data['upper_bound']),
541549
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)