diff --git a/mypy/build.py b/mypy/build.py index 0ee6a27eca0f4..4925dba66efac 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2856,6 +2856,13 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No # If the former, parse_file() is a no-op. graph[id].parse_file() graph[id].fix_suppressed_dependencies(graph) + if 'typing' in scc: + # For historical reasons we need to manually add typing aliases + # for built-in generic collections, see docstring of + # SemanticAnalyzerPass2.add_builtin_aliases for details. + typing_mod = graph['typing'].tree + assert typing_mod, "The typing module was not parsed" + manager.semantic_analyzer.add_builtin_aliases(typing_mod) for id in fresh: graph[id].fix_cross_refs() for id in stale: diff --git a/mypy/checker.py b/mypy/checker.py index f009a668b2be1..2ee674b992aff 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1441,10 +1441,10 @@ def check_compatibility(self, name: str, base1: TypeInfo, first = base1[name] second = base2[name] first_type = first.type - if first_type is None and isinstance(first.node, FuncDef): + if first_type is None and isinstance(first.node, FuncBase): first_type = self.function_type(first.node) second_type = second.type - if second_type is None and isinstance(second.node, FuncDef): + if second_type is None and isinstance(second.node, FuncBase): second_type = self.function_type(second.node) # TODO: What if some classes are generic? if (isinstance(first_type, FunctionLike) and @@ -2835,7 +2835,7 @@ def intersect_instance_callable(self, typ: Instance, callable_type: CallableType func_def = FuncDef('__call__', [], Block([]), callable_type) func_def._fullname = cdef.fullname + '.__call__' func_def.info = info - info.names['__call__'] = SymbolTableNode(MDEF, func_def, callable_type) + info.names['__call__'] = SymbolTableNode(MDEF, func_def) cur_module.names[gen_name] = SymbolTableNode(GDEF, info) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6669dc7cd627d..bf36f83985fe6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4,7 +4,9 @@ from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional, Iterable, Sequence, Any from mypy.errors import report_internal_error -from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any, set_any_tvars +from mypy.typeanal import ( + has_any_from_unimported_type, check_for_explicit_any, set_any_tvars, expand_type_alias +) from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef, TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, @@ -21,8 +23,8 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, - TypeAliasExpr, BackquoteExpr, EnumCallExpr, - ARG_POS, ARG_OPT, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE + TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, ClassDef, Block, SymbolTable, + ARG_POS, ARG_OPT, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, LITERAL_TYPE, REVEAL_TYPE ) from mypy.literals import literal from mypy import nodes @@ -181,6 +183,14 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = self.named_type('builtins.object') elif isinstance(node, Decorator): result = self.analyze_var_ref(node.var, e) + elif isinstance(node, TypeAlias): + # Something that refers to a type alias appears in runtime context. + # Note that we suppress bogus errors for alias redefinitions, + # they are already reported in semanal.py. + result = self.alias_type_in_runtime_context(node.target, node.alias_tvars, + node.no_args, e, + alias_definition=e.is_alias_rvalue + or lvalue) else: # Unknown reference; use any type implicitly to avoid # generating extra type errors. @@ -220,8 +230,8 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: pass if ((isinstance(typ, IndexExpr) and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))) - # node.kind == TYPE_ALIAS only for aliases like It = Iterable[int]. - or (isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS)): + or (isinstance(typ, NameExpr) and node and + isinstance(node.node, TypeAlias) and not node.node.no_args)): self.msg.type_arguments_not_allowed(e) if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo): if typ.node.typeddict_type: @@ -2094,60 +2104,119 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type: return NoneTyp() def visit_type_application(self, tapp: TypeApplication) -> Type: - """Type check a type application (expr[type, ...]).""" - tp = self.accept(tapp.expr) - if isinstance(tp, CallableType): - if not tp.is_type_obj(): + """Type check a type application (expr[type, ...]). + + There are two different options here, depending on whether expr refers + to a type alias or directly to a generic class. In the first case we need + to use a dedicated function typeanal.expand_type_aliases. This + is due to the fact that currently type aliases machinery uses + unbound type variables, while normal generics use bound ones; + see TypeAlias docstring for more details. + """ + if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): + # Subscription of a (generic) alias in runtime context, expand the alias. + target = tapp.expr.node.target + all_vars = tapp.expr.node.alias_tvars + item = expand_type_alias(target, all_vars, tapp.types, self.chk.fail, + tapp.expr.node.no_args, tapp) + if isinstance(item, Instance): + tp = type_object_type(item.type, self.named_type) + return self.apply_type_arguments_to_callable(tp, item.args, tapp) + else: self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) - if len(tp.variables) != len(tapp.types): - self.msg.incompatible_type_application(len(tp.variables), - len(tapp.types), tapp) return AnyType(TypeOfAny.from_error) - return self.apply_generic_arguments(tp, tapp.types, tapp) - elif isinstance(tp, Overloaded): + # Type application of a normal generic class in runtime context. + # This is typically used as `x = G[int]()`. + tp = self.accept(tapp.expr) + if isinstance(tp, (CallableType, Overloaded)): if not tp.is_type_obj(): self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) - for item in tp.items(): - if len(item.variables) != len(tapp.types): - self.msg.incompatible_type_application(len(item.variables), - len(tapp.types), tapp) - return AnyType(TypeOfAny.from_error) - return Overloaded([self.apply_generic_arguments(item, tapp.types, tapp) - for item in tp.items()]) + return self.apply_type_arguments_to_callable(tp, tapp.types, tapp) if isinstance(tp, AnyType): return AnyType(TypeOfAny.from_another_any, source_any=tp) return AnyType(TypeOfAny.special_form) def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: - """Get type of a type alias (could be generic) in a runtime expression.""" - if isinstance(alias.type, Instance) and alias.type.invalid: + """Right hand side of a type alias definition. + + It has the same type as if the alias itself was used in a runtime context. + For example, here: + + A = reveal_type(List[T]) + reveal_type(A) + + both `reveal_type` instances will reveal the same type `def (...) -> builtins.list[Any]`. + Note that type variables are implicitly substituted with `Any`. + """ + return self.alias_type_in_runtime_context(alias.type, alias.tvars, alias.no_args, + alias, alias_definition=True) + + def alias_type_in_runtime_context(self, target: Type, alias_tvars: List[str], + no_args: bool, ctx: Context, + *, + alias_definition: bool = False) -> Type: + """Get type of a type alias (could be generic) in a runtime expression. + + Note that this function can be called only if the alias appears _not_ + as a target of type application, which is treated separately in the + visit_type_application method. Some examples where this method is called are + casts and instantiation: + + class LongName(Generic[T]): ... + A = LongName[int] + + x = A() + y = cast(A, ...) + """ + if isinstance(target, Instance) and target.invalid: # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) - item = alias.type - if not alias.in_runtime: - # We don't replace TypeVar's with Any for alias used as Alias[T](42). - item = set_any_tvars(item, alias.tvars, alias.line, alias.column) + # If this is a generic alias, we set all variables to `Any`. + # For example: + # A = List[Tuple[T, T]] + # x = A() <- same as List[Tuple[Any, Any]], see PEP 484. + item = set_any_tvars(target, alias_tvars, ctx.line, ctx.column) if isinstance(item, Instance): # Normally we get a callable type (or overloaded) with .is_type_obj() true # representing the class's constructor tp = type_object_type(item.type, self.named_type) + if no_args: + return tp + return self.apply_type_arguments_to_callable(tp, item.args, ctx) + elif (isinstance(item, TupleType) and + # Tuple[str, int]() fails at runtime, only named tuples and subclasses work. + item.fallback.type.fullname() != 'builtins.tuple'): + return type_object_type(item.fallback.type, self.named_type) + elif isinstance(item, AnyType): + return AnyType(TypeOfAny.from_another_any, source_any=item) else: - # This type is invalid in most runtime contexts - # and corresponding an error will be reported. - return alias.fallback + if alias_definition: + return AnyType(TypeOfAny.special_form) + # This type is invalid in most runtime contexts. + self.msg.alias_invalid_in_runtime_context(item, ctx) + return AnyType(TypeOfAny.from_error) + + def apply_type_arguments_to_callable(self, tp: Type, args: List[Type], ctx: Context) -> Type: + """Apply type arguments to a generic callable type coming from a type object. + + This will first perform type arguments count checks, report the + error as needed, and return the correct kind of Any. As a special + case this returns Any for non-callable types, because if type object type + is not callable, then an error should be already reported. + """ if isinstance(tp, CallableType): - if len(tp.variables) != len(item.args): + if len(tp.variables) != len(args): self.msg.incompatible_type_application(len(tp.variables), - len(item.args), item) + len(args), ctx) return AnyType(TypeOfAny.from_error) - return self.apply_generic_arguments(tp, item.args, item) - elif isinstance(tp, Overloaded): + return self.apply_generic_arguments(tp, args, ctx) + if isinstance(tp, Overloaded): for it in tp.items(): - if len(it.variables) != len(item.args): + if len(it.variables) != len(args): self.msg.incompatible_type_application(len(it.variables), - len(item.args), item) + len(args), ctx) return AnyType(TypeOfAny.from_error) - return Overloaded([self.apply_generic_arguments(it, item.args, item) + return Overloaded([self.apply_generic_arguments(it, args, ctx) for it in tp.items()]) return AnyType(TypeOfAny.special_form) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0c0a3c5af5d58..ef9fa2b20c7c4 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -9,8 +9,7 @@ ) from mypy.nodes import ( TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr, - ARG_POS, ARG_STAR, ARG_STAR2, - Decorator, OverloadedFuncDef, + ARG_POS, ARG_STAR, ARG_STAR2, Decorator, OverloadedFuncDef, TypeAlias ) from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype @@ -18,6 +17,7 @@ from mypy.infer import infer_type_arguments from mypy.typevars import fill_typevars from mypy.plugin import AttributeContext +from mypy.typeanal import set_any_tvars from mypy import messages from mypy import subtypes from mypy import meet @@ -249,6 +249,17 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, v = Var(name, type=type_object_type(vv, builtin_type)) v.info = info + if isinstance(vv, TypeAlias) and isinstance(vv.target, Instance): + # Similar to the above TypeInfo case, we allow using + # qualified type aliases in runtime context if it refers to an + # instance type. For example: + # class C: + # A = List[int] + # x = C.A() <- this is OK + typ = instance_alias_type(vv, builtin_type) + v = Var(name, type=typ) + v.info = info + if isinstance(v, Var): return analyze_var(name, v, itype, info, node, is_lvalue, msg, original_type, not_ready_callback, chk=chk) @@ -291,6 +302,19 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, return msg.has_no_attr(original_type, itype, name, node) +def instance_alias_type(alias: TypeAlias, + builtin_type: Callable[[str], Instance]) -> Type: + """Type of a type alias node targeting an instance, when appears in runtime context. + + As usual, we first erase any unbound type variables to Any. + """ + assert isinstance(alias.target, Instance), "Must be called only with aliases to classes" + target = set_any_tvars(alias.target, alias.alias_tvars, alias.line, alias.column) + assert isinstance(target, Instance) + tp = type_object_type(target.type, builtin_type) + return expand_type_by_instance(tp, target) + + def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context, is_lvalue: bool, msg: MessageBuilder, original_type: Type, not_ready_callback: Callable[[str, Context], None], *, @@ -430,7 +454,7 @@ def analyze_class_attribute_access(itype: Instance, return None is_decorated = isinstance(node.node, Decorator) - is_method = is_decorated or isinstance(node.node, FuncDef) + is_method = is_decorated or isinstance(node.node, FuncBase) if is_lvalue: if is_method: msg.cant_assign_to_method(context) @@ -467,6 +491,9 @@ def analyze_class_attribute_access(itype: Instance, # Reference to a module object. return builtin_type('types.ModuleType') + if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance): + return instance_alias_type(node.node, builtin_type) + if is_decorated: assert isinstance(node.node, Decorator) if node.node.type: diff --git a/mypy/fixup.py b/mypy/fixup.py index 75621e640f340..f1986394ae74d 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -5,7 +5,7 @@ from mypy.nodes import ( MypyFile, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, FuncDef, OverloadedFuncDef, Decorator, Var, - TypeVarExpr, ClassDef, Block, TYPE_ALIAS + TypeVarExpr, ClassDef, Block, TypeAlias, ) from mypy.types import ( CallableType, Instance, Overloaded, TupleType, TypedDictType, @@ -76,26 +76,17 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None: self.quick_and_dirty) if stnode is not None: value.node = stnode.node - value.type_override = stnode.type_override - if (self.quick_and_dirty and value.kind == TYPE_ALIAS and - stnode.type_override is None): - value.type_override = Instance(stale_info(self.modules), []) - value.alias_tvars = stnode.alias_tvars or [] elif not self.quick_and_dirty: assert stnode is not None, "Could not find cross-ref %s" % (cross_ref,) else: # We have a missing crossref in quick mode, need to put something value.node = stale_info(self.modules) - if value.kind == TYPE_ALIAS: - value.type_override = Instance(stale_info(self.modules), []) else: if isinstance(value.node, TypeInfo): # TypeInfo has no accept(). TODO: Add it? self.visit_type_info(value.node) elif value.node is not None: value.node.accept(self) - if value.type_override is not None: - value.type_override.accept(self.type_fixer) def visit_func_def(self, func: FuncDef) -> None: if self.current_info is not None: @@ -140,6 +131,9 @@ def visit_var(self, v: Var) -> None: if v.type is not None: v.type.accept(self.type_fixer) + def visit_type_alias(self, a: TypeAlias) -> None: + a.target.accept(self.type_fixer) + class TypeFixer(TypeVisitor[None]): def __init__(self, modules: Dict[str, MypyFile], quick_and_dirty: bool) -> None: diff --git a/mypy/messages.py b/mypy/messages.py index c272ce75d9977..90fe745068f91 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -24,10 +24,10 @@ UninhabitedType, TypeOfAny, ForwardRef, UnboundType ) from mypy.nodes import ( - TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, + TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_builtin_aliases, ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode, - CallExpr, Expression, OverloadedFuncDef, + CallExpr ) # Constants that represent simple type checker error message, i.e. messages @@ -241,8 +241,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str: elif itype.type.fullname() == 'builtins.tuple': item_type_str = self.format_bare(itype.args[0]) return 'Tuple[{}, ...]'.format(item_type_str) - elif itype.type.fullname() in reverse_type_aliases: - alias = reverse_type_aliases[itype.type.fullname()] + elif itype.type.fullname() in reverse_builtin_aliases: + alias = reverse_builtin_aliases[itype.type.fullname()] alias = alias.split('.')[-1] items = [self.format_bare(arg) for arg in itype.args] return '{}[{}]'.format(alias, ', '.join(items)) @@ -830,6 +830,12 @@ def incompatible_type_application(self, expected_arg_count: int, self.fail('Type application has too few types ({} expected)' .format(expected_arg_count), context) + def alias_invalid_in_runtime_context(self, item: Type, ctx: Context) -> None: + kind = (' to Callable' if isinstance(item, CallableType) else + ' to Tuple' if isinstance(item, TupleType) else + ' to Union' if isinstance(item, UnionType) else '') + self.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) + def could_not_infer_type_arguments(self, callee_type: CallableType, n: int, context: Context) -> None: callee_name = callable_name(callee_type) diff --git a/mypy/nodes.py b/mypy/nodes.py index 39d60188bdd55..6470eca7e5abd 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -71,12 +71,11 @@ def get_column(self) -> int: # (2) a generic function that refers to the type variable in its signature. TVAR = 4 # type: int -TYPE_ALIAS = 6 # type: int # Placeholder for a name imported via 'from ... import'. Second phase of # semantic will replace this the actual imported reference. This is # needed so that we can detect whether a name has been imported during # XXX what? -UNBOUND_IMPORTED = 7 # type: int +UNBOUND_IMPORTED = 5 # type: int # RevealExpr node kinds REVEAL_TYPE = 0 # type: int @@ -92,7 +91,6 @@ def get_column(self) -> int: MDEF: 'Mdef', MODULE_REF: 'ModuleRef', TVAR: 'Tvar', - TYPE_ALIAS: 'TypeAlias', UNBOUND_IMPORTED: 'UnboundImported', } inverse_node_kinds = {_kind: _name for _name, _kind in node_kinds.items()} @@ -104,31 +102,29 @@ def get_column(self) -> int: '__package__': '__builtins__.str'} +# These aliases exist because built-in class objects are not subscriptable. +# For example `list[int]` fails at runtime. Instead List[int] should be used. type_aliases = { - 'typing.List': '__builtins__.list', - 'typing.Dict': '__builtins__.dict', - 'typing.Set': '__builtins__.set', - 'typing.FrozenSet': '__builtins__.frozenset', + 'typing.List': 'builtins.list', + 'typing.Dict': 'builtins.dict', + 'typing.Set': 'builtins.set', + 'typing.FrozenSet': 'builtins.frozenset', + 'typing.ChainMap': 'collections.ChainMap', + 'typing.Counter': 'collections.Counter', + 'typing.DefaultDict': 'collections.defaultdict', + 'typing.Deque': 'collections.deque', } -reverse_type_aliases = dict((name.replace('__builtins__', 'builtins'), alias) - for alias, name in type_aliases.items()) # type: Dict[str, str] - -collections_type_aliases = { - 'typing.ChainMap': '__mypy_collections__.ChainMap', - 'typing.Counter': '__mypy_collections__.Counter', - 'typing.DefaultDict': '__mypy_collections__.defaultdict', - 'typing.Deque': '__mypy_collections__.deque', +reverse_builtin_aliases = { + 'builtins.list': 'typing.List', + 'builtins.dict': 'typing.Dict', + 'builtins.set': 'typing.Set', + 'builtins.frozenset': 'typing.FrozenSet', } -reverse_collection_aliases = dict((name.replace('__mypy_collections__', 'collections'), alias) - for alias, name in - collections_type_aliases.items()) # type: Dict[str, str] - nongen_builtins = {'builtins.tuple': 'typing.Tuple', 'builtins.enumerate': ''} -nongen_builtins.update(reverse_type_aliases) -nongen_builtins.update(reverse_collection_aliases) +nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) class Node(Context): @@ -1269,7 +1265,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T: class RefExpr(Expression): """Abstract base class for name-like constructs""" - __slots__ = ('kind', 'node', 'fullname', 'is_new_def', 'is_inferred_def') + __slots__ = ('kind', 'node', 'fullname', 'is_new_def', 'is_inferred_def', 'is_alias_rvalue') def __init__(self) -> None: super().__init__() @@ -1286,6 +1282,8 @@ def __init__(self) -> None: # For members, after semantic analysis, this does not take base # classes into consideration at all; the type checker deals with these. self.is_inferred_def = False + # Is this expression appears as an rvalue of a valid type alias definition? + self.is_alias_rvalue = False class NameExpr(RefExpr): @@ -1886,21 +1884,22 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarExpr': class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" + # The target type. type = None # type: mypy.types.Type - # Simple fallback type for aliases that are invalid in runtime expressions - # (for example Union, Tuple, Callable). - fallback = None # type: mypy.types.Type - # This type alias is subscripted in a runtime expression like Alias[int](42) - # (not in a type context like type annotation or base class). - in_runtime = False # type: bool + # Names of unbound type variables used to define the alias + tvars = None # type: List[str] + # Whether this alias was defined in bare form. Used to distinguish + # between + # A = List + # and + # A = List[Any] + no_args = False # type: bool - def __init__(self, type: 'mypy.types.Type', tvars: List[str], - fallback: 'mypy.types.Type', in_runtime: bool = False) -> None: + def __init__(self, type: 'mypy.types.Type', tvars: List[str], no_args: bool) -> None: super().__init__() self.type = type - self.fallback = fallback - self.in_runtime = in_runtime self.tvars = tvars + self.no_args = no_args def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) @@ -2409,6 +2408,144 @@ def __getattribute__(self, attr: str) -> None: raise AssertionError('De-serialization failure: TypeInfo not fixed') +class TypeAlias(SymbolNode): + """ + A symbol node representing a type alias. + + Type alias is a static concept, in contrast to variables with types + like Type[...]. Namely: + * type aliases + - can be used in type context (annotations) + - cannot be re-assigned + * variables with type Type[...] + - cannot be used in type context + - but can be re-assigned + + An alias can be defined only by an assignment to a name (not any other lvalues). + + Such assignment defines an alias by default. To define a variable, + an explicit Type[...] annotation is required. As an exception, + at non-global scope non-subscripted rvalue creates a variable even without + an annotation. This exception exists to accommodate the common use case of + class-valued attributes. See SemanticAnalyzerPass2.check_and_set_up_type_alias + for details. + + Aliases can be generic. Currently, mypy uses unbound type variables for + generic aliases and identifies them by name. Essentially, type aliases + work as macros that expand textually. The definition and expansion rules are + following: + + 1. An alias targeting a generic class without explicit variables act as + the given class (this doesn't apply to Tuple and Callable, which are not proper + classes but special type constructors): + + A = List + AA = List[Any] + + x: A # same as List[Any] + x: A[int] # same as List[int] + + x: AA # same as List[Any] + x: AA[int] # Error! + + C = Callable # Same as Callable[..., Any] + T = Tuple # Same as Tuple[Any, ...] + + 2. An alias using explicit type variables in its rvalue expects + replacements (type arguments) for these variables. If missing, they + are treated as Any, like for other generics: + + B = List[Tuple[T, T]] + + x: B # same as List[Tuple[Any, Any]] + x: B[int] # same as List[Tuple[int, int]] + + def f(x: B[T]) -> T: ... # without T, Any would be used here + + 3. An alias can be defined using another aliases. In the definition + rvalue the Any substitution doesn't happen for top level unsubscripted + generic classes: + + A = List + B = A # here A is expanded to List, _not_ List[Any], + # to match the Python runtime behaviour + x: B[int] # same as List[int] + C = List[A] # this expands to List[List[Any]] + + AA = List[T] + D = AA # here AA expands to List[Any] + x: D[int] # Error! + + Note: the fact that we support aliases like `A = List` means that the target + type will be initially an instance type with wrong number of type arguments. + Such instances are all fixed in the third pass of semantic analyzis. + We therefore store the difference between `List` and `List[Any]` rvalues (targets) + using the `no_args` flag. See also TypeAliasExpr.no_args. + + Meaning of other fields: + + target: The target type. For generic aliases contains unbound type variables + as nested types. + _fullname: Qualified name of this type alias. This is used in particular + to track fine grained dependencies from aliases. + alias_tvars: Names of unbound type variables used to define this alias. + normalized: Used to distinguish between `A = List`, and `A = list`. Both + are internally stored using `builtins.list` (because `typing.List` is + 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. + """ + __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized', 'line', 'column') + + 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: + self._fullname = fullname + self.target = target + if alias_tvars is None: + alias_tvars = [] + self.alias_tvars = alias_tvars + self.no_args = no_args + self.normalized = normalized + super().__init__(line, column) + + def name(self) -> str: + return self._fullname.split('.')[-1] + + def fullname(self) -> str: + return self._fullname + + def serialize(self) -> JsonDict: + data = {'.class': 'TypeAlias', + 'fullname': self._fullname, + 'target': self.target.serialize(), + 'alias_tvars': self.alias_tvars, + 'no_args': self.no_args, + 'normalized': self.normalized, + 'line': self.line, + 'column': self.column + } # type: JsonDict + return data + + def accept(self, visitor: NodeVisitor[T]) -> T: + return visitor.visit_type_alias(self) + + @classmethod + def deserialize(cls, data: JsonDict) -> 'TypeAlias': + assert data['.class'] == 'TypeAlias' + fullname = data['fullname'] + alias_tvars = data['alias_tvars'] + target = mypy.types.deserialize_type(data['target']) + no_args = data['no_args'] + normalized = data['normalized'] + line = data['line'] + column = data['column'] + return cls(target, fullname, line, column, alias_tvars=alias_tvars, + no_args=no_args, normalized=normalized) + + class SymbolTableNode: """Description of a name binding in a symbol table. @@ -2434,11 +2571,7 @@ class SymbolTableNode: they should be correct. A few definitions get special kinds, including type variables (TVAR), - imported modules and module aliases (MODULE_REF), and type aliases - (TYPE_ALIAS). - - Type aliases are very special and have additional attributes that - are only used for them ('type_override', 'alias_tvars' at least). + imported modules and module aliases (MODULE_REF) Attributes: kind: Kind of node. Possible values: @@ -2447,17 +2580,11 @@ class SymbolTableNode: - MDEF: class member definition - TVAR: TypeVar(...) definition in any scope - MODULE_REF: reference to a module - - TYPE_ALIAS: type alias - UNBOUND_IMPORTED: temporary kind for imported names (we don't know the final kind yet) node: AST node of definition (among others, this can be FuncDef/Var/TypeInfo/TypeVarExpr/MypyFile, or None for a bound type variable or a cross_ref that hasn't been fixed up yet). - type_override: If this not None, override the type of the 'node' - attribute. This is only used for type aliases. - alias_tvars: For generic aliases this stores the (qualified) names - of type variables. (For example see - testGenericAliasWithTypeVarsFromDifferentModules.) module_public: If False, this name won't be imported via 'from import *'. This has no effect on names within classes. @@ -2465,14 +2592,7 @@ class SymbolTableNode: stub files) cross_ref: For deserialized MODULE_REF nodes, the referenced module name; for other nodes, optionally the name of the referenced object. - normalized: Used to distinguish between 'typing.List' and - 'builtins.list'. This is True when the former has been normalized - to the latter, and it allow us to reject 'list[str]' and similar. implicit: Was this defined by assignment to self attribute? - is_aliasing: Is this node refers to other node via node aliasing? - (This is currently used for simple aliases like `A = int` instead - of .type_override) - alias_name: TODO no_serialize: Do not serialize this node if True. This is used to prevent keys in the cache that refer to modules on which this file does not depend. Currently this can happen if there is a module not in build @@ -2485,47 +2605,34 @@ class SymbolTableNode: phase. TODO: Refactor build.py to make dependency tracking more transparent and/or refactor look-up functions to not require parent patching. + + NOTE: No other attributes should be added to this class unless they + are shared by all node kinds. """ __slots__ = ('kind', 'node', - 'type_override', - 'alias_tvars', 'module_public', 'module_hidden', 'cross_ref', - 'normalized', 'implicit', - 'is_aliasing', - 'alias_name', 'no_serialize', ) - # TODO: This is a mess. Refactor! - # TODO: Better describe how type aliases work. - def __init__(self, kind: int, node: Optional[SymbolNode], - typ: 'Optional[mypy.types.Type]' = None, module_public: bool = True, - normalized: bool = False, - alias_tvars: Optional[List[str]] = None, implicit: bool = False, module_hidden: bool = False, *, no_serialize: bool = False) -> None: self.kind = kind self.node = node - self.type_override = typ self.module_public = module_public - self.normalized = normalized - self.alias_tvars = alias_tvars self.implicit = implicit self.module_hidden = module_hidden self.cross_ref = None # type: Optional[str] - self.is_aliasing = False - self.alias_name = None # type: Optional[str] self.no_serialize = no_serialize @property @@ -2537,12 +2644,9 @@ def fullname(self) -> Optional[str]: @property def type(self) -> 'Optional[mypy.types.Type]': - # IDEA: Get rid of the Any type. - node = self.node # type: Any - if self.type_override is not None: - return self.type_override - elif ((isinstance(node, Var) or isinstance(node, FuncDef)) - and node.type is not None): + node = self.node + if ((isinstance(node, Var) or isinstance(node, FuncBase)) + and node.type is not None): return node.type elif isinstance(node, Decorator): return node.var.type @@ -2552,10 +2656,7 @@ def type(self) -> 'Optional[mypy.types.Type]': def copy(self) -> 'SymbolTableNode': new = SymbolTableNode(self.kind, self.node, - self.type_override, self.module_public, - self.normalized, - self.alias_tvars, self.implicit, self.module_hidden) new.cross_ref = self.cross_ref @@ -2584,8 +2685,6 @@ def serialize(self, prefix: str, name: str) -> JsonDict: data['module_hidden'] = True if not self.module_public: data['module_public'] = False - if self.normalized: - data['normalized'] = True if self.implicit: data['implicit'] = True if self.kind == MODULE_REF: @@ -2600,9 +2699,6 @@ def serialize(self, prefix: str, name: str) -> JsonDict: data['cross_ref'] = fullname return data data['node'] = self.node.serialize() - if self.type_override is not None: - data['type_override'] = self.type_override.serialize() - data['alias_tvars'] = self.alias_tvars return data @classmethod @@ -2617,18 +2713,11 @@ def deserialize(cls, data: JsonDict) -> 'SymbolTableNode': node = None if 'node' in data: node = SymbolNode.deserialize(data['node']) - typ = None - if 'type_override' in data: - typ = mypy.types.deserialize_type(data['type_override']) - stnode = SymbolTableNode(kind, node, typ=typ) - if 'alias_tvars' in data: - stnode.alias_tvars = data['alias_tvars'] + stnode = SymbolTableNode(kind, node) if 'module_hidden' in data: stnode.module_hidden = data['module_hidden'] if 'module_public' in data: stnode.module_public = data['module_public'] - if 'normalized' in data: - stnode.normalized = data['normalized'] if 'implicit' in data: stnode.implicit = data['implicit'] return stnode diff --git a/mypy/plugin.py b/mypy/plugin.py index f6bd02f923421..8bfd9f0383696 100644 --- a/mypy/plugin.py +++ b/mypy/plugin.py @@ -81,7 +81,7 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *, def anal_type(self, t: Type, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False, + allow_unbound_tvars: bool = False, third_pass: bool = False) -> Type: raise NotImplementedError diff --git a/mypy/semanal.py b/mypy/semanal.py index 058fc36221d7a..bde974c6974d5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -52,11 +52,11 @@ StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, type_aliases, YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode, - SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, + SetComprehension, DictionaryComprehension, TypeAlias, TypeAliasExpr, YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, nongen_builtins, - collections_type_aliases, get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS + get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS ) from mypy.literals import literal from mypy.tvar_scope import TypeVarScope @@ -158,9 +158,9 @@ # test-data/unit/fixtures/) that provides the definition. This is used for # generating better error messages when running mypy tests only. SUGGESTED_TEST_FIXTURES = { - 'typing.List': 'list.pyi', - 'typing.Dict': 'dict.pyi', - 'typing.Set': 'set.pyi', + 'builtins.list': 'list.pyi', + 'builtins.dict': 'dict.pyi', + 'builtins.set': 'set.pyi', 'builtins.bool': 'bool.pyi', 'builtins.Exception': 'exception.pyi', 'builtins.BaseException': 'exception.pyi', @@ -619,8 +619,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: defn.is_static = static_status[0] if self.type and not self.is_func_scope(): - self.type.names[defn.name()] = SymbolTableNode(MDEF, defn, - typ=defn.type) + self.type.names[defn.name()] = SymbolTableNode(MDEF, defn) defn.info = self.type elif self.is_func_scope(): self.add_local(defn, defn) @@ -1295,7 +1294,7 @@ def named_type_or_none(self, qualified_name: str, return None node = sym.node assert isinstance(node, TypeInfo) - if args: + if args is not None: # TODO: assert len(args) == len(node.defn.type_vars) return Instance(node, args) return Instance(node, [AnyType(TypeOfAny.unannotated)] * len(node.defn.type_vars)) @@ -1405,7 +1404,6 @@ def visit_import_from(self, imp: ImportFrom) -> None: self.add_symbol(name, symbol, imp) continue if node and node.kind != UNBOUND_IMPORTED and not node.module_hidden: - node = self.normalize_type_alias(node, imp) if not node: # Normalization failed because target is not defined. Avoid duplicate # error messages by marking the imported name as unknown. @@ -1422,13 +1420,8 @@ def visit_import_from(self, imp: ImportFrom) -> None: module_public = not self.is_stub_file or as_id is not None module_hidden = not module_public and possible_module_id not in self.modules symbol = SymbolTableNode(node.kind, node.node, - node.type_override, module_public=module_public, - normalized=node.normalized, - alias_tvars=node.alias_tvars, module_hidden=module_hidden) - symbol.is_aliasing = node.is_aliasing - symbol.alias_name = node.alias_name self.add_symbol(imported_id, symbol, imp) elif module and not missing: # Missing attribute. @@ -1438,6 +1431,14 @@ def visit_import_from(self, imp: ImportFrom) -> None: message += " {}".format(extra) self.fail(message, imp) self.add_unknown_symbol(as_id or id, imp, is_import=True) + + if import_id == 'typing': + # The user probably has a missing definition in a test fixture. Let's verify. + fullname = 'builtins.{}'.format(id.lower()) + if (self.lookup_fully_qualified_or_none(fullname) is None and + fullname in SUGGESTED_TEST_FIXTURES): + # Yes. Generate a helpful note. + self.add_fixture_note(fullname, imp) else: # Missing module. self.add_unknown_symbol(as_id or id, imp, is_import=True) @@ -1470,7 +1471,7 @@ def process_import_over_existing_name(self, module_symbol: SymbolTableNode, import_node: ImportBase) -> bool: if (existing_symbol.kind in (LDEF, GDEF, MDEF) and - isinstance(existing_symbol.node, (Var, FuncDef, TypeInfo, Decorator))): + isinstance(existing_symbol.node, (Var, FuncDef, TypeInfo, Decorator, TypeAlias))): # This is a valid import over an existing definition in the file. Construct a dummy # assignment that we'll use to type check the import. lvalue = NameExpr(imported_id) @@ -1479,6 +1480,10 @@ def process_import_over_existing_name(self, rvalue = NameExpr(imported_id) rvalue.kind = module_symbol.kind rvalue.node = module_symbol.node + if isinstance(rvalue.node, TypeAlias): + # Suppress bogus errors from the dummy assignment if rvalue is an alias. + # Otherwise mypy may complain that alias is invalid in runtime context. + rvalue.is_alias_rvalue = True assignment = AssignmentStmt([lvalue], rvalue) for node in assignment, lvalue, rvalue: node.set_line(import_node) @@ -1486,38 +1491,6 @@ def process_import_over_existing_name(self, return True return False - def normalize_type_alias(self, node: SymbolTableNode, - ctx: Context) -> Optional[SymbolTableNode]: - """If node refers to a built-in type alias, normalize it. - - An example normalization is 'typing.List' -> '__builtins__.list'. - - By default, if the node doesn't refer to a built-in type alias, return - the original node. If normalization fails because the target isn't - defined, return None. - """ - normalized = False - fullname = node.fullname - if fullname in type_aliases: - # Node refers to an aliased type such as typing.List; normalize. - new_node = self.lookup_qualified(type_aliases[fullname], ctx) - if new_node is None: - self.add_fixture_note(fullname, ctx) - return None - normalized = True - if fullname in collections_type_aliases: - # Similar, but for types from the collections module like typing.DefaultDict - self.add_module_symbol('collections', '__mypy_collections__', False, ctx) - new_node = self.lookup_qualified(collections_type_aliases[fullname], ctx) - normalized = True - if normalized: - assert new_node is not None, "Collection node not found" - node = SymbolTableNode(new_node.kind, new_node.node, new_node.type_override, - normalized=True, alias_tvars=new_node.alias_tvars) - node.is_aliasing = new_node.is_aliasing - node.alias_name = new_node.alias_name - return node - def add_fixture_note(self, fullname: str, ctx: Context) -> None: self.note('Maybe your test fixture does not define "{}"?'.format(fullname), ctx) if fullname in SUGGESTED_TEST_FIXTURES: @@ -1541,23 +1514,16 @@ def visit_import_all(self, i: ImportAll) -> None: node = self.dereference_module_cross_ref(orig_node) if node is None: continue - new_node = self.normalize_type_alias(node, i) # if '__all__' exists, all nodes not included have had module_public set to # False, and we can skip checking '_' because it's been explicitly included. - if (new_node and new_node.module_public and - (not name.startswith('_') or '__all__' in m.names)): + if (node.module_public and (not name.startswith('_') or '__all__' in m.names)): existing_symbol = self.lookup_current_scope(name) if existing_symbol: # Import can redefine a variable. They get special treatment. if self.process_import_over_existing_name( - name, existing_symbol, new_node, i): + name, existing_symbol, node, i): continue - symbol = SymbolTableNode(new_node.kind, new_node.node, - new_node.type_override, - normalized=new_node.normalized, - alias_tvars=new_node.alias_tvars) - symbol.is_aliasing = new_node.is_aliasing - symbol.alias_name = new_node.alias_name + symbol = SymbolTableNode(node.kind, node.node) self.add_symbol(name, symbol, i) i.imported_names.append(name) else: @@ -1598,7 +1564,7 @@ def visit_block_maybe(self, b: Optional[Block]) -> None: def type_analyzer(self, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False, + allow_unbound_tvars: bool = False, third_pass: bool = False) -> TypeAnalyser: if tvar_scope is None: tvar_scope = self.tvar_scope @@ -1607,7 +1573,7 @@ def type_analyzer(self, *, self.plugin, self.options, self.is_typeshed_stub_file, - aliasing=aliasing, + allow_unbound_tvars=allow_unbound_tvars, allow_tuple_literal=allow_tuple_literal, allow_unnormalized=self.is_stub_file, third_pass=third_pass) @@ -1618,10 +1584,10 @@ def type_analyzer(self, *, def anal_type(self, t: Type, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False, + allow_unbound_tvars: bool = False, third_pass: bool = False) -> Type: a = self.type_analyzer(tvar_scope=tvar_scope, - aliasing=aliasing, + allow_unbound_tvars=allow_unbound_tvars, allow_tuple_literal=allow_tuple_literal, third_pass=third_pass) typ = t.accept(a) @@ -1703,23 +1669,8 @@ def analyze_simple_literal_type(self, rvalue: Expression) -> Optional[Type]: return self.named_type_or_none('builtins.unicode') return None - def alias_fallback(self, tp: Type) -> Instance: - """Make a dummy Instance with no methods. It is used as a fallback type - to detect errors for non-Instance aliases (i.e. Unions, Tuples, Callables). - """ - # TODO: this has None fullname (and also can't be serialized), fix this. - kind = (' to Callable' if isinstance(tp, CallableType) else - ' to Tuple' if isinstance(tp, TupleType) else - ' to Union' if isinstance(tp, UnionType) else '') - cdef = ClassDef('Type alias' + kind, Block([])) - fb_info = TypeInfo(SymbolTable(), cdef, self.cur_mod_id) - fb_info.bases = [self.object_type()] - fb_info.mro = [fb_info, self.object_type().type] - return Instance(fb_info, []) - - def analyze_alias(self, rvalue: Expression, - warn_bound_tvar: bool = False) -> Tuple[Optional[Type], List[str], - Set[str], List[str]]: + def analyze_alias(self, rvalue: Expression) -> Tuple[Optional[Type], List[str], + Set[str], List[str]]: """Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable). If yes, return the corresponding type, a list of @@ -1738,10 +1689,9 @@ def analyze_alias(self, rvalue: Expression, self.plugin, self.options, self.is_typeshed_stub_file, - allow_unnormalized=True, + allow_unnormalized=self.is_stub_file, in_dynamic_func=dynamic, - global_scope=global_scope, - warn_bound_tvar=warn_bound_tvar) + global_scope=global_scope) typ = None # type: Optional[Type] if res: typ, depends_on = res @@ -1769,7 +1719,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: # Second rule: Explicit type (cls: Type[A] = A) always creates variable, not alias. return non_global_scope = self.type or self.is_func_scope() - if isinstance(s.rvalue, NameExpr) and non_global_scope and lvalue.is_inferred_def: + if isinstance(s.rvalue, RefExpr) and non_global_scope and lvalue.is_inferred_def: # Third rule: Non-subscripted right hand side creates a variable # at class and function scopes. For example: # @@ -1782,23 +1732,23 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: # annotations (see the second rule). return rvalue = s.rvalue - res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias(rvalue, - warn_bound_tvar=True) + res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias(rvalue) if not res: return s.is_alias_def = True node = self.lookup(lvalue.name, lvalue) assert node is not None - if lvalue.fullname is not None: - node.alias_name = lvalue.fullname + assert node.node is not None self.add_type_alias_deps(depends_on) + # In addition to the aliases used, we add deps on unbound + # type variables, since they are erased from target type. self.add_type_alias_deps(qualified_tvars) # The above are only direct deps on other aliases. # For subscripted aliases, type deps from expansion are added in deps.py # (because the type is stored) if not lvalue.is_inferred_def: # Type aliases can't be re-defined. - if node and (node.kind == TYPE_ALIAS or isinstance(node.node, TypeInfo)): + if isinstance(node.node, (TypeAlias, TypeInfo)): self.fail('Cannot assign multiple types to name "{}"' ' without an explicit "Type[...]" annotation' .format(lvalue.name), lvalue) @@ -1808,32 +1758,18 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: # when this type alias gets "inlined", the Any is not explicit anymore, # so we need to replace it with non-explicit Anys res = make_any_non_explicit(res) - if isinstance(res, Instance) and not res.args and isinstance(rvalue, RefExpr): - # For simple (on-generic) aliases we use aliasing TypeInfo's - # to allow using them in runtime context where it makes sense. - node.node = res.type - node.is_aliasing = True - if isinstance(rvalue, RefExpr): - # For non-subscripted aliases we add type deps right here - # (because the node is stored, not type) - # TODO: currently subscripted and unsubscripted aliases are processed differently - # This leads to duplication of most of the logic with small variations. - # Fix this. - self.add_type_alias_deps({node.node.fullname()}) - sym = self.lookup_type_node(rvalue) - if sym: - node.normalized = sym.normalized - return - node.kind = TYPE_ALIAS - node.type_override = res - node.alias_tvars = alias_tvars - if isinstance(rvalue, (IndexExpr, CallExpr)): - # We only need this for subscripted aliases, since simple aliases - # are already processed using aliasing TypeInfo's above. - rvalue.analyzed = TypeAliasExpr(res, node.alias_tvars, - fallback=self.alias_fallback(res)) - rvalue.analyzed.line = rvalue.line - rvalue.analyzed.column = rvalue.column + no_args = isinstance(res, Instance) and not res.args + if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)` + s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) + s.rvalue.analyzed.line = s.line + # we use the column from resulting target, to get better location for errors + s.rvalue.analyzed.column = res.column + elif isinstance(s.rvalue, RefExpr): + s.rvalue.is_alias_rvalue = True + node.node = TypeAlias(res, node.node.fullname(), s.line, s.column, + alias_tvars=alias_tvars, no_args=no_args) + if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): + node.node.normalized = rvalue.node.normalized def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, @@ -1868,7 +1804,8 @@ def analyze_lvalue(self, lval: Lvalue, nested: bool = False, if lval.kind == GDEF: # Since the is_new_def flag is set, this must have been analyzed # already in the first pass and added to the symbol table. - assert lval.node.name() in self.globals + # An exception is typing module with incomplete test fixtures. + assert lval.node.name() in self.globals or self.cur_mod_id == 'typing' elif (self.locals[-1] is not None and lval.name not in self.locals[-1] and lval.name not in self.global_decls[-1] and lval.name not in self.nonlocal_decls[-1]): @@ -2545,12 +2482,6 @@ def visit_exec_stmt(self, s: ExecStmt) -> None: # Expressions # - def add_symbol_alias_deps(self, n: Optional[SymbolTableNode]) -> None: - """Add type alias dependencies for a RefExpr node.""" - if n and n.is_aliasing: - assert n.alias_name is not None - self.add_type_alias_deps([n.alias_name]) - def visit_name_expr(self, expr: NameExpr) -> None: n = self.lookup(expr.name, expr) if n: @@ -2558,7 +2489,6 @@ def visit_name_expr(self, expr: NameExpr) -> None: self.fail("'{}' is a type variable and only valid in type " "context".format(expr.name), expr) else: - self.add_symbol_alias_deps(n) expr.kind = n.kind expr.node = n.node expr.fullname = n.fullname @@ -2767,10 +2697,8 @@ def visit_member_expr(self, expr: MemberExpr) -> None: # else: # names = file.names n = file.names.get(expr.name, None) if file is not None else None - self.add_symbol_alias_deps(n) n = self.dereference_module_cross_ref(n) if n and not n.module_hidden: - n = self.normalize_type_alias(n, expr) if not n: return n = self.rebind_symbol_table_node(n) @@ -2783,7 +2711,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None: and '__getattr__' in file.names): # If there is a module-level __getattr__, then any attribute on the module is valid # per PEP 484. - getattr_defn = self.normalize_type_alias(file.names['__getattr__'], expr) + getattr_defn = file.names['__getattr__'] if not getattr_defn: typ = AnyType(TypeOfAny.from_error) # type: Type elif isinstance(getattr_defn.node, (FuncDef, Var)): @@ -2828,11 +2756,14 @@ def visit_member_expr(self, expr: MemberExpr) -> None: formal_arg = func_def.type.argument_by_name(base.node.name()) if formal_arg and formal_arg.pos == 0: type_info = self.type + elif isinstance(base.node, TypeAlias) and base.node.no_args: + if isinstance(base.node.target, Instance): + type_info = base.node.target.type + if type_info: n = type_info.names.get(expr.name) - self.add_symbol_alias_deps(n) - if n is not None and (n.kind == MODULE_REF or isinstance(n.node, TypeInfo)): - n = self.normalize_type_alias(n, expr) + if n is not None and (n.kind == MODULE_REF or isinstance(n.node, (TypeInfo, + TypeAlias))): if not n: return expr.kind = n.kind @@ -2869,20 +2800,9 @@ def visit_index_expr(self, expr: IndexExpr) -> None: and isinstance(expr.base.node, TypeInfo) and not expr.base.node.is_generic()): expr.index.accept(self) - elif isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS: - # Special form -- subscripting a generic type alias. - # Perform the type substitution and create a new alias. - res, alias_tvars, depends_on, _ = self.analyze_alias(expr) - assert res is not None, "Failed analyzing already defined alias" - expr.analyzed = TypeAliasExpr(res, alias_tvars, fallback=self.alias_fallback(res), - in_runtime=True) - expr.analyzed.line = expr.line - expr.analyzed.column = expr.column - # We also store fine-grained dependencies to correctly re-process nodes - # with situations like `L = LongGeneric; x = L[int]()`. - self.add_type_alias_deps(depends_on) - elif refers_to_class_or_function(expr.base): - # Special form -- type application. + elif (isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias) or + refers_to_class_or_function(expr.base)): + # Special form -- type application (either direct or via type aliasing). # Translate index to an unanalyzed type. types = [] # type: List[Type] if isinstance(expr.index, TupleExpr): @@ -2895,14 +2815,27 @@ def visit_index_expr(self, expr: IndexExpr) -> None: except TypeTranslationError: self.fail('Type expected within [...]', expr) return - typearg = self.anal_type(typearg, aliasing=True) + # We always allow unbound type variables in IndexExpr, since we + # may be analysing a type alias definition rvalue. The error will be + # reported elsewhere if it is not the case. + typearg = self.anal_type(typearg, allow_unbound_tvars=True) types.append(typearg) expr.analyzed = TypeApplication(expr.base, types) expr.analyzed.line = expr.line - # list, dict, set are not directly subscriptable - n = self.lookup_type_node(expr.base) - if n and not n.normalized and n.fullname in nongen_builtins: - self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr) + # Types list, dict, set are not subscriptable, prohibit this if + # subscripted either via type alias... + if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): + alias = expr.base.node + if isinstance(alias.target, Instance): + name = alias.target.type.fullname() + if (alias.no_args and # this avoids bogus errors for already reported aliases + name in nongen_builtins and not alias.normalized): + self.fail(no_subscript_builtin_alias(name, propose_alt=False), expr) + # ...or directly. + else: + n = self.lookup_type_node(expr.base) + if n and n.fullname in nongen_builtins: + self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr) else: expr.index.accept(self) @@ -3132,13 +3065,14 @@ def lookup_qualified(self, name: str, ctx: Context, if n.node.fullname() == self.cur_mod_id: names = self.globals n = names.get(parts[i], None) + if n and isinstance(n.node, ImportedName): + n = self.dereference_module_cross_ref(n) # TODO: What if node is Var or FuncDef? if not n: if not suppress_errors: self.name_not_defined(name, ctx) break if n: - n = self.normalize_type_alias(n, ctx) if n and n.module_hidden: self.name_not_defined(name, ctx) if n and not n.module_hidden: @@ -3151,8 +3085,8 @@ def rebind_symbol_table_node(self, n: SymbolTableNode) -> Optional[SymbolTableNo If the reference is removed in the new version, return None. """ - # TODO: Handle type aliases, type variables and other sorts of references - if isinstance(n.node, (FuncDef, OverloadedFuncDef, TypeInfo, Var)): + # TODO: Handle type variables and other sorts of references + if isinstance(n.node, (FuncDef, OverloadedFuncDef, TypeInfo, Var, TypeAlias)): # TODO: Why is it possible for fullname() to be None, even though it's not # annotated as Optional[str]? # TODO: Do this for all modules in the set of modified files @@ -3170,6 +3104,29 @@ def builtin_type(self, fully_qualified_name: str) -> Instance: assert isinstance(node, TypeInfo) return Instance(node, [AnyType(TypeOfAny.special_form)] * len(node.defn.type_vars)) + def add_builtin_aliases(self, tree: MypyFile) -> None: + """Add builtin type aliases to typing module. + + For historical reasons, the aliases like `List = list` are not defined + in typeshed stubs for typing module. Instead we need to manually add the + corresponding nodes on the fly. We explicitly mark these aliases as normalized, + so that a user can write `typing.List[int]`. + """ + assert tree.fullname() == 'typing' + for alias, target_name in type_aliases.items(): + name = alias.split('.')[-1] + n = self.lookup_fully_qualified_or_none(target_name) + if n: + target = self.named_type_or_none(target_name, []) + assert target is not None + alias_node = TypeAlias(target, alias, line=-1, column=-1, # there is no context + no_args=True, normalized=True) + tree.names[name] = SymbolTableNode(GDEF, alias_node) + else: + # Built-in target not defined, remove the original fake + # definition to trigger a better error message. + tree.names.pop(name, None) + def lookup_fully_qualified(self, name: str) -> SymbolTableNode: """Lookup a fully qualified name. @@ -3385,7 +3342,11 @@ def replace_implicit_first_type(sig: FunctionLike, new: Type) -> FunctionLike: def refers_to_fullname(node: Expression, fullname: str) -> bool: """Is node a name or member expression with the given full name?""" - return isinstance(node, RefExpr) and node.fullname == fullname + if not isinstance(node, RefExpr): + return False + return (node.fullname == fullname or + isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance) + and node.node.target.type.fullname() == fullname) def refers_to_class_or_function(node: Expression) -> bool: diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 947afc0c792be..6a554557f2b18 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -18,7 +18,7 @@ Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr, CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue, TupleExpr, RevealExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef, - MDEF, + MDEF, TypeAlias ) from mypy.types import ( Type, Instance, AnyType, TypeOfAny, CallableType, TupleType, TypeVarType, TypedDictType, @@ -243,15 +243,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: if isinstance(analyzed, NamedTupleExpr): self.analyze(analyzed.info.tuple_type, analyzed, warn=True) self.analyze_info(analyzed.info) - # We need to pay additional attention to assignments that define a type alias. - # The resulting type is also stored in the 'type_override' attribute of - # the corresponding SymbolTableNode. if isinstance(s.lvalues[0], RefExpr) and isinstance(s.lvalues[0].node, Var): self.analyze(s.lvalues[0].node.type, s.lvalues[0].node) - if isinstance(s.lvalues[0], NameExpr): - node = self.sem.lookup(s.lvalues[0].name, s, suppress_errors=True) - if node: - self.analyze(node.type_override, node) # Subclass attribute assignments with no type annotation should be # assumed to be classvar if overriding a declared classvar from the base # class. @@ -288,8 +281,7 @@ def visit_type_application(self, e: TypeApplication) -> None: # Helpers - def perform_transform(self, node: Union[Node, SymbolTableNode], - transform: Callable[[Type], Type]) -> None: + def perform_transform(self, node: Node, transform: Callable[[Type], Type]) -> None: """Apply transform to all types associated with node.""" if isinstance(node, ForStmt): if node.index_type: @@ -305,6 +297,8 @@ def perform_transform(self, node: Union[Node, SymbolTableNode], TypeAliasExpr, Var)): assert node.type, "Scheduled patch for non-existent type" node.type = transform(node.type) + if isinstance(node, TypeAlias): + node.target = transform(node.target) if isinstance(node, NewTypeExpr): assert node.old_type, "Scheduled patch for non-existent type" node.old_type = transform(node.old_type) @@ -337,9 +331,6 @@ def perform_transform(self, node: Union[Node, SymbolTableNode], transform(node.info.tuple_type)) if isinstance(node, TypeApplication): node.types = [transform(t) for t in node.types] - if isinstance(node, SymbolTableNode): - assert node.type_override, "Scheduled patch for non-existent type" - node.type_override = transform(node.type_override) if isinstance(node, TypeInfo): for tvar in node.defn.type_vars: if tvar.upper_bound: @@ -370,7 +361,7 @@ def transform_types_in_lvalue(self, lvalue: Lvalue, for item in lvalue.items: self.transform_types_in_lvalue(item, transform) - def analyze(self, type: Optional[Type], node: Union[Node, SymbolTableNode], + def analyze(self, type: Optional[Type], node: Node, warn: bool = False) -> None: # Recursive type warnings are only emitted on type definition 'node's, marked by 'warn' # Flags appeared during analysis of 'type' are collected in this dict. @@ -378,7 +369,11 @@ def analyze(self, type: Optional[Type], node: Union[Node, SymbolTableNode], if type: analyzer = self.make_type_analyzer(indicator) type.accept(analyzer) - self.check_for_omitted_generics(type) + if not (isinstance(node, TypeAlias) and node.no_args): + # We skip bare type aliases like `A = List`, these + # are still valid. In contrast, use/expansion points + # like `x: A` will be flagged. + self.check_for_omitted_generics(type) self.generate_type_patches(node, indicator, warn) if analyzer.aliases_used: target = self.scope.current_target() @@ -399,8 +394,8 @@ def analyze_types(self, types: List[Type], node: Node) -> None: def analyze_symbol_table(self, names: SymbolTable) -> None: """Analyze types in symbol table nodes only (shallow).""" for node in names.values(): - if node.type_override: - self.analyze(node.type_override, node) + if isinstance(node.node, TypeAlias): + self.analyze(node.node.target, node.node) def make_scoped_patch(self, fn: Callable[[], None]) -> Callable[[], None]: saved_scope = self.scope.save() @@ -411,7 +406,7 @@ def patch() -> None: return patch def generate_type_patches(self, - node: Union[Node, SymbolTableNode], + node: Node, indicator: Dict[str, bool], warn: bool) -> None: if indicator.get('forward') or indicator.get('synthetic'): diff --git a/mypy/semanal_shared.py b/mypy/semanal_shared.py index c169df8555937..ad81198a32a6e 100644 --- a/mypy/semanal_shared.py +++ b/mypy/semanal_shared.py @@ -84,7 +84,7 @@ def accept(self, node: Node) -> None: def anal_type(self, t: Type, *, tvar_scope: Optional[TypeVarScope] = None, allow_tuple_literal: bool = False, - aliasing: bool = False, + allow_unbound_tvars: bool = False, third_pass: bool = False) -> Type: raise NotImplementedError diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index e24001cbffcc8..d95aa1d49c20e 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -53,8 +53,8 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from typing import Set, Dict, Tuple, Optional, Sequence, Union from mypy.nodes import ( - SymbolTable, TypeInfo, Var, SymbolNode, Decorator, TypeVarExpr, - FuncBase, OverloadedFuncDef, FuncItem, MODULE_REF, TYPE_ALIAS, UNBOUND_IMPORTED, TVAR + SymbolTable, TypeInfo, Var, SymbolNode, Decorator, TypeVarExpr, TypeAlias, + FuncBase, OverloadedFuncDef, FuncItem, MODULE_REF, UNBOUND_IMPORTED, TVAR ) from mypy.types import ( Type, TypeVisitor, UnboundType, AnyType, NoneTyp, UninhabitedType, @@ -146,15 +146,17 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> Dict[str, Sna node.variance, [snapshot_type(value) for value in node.values], snapshot_type(node.upper_bound)) - elif symbol.kind == TYPE_ALIAS: + elif isinstance(symbol.node, TypeAlias): result[name] = ('TypeAlias', - symbol.alias_tvars, - snapshot_optional_type(symbol.type_override)) + symbol.node.alias_tvars, + symbol.node.normalized, + symbol.node.no_args, + snapshot_optional_type(symbol.node.target)) else: assert symbol.kind != UNBOUND_IMPORTED if node and get_prefix(node.fullname()) != name_prefix: # This is a cross-reference to a node defined in another module. - result[name] = ('CrossRef', common, symbol.normalized) + result[name] = ('CrossRef', common) else: result[name] = snapshot_definition(node, common) return result diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 15f4ee1302887..5265f26194b19 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -256,7 +256,6 @@ def visit_enum_call_expr(self, node: EnumCallExpr) -> None: def visit_type_alias_expr(self, node: TypeAliasExpr) -> None: self.fixup_type(node.type) - self.fixup_type(node.fallback) super().visit_type_alias_expr(node) # Others @@ -439,9 +438,6 @@ def replace_nodes_in_symbol_table(symbols: SymbolTable, # Handle them here just in case these aren't exposed through the AST. # TODO: Is this necessary? fixup_var(node.node, replacements) - override = node.type_override - if override: - override.accept(TypeReplaceVisitor(replacements)) def fixup_var(node: Var, replacements: Dict[SymbolNode, SymbolNode]) -> None: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f006b3d7f3907..560d151e18ad1 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -352,6 +352,8 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: self.add_dependency(make_trigger(class_name + '.__new__')) if isinstance(rvalue, IndexExpr) and isinstance(rvalue.analyzed, TypeAliasExpr): self.add_type_dependencies(rvalue.analyzed.type) + elif typ: + self.add_type_dependencies(typ) else: # Normal assignment super().visit_assignment_stmt(o) diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 08f102e1794d0..8b010151fe7ba 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -177,9 +177,6 @@ def format_symbol_table_node(self, node: SymbolTableNode) -> str: self.id_mapper.id(node.node)) else: s = '? ({})'.format(type(node.node)) - if node.type_override: - override = self.format_type(node.type_override) - s += '(type_override={})'.format(override) if (isinstance(node.node, Var) and node.node.type and not node.node.fullname().startswith('typing.')): typestr = self.format_type(node.node.type) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 7068b4c06a8d6..8166db78cfa6a 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -478,8 +478,7 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: self.type(node.upper_bound), variance=node.variance) def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr: - return TypeAliasExpr(node.type, node.tvars, - fallback=node.fallback, in_runtime=node.in_runtime) + return TypeAliasExpr(node.type, node.tvars, node.no_args) def visit_newtype_expr(self, node: NewTypeExpr) -> NewTypeExpr: res = NewTypeExpr(node.name, node.old_type, line=node.line) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9e878109a3314..841dd51dad950 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -19,10 +19,10 @@ ) from mypy.nodes import ( - TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, + TVAR, MODULE_REF, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, IndexExpr, RefExpr, nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED, ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr, FuncDef, CallExpr, NameExpr, - Decorator, ImportedName, type_aliases + Decorator, ImportedName, TypeAlias ) from mypy.tvar_scope import TypeVarScope from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -60,8 +60,7 @@ def analyze_type_alias(node: Expression, is_typeshed_stub: bool, allow_unnormalized: bool = False, in_dynamic_func: bool = False, - global_scope: bool = True, - warn_bound_tvar: bool = False) -> Optional[Tuple[Type, Set[str]]]: + global_scope: bool = True) -> Optional[Tuple[Type, Set[str]]]: """Analyze r.h.s. of a (potential) type alias definition. If `node` is valid as a type alias rvalue, return the resulting type and a set of @@ -80,15 +79,15 @@ def analyze_type_alias(node: Expression, node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or - node.fullname == 'typing.Any' or - node.kind == TYPE_ALIAS): + node.fullname in ('typing.Any', 'typing.Tuple', 'typing.Callable') or + isinstance(node.node, TypeAlias)): return None elif isinstance(node, IndexExpr): base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or base.fullname in type_constructors or - base.kind == TYPE_ALIAS): + isinstance(base.node, TypeAlias)): return None # Enums can't be generic, and without this check we may incorrectly interpret indexing # an Enum class as creating a type alias. @@ -115,8 +114,8 @@ def analyze_type_alias(node: Expression, except TypeTranslationError: api.fail('Invalid type alias', node) return None - analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, aliasing=True, - allow_unnormalized=allow_unnormalized, warn_bound_tvar=warn_bound_tvar) + analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub, + allow_unnormalized=allow_unnormalized, defining_alias=True) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope res = type.accept(analyzer) @@ -148,26 +147,30 @@ def __init__(self, plugin: Plugin, options: Options, is_typeshed_stub: bool, *, - aliasing: bool = False, + defining_alias: bool = False, allow_tuple_literal: bool = False, allow_unnormalized: bool = False, - third_pass: bool = False, - warn_bound_tvar: bool = False) -> None: + allow_unbound_tvars: bool = False, + third_pass: bool = False) -> None: self.api = api self.lookup = api.lookup_qualified self.lookup_fqn_func = api.lookup_fully_qualified self.fail_func = api.fail self.note_func = api.note self.tvar_scope = tvar_scope - self.aliasing = aliasing + # Are we analysing a type alias definition rvalue? + self.defining_alias = defining_alias self.allow_tuple_literal = allow_tuple_literal # Positive if we are analyzing arguments of another (outer) type self.nesting_level = 0 + # Should we allow unnormalized types like `list[int]` + # (currently allowed in stubs)? self.allow_unnormalized = allow_unnormalized + # Should we accept unbound type variables (always OK in aliases)? + self.allow_unbound_tvars = allow_unbound_tvars or defining_alias self.plugin = plugin self.options = options self.is_typeshed_stub = is_typeshed_stub - self.warn_bound_tvar = warn_bound_tvar self.third_pass = third_pass # Names of type aliases encountered while analysing a type will be collected here. self.aliases_used = set() # type: Set[str] @@ -195,15 +198,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # # TODO: Remove this special case. return AnyType(TypeOfAny.implementation_artifact) - if sym.fullname in type_aliases: - # Resolve forward reference to type alias like 'typing.List'. - # TODO: Unify how type aliases are handled; currently we resolve them in two - # places (the other is in the semantic analyzer pass 2). - resolved = type_aliases[sym.fullname] - new = self.api.lookup_qualified(resolved, t) - if new: - sym = new.copy() - sym.normalized = True if sym.node is None: # UNBOUND_IMPORTED can happen if an unknown name was imported. if sym.kind != UNBOUND_IMPORTED: @@ -214,13 +208,14 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: if hook: return hook(AnalyzeTypeContext(t, t, self)) if (fullname in nongen_builtins and t.args and - not sym.normalized and not self.allow_unnormalized): - self.fail(no_subscript_builtin_alias(fullname), t) + not self.allow_unnormalized): + self.fail(no_subscript_builtin_alias(fullname, + propose_alt=not self.defining_alias), t) if self.tvar_scope: tvar_def = self.tvar_scope.get_binding(sym) else: tvar_def = None - if self.warn_bound_tvar and sym.kind == TVAR and tvar_def is not None: + if sym.kind == TVAR and tvar_def is not None and self.defining_alias: self.fail('Can\'t use bound type variable "{}"' ' to define generic alias'.format(t.name), t) return AnyType(TypeOfAny.from_error) @@ -238,9 +233,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # Bare 'Tuple' is same as 'tuple' if self.options.disallow_any_generics and not self.is_typeshed_stub: self.fail(messages.BARE_GENERIC, t) - typ = self.named_type('builtins.tuple', line=t.line, column=t.column) - typ.from_generic_builtin = True - return typ + return self.named_type('builtins.tuple', line=t.line, column=t.column) if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) instance = self.named_type('builtins.tuple', [self.anal_type(t.args[0])]) @@ -282,61 +275,60 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: return item elif fullname in ('mypy_extensions.NoReturn', 'typing.NoReturn'): return UninhabitedType(is_noreturn=True) - elif sym.kind == TYPE_ALIAS: - if sym.alias_name is not None: - self.aliases_used.add(sym.alias_name) - override = sym.type_override - all_vars = sym.alias_tvars - assert override is not None + elif isinstance(sym.node, TypeAlias): + self.aliases_used.add(sym.node.fullname()) + all_vars = sym.node.alias_tvars + target = sym.node.target an_args = self.anal_array(t.args) - if all_vars is not None: - exp_len = len(all_vars) - else: - exp_len = 0 - act_len = len(an_args) - if exp_len > 0 and act_len == 0: - # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] - assert all_vars is not None - return set_any_tvars(override, all_vars, t.line, t.column) - if exp_len == 0 and act_len == 0: - return override - if act_len != exp_len: - self.fail('Bad number of arguments for type alias, expected: %s, given: %s' - % (exp_len, act_len), t) - return set_any_tvars(override, all_vars or [], - t.line, t.column, implicit=False) - assert all_vars is not None - return replace_alias_tvars(override, all_vars, an_args, t.line, t.column) + return expand_type_alias(target, all_vars, an_args, self.fail, sym.node.no_args, t) elif not isinstance(sym.node, TypeInfo): + # Something unusual. We try our best to find out what it is. name = sym.fullname if name is None: name = sym.node.name() + # Option 1: + # Something with an Any type -- make it an alias for Any in a type + # context. This is slightly problematic as it allows using the type 'Any' + # as a base class -- however, this will fail soon at runtime so the problem + # is pretty minor. if isinstance(sym.node, Var) and isinstance(sym.node.type, AnyType): - # Something with an Any type -- make it an alias for Any in a type - # context. This is slightly problematic as it allows using the type 'Any' - # as a base class -- however, this will fail soon at runtime so the problem - # is pretty minor. return AnyType(TypeOfAny.from_unimported_type, missing_import_name=sym.node.type.missing_import_name) - # Allow unbound type variables when defining an alias - if not (self.aliasing and sym.kind == TVAR and - (not self.tvar_scope or self.tvar_scope.get_binding(sym) is None)): - if (not self.third_pass and not self.in_dynamic_func and - not (isinstance(sym.node, (FuncDef, Decorator)) or - isinstance(sym.node, Var) and sym.node.is_ready) and - not (sym.kind == TVAR and tvar_def is None)): - if t.args and not self.global_scope: + # Option 2: + # Unbound type variable. Currently these may be still valid, + # for example when defining a generic type alias. + unbound_tvar = ((sym.kind == TVAR) and + (not self.tvar_scope or self.tvar_scope.get_binding(sym) is None)) + if self.allow_unbound_tvars and unbound_tvar and not self.third_pass: + return t + # Option 3: + # If it is not something clearly bad (like a known function, variable, + # type variable, or module), and it is still not too late, we try deferring + # this type using a forward reference wrapper. It will be revisited in + # the third pass. + allow_forward_ref = not (self.third_pass or + isinstance(sym.node, (FuncDef, Decorator)) or + isinstance(sym.node, Var) and sym.node.is_ready or + sym.kind in (MODULE_REF, TVAR)) + if allow_forward_ref: + # We currently can't support subscripted forward refs in functions; + # see https://github.com/python/mypy/pull/3952#discussion_r139950690 + # for discussion. + if t.args and not self.global_scope: + if not self.in_dynamic_func: self.fail('Unsupported forward reference to "{}"'.format(t.name), t) - return AnyType(TypeOfAny.from_error) - return ForwardRef(t) - self.fail('Invalid type "{}"'.format(name), t) - if self.third_pass and sym.kind == TVAR: - self.note_func("Forward references to type variables are prohibited", t) + return AnyType(TypeOfAny.from_error) + return ForwardRef(t) + # None of the above options worked, we give up. + self.fail('Invalid type "{}"'.format(name), t) + if self.third_pass and sym.kind == TVAR: + self.note_func("Forward references to type variables are prohibited", t) + return AnyType(TypeOfAny.from_error) + # TODO: Would it be better to always return Any instead of UnboundType + # in case of an error? On one hand, UnboundType has a name so error messages + # are more detailed, on the other hand, some of them may be bogus. return t info = sym.node # type: TypeInfo - if sym.is_aliasing: - if sym.alias_name is not None: - self.aliases_used.add(sym.alias_name) if len(t.args) > 0 and info.fullname() == 'builtins.tuple': fallback = Instance(info, [AnyType(TypeOfAny.special_form)], t.line) return TupleType(self.anal_array(t.args), fallback, t.line) @@ -347,7 +339,19 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # valid count at this point. Thus we may construct an # Instance with an invalid number of type arguments. instance = Instance(info, self.anal_array(t.args), t.line, t.column) - instance.from_generic_builtin = sym.normalized + if not t.args and self.options.disallow_any_generics and not self.defining_alias: + # We report/patch invalid built-in instances already during second pass. + # This is done to avoid storing additional state on instances. + # All other (including user defined) generics will be patched/reported + # in the third pass. + if not self.is_typeshed_stub and info.fullname() in nongen_builtins: + alternative = nongen_builtins[info.fullname()] + self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) + any_type = AnyType(TypeOfAny.from_error, line=t.line) + else: + any_type = AnyType(TypeOfAny.from_omitted_generics, line=t.line) + instance.args = [any_type] * len(info.type_vars) + tup = info.tuple_type if tup is not None: # The class has a Tuple[...] base class so it will be @@ -403,7 +407,7 @@ def visit_type_var(self, t: TypeVarType) -> Type: def visit_callable_type(self, t: CallableType, nested: bool = True) -> Type: # Every Callable can bind its own type variables, if they're not in the outer scope with self.tvar_scope_frame(): - if self.aliasing: + if self.defining_alias: variables = t.variables else: variables = self.bind_function_type_variables(t, t) @@ -705,20 +709,8 @@ def visit_instance(self, t: Instance) -> None: # Check type argument count. if len(t.args) != len(info.type_vars): if len(t.args) == 0: - from_builtins = t.type.fullname() in nongen_builtins and not t.from_generic_builtin - if (self.options.disallow_any_generics and - not self.is_typeshed_stub and - from_builtins): - alternative = nongen_builtins[t.type.fullname()] - self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) - # Insert implicit 'Any' type arguments. - if from_builtins: - # this 'Any' was already reported elsewhere - any_type = AnyType(TypeOfAny.special_form, - line=t.line, column=t.column) - else: - any_type = AnyType(TypeOfAny.from_omitted_generics, - line=t.line, column=t.column) + any_type = AnyType(TypeOfAny.from_omitted_generics, + line=t.line, column=t.column) t.args = [any_type] * len(info.type_vars) return # Invalid number of type parameters. @@ -832,6 +824,42 @@ def anal_type(self, tp: UnboundType) -> Type: TypeVarList = List[Tuple[str, TypeVarExpr]] +def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type], + fail: Callable[[str, Context], None], no_args: bool, ctx: Context) -> Type: + """Expand a (generic) type alias target following the rules outlined in TypeAlias docstring. + + Here: + target: original target type (contains unbound type variables) + alias_tvars: type variable names + args: types to be substituted in place of type variables + fail: error reporter callback + no_args: whether original definition used a bare generic `A = List` + ctx: context where expansion happens + """ + exp_len = len(alias_tvars) + act_len = len(args) + if exp_len > 0 and act_len == 0: + # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] + assert alias_tvars is not None + return set_any_tvars(target, alias_tvars, ctx.line, ctx.column) + if exp_len == 0 and act_len == 0: + if no_args: + assert isinstance(target, Instance) + return Instance(target.type, [], line=ctx.line, column=ctx.column) + return target + if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and no_args: + tp = Instance(target.type, args) + tp.line = ctx.line + tp.column = ctx.column + return tp + if act_len != exp_len: + fail('Bad number of arguments for type alias, expected: %s, given: %s' + % (exp_len, act_len), ctx) + return set_any_tvars(target, alias_tvars or [], + ctx.line, ctx.column, implicit=False) + return replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column) + + def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], newline: int, newcolumn: int) -> Type: """Replace type variables in a generic type alias tp with substitutions subs diff --git a/mypy/types.py b/mypy/types.py index 9075ada907d29..f9d5d3c23a343 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -495,7 +495,7 @@ class Instance(Type): The list of type variables may be empty. """ - __slots__ = ('type', 'args', 'erased', 'invalid', 'from_generic_builtin', 'type_ref') + __slots__ = ('type', 'args', 'erased', 'invalid', 'type_ref') def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type], line: int = -1, column: int = -1, erased: bool = False) -> None: @@ -505,8 +505,6 @@ def __init__(self, typ: mypy.nodes.TypeInfo, args: List[Type], self.args = args self.erased = erased # True if result of type variable substitution self.invalid = False # True if recovered after incorrect number of type arguments error - # True if created from a generic builtin (e.g. list() or set()) - self.from_generic_builtin = False self.type_ref = None # type: Optional[str] def accept(self, visitor: 'TypeVisitor[T]') -> T: diff --git a/mypy/visitor.py b/mypy/visitor.py index 1bb941e4cb4f9..d30fc9693f801 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -351,6 +351,9 @@ def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: def visit_var(self, o: 'mypy.nodes.Var') -> T: pass + def visit_type_alias(self, o: 'mypy.nodes.TypeAlias') -> T: + pass + # Statements def visit_block(self, o: 'mypy.nodes.Block') -> T: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index cad65285ebac8..0f955a5e48a6c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2244,7 +2244,7 @@ class B: a_int = SameA[int] reveal_type(B().a_any) # E: Revealed type is 'def () -> __main__.A[Any, Any]' -reveal_type(B().a_int()) # E: Revealed type is '__main__.A[builtins.int*, builtins.int*]' +reveal_type(B().a_int()) # E: Revealed type is '__main__.A[builtins.int, builtins.int]' class C: a_int = SameA[int] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 58c9b11e368a6..6e6b99ac744cb 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -963,8 +963,8 @@ y: U reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' reveal_type(y) # E: Revealed type is 'builtins.int' -U[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 -O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +U[int] # E: Type application targets a non-generic function or class +O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 # E: Type application is only supported for generic classes [out] [case testAliasesInClassBodyNormalVsSubscripted] @@ -977,8 +977,7 @@ class C: b = Union[int, str] # This is an alias c: Type[object] = Iterable[int] # This is however also a variable a = B - b = int # E: Cannot assign multiple types to name "b" without an explicit "Type[...]" annotation \ - # E: Incompatible types in assignment (expression has type "Type[int]", variable has type "Type alias to Union") + b = int # E: Cannot assign multiple types to name "b" without an explicit "Type[...]" annotation c = int def f(self, x: a) -> None: pass # E: Invalid type "__main__.C.a" def g(self, x: b) -> None: pass @@ -1017,15 +1016,19 @@ CA = Callable[[T], int] TA = Tuple[T, int] UA = Union[T, int] -cs = CA[str] + 1 # E: Unsupported left operand type for + ("Type alias to Callable") +cs = CA + 1 # E: The type alias to Callable is invalid in runtime context reveal_type(cs) # E: Revealed type is 'Any' -ts = TA[str]() # E: "Type alias to Tuple" not callable +ts = TA() # E: The type alias to Tuple is invalid in runtime context reveal_type(ts) # E: Revealed type is 'Any' -us = UA[str].x # E: "Type alias to Union" has no attribute "x" +us = UA.x # E: The type alias to Union is invalid in runtime context reveal_type(us) # E: Revealed type is 'Any' +xx = CA[str] + 1 # E: Type application is only supported for generic classes +yy = TA[str]() # E: Type application is only supported for generic classes +zz = UA[str].x # E: Type application is only supported for generic classes + [out] [case testGenericTypeAliasesTypeVarBinding] diff --git a/test-data/unit/check-incomplete-fixture.test b/test-data/unit/check-incomplete-fixture.test index 5796ba28377a8..d29d6d7a93681 100644 --- a/test-data/unit/check-incomplete-fixture.test +++ b/test-data/unit/check-incomplete-fixture.test @@ -16,24 +16,24 @@ m.x # E: "object" has no attribute "x" from typing import List def f(x: List[int]) -> None: pass [out] -main:1: error: Name '__builtins__.list' is not defined -main:1: note: Maybe your test fixture does not define "typing.List"? +main:1: error: Module 'typing' has no attribute 'List' +main:1: note: Maybe your test fixture does not define "builtins.list"? main:1: note: Consider adding [builtins fixtures/list.pyi] to your test description [case testDictMissingFromStubs] from typing import Dict def f(x: Dict[int]) -> None: pass [out] -main:1: error: Name '__builtins__.dict' is not defined -main:1: note: Maybe your test fixture does not define "typing.Dict"? +main:1: error: Module 'typing' has no attribute 'Dict' +main:1: note: Maybe your test fixture does not define "builtins.dict"? main:1: note: Consider adding [builtins fixtures/dict.pyi] to your test description [case testSetMissingFromStubs] from typing import Set def f(x: Set[int]) -> None: pass [out] -main:1: error: Name '__builtins__.set' is not defined -main:1: note: Maybe your test fixture does not define "typing.Set"? +main:1: error: Module 'typing' has no attribute 'Set' +main:1: note: Maybe your test fixture does not define "builtins.set"? main:1: note: Consider adding [builtins fixtures/set.pyi] to your test description [case testBoolMissingFromStubs] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index a9c6fa0e75236..d0d6656f741ac 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -1452,7 +1452,7 @@ last = XT[G]() reveal_type(notes) # E: Revealed type is 'y.G[y.G[builtins.int]]' reveal_type(another) # E: Revealed type is 'y.G[y.G*[builtins.int]]' reveal_type(second) # E: Revealed type is 'y.G[builtins.str*]' -reveal_type(last) # E: Revealed type is 'y.G[y.G*]' +reveal_type(last) # E: Revealed type is 'y.G[y.G*[Any]]' [file y.py] from typing import Generic, TypeVar diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test index 94bba5045016b..718e96c901b6e 100644 --- a/test-data/unit/check-namedtuple.test +++ b/test-data/unit/check-namedtuple.test @@ -486,7 +486,7 @@ def f(x: a.N) -> None: tmp/b.py:4: error: Revealed type is 'Tuple[Any, fallback=a.N]' tmp/b.py:6: error: Revealed type is 'Tuple[Any, fallback=a.N]' -[case testSimpleSelfReferrentialNamedTuple] +[case testSimpleSelfReferentialNamedTuple] from typing import NamedTuple class MyNamedTuple(NamedTuple): parent: 'MyNamedTuple' @@ -500,7 +500,7 @@ reveal_type(x.parent) main:2: error: Recursive types not fully supported yet, nested types replaced with "Any" main:9: error: Revealed type is 'Tuple[Any, fallback=__main__.MyNamedTuple]' --- Some crazy selef-referential named tuples and types dicts +-- Some crazy self-referential named tuples and types dicts -- to be sure that everything works [case testCrossFileNamedTupleForwardRefs] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index d44398885702e..0a3ddffb13a07 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -58,8 +58,7 @@ U = Union[int, str] [case testProhibitReassigningAliases] A = float -A = int # E: Cannot assign to a type \ - # E: Cannot assign multiple types to name "A" without an explicit "Type[...]" annotation +A = int # E: Cannot assign multiple types to name "A" without an explicit "Type[...]" annotation [out] [case testProhibitReassigningSubscriptedAliases] @@ -278,3 +277,152 @@ z: Optional[int] x = y # E: Incompatible types in assignment (expression has type "Optional[int]", variable has type "int") y = z [builtins fixtures/bool.pyi] + +[case testAliasToTupleAndCallable] +from typing import Callable, Tuple +C = Callable +T = Tuple +c: C +t: T +reveal_type(c) # E: Revealed type is 'def (*Any, **Any) -> Any' +reveal_type(t) # E: Revealed type is 'builtins.tuple[Any]' +bad: C[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +also_bad: T[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +[builtins fixtures/tuple.pyi] +[out] + +[case testAliasRefOnClass] +from typing import Generic, TypeVar, Type + +T = TypeVar('T') +class C(Generic[T]): + pass + +class N: + A = C[T] + B = C[int] + +x: N.A[C] +reveal_type(x) # E: Revealed type is '__main__.C[__main__.C[Any]]' + +xx = N.A[C]() +reveal_type(xx) # E: Revealed type is '__main__.C[__main__.C*[Any]]' + +y = N.A() +reveal_type(y) # E: Revealed type is '__main__.C[Any]' + +M = N +b = M.A[int]() +reveal_type(b) # E: Revealed type is '__main__.C[builtins.int*]' + +n: Type[N] +w = n.B() +reveal_type(w) # E: Revealed type is '__main__.C[builtins.int]' +[out] + +[case testTypeAliasesToNamedTuple] +from nt import C, D, E + +A1 = C +A2 = D +A3 = E + +class Cls: + A1 = C + A2 = D + A3 = E + +A1('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" +a1 = A1(1) +reveal_type(a1) # E: Revealed type is 'Tuple[builtins.int, fallback=nt.C]' + +A2(0) # E: Argument 1 to "D" has incompatible type "int"; expected "str" +a2 = A2('yes') +reveal_type(a2) # E: Revealed type is 'Tuple[builtins.str, fallback=nt.D]' + +a3 = A3() +reveal_type(a3) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=nt.E]' + +Cls.A1('no') # E: Argument 1 has incompatible type "str"; expected "int" +ca1 = Cls.A1(1) +reveal_type(ca1) # E: Revealed type is 'Tuple[builtins.int, fallback=nt.C]' + +Cls.A2(0) # E: Argument 1 has incompatible type "int"; expected "str" +ca2 = Cls.A2('yes') +reveal_type(ca2) # E: Revealed type is 'Tuple[builtins.str, fallback=nt.D]' + +ca3 = Cls.A3() +reveal_type(ca3) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=nt.E]' +[file nt.pyi] +from typing import NamedTuple, Tuple + +class C(NamedTuple): + x: int + +D = NamedTuple('D', [('y', str)]) + +class E(Tuple[int, str]): + pass +[builtins fixtures/tuple.pyi] +[out] + +[case testTypeAliasesToAny] +from typing import Any + +A = Any +A # this should not fail +[out] + +[case testDoubleImportsOfAnAlias] +from b import * +from c import * +[file b.py] +from a import A +[file c.py] +from a import A +[file a.py] +A = int +[out] + +[case testDoubleImportsOfAnAlias2] +from b import A +from c import A +[file b.py] +from a import A +[file c.py] +from a import A +[file a.py] +A = int +[out] + +[case testDoubleImportsOfAnAlias3] +from b import * +from c import * +[file b.py] +from a import A +[file c.py] +from a import A +[file a.py] +from typing import Union +A = Union[None] +[out] + +[case testAliasToClassMethod] +from typing import TypeVar, Generic, Union, Type + +T = TypeVar('T', bound=C) + +MYPY = False +if MYPY: + test = classmethod + +class C: + @test + def meth(cls: Type[T], arg: int) -> Union[T, int]: ... + +class D(C): ... + +reveal_type(D.meth(1)) # E: Revealed type is 'Union[__main__.D*, builtins.int]' +reveal_type(D().meth(1)) # E: Revealed type is 'Union[__main__.D*, builtins.int]' +[builtins fixtures/classmethod.pyi] +[out] diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index 14b6a70229277..46c8a10b2d807 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -241,7 +241,7 @@ class M(type): -> m.C -> m -> m - -> , m + -> , , m -> m [case testMetaclassDepsDeclaredNested] @@ -386,7 +386,7 @@ class I: pass -> , m -> -> - -> m, m.C + -> m.C [case testAliasDepsGenericMod] from mod import I, S, D @@ -700,6 +700,8 @@ class S: pass -> , m, m.f -> , m, m.f -> , m, m.f + -> m + -> m [case testAliasDepsNestedClass] from mod import I, S, D @@ -898,7 +900,7 @@ class D: -> m, m.A.meth, m.f -> m.f -> m.f - -> , , m, m.A.meth, m.f + -> , , m, m.A.meth [case testFuncBasedEnum] from enum import Enum diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index b1f81cb0b833a..9d0205bd941ea 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -3538,7 +3538,7 @@ from b import B B(int()) [out] == -c.py:2: error: Argument 1 has incompatible type "int"; expected "str" +c.py:2: error: Argument 1 to "A" has incompatible type "int"; expected "str" [case testAliasFineInitGenericFunc] import c @@ -3564,7 +3564,7 @@ from b import B def f() -> None: B(str()) [out] -c.py:3: error: Argument 1 has incompatible type "str"; expected "int" +c.py:3: error: Argument 1 to "A" has incompatible type "str"; expected "int" == [case testAliasFineInitChainedMod] @@ -4885,11 +4885,12 @@ import T == == main:3: error: "C" expects no type arguments, but 1 given +main:3: error: Invalid type "T" main:5: error: Free type variable expected in Generic[...] main:6: error: Invalid type "T" +main:9: error: Invalid type "T" main:9: error: Bad number of arguments for type alias, expected: 0, given: 1 main:9: error: "C" expects no type arguments, but 1 given -main:9: error: Invalid type "T" [case testChangeClassToModule] import a @@ -7200,7 +7201,6 @@ def fu() -> int: [out] == - [case testRefreshOptions] [file mypy.ini] [[mypy] @@ -7219,6 +7219,46 @@ x = [] # type: List [out] == +[case testImportOnTopOfAlias1] +from a import A +x: A +[file a.py] +from typing import TypeVar, List +T = TypeVar('T') +A = List[T] +[file a.py.2] +from typing import TypeVar, List +T = TypeVar('T') +A = List[T] +from b import A +[file b.py] +# empty +[builtins fixtures/list.pyi] +[out] +== +a.py:4: error: Module 'b' has no attribute 'A' +a.py:4: error: Name 'A' already defined on line 3 + +-- the order of errors is different with cache +[case testImportOnTopOfAlias2] +from a import A +x: A +[file a.py] +from typing import TypeVar, List +T = TypeVar('T') +A = List[T] +[file a.py.2] +from typing import TypeVar, List +T = TypeVar('T') +A = List[T] +from b import A as A +[file b.py] +def A(x: str) -> str: pass +[builtins fixtures/list.pyi] +[out] +== +a.py:4: error: Incompatible import of "A" (imported name has type "Callable[[str], str]", local name has type "Type[List[Any]]") + [case testRefreshForWithTypeComment1] [file a.py] from typing import List diff --git a/test-data/unit/fixtures/classmethod.pyi b/test-data/unit/fixtures/classmethod.pyi index bac4ea7d153bc..5aff9f8f1006f 100644 --- a/test-data/unit/fixtures/classmethod.pyi +++ b/test-data/unit/fixtures/classmethod.pyi @@ -12,8 +12,8 @@ class type: class function: pass # Dummy definitions. -classmethod = object() -staticmethod = object() +class classmethod: pass +class staticmethod: pass class int: @classmethod diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index a7dd72fab270c..736fa15bfef73 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -980,7 +980,7 @@ target: Generic: Var<2> T: TypeVarExpr<3> TypeVar: Var<4> - X: Var<5>(type_override=target.A[builtins.int<6>]<1>)(def () -> target.A[builtins.int*<6>]<1>) + X: TypeAlias<5> ==> __main__: target: MypyFile<0> @@ -989,7 +989,7 @@ target: Generic: Var<2> T: TypeVarExpr<3> TypeVar: Var<4> - X: Var<5>(type_override=target.A[builtins.str<7>]<1>)(def () -> target.A[builtins.str*<7>]<1>) + X: TypeAlias<5> [case testGenericFunction_types] import target @@ -1163,7 +1163,7 @@ __main__: target: MypyFile<0> target: Iterator: TypeInfo<1> - List: TypeInfo<2> + List: TypeAlias<2> Tuple: Var<3> contextmanager: FuncDef<4> f: Decorator<5> @@ -1172,7 +1172,7 @@ __main__: target: MypyFile<0> target: Iterator: TypeInfo<1> - List: TypeInfo<2> + List: TypeAlias<2> Tuple: Var<3> contextmanager: FuncDef<4> f: Decorator<5> diff --git a/test-data/unit/semanal-typealiases.test b/test-data/unit/semanal-typealiases.test index 5178a71f871f2..edc215c29c406 100644 --- a/test-data/unit/semanal-typealiases.test +++ b/test-data/unit/semanal-typealiases.test @@ -46,7 +46,7 @@ MypyFile:1( ImportFrom:1(typing, [List]) ExpressionStmt:2( TypeApplication:2( - NameExpr(List [builtins.list]) + NameExpr(List [typing.List]) Types( builtins.list[builtins.int])))) @@ -61,7 +61,7 @@ MypyFile:1( TypeApplication:2( MemberExpr:2( NameExpr(typing) - List [builtins.list]) + List [typing.List]) Types( builtins.list[builtins.int]))))