Skip to content

Commit 6d8d50c

Browse files
authored
Refactor type aliases (#5221)
This refactors type aliases by introducing a dedicated symbol node `TypeAlias`, and removing all _ad-hoc_ attributes from `SymbolTableNode`. This simplifies the overall logic, and also the fine grained dependency tracking (since now aliases have names). Most of the logic is straightforward. Few comments: * It looks like `.type_override` was also used for some overload hacks, I replaced those with a proper fix. * Somehow my changes exposed a small `ImportedName` bug, I can't find a simple repro (typeshed check test was failing), but the fix is straightforward. * In some tests revealed type don't have `*` anymore, I think this is more consistent. * There few other small changes in tests, I think they are harmless.
1 parent eb3a3dd commit 6d8d50c

30 files changed

+822
-456
lines changed

mypy/build.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2856,6 +2856,13 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
28562856
# If the former, parse_file() is a no-op.
28572857
graph[id].parse_file()
28582858
graph[id].fix_suppressed_dependencies(graph)
2859+
if 'typing' in scc:
2860+
# For historical reasons we need to manually add typing aliases
2861+
# for built-in generic collections, see docstring of
2862+
# SemanticAnalyzerPass2.add_builtin_aliases for details.
2863+
typing_mod = graph['typing'].tree
2864+
assert typing_mod, "The typing module was not parsed"
2865+
manager.semantic_analyzer.add_builtin_aliases(typing_mod)
28592866
for id in fresh:
28602867
graph[id].fix_cross_refs()
28612868
for id in stale:

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,10 +1441,10 @@ def check_compatibility(self, name: str, base1: TypeInfo,
14411441
first = base1[name]
14421442
second = base2[name]
14431443
first_type = first.type
1444-
if first_type is None and isinstance(first.node, FuncDef):
1444+
if first_type is None and isinstance(first.node, FuncBase):
14451445
first_type = self.function_type(first.node)
14461446
second_type = second.type
1447-
if second_type is None and isinstance(second.node, FuncDef):
1447+
if second_type is None and isinstance(second.node, FuncBase):
14481448
second_type = self.function_type(second.node)
14491449
# TODO: What if some classes are generic?
14501450
if (isinstance(first_type, FunctionLike) and
@@ -2835,7 +2835,7 @@ def intersect_instance_callable(self, typ: Instance, callable_type: CallableType
28352835
func_def = FuncDef('__call__', [], Block([]), callable_type)
28362836
func_def._fullname = cdef.fullname + '.__call__'
28372837
func_def.info = info
2838-
info.names['__call__'] = SymbolTableNode(MDEF, func_def, callable_type)
2838+
info.names['__call__'] = SymbolTableNode(MDEF, func_def)
28392839

28402840
cur_module.names[gen_name] = SymbolTableNode(GDEF, info)
28412841

mypy/checkexpr.py

Lines changed: 106 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional, Iterable, Sequence, Any
55

66
from mypy.errors import report_internal_error
7-
from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any, set_any_tvars
7+
from mypy.typeanal import (
8+
has_any_from_unimported_type, check_for_explicit_any, set_any_tvars, expand_type_alias
9+
)
810
from mypy.types import (
911
Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef,
1012
TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType,
@@ -21,8 +23,8 @@
2123
ConditionalExpr, ComparisonExpr, TempNode, SetComprehension,
2224
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
2325
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
24-
TypeAliasExpr, BackquoteExpr, EnumCallExpr,
25-
ARG_POS, ARG_OPT, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE
26+
TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, ClassDef, Block, SymbolTable,
27+
ARG_POS, ARG_OPT, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, LITERAL_TYPE, REVEAL_TYPE
2628
)
2729
from mypy.literals import literal
2830
from mypy import nodes
@@ -181,6 +183,14 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type:
181183
result = self.named_type('builtins.object')
182184
elif isinstance(node, Decorator):
183185
result = self.analyze_var_ref(node.var, e)
186+
elif isinstance(node, TypeAlias):
187+
# Something that refers to a type alias appears in runtime context.
188+
# Note that we suppress bogus errors for alias redefinitions,
189+
# they are already reported in semanal.py.
190+
result = self.alias_type_in_runtime_context(node.target, node.alias_tvars,
191+
node.no_args, e,
192+
alias_definition=e.is_alias_rvalue
193+
or lvalue)
184194
else:
185195
# Unknown reference; use any type implicitly to avoid
186196
# generating extra type errors.
@@ -220,8 +230,8 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
220230
pass
221231
if ((isinstance(typ, IndexExpr)
222232
and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr)))
223-
# node.kind == TYPE_ALIAS only for aliases like It = Iterable[int].
224-
or (isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS)):
233+
or (isinstance(typ, NameExpr) and node and
234+
isinstance(node.node, TypeAlias) and not node.node.no_args)):
225235
self.msg.type_arguments_not_allowed(e)
226236
if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo):
227237
if typ.node.typeddict_type:
@@ -2094,60 +2104,119 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type:
20942104
return NoneTyp()
20952105

