Skip to content

Commit 4073dad

Browse files
committed
Fix crash with type alias inside __init__ in incremental mode
Don't create `TypeAliasType` for aliases defined within functions, since the target can't be looked up using the full name, as we drop function symbol tables after we've finished processing them. Fixes #7281.
1 parent 389a172 commit 4073dad

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

mypy/nodes.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,15 +2796,18 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here
27962796
itself an alias), while the second cannot be subscripted because of
27972797
Python runtime limitation.
27982798
line and column: Line an column on the original alias definition.
2799+
eager: If True, immediately expand alias when referred to (useful for aliases
2800+
within functions that can't be looked up from the symbol table)
27992801
"""
28002802
__slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized',
2801-
'line', 'column', '_is_recursive')
2803+
'line', 'column', '_is_recursive', 'eager')
28022804

28032805
def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: int,
28042806
*,
28052807
alias_tvars: Optional[List[str]] = None,
28062808
no_args: bool = False,
2807-
normalized: bool = False) -> None:
2809+
normalized: bool = False,
2810+
eager: bool = False) -> None:
28082811
self._fullname = fullname
28092812
self.target = target
28102813
if alias_tvars is None:
@@ -2815,6 +2818,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column:
28152818
# This attribute is manipulated by TypeAliasType. If non-None,
28162819
# it is the cached value.
28172820
self._is_recursive = None # type: Optional[bool]
2821+
self.eager = eager
28182822
super().__init__(line, column)
28192823

28202824
@property

mypy/semanal.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2593,10 +2593,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
25932593
# Note: with the new (lazy) type alias representation we only need to set no_args to True
25942594
# if the expected number of arguments is non-zero, so that aliases like A = List work.
25952595
# However, eagerly expanding aliases like Text = str is a nice performance optimization.
2596-
no_args = isinstance(res, Instance) and not res.args # type: ignore
2596+
no_args = isinstance(res, Instance) and not res.args # type: ignore[misc]
25972597
fix_instance_types(res, self.fail, self.note, self.options.python_version)
2598-
alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column,
2599-
alias_tvars=alias_tvars, no_args=no_args)
2598+
# Aliases defined within functions can't be accessed outside
2599+
# the function, since the symbol table will no longer
2600+
# exist. Work around by expanding them eagerly when used.
2601+
eager = self.is_func_scope()
2602+
alias_node = TypeAlias(res,
2603+
self.qualified_name(lvalue.name),
2604+
s.line,
2605+
s.column,
2606+
alias_tvars=alias_tvars,
2607+
no_args=no_args,
2608+
eager=eager)
26002609
if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)`
26012610
s.rvalue.analyzed = TypeAliasExpr(alias_node)
26022611
s.rvalue.analyzed.line = s.line

mypy/typeanal.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
247247
python_version=self.options.python_version,
248248
use_generic_error=True,
249249
unexpanded_type=t)
250+
if node.eager:
251+
# TODO: Generate error if recursive (once we have recursive types)
252+
res = get_proper_type(res)
250253
return res
251254
elif isinstance(node, TypeInfo):
252255
return self.analyze_type_with_type_info(node, t.args, t)

test-data/unit/check-incremental.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5532,3 +5532,32 @@ def g() -> None:
55325532
n: NT = NT(y='x')
55335533

55345534
[builtins fixtures/tuple.pyi]
5535+
5536+
[case testIncrementalNestedTypeAlias]
5537+
import a
5538+
5539+
[file a.py]
5540+
import b
5541+
5542+
[file a.py.2]
5543+
import b
5544+
reveal_type(b.C().x)
5545+
reveal_type(b.D().x)
5546+
5547+
[file b.py]
5548+
from typing import List
5549+
5550+
class C:
5551+
def __init__(self) -> None:
5552+
Alias = List[int]
5553+
self.x = [] # type: Alias
5554+
5555+
class D:
5556+
def __init__(self) -> None:
5557+
Alias = List[str]
5558+
self.x = [] # type: Alias
5559+
5560+
[builtins fixtures/list.pyi]
5561+
[out2]
5562+
tmp/a.py:2: note: Revealed type is "builtins.list[builtins.int]"
5563+
tmp/a.py:3: note: Revealed type is "builtins.list[builtins.str]"

0 commit comments

Comments
 (0)