Skip to content

Commit 1b96a2a

Browse files
committed
feat: add mapped_type_vars to TypeInfo
1 parent ec4ccb0 commit 1b96a2a

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

mypy/mro.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Callable
44

55
from mypy.nodes import TypeInfo
6-
from mypy.types import Instance
6+
from mypy.types import Instance, ProperType, TypeVarLikeType
77
from mypy.typestate import type_state
88

99

@@ -15,11 +15,61 @@ def calculate_mro(info: TypeInfo, obj_type: Callable[[], Instance] | None = None
1515
mro = linearize_hierarchy(info, obj_type)
1616
assert mro, f"Could not produce a MRO at all for {info}"
1717
info.mro = mro
18+
fill_mapped_type_vars(info)
1819
# The property of falling back to Any is inherited.
1920
info.fallback_to_any = any(baseinfo.fallback_to_any for baseinfo in info.mro)
2021
type_state.reset_all_subtype_caches_for(info)
2122

2223

24+
def fill_mapped_type_vars(info: TypeInfo) -> None:
25+
"""Calculates the final TypeVar value from inheritor to parent.
26+
27+
class A[T1]:
28+
# mapped_type_vars = {T1: str}
29+
30+
class B[T2]:
31+
# mapped_type_vars = {T2: T4}
32+
33+
class C[T3](B[T3]):
34+
# mapped_type_vars = {T3: T4}
35+
36+
class D[T4](C[T4], A[str]):
37+
# mapped_type_vars = {}
38+
"""
39+
bases = {b.type: b for b in info.bases}
40+
41+
for subinfo in filter(lambda x: x.is_generic, info.mro):
42+
if base_info := bases.get(subinfo):
43+
subinfo.mapped_type_vars = {
44+
tv: actual_type for tv, actual_type in zip(subinfo.defn.type_vars, base_info.args)
45+
}
46+
info.mapped_type_vars |= subinfo.mapped_type_vars
47+
48+
final_mapped_type_vars: dict[TypeVarLikeType, ProperType] = {}
49+
for k, v in info.mapped_type_vars.items():
50+
final_mapped_type_vars[k] = _resolve_mappped_vars(info.mapped_type_vars, v)
51+
52+
for subinfo in filter(lambda x: x.is_generic, info.mro):
53+
_resolve_info_type_vars(subinfo, final_mapped_type_vars)
54+
55+
56+
def _resolve_info_type_vars(
57+
info: TypeInfo, mapped_type_vars: dict[TypeVarLikeType, ProperType]
58+
) -> None:
59+
final_mapped_type_vars = {}
60+
for tv in info.defn.type_vars:
61+
final_mapped_type_vars[tv] = _resolve_mappped_vars(mapped_type_vars, tv)
62+
info.mapped_type_vars = final_mapped_type_vars
63+
64+
65+
def _resolve_mappped_vars(
66+
mapped_type_vars: dict[TypeVarLikeType, ProperType], key: ProperType
67+
) -> ProperType:
68+
if key in mapped_type_vars:
69+
return _resolve_mappped_vars(mapped_type_vars, mapped_type_vars[key])
70+
return key
71+
72+
2373
class MroError(Exception):
2474
"""Raised if a consistent mro cannot be determined for a class."""
2575

mypy/nodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,7 @@ class is generic then it will be a type constructor of higher kind.
29442944
"fallback_to_any",
29452945
"meta_fallback_to_any",
29462946
"type_vars",
2947+
"mapped_type_vars",
29472948
"has_param_spec_type",
29482949
"bases",
29492950
"_promote",
@@ -3048,6 +3049,8 @@ class is generic then it will be a type constructor of higher kind.
30483049

30493050
# Generic type variable names (full names)
30503051
type_vars: list[str]
3052+
# Map of current class TypeVars and Inheritor specified type to calculate real type in MRO
3053+
mapped_type_vars: dict[mypy.types.TypeVarLikeType, mypy.types.ProperType]
30513054

30523055
# Whether this class has a ParamSpec type variable
30533056
has_param_spec_type: bool
@@ -3139,6 +3142,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
31393142
self.defn = defn
31403143
self.module_name = module_name
31413144
self.type_vars = []
3145+
self.mapped_type_vars = {}
31423146
self.has_param_spec_type = False
31433147
self.has_type_var_tuple_type = False
31443148
self.bases = []

test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Generic, TypeVar
2+
3+
T1 = TypeVar("T1")
4+
T2 = TypeVar("T2")
5+
T3 = TypeVar("T3")
6+
7+
8+
class A(Generic[T1]):
9+
x: T1
10+
11+
class B(Generic[T2]):
12+
x: T2
13+
14+
class C(B[T3]):
15+
pass
16+
17+
class D(C[str], A[str]):
18+
pass
19+
20+
21+
# TypeInfo(
22+
# Name(test.C)
23+
# Bases(test.A[builtins.str], test.B[builtins.str])
24+
# Mro(test.C, test.A, test.B, builtins.object)
25+
# Names())
26+
# )
27+
# TypeInfo(
28+
# Name(test.A)
29+
# Bases(builtins.object)
30+
# Mro(test.A, builtins.object)
31+
# Names(
32+
# x (T`1)
33+
# )
34+
# )
35+
# TypeInfo(
36+
# Name(test.B)
37+
# Bases(builtins.object)
38+
# Mro(test.B, builtins.object)
39+
# Names(
40+
# x (T`1))
41+
# )

0 commit comments

Comments
 (0)