20962106
def visit_type_application(self, tapp: TypeApplication) -> Type:
2097-
"""Type check a type application (expr[type, ...])."""
2098-
tp = self.accept(tapp.expr)
2099-
if isinstance(tp, CallableType):
2100-
if not tp.is_type_obj():
2107+
"""Type check a type application (expr[type, ...]).
2108+
2109+
There are two different options here, depending on whether expr refers
2110+
to a type alias or directly to a generic class. In the first case we need
2111+
to use a dedicated function typeanal.expand_type_aliases. This
2112+
is due to the fact that currently type aliases machinery uses
2113+
unbound type variables, while normal generics use bound ones;
2114+
see TypeAlias docstring for more details.
2115+
"""
2116+
if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias):
2117+
# Subscription of a (generic) alias in runtime context, expand the alias.
2118+
target = tapp.expr.node.target
2119+
all_vars = tapp.expr.node.alias_tvars
2120+
item = expand_type_alias(target, all_vars, tapp.types, self.chk.fail,
2121+
tapp.expr.node.no_args, tapp)
2122+
if isinstance(item, Instance):
2123+
tp = type_object_type(item.type, self.named_type)
2124+
return self.apply_type_arguments_to_callable(tp, item.args, tapp)
2125+
else:
21012126
self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp)
2102-
if len(tp.variables) != len(tapp.types):
2103-
self.msg.incompatible_type_application(len(tp.variables),
2104-
len(tapp.types), tapp)
21052127
return AnyType(TypeOfAny.from_error)
2106-
return self.apply_generic_arguments(tp, tapp.types, tapp)
2107-
elif isinstance(tp, Overloaded):
2128+
# Type application of a normal generic class in runtime context.
2129+
# This is typically used as `x = G[int]()`.
2130+
tp = self.accept(tapp.expr)
2131+
if isinstance(tp, (CallableType, Overloaded)):
21082132
if not tp.is_type_obj():
21092133
self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp)
2110-
for item in tp.items():
2111-
if len(item.variables) != len(tapp.types):
2112-
self.msg.incompatible_type_application(len(item.variables),
2113-
len(tapp.types), tapp)
2114-
return AnyType(TypeOfAny.from_error)
2115-
return Overloaded([self.apply_generic_arguments(item, tapp.types, tapp)
2116-
for item in tp.items()])
2134+
return self.apply_type_arguments_to_callable(tp, tapp.types, tapp)
21172135
if isinstance(tp, AnyType):
21182136
return AnyType(TypeOfAny.from_another_any, source_any=tp)
21192137
return AnyType(TypeOfAny.special_form)
21202138

