Skip to content

Commit 80cb5f4

Browse files
committed
WIP 3
1 parent fca0cca commit 80cb5f4

File tree

3 files changed

+71
-11
lines changed

3 files changed

+71
-11
lines changed

mypy/semanal.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
SELF_TYPE_NAMES,
227227
FindTypeVarVisitor,
228228
TypeAnalyser,
229+
TypeVarDefaultTranslator,
229230
TypeVarLikeList,
230231
analyze_type_alias,
231232
check_for_explicit_any,
@@ -1954,15 +1955,8 @@ class Foo(Bar, Generic[T]): ...
19541955
del base_type_exprs[i]
19551956
tvar_defs: list[TypeVarLikeType] = []
19561957
for name, tvar_expr in declared_tvars:
1957-
tvar_expr_default = tvar_expr.default
1958-
if isinstance(tvar_expr_default, UnboundType):
1959-
# TODO: - detect out of order and self-referencing TypeVars
1960-
# - nested default types, e.g. list[T1]
1961-
n = self.lookup_qualified(
1962-
tvar_expr_default.name, tvar_expr_default, suppress_errors=True
1963-
)
1964-
if n is not None and (default := self.tvar_scope.get_binding(n)) is not None:
1965-
tvar_expr.default = default
1958+
# TODO: detect out of order and self-referencing TypeVars
1959+
tvar_expr.default = tvar_expr.default.accept(TypeVarDefaultTranslator(self))
19661960
tvar_def = self.tvar_scope.bind_new(name, tvar_expr)
19671961
tvar_defs.append(tvar_def)
19681962
return base_type_exprs, tvar_defs, is_protocol
@@ -2855,6 +2849,9 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
28552849
with self.allow_unbound_tvars_set():
28562850
s.rvalue.accept(self)
28572851
self.basic_type_applications = old_basic_type_applications
2852+
elif self.can_possibly_be_typevarlike_declaration(s):
2853+
with self.allow_unbound_tvars_set():
2854+
s.rvalue.accept(self)
28582855
else:
28592856
s.rvalue.accept(self)
28602857

@@ -3031,6 +3028,24 @@ def can_possibly_be_type_form(self, s: AssignmentStmt) -> bool:
30313028
# Something that looks like Foo = Bar[Baz, ...]
30323029
return True
30333030

3031+
def can_possibly_be_typevarlike_declaration(self, s: AssignmentStmt) -> bool:
3032+
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
3033+
return False
3034+
if not isinstance(s.rvalue, CallExpr) or not isinstance(s.rvalue.callee, NameExpr):
3035+
return False
3036+
ref = s.rvalue.callee
3037+
ref.accept(self)
3038+
if ref.fullname not in {
3039+
"typing.TypeVar",
3040+
"typing_extensions.TypeVar",
3041+
"typing.ParamSpec",
3042+
"typing_extensions.ParamSpec",
3043+
"typing.TypeVarTuple",
3044+
"typing_extensions.TypeVarTuple",
3045+
}:
3046+
return False
3047+
return True
3048+
30343049
def is_type_ref(self, rv: Expression, bare: bool = False) -> bool:
30353050
"""Does this expression refer to a type?
30363051

mypy/typeanal.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@
3838
)
3939
from mypy.options import Options
4040
from mypy.plugin import AnalyzeTypeContext, Plugin, TypeAnalyzerPluginInterface
41-
from mypy.semanal_shared import SemanticAnalyzerCoreInterface, paramspec_args, paramspec_kwargs
41+
from mypy.semanal_shared import (
42+
SemanticAnalyzerCoreInterface,
43+
SemanticAnalyzerInterface,
44+
paramspec_args,
45+
paramspec_kwargs,
46+
)
4247
from mypy.state import state
4348
from mypy.tvar_scope import TypeVarLikeScope
4449
from mypy.types import (
@@ -2508,3 +2513,24 @@ def process_types(self, types: list[Type] | tuple[Type, ...]) -> None:
25082513
else:
25092514
for t in types:
25102515
t.accept(self)
2516+
2517+
2518+
class TypeVarDefaultTranslator(TrivialSyntheticTypeTranslator):
2519+
def __init__(self, api: SemanticAnalyzerInterface) -> None:
2520+
self.api = api
2521+
self.seen_aliases: set[TypeAliasType] | None = None
2522+
2523+
def visit_unbound_type(self, t: UnboundType) -> Type:
2524+
n = self.api.lookup_qualified(t.name, t, suppress_errors=True)
2525+
if n is not None and (type_var := self.api.tvar_scope.get_binding(n)):
2526+
return type_var
2527+
return super().visit_unbound_type(t)
2528+
2529+
def visit_type_alias_type(self, t: TypeAliasType) -> Type:
2530+
if self.seen_aliases is None:
2531+
self.seen_aliases = set()
2532+
elif t in self.seen_aliases:
2533+
return t
2534+
self.seen_aliases.add(t)
2535+
t.args = [arg.accept(self) for arg in t.args]
2536+
return t

test-data/unit/check-typevar-defaults.test

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,11 +379,12 @@ class ClassD1(Generic[T1, T2]): ...
379379

380380
[case testTypeVarDefaultsClassRecursive1]
381381
# flags: --disallow-any-generics
382-
from typing import Generic, TypeVar
382+
from typing import Generic, TypeVar, List
383383

384384
T1 = TypeVar("T1", default=str)
385385
T2 = TypeVar("T2", default=T1)
386386
T3 = TypeVar("T3", default=T2)
387+
T4 = TypeVar("T4", default=List[T1])
387388

388389
class ClassD1(Generic[T1, T2]): ...
389390

@@ -425,6 +426,24 @@ def func_d2(
425426
n = ClassD2[int, float, str]()
426427
reveal_type(n) # N: Revealed type is "__main__.ClassD2[builtins.int, builtins.float, builtins.str]"
427428

429+
class ClassD3(Generic[T1, T4]): ...
430+
431+
def func_d3(
432+
a: ClassD3,
433+
b: ClassD3[int],
434+
c: ClassD3[int, float],
435+
) -> None:
436+
reveal_type(a) # N: Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]"
437+
reveal_type(b) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]"
438+
reveal_type(c) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.float]"
439+
440+
# k = ClassD3()
441+
# reveal_type(k) # Revealed type is "__main__.ClassD3[builtins.str, builtins.list[builtins.str]]"
442+
l = ClassD3[int]()
443+
reveal_type(l) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.list[builtins.int]]"
444+
m = ClassD3[int, float]()
445+
reveal_type(m) # N: Revealed type is "__main__.ClassD3[builtins.int, builtins.float]"
446+
428447
[case testTypeVarDefaultsClassRecursiveMultipleFiles]
429448
# flags: --disallow-any-generics
430449
from typing import Generic, TypeVar

0 commit comments

Comments
 (0)