diff --git a/mypy/nodes.py b/mypy/nodes.py index 9d83e4a3be1e..49b6a0cd827f 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2796,15 +2796,18 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here itself an alias), while the second cannot be subscripted because of Python runtime limitation. line and column: Line an column on the original alias definition. + eager: If True, immediately expand alias when referred to (useful for aliases + within functions that can't be looked up from the symbol table) """ __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized', - 'line', 'column', '_is_recursive') + 'line', 'column', '_is_recursive', 'eager') def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: int, *, alias_tvars: Optional[List[str]] = None, no_args: bool = False, - normalized: bool = False) -> None: + normalized: bool = False, + eager: bool = False) -> None: self._fullname = fullname self.target = target if alias_tvars is None: @@ -2815,6 +2818,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: # This attribute is manipulated by TypeAliasType. If non-None, # it is the cached value. self._is_recursive = None # type: Optional[bool] + self.eager = eager super().__init__(line, column) @property diff --git a/mypy/semanal.py b/mypy/semanal.py index 0ca583eadc03..f157c795d939 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2593,10 +2593,19 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: # Note: with the new (lazy) type alias representation we only need to set no_args to True # if the expected number of arguments is non-zero, so that aliases like A = List work. # However, eagerly expanding aliases like Text = str is a nice performance optimization. - no_args = isinstance(res, Instance) and not res.args # type: ignore + no_args = isinstance(res, Instance) and not res.args # type: ignore[misc] fix_instance_types(res, self.fail, self.note, self.options.python_version) - alias_node = TypeAlias(res, self.qualified_name(lvalue.name), s.line, s.column, - alias_tvars=alias_tvars, no_args=no_args) + # Aliases defined within functions can't be accessed outside + # the function, since the symbol table will no longer + # exist. Work around by expanding them eagerly when used. + eager = self.is_func_scope() + alias_node = TypeAlias(res, + self.qualified_name(lvalue.name), + s.line, + s.column, + alias_tvars=alias_tvars, + no_args=no_args, + eager=eager) if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` s.rvalue.analyzed = TypeAliasExpr(alias_node) s.rvalue.analyzed.line = s.line diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 7917ec1ba1c1..09824dd93e6c 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -247,6 +247,9 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool) python_version=self.options.python_version, use_generic_error=True, unexpanded_type=t) + if node.eager: + # TODO: Generate error if recursive (once we have recursive types) + res = get_proper_type(res) return res elif isinstance(node, TypeInfo): return self.analyze_type_with_type_info(node, t.args, t) diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 372c3e587991..ae39599a18c6 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -5532,3 +5532,32 @@ def g() -> None: n: NT = NT(y='x') [builtins fixtures/tuple.pyi] + +[case testIncrementalNestedTypeAlias] +import a + +[file a.py] +import b + +[file a.py.2] +import b +reveal_type(b.C().x) +reveal_type(b.D().x) + +[file b.py] +from typing import List + +class C: + def __init__(self) -> None: + Alias = List[int] + self.x = [] # type: Alias + +class D: + def __init__(self) -> None: + Alias = List[str] + self.x = [] # type: Alias + +[builtins fixtures/list.pyi] +[out2] +tmp/a.py:2: note: Revealed type is "builtins.list[builtins.int]" +tmp/a.py:3: note: Revealed type is "builtins.list[builtins.str]"