Skip to content

Commit 3faf87d

Browse files
committed
Fix crash on typevar with forward ref used in other module
1 parent dec6528 commit 3faf87d

File tree

5 files changed

+105
-9
lines changed

5 files changed

+105
-9
lines changed

mypy/plugins/proper_plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def is_special_target(right: ProperType) -> bool:
108108
"mypy.types.RequiredType",
109109
"mypy.types.ReadOnlyType",
110110
"mypy.types.TypeGuardedType",
111+
"mypy.types.PlaceholderType",
111112
):
112113
# Special case: these are not valid targets for a type alias and thus safe.
113114
# TODO: introduce a SyntheticType base to simplify this?

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4941,7 +4941,7 @@ def get_typevarlike_argument(
49414941
)
49424942
if analyzed is None:
49434943
# Type variables are special: we need to place them in the symbol table
4944-
# soon, even if upper bound is not ready yet. Otherwise avoiding
4944+
# soon, even if upper bound is not ready yet. Otherwise, avoiding
49454945
# a "deadlock" in this common pattern would be tricky:
49464946
# T = TypeVar('T', bound=Custom[Any])
49474947
# class Custom(Generic[T]):

mypy/semanal_typeargs.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,12 @@ def validate_args(
176176
code=codes.VALID_TYPE,
177177
)
178178
continue
179+
if self.in_type_alias_expr and isinstance(arg, TypeVarType):
180+
# Type aliases are allowed to use unconstrained type variables
181+
# error will be checked at substitution point.
182+
continue
179183
if tvar.values:
180184
if isinstance(arg, TypeVarType):
181-
if self.in_type_alias_expr:
182-
# Type aliases are allowed to use unconstrained type variables
183-
# error will be checked at substitution point.
184-
continue
185185
arg_values = arg.values
186186
if not arg_values:
187187
is_error = True
@@ -205,10 +205,6 @@ def validate_args(
205205
and upper_bound.type.fullname == "builtins.object"
206206
)
207207
if not object_upper_bound and not is_subtype(arg, upper_bound):
208-
if self.in_type_alias_expr and isinstance(arg, TypeVarType):
209-
# Type aliases are allowed to use unconstrained type variables
210-
# error will be checked at substitution point.
211-
continue
212208
is_error = True
213209
self.fail(
214210
message_registry.INVALID_TYPEVAR_ARG_BOUND.format(

mypy/typeanal.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,15 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
346346
if hook is not None:
347347
return hook(AnalyzeTypeContext(t, t, self))
348348
tvar_def = self.tvar_scope.get_binding(sym)
349+
if tvar_def is not None:
350+
# We need to cover special-case explained in get_typevarlike_argument() here,
351+
# since otherwise the deferral will not be triggered if the type variable is
352+
# used in a different module. Using isinstance() should be safe for this purpose.
353+
tvar_params = [tvar_def.upper_bound, tvar_def.default]
354+
if isinstance(tvar_def, TypeVarType):
355+
tvar_params += tvar_def.values
356+
if any(isinstance(tp, PlaceholderType) for tp in tvar_params):
357+
self.api.defer()
349358
if isinstance(sym.node, ParamSpecExpr):
350359
if tvar_def is None:
351360
if self.allow_unbound_tvars:

test-data/unit/check-type-aliases.test

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,3 +1351,93 @@ reveal_type(D(x="asdf")) # E: No overload variant of "dict" matches argument ty
13511351
# N: def __init__(self, arg: Iterable[tuple[str, int]], **kwargs: int) -> dict[str, int] \
13521352
# N: Revealed type is "Any"
13531353
[builtins fixtures/dict.pyi]
1354+
1355+
[case testTypeAliasesInCyclicImport1]
1356+
import p.aliases
1357+
1358+
[file p/__init__.py]
1359+
[file p/aliases.py]
1360+
from typing_extensions import TypeAlias
1361+
from .defs import C, Alias1
1362+
1363+
Alias2: TypeAlias = Alias1[C]
1364+
1365+
[file p/defs.py]
1366+
from typing import TypeVar
1367+
from typing_extensions import TypeAlias
1368+
import p.aliases
1369+
1370+
C = TypeVar("C", bound="SomeClass")
1371+
Alias1: TypeAlias = C
1372+
1373+
class SomeClass:
1374+
pass
1375+
[builtins fixtures/tuple.pyi]
1376+
[typing fixtures/typing-full.pyi]
1377+
1378+
[case testTypeAliasesInCyclicImport2]
1379+
import p.aliases
1380+
1381+
[file p/__init__.py]
1382+
[file p/aliases.py]
1383+
from typing_extensions import TypeAlias
1384+
from .defs import C, Alias1
1385+
1386+
Alias2: TypeAlias = Alias1[C]
1387+
1388+
[file p/defs.py]
1389+
from typing import TypeVar, Union
1390+
from typing_extensions import TypeAlias
1391+
import p.aliases
1392+
1393+
C = TypeVar("C", bound="SomeClass")
1394+
Alias1: TypeAlias = Union[C, int]
1395+
1396+
class SomeClass:
1397+
pass
1398+
[builtins fixtures/tuple.pyi]
1399+
1400+
[case testTypeAliasesInCyclicImport3]
1401+
import p.aliases
1402+
1403+
[file p/__init__.py]
1404+
[file p/aliases.py]
1405+
from typing_extensions import TypeAlias
1406+
from .defs import C, Alias1
1407+
1408+
Alias2: TypeAlias = Alias1[C]
1409+
1410+
[file p/defs.py]
1411+
from typing import TypeVar
1412+
from typing_extensions import TypeAlias
1413+
import p.aliases
1414+
1415+
C = TypeVar("C", bound="list[SomeClass]")
1416+
Alias1: TypeAlias = C
1417+
1418+
class SomeClass:
1419+
pass
1420+
[builtins fixtures/tuple.pyi]
1421+
[typing fixtures/typing-full.pyi]
1422+
1423+
[case testTypeAliasesInCyclicImport4]
1424+
import p.aliases
1425+
1426+
[file p/__init__.py]
1427+
[file p/aliases.py]
1428+
from typing_extensions import TypeAlias
1429+
from .defs import C, Alias1
1430+
1431+
Alias2: TypeAlias = Alias1[C]
1432+
1433+
[file p/defs.py]
1434+
from typing import TypeVar, Union
1435+
from typing_extensions import TypeAlias
1436+
import p.aliases
1437+
1438+
C = TypeVar("C", bound="list[SomeClass]")
1439+
Alias1: TypeAlias = Union[C, int]
1440+
1441+
class SomeClass:
1442+
pass
1443+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)