21212139
def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type:
2122-
"""Get type of a type alias (could be generic) in a runtime expression."""
2123-
if isinstance(alias.type, Instance) and alias.type.invalid:
2140+
"""Right hand side of a type alias definition.
2141+
2142+
It has the same type as if the alias itself was used in a runtime context.
2143+
For example, here:
2144+
2145+
A = reveal_type(List[T])
2146+
reveal_type(A)
2147+
2148+
both `reveal_type` instances will reveal the same type `def (...) -> builtins.list[Any]`.
2149+
Note that type variables are implicitly substituted with `Any`.
2150+
"""
2151+
return self.alias_type_in_runtime_context(alias.type, alias.tvars, alias.no_args,
2152+
alias, alias_definition=True)
2153+
2154+
def alias_type_in_runtime_context(self, target: Type, alias_tvars: List[str],
2155+
no_args: bool, ctx: Context,
2156+
*,
2157+
alias_definition: bool = False) -> Type:
2158+
"""Get type of a type alias (could be generic) in a runtime expression.
2159+
2160+
Note that this function can be called only if the alias appears _not_
2161+
as a target of type application, which is treated separately in the
2162+
visit_type_application method. Some examples where this method is called are
2163+
casts and instantiation:
2164+
2165+
class LongName(Generic[T]): ...
2166+
A = LongName[int]
2167+
2168+
x = A()
2169+
y = cast(A, ...)
2170+
"""
2171+
if isinstance(target, Instance) and target.invalid:
21242172
# An invalid alias, error already has been reported
21252173
return AnyType(TypeOfAny.from_error)
2126-
item = alias.type
2127-
if not alias.in_runtime:
2128-
# We don't replace TypeVar's with Any for alias used as Alias[T](42).
2129-
item = set_any_tvars(item, alias.tvars, alias.line, alias.column)
2174+
# If this is a generic alias, we set all variables to `Any`.
2175+
# For example:
2176+
# A = List[Tuple[T, T]]
2177+
# x = A() <- same as List[Tuple[Any, Any]], see PEP 484.
2178+
item = set_any_tvars(target, alias_tvars, ctx.line, ctx.column)
21302179
if isinstance(item, Instance):
21312180
# Normally we get a callable type (or overloaded) with .is_type_obj() true
21322181
# representing the class's constructor
21332182
tp = type_object_type(item.type, self.named_type)
2183+
if no_args:
2184+
return tp
2185+
return self.apply_type_arguments_to_callable(tp, item.args, ctx)
2186+
elif (isinstance(item, TupleType) and
2187+
# Tuple[str, int]() fails at runtime, only named tuples and subclasses work.
2188+
item.fallback.type.fullname() != 'builtins.tuple'):
2189+
return type_object_type(item.fallback.type, self.named_type)
2190+
elif isinstance(item, AnyType):
2191+
return AnyType(TypeOfAny.from_another_any, source_any=item)
21342192
else:
2135-
# This type is invalid in most runtime contexts
2136-
# and corresponding an error will be reported.
2137-
return alias.fallback
2193+
if alias_definition:
2194+
return AnyType(TypeOfAny.special_form)
2195+
# This type is invalid in most runtime contexts.
2196+
self.msg.alias_invalid_in_runtime_context(item, ctx)
2197+
return AnyType(TypeOfAny.from_error)
2198+
2199+
def apply_type_arguments_to_callable(self, tp: Type, args: List[Type], ctx: Context) -> Type:
2200+
"""Apply type arguments to a generic callable type coming from a type object.
2201+
2202+
This will first perform type arguments count checks, report the
2203+
error as needed, and return the correct kind of Any. As a special
2204+
case this returns Any for non-callable types, because if type object type
2205+
is not callable, then an error should be already reported.
2206+
"""
21382207
if isinstance(tp, CallableType):
2139-
if len(tp.variables) != len(item.args):
2208+
if len(tp.variables) != len(args):
21402209
self.msg.incompatible_type_application(len(tp.variables),
2141-
len(item.args), item)
2210+
len(args), ctx)
21422211
return AnyType(TypeOfAny.from_error)
2143-
return self.apply_generic_arguments(tp, item.args, item)
2144-
elif isinstance(tp, Overloaded):
2212+
return self.apply_generic_arguments(tp, args, ctx)
2213+
if isinstance(tp, Overloaded):
21452214
for it in tp.items():
2146-
if len(it.variables) != len(item.args):
2215+
if len(it.variables) != len(args):
21472216
self.msg.incompatible_type_application(len(it.variables),
2148-
len(item.args), item)
2217+
len(args), ctx)
21492218
return AnyType(TypeOfAny.from_error)
2150-
return Overloaded([self.apply_generic_arguments(it, item.args, item)
2219+
return Overloaded([self.apply_generic_arguments(it, args, ctx)
21512220
for it in tp.items()])
21522221
return AnyType(TypeOfAny.special_form)
21532222

mypy/checkmember.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
)
1010
from mypy.nodes import (
1111
TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr,
12-
ARG_POS, ARG_STAR, ARG_STAR2,
13-
Decorator, OverloadedFuncDef,
12+
ARG_POS, ARG_STAR, ARG_STAR2, Decorator, OverloadedFuncDef, TypeAlias
1413
)
1514
from mypy.messages import MessageBuilder
1615
from mypy.maptype import map_instance_to_supertype
1716
from mypy.expandtype import expand_type_by_instance, expand_type, freshen_function_type_vars
1817
from mypy.infer import infer_type_arguments
1918
from mypy.typevars import fill_typevars
2019
from mypy.plugin import AttributeContext
20+
from mypy.typeanal import set_any_tvars
2121
from mypy import messages
2222
from mypy import subtypes
2323
from mypy import meet
@@ -249,6 +249,17 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
249249
v = Var(name, type=type_object_type(vv, builtin_type))
250250
v.info = info
251251

252+
if isinstance(vv, TypeAlias) and isinstance(vv.target, Instance):
253+
# Similar to the above TypeInfo case, we allow using
254+
# qualified type aliases in runtime context if it refers to an
255+
# instance type. For example:
256+
# class C:
257+
# A = List[int]
258+
# x = C.A() <- this is OK
259+
typ = instance_alias_type(vv, builtin_type)
260+
v = Var(name, type=typ)
261+
v.info = info
262+
252263
if isinstance(v, Var):
253264
return analyze_var(name, v, itype, info, node, is_lvalue, msg,
254265
original_type, not_ready_callback, chk=chk)
@@ -291,6 +302,19 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
291302
return msg.has_no_attr(original_type, itype, name, node)
292303

293304

305+
def instance_alias_type(alias: TypeAlias,
306+
builtin_type: Callable[[str], Instance]) -> Type:
307+
"""Type of a type alias node targeting an instance, when appears in runtime context.
308+
309+
As usual, we first erase any unbound type variables to Any.
310+
"""
311+
assert isinstance(alias.target, Instance), "Must be called only with aliases to classes"
312+
target = set_any_tvars(alias.target, alias.alias_tvars, alias.line, alias.column)
313+
assert isinstance(target, Instance)
314+
tp = type_object_type(target.type, builtin_type)
315+
return expand_type_by_instance(tp, target)
316+
317+
294318
def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context,
295319
is_lvalue: bool, msg: MessageBuilder, original_type: Type,
296320
not_ready_callback: Callable[[str, Context], None], *,
@@ -430,7 +454,7 @@ def analyze_class_attribute_access(itype: Instance,
430454
return None
431455

432456
is_decorated = isinstance(node.node, Decorator)
433-
is_method = is_decorated or isinstance(node.node, FuncDef)
457+
is_method = is_decorated or isinstance(node.node, FuncBase)
434458
if is_lvalue:
435459
if is_method:
436460
msg.cant_assign_to_method(context)
@@ -467,6 +491,9 @@ def analyze_class_attribute_access(itype: Instance,
467491
# Reference to a module object.
468492
return builtin_type('types.ModuleType')
469493

494+
if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance):
495+
return instance_alias_type(node.node, builtin_type)
496+
470497
if is_decorated:
471498
assert isinstance(node.node, Decorator)
472499
if node.node.type:

mypy/fixup.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from mypy.nodes import (
66
MypyFile, SymbolNode, SymbolTable, SymbolTableNode,
77
TypeInfo, FuncDef, OverloadedFuncDef, Decorator, Var,
8-
TypeVarExpr, ClassDef, Block, TYPE_ALIAS
8+
TypeVarExpr, ClassDef, Block, TypeAlias,
99
)
1010
from mypy.types import (
1111
CallableType, Instance, Overloaded, TupleType, TypedDictType,
@@ -76,26 +76,17 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None:
7676
self.quick_and_dirty)
7777
if stnode is not None:
7878
value.node = stnode.node
79-
value.type_override = stnode.type_override
80-
if (self.quick_and_dirty and value.kind == TYPE_ALIAS and
81-
stnode.type_override is None):
82-
value.type_override = Instance(stale_info(self.modules), [])
83-
value.alias_tvars = stnode.alias_tvars or []
8479
elif not self.quick_and_dirty:
8580
assert stnode is not None, "Could not find cross-ref %s" % (cross_ref,)
8681
else:
8782
# We have a missing crossref in quick mode, need to put something
8883
value.node = stale_info(self.modules)
89-
if value.kind == TYPE_ALIAS:
90-
value.type_override = Instance(stale_info(self.modules), [])
9184
else:
9285
if isinstance(value.node, TypeInfo):
9386
# TypeInfo has no accept(). TODO: Add it?
9487
self.visit_type_info(value.node)
9588
elif value.node is not None:
9689
value.node.accept(self)
97-
if value.type_override is not None:
98-
value.type_override.accept(self.type_fixer)
9990

10091
def visit_func_def(self, func: FuncDef) -> None:
10192
if self.current_info is not None:
@@ -140,6 +131,9 @@ def visit_var(self, v: Var) -> None:
140131
if v.type is not None:
141132
v.type.accept(self.type_fixer)
142133

134+
def visit_type_alias(self, a: TypeAlias) -> None:
135+
a.target.accept(self.type_fixer)
136+
143137

144138
class TypeFixer(TypeVisitor[None]):
145139
def __init__(self, modules: Dict[str, MypyFile], quick_and_dirty: bool) -> None:

0 commit comments

Comments
 (0)