From e99a5a647a98a511b7a32ffa5062fe5424e17f2f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 6 Jun 2018 17:58:03 +0100 Subject: [PATCH 01/29] Start removing some old type alias stuff --- mypy/checkexpr.py | 35 +----------- mypy/fixup.py | 15 ++---- mypy/nodes.py | 126 +++++++++++++++----------------------------- mypy/semanal.py | 19 ++----- mypy/server/deps.py | 2 +- mypy/typeanal.py | 2 +- 6 files changed, 53 insertions(+), 146 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 69c72198e32d0..a6549955e0d79 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -21,7 +21,7 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, - TypeAliasExpr, BackquoteExpr, EnumCallExpr, + BackquoteExpr, EnumCallExpr, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE ) from mypy.literals import literal @@ -2061,39 +2061,6 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: 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: - # 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 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) - else: - # This type is invalid in most runtime contexts - # and corresponding an error will be reported. - return alias.fallback - if isinstance(tp, CallableType): - if len(tp.variables) != len(item.args): - self.msg.incompatible_type_application(len(tp.variables), - len(item.args), item) - return AnyType(TypeOfAny.from_error) - return self.apply_generic_arguments(tp, item.args, item) - elif isinstance(tp, Overloaded): - for it in tp.items(): - if len(it.variables) != len(item.args): - self.msg.incompatible_type_application(len(it.variables), - len(item.args), item) - return AnyType(TypeOfAny.from_error) - return Overloaded([self.apply_generic_arguments(it, item.args, item) - for it in tp.items()]) - return AnyType(TypeOfAny.special_form) - def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" return self.check_lst_expr(e.items, 'builtins.list', '', e) diff --git a/mypy/fixup.py b/mypy/fixup.py index fa7edd40a4874..0bcc68410b4f6 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -5,8 +5,7 @@ from mypy.nodes import ( MypyFile, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, FuncDef, OverloadedFuncDef, Decorator, Var, - TypeVarExpr, ClassDef, Block, - LDEF, MDEF, GDEF, TYPE_ALIAS + TypeVarExpr, ClassDef, Block, TypeAlias, ) from mypy.types import ( CallableType, EllipsisType, Instance, Overloaded, TupleType, TypedDictType, @@ -77,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: @@ -141,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/nodes.py b/mypy/nodes.py index 61d86df0c5374..3ca350f5e74a6 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()} @@ -1419,8 +1417,8 @@ class IndexExpr(Expression): # Inferred __getitem__ method type method_type = None # type: mypy.types.Type # If not None, this is actually semantically a type application - # Class[type, ...] or a type alias initializer. - analyzed = None # type: Union[TypeApplication, TypeAliasExpr, None] + # Class[type, ...]. + analyzed = None # type: Union[TypeApplication, None] def __init__(self, base: Expression, index: Expression) -> None: super().__init__() @@ -1897,29 +1895,6 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarExpr': data['variance']) -class TypeAliasExpr(Expression): - """Type alias expression (rvalue).""" - - 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 - - def __init__(self, type: 'mypy.types.Type', tvars: List[str], - fallback: 'mypy.types.Type', in_runtime: bool = False) -> None: - super().__init__() - self.type = type - self.fallback = fallback - self.in_runtime = in_runtime - self.tvars = tvars - - def accept(self, visitor: ExpressionVisitor[T]) -> T: - return visitor.visit_type_alias_expr(self) - - class NamedTupleExpr(Expression): """Named tuple expression namedtuple(...) or NamedTuple(...).""" @@ -2423,6 +2398,41 @@ def __getattribute__(self, attr: str) -> None: raise AssertionError('De-serialization failure: TypeInfo not fixed') +class TypeAlias(SymbolNode): + + __slots__ = ('target', '_fullname', 'alias_tvars') + + def __init__(self, target: 'mypy.types.Type', fullname: str, + alias_tvars: Optional[List[str]] = None) -> None: + self._fullname = fullname + self.target = target + if alias_tvars is None: + alias_tvars = [] + self.alias_tvars = alias_tvars + + 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, + } # type: JsonDict + return data + + @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']) + return cls(target, fullname, alias_tvars) + + class SymbolTableNode: """Description of a name binding in a symbol table. @@ -2448,11 +2458,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: @@ -2461,17 +2467,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. @@ -2479,14 +2479,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 @@ -2503,43 +2496,28 @@ class SymbolTableNode: __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 @@ -2551,12 +2529,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, FuncDef)) + and node.type is not None): return node.type elif isinstance(node, Decorator): return node.var.type @@ -2566,10 +2541,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 @@ -2598,8 +2570,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: @@ -2614,9 +2584,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 @@ -2631,18 +2598,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/semanal.py b/mypy/semanal.py index b241cb047acd2..6cddfa6fd835d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -52,7 +52,7 @@ StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_NAMED_OPT, type_aliases, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode, - SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, + SetComprehension, DictionaryComprehension, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, ImportedName, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, nongen_builtins, @@ -1373,13 +1373,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. @@ -1463,10 +1458,7 @@ def normalize_type_alias(self, node: SymbolTableNode, 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 + node = SymbolTableNode(new_node.kind, new_node.node) return node def add_fixture_note(self, fullname: str, ctx: Context) -> None: @@ -1503,12 +1495,7 @@ def visit_import_all(self, i: ImportAll) -> None: if self.process_import_over_existing_name( name, existing_symbol, new_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(new_node.kind, new_node.node) self.add_symbol(name, symbol, i) i.imported_names.append(name) else: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f3df6fdb7b0ea..ba01262e93e63 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -92,7 +92,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a ComparisonExpr, GeneratorExpr, DictionaryComprehension, StarExpr, PrintStmt, ForStmt, WithStmt, TupleExpr, ListExpr, OperatorAssignmentStmt, DelStmt, YieldFromExpr, Decorator, Block, TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, SuperExpr, Var, NamedTupleExpr, TypedDictExpr, - LDEF, MDEF, GDEF, FuncItem, TypeAliasExpr, NewTypeExpr, ImportAll, EnumCallExpr, AwaitExpr, + LDEF, MDEF, GDEF, FuncItem, NewTypeExpr, ImportAll, EnumCallExpr, AwaitExpr, op_methods, reverse_op_methods, ops_with_inplace_method, unary_op_methods ) from mypy.traverser import TraverserVisitor diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 70c0766d53076..e9e28a66e16c5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -21,7 +21,7 @@ ) from mypy.nodes import ( - TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, + TVAR, 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, Node, ImportedName, type_aliases From 90e8110e9ee82910af9d43ae2a47a73562c0ab18 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Jun 2018 17:26:18 +0100 Subject: [PATCH 02/29] Continue removing some type alias stuff --- mypy/checker.py | 2 +- mypy/checkexpr.py | 7 +-- mypy/literals.py | 5 +- mypy/messages.py | 7 +-- mypy/nodes.py | 35 +++++------ mypy/semanal.py | 133 ++++++++-------------------------------- mypy/semanal_pass3.py | 34 ++-------- mypy/server/astdiff.py | 11 ++-- mypy/server/astmerge.py | 10 +-- mypy/server/deps.py | 3 +- mypy/strconv.py | 3 - mypy/test/testmerge.py | 3 - mypy/treetransform.py | 11 +--- mypy/typeanal.py | 45 +++++--------- mypy/types.py | 4 +- mypy/visitor.py | 7 --- 16 files changed, 78 insertions(+), 242 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7875672bd59b2..5c147f79a50ad 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2824,7 +2824,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 a6549955e0d79..1d89092e71c97 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -21,7 +21,7 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, - BackquoteExpr, EnumCallExpr, + BackquoteExpr, EnumCallExpr, TypeAlias, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE ) from mypy.literals import literal @@ -219,9 +219,8 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: # Undefined names should already be reported in semantic analysis. 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)): + and isinstance(typ.analyzed, TypeApplication)) + or (isinstance(typ, NameExpr) and node and isinstance(node.node, TypeAlias))): self.msg.type_arguments_not_allowed(e) if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo): if typ.node.typeddict_type: diff --git a/mypy/literals.py b/mypy/literals.py index 44616564a72e7..09c5f86aa2053 100644 --- a/mypy/literals.py +++ b/mypy/literals.py @@ -6,7 +6,7 @@ UnicodeExpr, ListExpr, TupleExpr, SetExpr, DictExpr, CallExpr, SliceExpr, CastExpr, ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr, TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension, - GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr, + GeneratorExpr, BackquoteExpr, TypeVarExpr, NamedTupleExpr, EnumCallExpr, TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, ) from mypy.visitor import ExpressionVisitor @@ -205,9 +205,6 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> None: def visit_type_var_expr(self, e: TypeVarExpr) -> None: return None - def visit_type_alias_expr(self, e: TypeAliasExpr) -> None: - return None - def visit_namedtuple_expr(self, e: NamedTupleExpr) -> None: return None diff --git a/mypy/messages.py b/mypy/messages.py index a5bc2e1aa9f68..ec6287db94416 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -24,7 +24,7 @@ UninhabitedType, TypeOfAny, ForwardRef, UnboundType ) from mypy.nodes import ( - TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, + TypeInfo, Context, MypyFile, op_methods, FuncDef, ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode, CallExpr, Expression @@ -241,11 +241,6 @@ 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()] - alias = alias.split('.')[-1] - items = [self.format_bare(arg) for arg in itype.args] - return '{}[{}]'.format(alias, ', '.join(items)) else: # There are type arguments. Convert the arguments to strings. # If the result is too long, replace arguments with [...]. diff --git a/mypy/nodes.py b/mypy/nodes.py index 3ca350f5e74a6..0cd6ddc54659c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -107,26 +107,18 @@ def get_column(self) -> int: '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_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.replace('__builtins__', 'builtins'), alias) + for alias, name in type_aliases.items()) + class Node(Context): @@ -2400,15 +2392,20 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): - __slots__ = ('target', '_fullname', 'alias_tvars') + __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on') def __init__(self, target: 'mypy.types.Type', fullname: str, - alias_tvars: Optional[List[str]] = None) -> None: + *, + alias_tvars: Optional[List[str]] = None, + depends_on: Optional[List[str]] = None) -> None: self._fullname = fullname self.target = target if alias_tvars is None: alias_tvars = [] self.alias_tvars = alias_tvars + if depends_on is None: + depends_on = [] + self.depends_on = depends_on def name(self) -> str: return self._fullname.split('.')[-1] @@ -2421,6 +2418,7 @@ def serialize(self) -> JsonDict: 'fullname': self._fullname, 'target': self.target.serialize(), 'alias_tvars': self.alias_tvars, + 'depends_on': self.depends_on } # type: JsonDict return data @@ -2429,8 +2427,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': assert data['.class'] == 'TypeAlias' fullname = data['fullname'] alias_tvars = data['alias_tvars'] + depends_on = data['depends_on'] target = mypy.types.deserialize_type(data['target']) - return cls(target, fullname, alias_tvars) + return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on) class SymbolTableNode: diff --git a/mypy/semanal.py b/mypy/semanal.py index 6cddfa6fd835d..3e46ddf635072 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -52,11 +52,11 @@ StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_NAMED_OPT, type_aliases, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode, - SetComprehension, DictionaryComprehension, + SetComprehension, DictionaryComprehension, TypeAlias, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, ImportedName, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, 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 @@ -291,6 +291,11 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, v.type = self.anal_type(v.type) v.is_ready = True + if self.cur_mod_id == 'typing': + for alias, target_name in type_aliases.items(): + name = alias.split('.')[-1] + self.cur_mod_node.names[name] = SymbolTableNode(GDEF, TypeAlias(self.builtin_type(target_name), alias)) + defs = file_node.defs self.scope.enter_file(file_node.fullname()) for d in defs: @@ -589,8 +594,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: return 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) @@ -1356,7 +1360,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. @@ -1432,35 +1435,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) - 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: @@ -1484,18 +1458,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) + symbol = SymbolTableNode(node.kind, node.node) self.add_symbol(name, symbol, i) i.imported_names.append(name) else: @@ -1641,20 +1613,6 @@ 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]]: @@ -1727,16 +1685,16 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: 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) + depends_on.add(node.node.fullname()) 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) @@ -1746,32 +1704,8 @@ 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 + node.node = TypeAlias(res, node.node.fullname(), alias_tvars=qualified_tvars, + depends_on=list(depends_on)) def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, @@ -2485,9 +2419,8 @@ def visit_exec_stmt(self, s: ExecStmt) -> None: 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]) + if n and isinstance(n.node, TypeAlias): + self.add_type_alias_deps([n.node.fullname()]) def visit_name_expr(self, expr: NameExpr) -> None: n = self.lookup(expr.name, expr) @@ -2708,7 +2641,6 @@ def visit_member_expr(self, expr: MemberExpr) -> 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) @@ -2721,7 +2653,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)): @@ -2770,7 +2702,6 @@ def visit_member_expr(self, expr: MemberExpr) -> None: 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 not n: return expr.kind = n.kind @@ -2807,21 +2738,11 @@ 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): + elif refers_to_class_or_function(expr.base) or isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): # Special form -- type application. # Translate index to an unanalyzed type. + if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): + self.add_type_alias_deps(expr.base.node.depends_on) types = [] # type: List[Type] if isinstance(expr.index, TupleExpr): items = expr.index.items @@ -2837,10 +2758,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: 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) + # TODOALIAS list[int] else: expr.index.accept(self) @@ -3076,7 +2994,6 @@ def lookup_qualified(self, name: str, ctx: Context, 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: @@ -3089,8 +3006,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 diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 6b70025ed23bc..2f1a6bb132236 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -16,7 +16,7 @@ from mypy import messages, experiments from mypy.nodes import ( Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef, - Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr, + Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, NameExpr, CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue, TupleExpr, RevealExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef, MDEF, @@ -69,7 +69,6 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, with experiments.strict_optional_set(options.strict_optional): self.scope.enter_file(file_node.fullname()) self.accept(file_node) - self.analyze_symbol_table(file_node.names) self.scope.leave() del self.cur_mod_node self.patches = [] @@ -148,7 +147,6 @@ def visit_class_def(self, tdef: ClassDef) -> None: self.analyze(tdef.analyzed.info.tuple_type, tdef.analyzed, warn=True) self.analyze_info(tdef.analyzed.info) super().visit_class_def(tdef) - self.analyze_symbol_table(tdef.info.names) self.scope.leave() def visit_decorator(self, dec: Decorator) -> None: @@ -218,8 +216,6 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: NewType, TypedDict, NamedTuple, and TypeVar. """ self.analyze(s.type, s) - if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr): - self.analyze(s.rvalue.analyzed.type, s.rvalue.analyzed, warn=True) if isinstance(s.rvalue, CallExpr): analyzed = s.rvalue.analyzed if isinstance(analyzed, NewTypeExpr): @@ -243,15 +239,6 @@ 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 +275,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: @@ -301,8 +287,7 @@ def perform_transform(self, node: Union[Node, SymbolTableNode], for n in node.target: if isinstance(n, NameExpr) and isinstance(n.node, Var) and n.node.type: n.node.type = transform(n.node.type) - if isinstance(node, (FuncDef, OverloadedFuncDef, CastExpr, AssignmentStmt, - TypeAliasExpr, Var)): + if isinstance(node, (FuncDef, OverloadedFuncDef, CastExpr, AssignmentStmt, Var)): assert node.type, "Scheduled patch for non-existent type" node.type = transform(node.type) if isinstance(node, NewTypeExpr): @@ -337,9 +322,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 +352,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. @@ -396,12 +378,6 @@ def analyze_types(self, types: List[Type], node: Node) -> None: self.cur_mod_node.alias_deps[target].update(analyzer.aliases_used) self.generate_type_patches(node, indicator, warn=False) - 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) - def make_scoped_patch(self, fn: Callable[[], None]) -> Callable[[], None]: saved_scope = self.scope.save() @@ -411,7 +387,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/server/astdiff.py b/mypy/server/astdiff.py index 30b449c9f316c..bb9f5ce459e34 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -54,7 +54,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from mypy.nodes import ( SymbolTable, SymbolTableNode, TypeInfo, Var, MypyFile, SymbolNode, Decorator, TypeVarExpr, - OverloadedFuncDef, FuncItem, MODULE_REF, TYPE_ALIAS, UNBOUND_IMPORTED, TVAR + OverloadedFuncDef, FuncItem, MODULE_REF, UNBOUND_IMPORTED, TVAR, TypeAlias ) from mypy.types import ( Type, TypeVisitor, UnboundType, TypeList, AnyType, NoneTyp, UninhabitedType, @@ -146,15 +146,16 @@ 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.depends_on, + 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 7ab4fd728364e..2f5227482b33f 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -50,7 +50,7 @@ from mypy.nodes import ( Node, MypyFile, SymbolTable, Block, AssignmentStmt, NameExpr, MemberExpr, RefExpr, TypeInfo, FuncDef, ClassDef, NamedTupleExpr, SymbolNode, Var, Statement, SuperExpr, NewTypeExpr, - OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, FuncBase, TypeAliasExpr, CallExpr, + OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, FuncBase, CallExpr, CastExpr, MDEF ) @@ -254,11 +254,6 @@ def visit_enum_call_expr(self, node: EnumCallExpr) -> None: self.process_synthetic_type_info(node.info) super().visit_enum_call_expr(node) - 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 def visit_var(self, node: Var) -> None: @@ -439,9 +434,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 ba01262e93e63..d6aaf5a264293 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -350,8 +350,7 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: class_name = typ.type_object().fullname() self.add_dependency(make_trigger(class_name + '.__init__')) self.add_dependency(make_trigger(class_name + '.__new__')) - if isinstance(rvalue, IndexExpr) and isinstance(rvalue.analyzed, TypeAliasExpr): - self.add_type_dependencies(rvalue.analyzed.type) + # TODOALIAS deps from subscripted? else: # Normal assignment super().visit_assignment_stmt(o) diff --git a/mypy/strconv.py b/mypy/strconv.py index 8814e29e795d2..6f4f6e3e57414 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -452,9 +452,6 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> str: a += ['UpperBound({})'.format(o.upper_bound)] return self.dump(a, o) - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> str: - return 'TypeAliasExpr({})'.format(o.type) - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> str: return 'NamedTupleExpr:{}({}, {})'.format(o.line, o.info.name(), diff --git a/mypy/test/testmerge.py b/mypy/test/testmerge.py index 24ad912120537..78851a53dfb12 100644 --- a/mypy/test/testmerge.py +++ b/mypy/test/testmerge.py @@ -179,9 +179,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 140d514e41381..36c69f6c383fa 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -18,7 +18,7 @@ SymbolTable, RefExpr, TypeVarExpr, NewTypeExpr, PromoteExpr, ComparisonExpr, TempNode, StarExpr, Statement, Expression, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension, - DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr, + DictionaryComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, OverloadPart, EnumCallExpr, REVEAL_TYPE ) @@ -415,10 +415,7 @@ def visit_index_expr(self, node: IndexExpr) -> IndexExpr: if node.method_type: new.method_type = self.type(node.method_type) if node.analyzed: - if isinstance(node.analyzed, TypeApplication): - new.analyzed = self.visit_type_application(node.analyzed) - else: - new.analyzed = self.visit_type_alias_expr(node.analyzed) + new.analyzed = self.visit_type_application(node.analyzed) new.analyzed.set_line(node.analyzed.line) return new @@ -474,10 +471,6 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: self.types(node.values), 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) - def visit_newtype_expr(self, node: NewTypeExpr) -> NewTypeExpr: res = NewTypeExpr(node.name, node.old_type, line=node.line) res.info = node.info diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e9e28a66e16c5..1e5d76cfc4ed5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -24,7 +24,7 @@ TVAR, 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, Node, ImportedName, type_aliases + Decorator, Node, ImportedName, type_aliases, TypeAlias ) from mypy.tvar_scope import TypeVarScope from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -83,14 +83,14 @@ def analyze_type_alias(node: Expression, return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or - node.kind == TYPE_ALIAS): + 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. @@ -197,15 +197,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: @@ -216,7 +207,7 @@ 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): + not self.allow_unnormalized): self.fail(no_subscript_builtin_alias(fullname), t) if self.tvar_scope: tvar_def = self.tvar_scope.get_binding(sym) @@ -240,9 +231,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])]) @@ -284,12 +273,10 @@ 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) @@ -299,16 +286,16 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: 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) + return set_any_tvars(target, all_vars, t.line, t.column) if exp_len == 0 and act_len == 0: - return override + return target 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 [], + return set_any_tvars(target, 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 replace_alias_tvars(target, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): name = sym.fullname if name is None: @@ -336,9 +323,6 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: self.note_func("Forward references to type variables are prohibited", t) 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) @@ -349,7 +333,6 @@ 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 tup = info.tuple_type if tup is not None: # The class has a Tuple[...] base class so it will be @@ -707,7 +690,7 @@ 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 + from_builtins = t.type.fullname() in nongen_builtins if (self.options.disallow_any_generics and not self.is_typeshed_stub and from_builtins): diff --git a/mypy/types.py b/mypy/types.py index 1d47117d4991a..d00d0f1bb796c 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 97a05dc72a71c..8f3db96ac13b1 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -148,10 +148,6 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass - @abstractmethod - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: - pass - @abstractmethod def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass @@ -512,9 +508,6 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: - pass - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass From 441bd171d2d24f0aa13f66e01435f234c05ad6fa Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 7 Jun 2018 22:02:15 +0100 Subject: [PATCH 03/29] Return TypeAliasExpr; some clean-ups --- mypy/build.py | 1 + mypy/checker.py | 2 +- mypy/checkexpr.py | 8 ++++++-- mypy/literals.py | 5 ++++- mypy/nodes.py | 32 ++++++++++++++++++++++++-------- mypy/semanal.py | 10 +++++++--- mypy/semanal_pass3.py | 7 +++++-- mypy/server/astmerge.py | 6 +++++- mypy/server/deps.py | 5 +++-- mypy/strconv.py | 3 +++ mypy/treetransform.py | 10 ++++++++-- mypy/typeanal.py | 11 ++++++++++- mypy/visitor.py | 10 ++++++++++ 13 files changed, 87 insertions(+), 23 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 1992cecca4756..9a264e6d9d791 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2581,6 +2581,7 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: # typing}. Of these, builtins must be processed last or else # some builtin objects will be incompletely processed.) if 'builtins' in ascc: + pass scc.remove('builtins') scc.append('builtins') if manager.options.verbosity >= 2: diff --git a/mypy/checker.py b/mypy/checker.py index 5c147f79a50ad..948d296f60b21 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1232,7 +1232,7 @@ def check_method_override_for_base_with_name( # it can be checked for compatibility. original_type = base_attr.type if original_type is None: - if isinstance(base_attr.node, FuncDef): + if isinstance(base_attr.node, FuncBase): original_type = self.function_type(base_attr.node) elif isinstance(base_attr.node, Decorator): original_type = self.function_type(base_attr.node.func) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 1d89092e71c97..c982866857eab 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -21,7 +21,7 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, - BackquoteExpr, EnumCallExpr, TypeAlias, + TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE ) from mypy.literals import literal @@ -219,7 +219,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: # Undefined names should already be reported in semantic analysis. pass if ((isinstance(typ, IndexExpr) - and isinstance(typ.analyzed, TypeApplication)) + and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))) or (isinstance(typ, NameExpr) and node and isinstance(node.node, TypeAlias))): self.msg.type_arguments_not_allowed(e) if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo): @@ -2060,6 +2060,10 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: 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.""" + return AnyType(TypeOfAny.special_form) + def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" return self.check_lst_expr(e.items, 'builtins.list', '', e) diff --git a/mypy/literals.py b/mypy/literals.py index 09c5f86aa2053..7d14113bfc883 100644 --- a/mypy/literals.py +++ b/mypy/literals.py @@ -8,7 +8,7 @@ TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension, GeneratorExpr, BackquoteExpr, TypeVarExpr, NamedTupleExpr, EnumCallExpr, TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, -) + TypeAliasExpr) from mypy.visitor import ExpressionVisitor # [Note Literals and literal_hash] @@ -205,6 +205,9 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> None: def visit_type_var_expr(self, e: TypeVarExpr) -> None: return None + def visit_type_alias_expr(self, e: TypeAliasExpr) -> None: + return None + def visit_namedtuple_expr(self, e: NamedTupleExpr) -> None: return None diff --git a/mypy/nodes.py b/mypy/nodes.py index 0cd6ddc54659c..1dbcdd401663e 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -103,10 +103,10 @@ def get_column(self) -> int: 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', @@ -116,8 +116,7 @@ def get_column(self) -> int: nongen_builtins = {'builtins.tuple': 'typing.Tuple', 'builtins.enumerate': ''} -nongen_builtins.update((name.replace('__builtins__', 'builtins'), alias) - for alias, name in type_aliases.items()) +nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) @@ -1409,8 +1408,8 @@ class IndexExpr(Expression): # Inferred __getitem__ method type method_type = None # type: mypy.types.Type # If not None, this is actually semantically a type application - # Class[type, ...]. - analyzed = None # type: Union[TypeApplication, None] + # Class[type, ...] or a type alias initializer. + analyzed = None # type: Union[TypeApplication, TypeAliasExpr, None] def __init__(self, base: Expression, index: Expression) -> None: super().__init__() @@ -1887,6 +1886,20 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarExpr': data['variance']) +class TypeAliasExpr(Expression): + """Type alias expression (rvalue).""" + + type = None # type: mypy.types.Type + tvars = None # type: List[str] + + def __init__(self, type: 'mypy.types.Type', tvars: List[str]) -> None: + super().__init__() + self.type = type + self.tvars = tvars + + def accept(self, visitor: ExpressionVisitor[T]) -> T: + return visitor.visit_type_alias_expr(self) + class NamedTupleExpr(Expression): """Named tuple expression namedtuple(...) or NamedTuple(...).""" @@ -2422,6 +2435,9 @@ def serialize(self) -> JsonDict: } # 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' diff --git a/mypy/semanal.py b/mypy/semanal.py index 3e46ddf635072..bd89afaf7243a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -52,7 +52,7 @@ StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, ARG_NAMED_OPT, type_aliases, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode, - SetComprehension, DictionaryComprehension, TypeAlias, + SetComprehension, DictionaryComprehension, TypeAlias, TypeAliasExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, EnumCallExpr, ImportedName, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ARG_OPT, nongen_builtins, @@ -294,7 +294,9 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, if self.cur_mod_id == 'typing': for alias, target_name in type_aliases.items(): name = alias.split('.')[-1] - self.cur_mod_node.names[name] = SymbolTableNode(GDEF, TypeAlias(self.builtin_type(target_name), alias)) + n = self.lookup_fully_qualified_or_none(target_name) + if n: + self.cur_mod_node.names[name] = SymbolTableNode(GDEF, TypeAlias(self.builtin_type(target_name), alias)) defs = file_node.defs self.scope.enter_file(file_node.fullname()) @@ -1704,7 +1706,9 @@ 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) - node.node = TypeAlias(res, node.node.fullname(), alias_tvars=qualified_tvars, + if isinstance(s.rvalue, IndexExpr): + s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars) + node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on)) def analyze_lvalue(self, lval: Lvalue, nested: bool = False, diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 2f1a6bb132236..1a8fe9ec3ed4a 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -16,7 +16,7 @@ from mypy import messages, experiments from mypy.nodes import ( Node, Expression, MypyFile, FuncDef, FuncItem, Decorator, RefExpr, Context, TypeInfo, ClassDef, - Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, NameExpr, + Block, TypedDictExpr, NamedTupleExpr, AssignmentStmt, IndexExpr, TypeAliasExpr, NameExpr, CallExpr, NewTypeExpr, ForStmt, WithStmt, CastExpr, TypeVarExpr, TypeApplication, Lvalue, TupleExpr, RevealExpr, SymbolTableNode, SymbolTable, Var, ARG_POS, OverloadedFuncDef, MDEF, @@ -216,6 +216,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: NewType, TypedDict, NamedTuple, and TypeVar. """ self.analyze(s.type, s) + if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr): + self.analyze(s.rvalue.analyzed.type, s.rvalue.analyzed, warn=True) if isinstance(s.rvalue, CallExpr): analyzed = s.rvalue.analyzed if isinstance(analyzed, NewTypeExpr): @@ -287,7 +289,8 @@ def perform_transform(self, node: Node, transform: Callable[[Type], Type]) -> No for n in node.target: if isinstance(n, NameExpr) and isinstance(n.node, Var) and n.node.type: n.node.type = transform(n.node.type) - if isinstance(node, (FuncDef, OverloadedFuncDef, CastExpr, AssignmentStmt, Var)): + if isinstance(node, (FuncDef, OverloadedFuncDef, CastExpr, AssignmentStmt, + TypeAliasExpr, Var)): assert node.type, "Scheduled patch for non-existent type" node.type = transform(node.type) if isinstance(node, NewTypeExpr): diff --git a/mypy/server/astmerge.py b/mypy/server/astmerge.py index 2f5227482b33f..3c9370c8451cd 100644 --- a/mypy/server/astmerge.py +++ b/mypy/server/astmerge.py @@ -50,7 +50,7 @@ from mypy.nodes import ( Node, MypyFile, SymbolTable, Block, AssignmentStmt, NameExpr, MemberExpr, RefExpr, TypeInfo, FuncDef, ClassDef, NamedTupleExpr, SymbolNode, Var, Statement, SuperExpr, NewTypeExpr, - OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, FuncBase, CallExpr, + OverloadedFuncDef, LambdaExpr, TypedDictExpr, EnumCallExpr, FuncBase, TypeAliasExpr, CallExpr, CastExpr, MDEF ) @@ -254,6 +254,10 @@ def visit_enum_call_expr(self, node: EnumCallExpr) -> None: self.process_synthetic_type_info(node.info) super().visit_enum_call_expr(node) + def visit_type_alias_expr(self, node: TypeAliasExpr) -> None: + self.fixup_type(node.type) + super().visit_type_alias_expr(node) + # Others def visit_var(self, node: Var) -> None: diff --git a/mypy/server/deps.py b/mypy/server/deps.py index d6aaf5a264293..f3df6fdb7b0ea 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -92,7 +92,7 @@ class 'mod.Cls'. This can also refer to an attribute inherited from a ComparisonExpr, GeneratorExpr, DictionaryComprehension, StarExpr, PrintStmt, ForStmt, WithStmt, TupleExpr, ListExpr, OperatorAssignmentStmt, DelStmt, YieldFromExpr, Decorator, Block, TypeInfo, FuncBase, OverloadedFuncDef, RefExpr, SuperExpr, Var, NamedTupleExpr, TypedDictExpr, - LDEF, MDEF, GDEF, FuncItem, NewTypeExpr, ImportAll, EnumCallExpr, AwaitExpr, + LDEF, MDEF, GDEF, FuncItem, TypeAliasExpr, NewTypeExpr, ImportAll, EnumCallExpr, AwaitExpr, op_methods, reverse_op_methods, ops_with_inplace_method, unary_op_methods ) from mypy.traverser import TraverserVisitor @@ -350,7 +350,8 @@ def visit_assignment_stmt(self, o: AssignmentStmt) -> None: class_name = typ.type_object().fullname() self.add_dependency(make_trigger(class_name + '.__init__')) self.add_dependency(make_trigger(class_name + '.__new__')) - # TODOALIAS deps from subscripted? + if isinstance(rvalue, IndexExpr) and isinstance(rvalue.analyzed, TypeAliasExpr): + self.add_type_dependencies(rvalue.analyzed.type) else: # Normal assignment super().visit_assignment_stmt(o) diff --git a/mypy/strconv.py b/mypy/strconv.py index 6f4f6e3e57414..8814e29e795d2 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -452,6 +452,9 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> str: a += ['UpperBound({})'.format(o.upper_bound)] return self.dump(a, o) + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> str: + return 'TypeAliasExpr({})'.format(o.type) + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> str: return 'NamedTupleExpr:{}({}, {})'.format(o.line, o.info.name(), diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 36c69f6c383fa..a212417486e87 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -18,7 +18,7 @@ SymbolTable, RefExpr, TypeVarExpr, NewTypeExpr, PromoteExpr, ComparisonExpr, TempNode, StarExpr, Statement, Expression, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension, - DictionaryComprehension, ComplexExpr, EllipsisExpr, + DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, OverloadPart, EnumCallExpr, REVEAL_TYPE ) @@ -415,7 +415,10 @@ def visit_index_expr(self, node: IndexExpr) -> IndexExpr: if node.method_type: new.method_type = self.type(node.method_type) if node.analyzed: - new.analyzed = self.visit_type_application(node.analyzed) + if isinstance(node.analyzed, TypeApplication): + new.analyzed = self.visit_type_application(node.analyzed) + else: + new.analyzed = self.visit_type_alias_expr(node.analyzed) new.analyzed.set_line(node.analyzed.line) return new @@ -471,6 +474,9 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr: self.types(node.values), self.type(node.upper_bound), variance=node.variance) + def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr: + return TypeAliasExpr(node.type, node.tvars) + def visit_newtype_expr(self, node: NewTypeExpr) -> NewTypeExpr: res = NewTypeExpr(node.name, node.old_type, line=node.line) res.info = node.info diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 1e5d76cfc4ed5..b29061b2527e7 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -197,6 +197,11 @@ 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: + # This is necessary if we process some other modules before 'typing'. + resolved = type_aliases[sym.fullname] + new = self.api.lookup_fully_qualified(resolved) + sym = new.copy() if sym.node is None: # UNBOUND_IMPORTED can happen if an unknown name was imported. if sym.kind != UNBOUND_IMPORTED: @@ -207,7 +212,7 @@ 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 self.allow_unnormalized): + not self.allow_unnormalized): self.fail(no_subscript_builtin_alias(fullname), t) if self.tvar_scope: tvar_def = self.tvar_scope.get_binding(sym) @@ -289,6 +294,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: return set_any_tvars(target, all_vars, t.line, t.column) if exp_len == 0 and act_len == 0: return target + if exp_len == 0 and act_len > 0 and isinstance(target, Instance): + tp = Instance(target.type, an_args) + tp.line = t.line + return tp if act_len != exp_len: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) diff --git a/mypy/visitor.py b/mypy/visitor.py index 8f3db96ac13b1..0623579914dbe 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -148,6 +148,10 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass + @abstractmethod + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: + pass + @abstractmethod def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass @@ -347,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: @@ -508,6 +515,9 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: pass + def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: + pass + def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: pass From 31d007c06721202bb951a7085459a4fe78c4e40a Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 11 Jun 2018 17:07:19 +0100 Subject: [PATCH 04/29] Start working on normalization --- mypy/build.py | 1 - mypy/typeanal.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 9a264e6d9d791..1992cecca4756 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2581,7 +2581,6 @@ def process_graph(graph: Graph, manager: BuildManager) -> None: # typing}. Of these, builtins must be processed last or else # some builtin objects will be incompletely processed.) if 'builtins' in ascc: - pass scc.remove('builtins') scc.append('builtins') if manager.options.verbosity >= 2: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index b29061b2527e7..eba24fab066a7 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -191,6 +191,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # type check below. sym = self.api.dereference_module_cross_ref(sym) if sym is not None: + normalized = False if isinstance(sym.node, ImportedName): # Forward reference to an imported name that hasn't been processed yet. # To maintain backward compatibility, these get translated to Any. @@ -201,6 +202,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # This is necessary if we process some other modules before 'typing'. resolved = type_aliases[sym.fullname] new = self.api.lookup_fully_qualified(resolved) + normalized = True sym = new.copy() if sym.node is None: # UNBOUND_IMPORTED can happen if an unknown name was imported. @@ -212,7 +214,7 @@ 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 self.allow_unnormalized): + not self.allow_unnormalized and not normalized): self.fail(no_subscript_builtin_alias(fullname), t) if self.tvar_scope: tvar_def = self.tvar_scope.get_binding(sym) From aabfcd4de3411ec80f94645f97e1ca23d335a495 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 11 Jun 2018 22:54:36 +0100 Subject: [PATCH 05/29] More fixes --- mypy/checker.py | 4 +-- mypy/checkexpr.py | 35 ++++++++++++++++++++++++- mypy/jkl | 7 ----- mypy/messages.py | 7 ++++- mypy/nodes.py | 8 +++++- mypy/semanal.py | 7 +++-- mypy/semanal_pass3.py | 14 +++++++++- mypy/typeanal.py | 19 +++++++++----- test-data/unit/check-namedtuple.test | 4 +-- test-data/unit/merge.test | 4 +-- test-data/unit/semanal-typealiases.test | 2 +- 11 files changed, 84 insertions(+), 27 deletions(-) delete mode 100644 mypy/jkl diff --git a/mypy/checker.py b/mypy/checker.py index 8b4584972b208..058682300a10d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1431,10 +1431,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 diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 8c396cf5e7b60..fc56d4e6f6c9b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -181,6 +181,11 @@ 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): + if isinstance(node.target, Instance) and not node.target.args: + result = type_object_type(node.target.type, self.named_type) + else: + result = AnyType(TypeOfAny.from_error) else: # Unknown reference; use any type implicitly to avoid # generating extra type errors. @@ -220,7 +225,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))) - or (isinstance(typ, NameExpr) and node and isinstance(node.node, TypeAlias))): + or (isinstance(typ, NameExpr) and node and + isinstance(node.node, TypeAlias))): self.msg.type_arguments_not_allowed(e) if isinstance(typ, RefExpr) and isinstance(typ.node, TypeInfo): if typ.node.typeddict_type: @@ -2062,8 +2068,35 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: 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: + # An invalid alias, error already has been reported + return AnyType(TypeOfAny.from_error) + item = alias.type + 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) + else: + # This type is invalid in most runtime contexts + # and corresponding an error will be reported. + return AnyType(TypeOfAny.special_form) + if isinstance(tp, CallableType): + if len(tp.variables) != len(item.args): + self.msg.incompatible_type_application(len(tp.variables), + len(item.args), item) + return AnyType(TypeOfAny.from_error) + return self.apply_generic_arguments(tp, item.args, item) + elif isinstance(tp, Overloaded): + for it in tp.items(): + if len(it.variables) != len(item.args): + self.msg.incompatible_type_application(len(it.variables), + len(item.args), item) + return AnyType(TypeOfAny.from_error) + return Overloaded([self.apply_generic_arguments(it, item.args, item) + for it in tp.items()]) return AnyType(TypeOfAny.special_form) + def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" return self.check_lst_expr(e.items, 'builtins.list', '', e) diff --git a/mypy/jkl b/mypy/jkl deleted file mode 100644 index 1aa7a89771ac0..0000000000000 --- a/mypy/jkl +++ /dev/null @@ -1,7 +0,0 @@ - try: - with open(meta_json, 'r') as f: - meta_str = f.read() - manager.trace('Meta {} {}'.format(id, meta_str.rstrip())) - meta = json.loads(meta_str) # TODO: Errors - except IOError: - manager.log('Could not load cache for {}: could not find {}'.format(id, meta_json)) \ No newline at end of file diff --git a/mypy/messages.py b/mypy/messages.py index ec6287db94416..d31cf80e994d0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -27,7 +27,7 @@ TypeInfo, Context, MypyFile, op_methods, FuncDef, ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, ReturnStmt, NameExpr, Var, CONTRAVARIANT, COVARIANT, SymbolNode, - CallExpr, Expression + CallExpr, reverse_builtin_aliases ) # Constants that represent simple type checker error message, i.e. messages @@ -241,6 +241,11 @@ 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_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)) else: # There are type arguments. Convert the arguments to strings. # If the result is too long, replace arguments with [...]. diff --git a/mypy/nodes.py b/mypy/nodes.py index b2a8e7c6440bb..ab777fe37be5c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -113,13 +113,18 @@ def get_column(self) -> int: 'typing.Deque': 'collections.deque', } +reverse_builtin_aliases = { + 'builtins.list': 'typing.List', + 'builtins.dict': 'typing.Dict', + 'builtins.set': 'typing.Set', + 'builtins.frozenset': 'typing.FrozenSet', +} nongen_builtins = {'builtins.tuple': 'typing.Tuple', 'builtins.enumerate': ''} nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) - class Node(Context): """Common base class for all non-type parse tree nodes.""" @@ -1880,6 +1885,7 @@ def __init__(self, type: 'mypy.types.Type', tvars: List[str]) -> None: def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) + class NamedTupleExpr(Expression): """Named tuple expression namedtuple(...) or NamedTuple(...).""" diff --git a/mypy/semanal.py b/mypy/semanal.py index 49ce8bb226398..cae52e0703da4 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -295,7 +295,8 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, name = alias.split('.')[-1] n = self.lookup_fully_qualified_or_none(target_name) if n: - self.cur_mod_node.names[name] = SymbolTableNode(GDEF, TypeAlias(self.builtin_type(target_name), alias)) + alias_node = TypeAlias(self.builtin_type(target_name), alias) + self.cur_mod_node.names[name] = SymbolTableNode(GDEF, alias_node) defs = file_node.defs self.scope.enter_file(file_node.fullname()) @@ -1707,6 +1708,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: res = make_any_non_explicit(res) if isinstance(s.rvalue, IndexExpr): s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars) + s.rvalue.analyzed.line = s.line node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on)) @@ -2741,7 +2743,8 @@ 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 refers_to_class_or_function(expr.base) or isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): + elif (refers_to_class_or_function(expr.base) or + isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias)): # Special form -- type application. # Translate index to an unanalyzed type. if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index cf4a97d6936fd..38d28b6ba9610 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, @@ -68,6 +68,7 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, with experiments.strict_optional_set(options.strict_optional): self.scope.enter_file(file_node.fullname()) self.accept(file_node) + self.analyze_symbol_table(file_node.names) self.scope.leave() del self.cur_mod_node self.patches = [] @@ -147,6 +148,7 @@ def visit_class_def(self, tdef: ClassDef) -> None: self.analyze(tdef.analyzed.info.tuple_type, tdef.analyzed, warn=True) self.analyze_info(tdef.analyzed.info) super().visit_class_def(tdef) + self.analyze_symbol_table(tdef.info.names) self.scope.leave() def visit_decorator(self, dec: Decorator) -> None: @@ -241,6 +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) + if isinstance(s.lvalues[0], RefExpr) and isinstance(s.lvalues[0].node, Var): + self.analyze(s.lvalues[0].node.type, s.lvalues[0].node) # Subclass attribute assignments with no type annotation should be # assumed to be classvar if overriding a declared classvar from the base # class. @@ -293,6 +297,8 @@ def perform_transform(self, node: Node, transform: Callable[[Type], Type]) -> No 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) @@ -381,6 +387,12 @@ def analyze_types(self, types: List[Type], node: Node) -> None: self.cur_mod_node.alias_deps[target].update(analyzer.aliases_used) self.generate_type_patches(node, indicator, warn=False) + def analyze_symbol_table(self, names: SymbolTable) -> None: + """Analyze types in symbol table nodes only (shallow).""" + for node in names.values(): + 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() diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 89c089de39799..a9f6c102ce044 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -22,7 +22,7 @@ TVAR, 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, TypeAlias + Decorator, ImportedName, type_aliases, TypeAlias, MODULE_REF ) from mypy.tvar_scope import TypeVarScope from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -322,7 +322,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: 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)): + not (sym.kind == TVAR and tvar_def is None or sym.kind == MODULE_REF)): if t.args and not self.global_scope: self.fail('Unsupported forward reference to "{}"'.format(t.name), t) return AnyType(TypeOfAny.from_error) @@ -342,6 +342,16 @@ 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) + if not t.args and self.options.disallow_any_generics: + from_builtins = info.fullname() in nongen_builtins + if not normalized: + if not self.is_typeshed_stub and from_builtins: + alternative = nongen_builtins[info.fullname()] + self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) + instance.args = [AnyType(TypeOfAny.from_error, line=t.line)] * len(info.type_vars) + else: + instance.args = [AnyType(TypeOfAny.from_omitted_generics, line=t.line)] * len(info.type_vars) + tup = info.tuple_type if tup is not None: # The class has a Tuple[...] base class so it will be @@ -700,11 +710,6 @@ def visit_instance(self, t: Instance) -> None: if len(t.args) != len(info.type_vars): if len(t.args) == 0: from_builtins = t.type.fullname() in nongen_builtins - 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 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/merge.test b/test-data/unit/merge.test index a7dd72fab270c..003b9c3e239de 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 diff --git a/test-data/unit/semanal-typealiases.test b/test-data/unit/semanal-typealiases.test index 5178a71f871f2..07fdfd17c90e4 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])))) From ed50401d33aff90537efd99633e4e18d48a29561 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 12 Jun 2018 18:30:38 +0100 Subject: [PATCH 06/29] Revert some runtime logic changes --- mypy/checkexpr.py | 10 ++++++++-- mypy/checkmember.py | 7 ++++++- mypy/semanal.py | 21 ++++++++++++++++----- mypy/server/deps.py | 2 ++ mypy/typeanal.py | 2 +- test-data/unit/check-classes.test | 4 ++-- test-data/unit/check-generics.test | 3 +-- test-data/unit/check-type-aliases.test | 3 +-- test-data/unit/fine-grained.test | 4 ++-- test-data/unit/merge.test | 4 ++-- test-data/unit/semanal-typealiases.test | 12 ++---------- 11 files changed, 43 insertions(+), 29 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fc56d4e6f6c9b..642f7716e19d7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -182,8 +182,14 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: elif isinstance(node, Decorator): result = self.analyze_var_ref(node.var, e) elif isinstance(node, TypeAlias): - if isinstance(node.target, Instance) and not node.target.args: - result = type_object_type(node.target.type, self.named_type) + tp = set_any_tvars(node.target, node.alias_tvars, e.line, e.column) + if isinstance(tp, Instance): + result = type_object_type(tp.type, self.named_type) + if isinstance(result, CallableType): + result = self.apply_generic_arguments(result, tp.args, e) + elif isinstance(result, Overloaded): + result = Overloaded([self.apply_generic_arguments(it, tp.args, e) + for it in result.items()]) else: result = AnyType(TypeOfAny.from_error) else: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 99298c04fd025..205b8b9e1d1ce 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -10,7 +10,7 @@ from mypy.nodes import ( TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr, ARG_POS, ARG_STAR, ARG_STAR2, - Decorator, OverloadedFuncDef, + Decorator, OverloadedFuncDef, TypeAlias ) from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype @@ -249,6 +249,11 @@ 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): + tp = type_object_type(vv.target.type, builtin_type) + v = Var(name, type=expand_type_by_instance(tp, vv.target)) + 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) diff --git a/mypy/semanal.py b/mypy/semanal.py index cae52e0703da4..bd328b4d0bf8b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1706,7 +1706,7 @@ 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(s.rvalue, IndexExpr): + if isinstance(s.rvalue, (IndexExpr, CallExpr)): s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars) s.rvalue.analyzed.line = s.line node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, @@ -2743,12 +2743,23 @@ 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 (refers_to_class_or_function(expr.base) or - isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias)): + elif isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): + # 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) + if res is None: + # Bad type alias, nothing to do here. + expr.analyzed = TypeAliasExpr(expr.base.node.target, []) + return + expr.analyzed = TypeAliasExpr(res, alias_tvars) + 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. # Translate index to an unanalyzed type. - if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): - self.add_type_alias_deps(expr.base.node.depends_on) types = [] # type: List[Type] if isinstance(expr.index, TupleExpr): items = expr.index.items diff --git a/mypy/server/deps.py b/mypy/server/deps.py index f006b3d7f3907..184a2e8147052 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) + else: + self.add_type_dependencies(typ) else: # Normal assignment super().visit_assignment_stmt(o) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index a9f6c102ce044..3cb458b878c59 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -342,7 +342,7 @@ 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) - if not t.args and self.options.disallow_any_generics: + if not t.args and self.options.disallow_any_generics and not self.aliasing: from_builtins = info.fullname() in nongen_builtins if not normalized: if not self.is_typeshed_stub and from_builtins: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b32f936685025..15804f86c2959 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2244,14 +2244,14 @@ 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] def __init__(self) -> None: self.aa = self.a_int() -reveal_type(C().aa) # E: Revealed type is '__main__.A[builtins.int*, builtins.int*]' +reveal_type(C().aa) # E: Revealed type is '__main__.A[builtins.int, builtins.int]' [out] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 58c9b11e368a6..82414e541fa75 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -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 diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index d44398885702e..cfbfaa068892f 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] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 037c71c221a5d..949ad62ccafdc 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -3511,7 +3511,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 @@ -3537,7 +3537,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] diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test index 003b9c3e239de..736fa15bfef73 100644 --- a/test-data/unit/merge.test +++ b/test-data/unit/merge.test @@ -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 07fdfd17c90e4..6afc494cbdb86 100644 --- a/test-data/unit/semanal-typealiases.test +++ b/test-data/unit/semanal-typealiases.test @@ -45,10 +45,7 @@ List[List[int]] MypyFile:1( ImportFrom:1(typing, [List]) ExpressionStmt:2( - TypeApplication:2( - NameExpr(List [typing.List]) - Types( - builtins.list[builtins.int])))) + TypeAliasExpr(builtins.list[builtins.list[builtins.int]]))) [case testTypeApplicationWithQualifiedTypeAlias] import typing @@ -58,12 +55,7 @@ typing.List[typing.List[int]] MypyFile:1( Import:1(typing) ExpressionStmt:2( - TypeApplication:2( - MemberExpr:2( - NameExpr(typing) - List [builtins.list]) - Types( - builtins.list[builtins.int])))) + TypeAliasExpr(builtins.list[builtins.list[builtins.int]]))) [case testSimpleTypeAlias] import typing From 048b782918c173e2bd5124be6b0c9401e1cf2cba Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 12 Jun 2018 23:31:46 +0100 Subject: [PATCH 07/29] Few more fixes --- mypy/checkexpr.py | 24 ++++++++++++++++-------- mypy/nodes.py | 12 ++++++++---- mypy/semanal.py | 13 ++++++++++--- mypy/typeanal.py | 7 +++++-- test-data/unit/check-functions.test | 2 +- test-data/unit/check-generics.test | 4 ++-- test-data/unit/fine-grained.test | 3 ++- 7 files changed, 44 insertions(+), 21 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 642f7716e19d7..85554f881e397 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -21,7 +21,7 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr, YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr, - TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, + TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, ClassDef, Block, SymbolTable, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, MODULE_REF, TVAR, LITERAL_TYPE, REVEAL_TYPE ) from mypy.literals import literal @@ -185,11 +185,12 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: tp = set_any_tvars(node.target, node.alias_tvars, e.line, e.column) if isinstance(tp, Instance): result = type_object_type(tp.type, self.named_type) - if isinstance(result, CallableType): - result = self.apply_generic_arguments(result, tp.args, e) - elif isinstance(result, Overloaded): - result = Overloaded([self.apply_generic_arguments(it, tp.args, e) - for it in result.items()]) + if not node.no_args: + if isinstance(result, CallableType): + result = self.apply_generic_arguments(result, tp.args, e) + elif isinstance(result, Overloaded): + result = Overloaded([self.apply_generic_arguments(it, tp.args, e) + for it in result.items()]) else: result = AnyType(TypeOfAny.from_error) else: @@ -232,7 +233,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: if ((isinstance(typ, IndexExpr) and isinstance(typ.analyzed, (TypeApplication, TypeAliasExpr))) or (isinstance(typ, NameExpr) and node and - isinstance(node.node, TypeAlias))): + 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: @@ -2085,7 +2086,14 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: else: # This type is invalid in most runtime contexts # and corresponding an error will be reported. - return AnyType(TypeOfAny.special_form) + kind = (' to Callable' if isinstance(item, CallableType) else + ' to Tuple' if isinstance(item, TupleType) else + ' to Union' if isinstance(item, UnionType) else '') + cdef = ClassDef('Type alias' + kind, Block([])) + fb_info = TypeInfo(SymbolTable(), cdef, self.chk.tree.fullname()) + fb_info.bases = [self.object_type()] + fb_info.mro = [fb_info, self.object_type().type] + return Instance(fb_info, []) if isinstance(tp, CallableType): if len(tp.variables) != len(item.args): self.msg.incompatible_type_application(len(tp.variables), diff --git a/mypy/nodes.py b/mypy/nodes.py index ab777fe37be5c..d86522591b6b0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2391,12 +2391,13 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): - __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on') + __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on', 'no_args') def __init__(self, target: 'mypy.types.Type', fullname: str, *, alias_tvars: Optional[List[str]] = None, - depends_on: Optional[List[str]] = None) -> None: + depends_on: Optional[List[str]] = None, + no_args: bool = False) -> None: self._fullname = fullname self.target = target if alias_tvars is None: @@ -2405,6 +2406,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, if depends_on is None: depends_on = [] self.depends_on = depends_on + self.no_args = no_args def name(self) -> str: return self._fullname.split('.')[-1] @@ -2417,7 +2419,8 @@ def serialize(self) -> JsonDict: 'fullname': self._fullname, 'target': self.target.serialize(), 'alias_tvars': self.alias_tvars, - 'depends_on': self.depends_on + 'depends_on': self.depends_on, + 'no_args': self.no_args } # type: JsonDict return data @@ -2431,7 +2434,8 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': alias_tvars = data['alias_tvars'] depends_on = data['depends_on'] target = mypy.types.deserialize_type(data['target']) - return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on) + no_args = data['no_args'] + return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on, no_args=no_args) class SymbolTableNode: diff --git a/mypy/semanal.py b/mypy/semanal.py index bd328b4d0bf8b..76ff8976d1576 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -56,7 +56,7 @@ YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, - get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS + get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, nongen_builtins ) from mypy.literals import literal from mypy.tvar_scope import TypeVarScope @@ -1709,8 +1709,9 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: if isinstance(s.rvalue, (IndexExpr, CallExpr)): s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars) s.rvalue.analyzed.line = s.line + no_args = isinstance(res, Instance) and not res.args node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, - depends_on=list(depends_on)) + depends_on=list(depends_on), no_args=no_args) def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, @@ -2757,6 +2758,9 @@ def visit_index_expr(self, expr: IndexExpr) -> None: # 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) + # list, dict, set are not directly subscriptable + if isinstance(expr.base.node.target, Instance) and expr.base.node.no_args and expr.base.node.target.type.fullname() in nongen_builtins: + self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), expr) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. @@ -2775,7 +2779,10 @@ def visit_index_expr(self, expr: IndexExpr) -> None: types.append(typearg) expr.analyzed = TypeApplication(expr.base, types) expr.analyzed.line = expr.line - # TODOALIAS list[int] + # list, dict, set are not directly subscriptable + n = self.lookup_type_node(expr.base) + if n and (not isinstance(expr.base.node, TypeAlias) or expr.base.node.no_args) and n.fullname in nongen_builtins: + self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr) else: expr.index.accept(self) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3cb458b878c59..861b95b57d1cf 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -199,7 +199,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: if sym.fullname in type_aliases: # This is necessary if we process some other modules before 'typing'. resolved = type_aliases[sym.fullname] - new = self.api.lookup_fully_qualified(resolved) + try: + new = self.api.lookup_fully_qualified(resolved) + except KeyError: # broken test fixture + return AnyType(TypeOfAny.from_error) normalized = True sym = new.copy() if sym.node is None: @@ -294,7 +297,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: return set_any_tvars(target, all_vars, t.line, t.column) if exp_len == 0 and act_len == 0: return target - if exp_len == 0 and act_len > 0 and isinstance(target, Instance): + if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and sym.node.no_args: tp = Instance(target.type, an_args) tp.line = t.line return tp diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 3b900ee5f5fc2..61eca6a0befdc 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1613,7 +1613,7 @@ L = Callable[[Arg(name='x', type=int)], int] # ok # I have commented out the following test because I don't know how to expect the "defined here" note part of the error. # M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias E: Unexpected keyword argument "gnome" for "Arg" N = Callable[[Arg(name=None, type=int)], int] # ok -O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable +O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable P = Callable[[mypy_extensions.VarArg(int)], int] # ok Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type" R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name" diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 82414e541fa75..7e3f2aa589d4a 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -963,7 +963,7 @@ 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 +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 [out] @@ -1075,7 +1075,7 @@ main:13: error: Value of type variable "S" of "A" cannot be "str" class A: ... Bad = A[int] # type: ignore -reveal_type(Bad) # E: Revealed type is 'Any' +reveal_type(Bad) # E: Revealed type is 'def () -> __main__.A' [out] [case testNoSubscriptionOfBuiltinAliases] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 949ad62ccafdc..39e1d959a288d 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -4858,11 +4858,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 From e3a6f3f2068c11bc4e18cabf2279fe98bb7b2126 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 13 Jun 2018 00:51:22 +0100 Subject: [PATCH 08/29] Some more fixes --- mypy/semanal.py | 15 ++++++++++++--- mypy/typeanal.py | 11 ++--------- test-data/unit/diff.test | 7 +------ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 76ff8976d1576..7a701caa6039d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -295,7 +295,12 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, name = alias.split('.')[-1] n = self.lookup_fully_qualified_or_none(target_name) if n: - alias_node = TypeAlias(self.builtin_type(target_name), alias) + any_type = AnyType(TypeOfAny.special_form) + if target_name.split('.')[-1] in ('list', 'set', 'deque', 'frozenset', 'Counter'): + args = [any_type] + else: + args = [any_type, any_type] + alias_node = TypeAlias(self.named_type_or_none(target_name, args), alias) self.cur_mod_node.names[name] = SymbolTableNode(GDEF, alias_node) defs = file_node.defs @@ -2781,7 +2786,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: expr.analyzed.line = expr.line # list, dict, set are not directly subscriptable n = self.lookup_type_node(expr.base) - if n and (not isinstance(expr.base.node, TypeAlias) or expr.base.node.no_args) and n.fullname in nongen_builtins: + 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) @@ -3061,7 +3066,11 @@ def lookup_fully_qualified(self, name: str) -> SymbolTableNode: next_sym = n.names[parts[i]] assert isinstance(next_sym.node, MypyFile) n = next_sym.node - return n.names[parts[-1]] + try: + return n.names[parts[-1]] + except KeyError: + self.name_not_defined(name, self.cur_mod_node) + raise def lookup_fully_qualified_or_none(self, fullname: str) -> Optional[SymbolTableNode]: """Lookup a fully qualified name that refers to a module-level definition. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 861b95b57d1cf..22b90d1125829 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -712,15 +712,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 - # 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. diff --git a/test-data/unit/diff.test b/test-data/unit/diff.test index d110f8efdb4c0..dbd89cfcf0e02 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -440,16 +440,11 @@ __main__.S [case testDifferentListTypes] from typing import List A = List -B = list -C = List [file next.py] from typing import List -A = List -B = list -C = list +A = list [builtins fixtures/list.pyi] [out] -__main__.C [case testDecoratorChangesSignature] from contextlib import contextmanager From 30d137fb15c6b96e746fc3ee73cfa82017c85616 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 13 Jun 2018 18:04:03 +0100 Subject: [PATCH 09/29] More test fixes --- mypy/build.py | 2 ++ mypy/nodes.py | 13 ++++++++---- mypy/semanal.py | 38 ++++++++++++++++++++-------------- mypy/semanal_pass3.py | 3 ++- mypy/tmp.txt | 5 ----- mypy/typeanal.py | 24 +++++++-------------- test-data/unit/deps-types.test | 6 ++++-- test-data/unit/diff.test | 7 ++++++- 8 files changed, 53 insertions(+), 45 deletions(-) delete mode 100644 mypy/tmp.txt diff --git a/mypy/build.py b/mypy/build.py index 6a31f1f89b4e5..46c60b872b1f1 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2833,6 +2833,8 @@ 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: + manager.semantic_analyzer.add_builtin_aliases(graph['typing'].tree) for id in fresh: graph[id].fix_cross_refs() for id in stale: diff --git a/mypy/nodes.py b/mypy/nodes.py index d86522591b6b0..4efda1462bf03 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2391,13 +2391,14 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): - __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on', 'no_args') + __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on', 'no_args', 'normalized') def __init__(self, target: 'mypy.types.Type', fullname: str, *, alias_tvars: Optional[List[str]] = None, depends_on: Optional[List[str]] = None, - no_args: bool = False) -> None: + no_args: bool = False, + normalized: bool = False) -> None: self._fullname = fullname self.target = target if alias_tvars is None: @@ -2407,6 +2408,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, depends_on = [] self.depends_on = depends_on self.no_args = no_args + self.normalized = normalized def name(self) -> str: return self._fullname.split('.')[-1] @@ -2420,7 +2422,8 @@ def serialize(self) -> JsonDict: 'target': self.target.serialize(), 'alias_tvars': self.alias_tvars, 'depends_on': self.depends_on, - 'no_args': self.no_args + 'no_args': self.no_args, + 'normalized': self.normalized } # type: JsonDict return data @@ -2435,7 +2438,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': depends_on = data['depends_on'] target = mypy.types.deserialize_type(data['target']) no_args = data['no_args'] - return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on, no_args=no_args) + normalized = data['normalized'] + return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on, + no_args=no_args, normalized=normalized) class SymbolTableNode: diff --git a/mypy/semanal.py b/mypy/semanal.py index 7a701caa6039d..8e74102c6da2a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -290,19 +290,6 @@ def visit_file(self, file_node: MypyFile, fnam: str, options: Options, v.type = self.anal_type(v.type) v.is_ready = True - if self.cur_mod_id == 'typing': - for alias, target_name in type_aliases.items(): - name = alias.split('.')[-1] - n = self.lookup_fully_qualified_or_none(target_name) - if n: - any_type = AnyType(TypeOfAny.special_form) - if target_name.split('.')[-1] in ('list', 'set', 'deque', 'frozenset', 'Counter'): - args = [any_type] - else: - args = [any_type, any_type] - alias_node = TypeAlias(self.named_type_or_none(target_name, args), alias) - self.cur_mod_node.names[name] = SymbolTableNode(GDEF, alias_node) - defs = file_node.defs self.scope.enter_file(file_node.fullname()) for d in defs: @@ -1276,7 +1263,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)) @@ -1717,6 +1704,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: no_args = isinstance(res, Instance) and not res.args node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on), no_args=no_args) + if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias) and rvalue.node.normalized: + node.node.normalized = True def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, @@ -1751,7 +1740,7 @@ 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 + 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]): @@ -2764,7 +2753,10 @@ def visit_index_expr(self, expr: IndexExpr) -> None: # with situations like `L = LongGeneric; x = L[int]()`. self.add_type_alias_deps(depends_on) # list, dict, set are not directly subscriptable - if isinstance(expr.base.node.target, Instance) and expr.base.node.no_args and expr.base.node.target.type.fullname() in nongen_builtins: + if (isinstance(expr.base.node.target, Instance) and + expr.base.node.no_args and + expr.base.node.target.type.fullname() in nongen_builtins + and not expr.base.node.normalized): self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), expr) elif refers_to_class_or_function(expr.base): # Special form -- type application. @@ -3017,6 +3009,8 @@ 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: @@ -3054,6 +3048,18 @@ 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: + 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: + alias_node = TypeAlias(self.named_type_or_none(target_name, []), alias, + no_args=True, normalized=True) + tree.names[name] = SymbolTableNode(GDEF, alias_node) + else: + tree.names.pop(name, None) + def lookup_fully_qualified(self, name: str) -> SymbolTableNode: """Lookup a fully qualified name. diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 38d28b6ba9610..0d42417023928 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -369,7 +369,8 @@ def analyze(self, type: Optional[Type], node: Node, 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): + self.check_for_omitted_generics(type) self.generate_type_patches(node, indicator, warn) if analyzer.aliases_used: target = self.scope.current_target() diff --git a/mypy/tmp.txt b/mypy/tmp.txt deleted file mode 100644 index 813eadd58cac7..0000000000000 --- a/mypy/tmp.txt +++ /dev/null @@ -1,5 +0,0 @@ -Users Runs Week (starts Sunday UTC) - 24 479 2018-05-20 - 20 267 2018-05-16 - 25 751 2018-05-06 - 23 397 2018-04-29 \ No newline at end of file diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 22b90d1125829..239d09ef9a450 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -189,22 +189,12 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # type check below. sym = self.api.dereference_module_cross_ref(sym) if sym is not None: - normalized = False if isinstance(sym.node, ImportedName): # Forward reference to an imported name that hasn't been processed yet. # To maintain backward compatibility, these get translated to Any. # # TODO: Remove this special case. return AnyType(TypeOfAny.implementation_artifact) - if sym.fullname in type_aliases: - # This is necessary if we process some other modules before 'typing'. - resolved = type_aliases[sym.fullname] - try: - new = self.api.lookup_fully_qualified(resolved) - except KeyError: # broken test fixture - return AnyType(TypeOfAny.from_error) - normalized = True - sym = new.copy() if sym.node is None: # UNBOUND_IMPORTED can happen if an unknown name was imported. if sym.kind != UNBOUND_IMPORTED: @@ -215,7 +205,7 @@ 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 self.allow_unnormalized and not normalized): + not self.allow_unnormalized): self.fail(no_subscript_builtin_alias(fullname), t) if self.tvar_scope: tvar_def = self.tvar_scope.get_binding(sym) @@ -296,10 +286,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: assert all_vars is not None return set_any_tvars(target, all_vars, t.line, t.column) if exp_len == 0 and act_len == 0: + if sym.node.no_args: + return Instance(target.type, [], line=t.line, column=t.column) return target if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and sym.node.no_args: tp = Instance(target.type, an_args) tp.line = t.line + tp.column = t.column return tp if act_len != exp_len: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' @@ -347,11 +340,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: instance = Instance(info, self.anal_array(t.args), t.line, t.column) if not t.args and self.options.disallow_any_generics and not self.aliasing: from_builtins = info.fullname() in nongen_builtins - if not normalized: - if not self.is_typeshed_stub and from_builtins: - alternative = nongen_builtins[info.fullname()] - self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) - instance.args = [AnyType(TypeOfAny.from_error, line=t.line)] * len(info.type_vars) + if not self.is_typeshed_stub and from_builtins: + alternative = nongen_builtins[info.fullname()] + self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) + instance.args = [AnyType(TypeOfAny.from_error, line=t.line)] * len(info.type_vars) else: instance.args = [AnyType(TypeOfAny.from_omitted_generics, line=t.line)] * len(info.type_vars) diff --git a/test-data/unit/deps-types.test b/test-data/unit/deps-types.test index 14b6a70229277..3d53d18cb482a 100644 --- a/test-data/unit/deps-types.test +++ b/test-data/unit/deps-types.test @@ -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/diff.test b/test-data/unit/diff.test index dbd89cfcf0e02..d110f8efdb4c0 100644 --- a/test-data/unit/diff.test +++ b/test-data/unit/diff.test @@ -440,11 +440,16 @@ __main__.S [case testDifferentListTypes] from typing import List A = List +B = list +C = List [file next.py] from typing import List -A = list +A = List +B = list +C = list [builtins fixtures/list.pyi] [out] +__main__.C [case testDecoratorChangesSignature] from contextlib import contextmanager From 4906cf2bdb899dbb8297ed6184213fafb005abbb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Jun 2018 17:38:48 +0100 Subject: [PATCH 10/29] Limit TypeAliasExpr to only r.h.s. of type alias definition --- mypy/checkexpr.py | 71 ++++++++++++------------- mypy/nodes.py | 3 +- mypy/semanal.py | 46 +++++++--------- mypy/typeanal.py | 52 +++++++++--------- test-data/unit/check-functions.test | 2 +- test-data/unit/check-generics.test | 17 +++--- test-data/unit/check-modules.test | 2 +- test-data/unit/semanal-typealiases.test | 12 ++++- 8 files changed, 105 insertions(+), 100 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 85554f881e397..6e60357b13450 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, @@ -182,17 +184,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: elif isinstance(node, Decorator): result = self.analyze_var_ref(node.var, e) elif isinstance(node, TypeAlias): - tp = set_any_tvars(node.target, node.alias_tvars, e.line, e.column) - if isinstance(tp, Instance): - result = type_object_type(tp.type, self.named_type) - if not node.no_args: - if isinstance(result, CallableType): - result = self.apply_generic_arguments(result, tp.args, e) - elif isinstance(result, Overloaded): - result = Overloaded([self.apply_generic_arguments(it, tp.args, e) - for it in result.items()]) - else: - result = AnyType(TypeOfAny.from_error) + result = self.alias_type_in_runtime(node.target, node.alias_tvars, node.no_args, e) else: # Unknown reference; use any type implicitly to avoid # generating extra type errors. @@ -2050,39 +2042,43 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type: 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(): + if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): + 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.safe_apply(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): + + 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.safe_apply(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: + return self.alias_type_in_runtime(alias.type, alias.tvars, alias.no_args, alias) + + def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], + no_args: bool, ctx: Context) -> Type: + if isinstance(target, Instance) and target.invalid: # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) - item = alias.type + 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.safe_apply(tp, item.args, ctx) else: # This type is invalid in most runtime contexts # and corresponding an error will be reported. @@ -2094,23 +2090,24 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: fb_info.bases = [self.object_type()] fb_info.mro = [fb_info, self.object_type().type] return Instance(fb_info, []) + + def safe_apply(self, tp: Type, args: List[Type], ctx: Context) -> Type: 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) - def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" return self.check_lst_expr(e.items, 'builtins.list', '', e) diff --git a/mypy/nodes.py b/mypy/nodes.py index 4efda1462bf03..23d3da33beda1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1877,10 +1877,11 @@ class TypeAliasExpr(Expression): type = None # type: mypy.types.Type tvars = None # type: List[str] - def __init__(self, type: 'mypy.types.Type', tvars: List[str]) -> None: + def __init__(self, type: 'mypy.types.Type', tvars: List[str], no_args: bool) -> None: super().__init__() self.type = type self.tvars = tvars + self.no_args = no_args def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8e74102c6da2a..61b35ff8abf98 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1698,10 +1698,10 @@ 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) + no_args = isinstance(res, Instance) and not res.args if isinstance(s.rvalue, (IndexExpr, CallExpr)): - s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars) + s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) s.rvalue.analyzed.line = s.line - no_args = isinstance(res, Instance) and not res.args node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on), no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias) and rvalue.node.normalized: @@ -2738,27 +2738,7 @@ 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 isinstance(expr.base.node, TypeAlias): - # 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) - if res is None: - # Bad type alias, nothing to do here. - expr.analyzed = TypeAliasExpr(expr.base.node.target, []) - return - expr.analyzed = TypeAliasExpr(res, alias_tvars) - 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) - # list, dict, set are not directly subscriptable - if (isinstance(expr.base.node.target, Instance) and - expr.base.node.no_args and - expr.base.node.target.type.fullname() in nongen_builtins - and not expr.base.node.normalized): - self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), expr) - elif refers_to_class_or_function(expr.base): + elif (isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias)) or refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. types = [] # type: List[Type] @@ -2776,10 +2756,22 @@ def visit_index_expr(self, expr: IndexExpr) -> None: 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 n.fullname in nongen_builtins: - self.fail(no_subscript_builtin_alias(n.fullname, propose_alt=False), expr) + if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): + # list, dict, set are not directly subscriptable + if (isinstance(expr.base.node.target, Instance) and + expr.base.node.no_args and + expr.base.node.target.type.fullname() in nongen_builtins + and not expr.base.node.normalized): + self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), + expr) + # We also store fine-grained dependencies to correctly re-process nodes + # with situations like `L = LongGeneric; x = L[int]()`. + self.add_type_alias_deps(expr.base.node.depends_on) + else: + # list, dict, set are not directly subscriptable + 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) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 239d09ef9a450..ba6773b95facd 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -276,31 +276,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: 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(target, all_vars, t.line, t.column) - if exp_len == 0 and act_len == 0: - if sym.node.no_args: - return Instance(target.type, [], line=t.line, column=t.column) - return target - if exp_len == 0 and act_len > 0 and isinstance(target, Instance) and sym.node.no_args: - tp = Instance(target.type, an_args) - tp.line = t.line - tp.column = t.column - return tp - 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(target, all_vars or [], - t.line, t.column, implicit=False) - assert all_vars is not None - return replace_alias_tvars(target, 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): name = sym.fullname if name is None: @@ -819,6 +795,32 @@ 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: + 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/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 61eca6a0befdc..9380dae95b8a0 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1613,7 +1613,7 @@ L = Callable[[Arg(name='x', type=int)], int] # ok # I have commented out the following test because I don't know how to expect the "defined here" note part of the error. # M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias E: Unexpected keyword argument "gnome" for "Arg" N = Callable[[Arg(name=None, type=int)], int] # ok -O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable +O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable P = Callable[[mypy_extensions.VarArg(int)], int] # ok Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type" R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name" diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 7e3f2aa589d4a..0862872e375ce 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -964,7 +964,7 @@ reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]' reveal_type(y) # E: Revealed type is 'builtins.int' 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 +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,7 +977,8 @@ 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 + 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") c = int def f(self, x: a) -> None: pass # E: Invalid type "__main__.C.a" def g(self, x: b) -> None: pass @@ -1016,15 +1017,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: Unsupported left operand type for + ("Type alias to Callable") reveal_type(cs) # E: Revealed type is 'Any' -ts = TA[str]() # E: "Type alias to Tuple" not callable +ts = TA() # E: "Type alias to Tuple" not callable 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: "Type alias to Union" has no attribute "x" reveal_type(us) # E: Revealed type is 'Any' +xx = CA[int]() # E: Type application is only supported for generic classes +yy = TA[int]() # E: Type application is only supported for generic classes +zz = UA[int]() # E: Type application is only supported for generic classes + [out] [case testGenericTypeAliasesTypeVarBinding] @@ -1075,7 +1080,7 @@ main:13: error: Value of type variable "S" of "A" cannot be "str" class A: ... Bad = A[int] # type: ignore -reveal_type(Bad) # E: Revealed type is 'def () -> __main__.A' +reveal_type(Bad) # E: Revealed type is 'Any' [out] [case testNoSubscriptionOfBuiltinAliases] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 3e28bd5e9c6ed..d7f71dbe270b0 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/semanal-typealiases.test b/test-data/unit/semanal-typealiases.test index 6afc494cbdb86..edc215c29c406 100644 --- a/test-data/unit/semanal-typealiases.test +++ b/test-data/unit/semanal-typealiases.test @@ -45,7 +45,10 @@ List[List[int]] MypyFile:1( ImportFrom:1(typing, [List]) ExpressionStmt:2( - TypeAliasExpr(builtins.list[builtins.list[builtins.int]]))) + TypeApplication:2( + NameExpr(List [typing.List]) + Types( + builtins.list[builtins.int])))) [case testTypeApplicationWithQualifiedTypeAlias] import typing @@ -55,7 +58,12 @@ typing.List[typing.List[int]] MypyFile:1( Import:1(typing) ExpressionStmt:2( - TypeAliasExpr(builtins.list[builtins.list[builtins.int]]))) + TypeApplication:2( + MemberExpr:2( + NameExpr(typing) + List [typing.List]) + Types( + builtins.list[builtins.int])))) [case testSimpleTypeAlias] import typing From 7070e5ae1df4a41bb64252a2fdbc028e5023ecb0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Jun 2018 20:23:47 +0100 Subject: [PATCH 11/29] Fix remaining tests --- mypy/build.py | 4 +++- mypy/checkmember.py | 2 +- mypy/nodes.py | 3 +-- mypy/semanal.py | 25 +++++++++++++------- mypy/server/deps.py | 2 +- mypy/treetransform.py | 2 +- test-data/unit/check-incomplete-fixture.test | 12 +++++----- 7 files changed, 29 insertions(+), 21 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 46c60b872b1f1..2fcf25de47927 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2834,7 +2834,9 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No graph[id].parse_file() graph[id].fix_suppressed_dependencies(graph) if 'typing' in scc: - manager.semantic_analyzer.add_builtin_aliases(graph['typing'].tree) + 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/checkmember.py b/mypy/checkmember.py index 205b8b9e1d1ce..a42d66c974bd7 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -435,7 +435,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) diff --git a/mypy/nodes.py b/mypy/nodes.py index 23d3da33beda1..9b8591c5965b0 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2518,7 +2518,6 @@ def __init__(self, kind: int, node: Optional[SymbolNode], module_public: bool = True, - normalized: bool = False, implicit: bool = False, module_hidden: bool = False, *, @@ -2541,7 +2540,7 @@ def fullname(self) -> Optional[str]: @property def type(self) -> 'Optional[mypy.types.Type]': node = self.node - if ((isinstance(node, Var) or isinstance(node, FuncDef)) + if ((isinstance(node, Var) or isinstance(node, FuncBase)) and node.type is not None): return node.type elif isinstance(node, Decorator): diff --git a/mypy/semanal.py b/mypy/semanal.py index 61b35ff8abf98..32d81651a9609 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -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', @@ -1381,6 +1381,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) @@ -1702,6 +1710,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: if isinstance(s.rvalue, (IndexExpr, CallExpr)): s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) s.rvalue.analyzed.line = s.line + s.rvalue.analyzed.column = res.column node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on), no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias) and rvalue.node.normalized: @@ -3046,7 +3055,9 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: name = alias.split('.')[-1] n = self.lookup_fully_qualified_or_none(target_name) if n: - alias_node = TypeAlias(self.named_type_or_none(target_name, []), alias, + target = self.named_type_or_none(target_name, []) + assert target is not None + alias_node = TypeAlias(target, alias, no_args=True, normalized=True) tree.names[name] = SymbolTableNode(GDEF, alias_node) else: @@ -3064,11 +3075,7 @@ def lookup_fully_qualified(self, name: str) -> SymbolTableNode: next_sym = n.names[parts[i]] assert isinstance(next_sym.node, MypyFile) n = next_sym.node - try: - return n.names[parts[-1]] - except KeyError: - self.name_not_defined(name, self.cur_mod_node) - raise + return n.names[parts[-1]] def lookup_fully_qualified_or_none(self, fullname: str) -> Optional[SymbolTableNode]: """Lookup a fully qualified name that refers to a module-level definition. diff --git a/mypy/server/deps.py b/mypy/server/deps.py index 184a2e8147052..560d151e18ad1 100644 --- a/mypy/server/deps.py +++ b/mypy/server/deps.py @@ -352,7 +352,7 @@ 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) - else: + elif typ: self.add_type_dependencies(typ) else: # Normal assignment diff --git a/mypy/treetransform.py b/mypy/treetransform.py index bce98b55985a4..5044e43f28bfd 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -475,7 +475,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) + 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/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] From 440392b1128b2f3368c1fdb0ef837cf9d506a178 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Jun 2018 22:27:04 +0100 Subject: [PATCH 12/29] Some more refactoring --- mypy/semanal.py | 25 +++++------- mypy/typeanal.py | 101 +++++++++++++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 57 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 32d81651a9609..591ac8dce1d11 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1510,7 +1510,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 @@ -1518,10 +1518,9 @@ def type_analyzer(self, *, tvar_scope, self.plugin, self.options, - self.is_typeshed_stub_file, - aliasing=aliasing, + self.is_stub_file, allow_tuple_literal=allow_tuple_literal, - allow_unnormalized=self.is_stub_file, + allow_unbound_tvars=allow_unbound_tvars, third_pass=third_pass) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack @@ -1530,11 +1529,11 @@ 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_tuple_literal=allow_tuple_literal, + allow_unbound_tvars=allow_unbound_tvars, third_pass=third_pass) typ = t.accept(a) self.add_type_alias_deps(a.aliases_used) @@ -1615,8 +1614,7 @@ def analyze_simple_literal_type(self, rvalue: Expression) -> Optional[Type]: return self.named_type_or_none('builtins.unicode') return None - def analyze_alias(self, rvalue: Expression, - warn_bound_tvar: bool = False) -> Tuple[Optional[Type], 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). @@ -1635,11 +1633,9 @@ def analyze_alias(self, rvalue: Expression, self.tvar_scope, self.plugin, self.options, - self.is_typeshed_stub_file, - allow_unnormalized=True, + 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 @@ -1680,8 +1676,7 @@ 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 @@ -2761,7 +2756,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: except TypeTranslationError: self.fail('Type expected within [...]', expr) return - typearg = self.anal_type(typearg, aliasing=True) + typearg = self.anal_type(typearg, allow_unbound_tvars=True) types.append(typearg) expr.analyzed = TypeApplication(expr.base, types) expr.analyzed.line = expr.line diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ba6773b95facd..9c6b109c0e345 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -57,11 +57,9 @@ def analyze_type_alias(node: Expression, tvar_scope: TypeVarScope, plugin: Plugin, options: Options, - is_typeshed_stub: bool, - allow_unnormalized: bool = False, + is_stub: bool, 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 @@ -115,8 +113,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_stub, + defining_alias=True) analyzer.in_dynamic_func = in_dynamic_func analyzer.global_scope = global_scope res = type.accept(analyzer) @@ -147,27 +145,25 @@ def __init__(self, tvar_scope: Optional[TypeVarScope], plugin: Plugin, options: Options, - is_typeshed_stub: bool, *, - aliasing: bool = False, + is_stub: bool, *, allow_tuple_literal: bool = False, - allow_unnormalized: bool = False, - third_pass: bool = False, - warn_bound_tvar: bool = False) -> None: + allow_unbound_tvars: bool = False, + defining_alias: 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 self.allow_tuple_literal = allow_tuple_literal # Positive if we are analyzing arguments of another (outer) type self.nesting_level = 0 - self.allow_unnormalized = allow_unnormalized + self.allow_unbound_tvars = allow_unbound_tvars or defining_alias + self.defining_alias = defining_alias self.plugin = plugin self.options = options - self.is_typeshed_stub = is_typeshed_stub - self.warn_bound_tvar = warn_bound_tvar + self.is_stub = is_stub 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] @@ -205,13 +201,13 @@ 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 self.allow_unnormalized): - self.fail(no_subscript_builtin_alias(fullname), t) + not self.is_stub): + 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) @@ -227,13 +223,14 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: elif fullname == 'typing.Tuple': if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' - if self.options.disallow_any_generics and not self.is_typeshed_stub: + if self.options.disallow_any_generics and not self.is_stub: self.fail(messages.BARE_GENERIC, t) 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])]) instance.line = t.line + instance.column = t.column return instance return self.tuple_type(self.anal_array(t.args)) elif fullname == 'typing.Union': @@ -278,30 +275,50 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: an_args = self.anal_array(t.args) 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 or sym.kind == MODULE_REF)): - 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 and + not isinstance(sym.node, (FuncDef, Decorator)) and + not (isinstance(sym.node, Var) and sym.node.is_ready) and + not sym.kind in (MODULE_REF, TVAR)) + if allow_forward_ref: + # We currently can't support subscripted forward refs in functions + # see ... 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 len(t.args) > 0 and info.fullname() == 'builtins.tuple': @@ -314,9 +331,9 @@ 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) - if not t.args and self.options.disallow_any_generics and not self.aliasing: + if not t.args and self.options.disallow_any_generics and not self.defining_alias: from_builtins = info.fullname() in nongen_builtins - if not self.is_typeshed_stub and from_builtins: + if not self.is_stub and from_builtins: alternative = nongen_builtins[info.fullname()] self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) instance.args = [AnyType(TypeOfAny.from_error, line=t.line)] * len(info.type_vars) @@ -378,7 +395,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) @@ -658,7 +675,7 @@ def __init__(self, api: SemanticAnalyzerCoreInterface, plugin: Plugin, options: Options, - is_typeshed_stub: bool, + is_stub: bool, indicator: Dict[str, bool], patches: List[Tuple[int, Callable[[], None]]]) -> None: self.api = api @@ -668,7 +685,7 @@ def __init__(self, self.note_func = api.note self.options = options self.plugin = plugin - self.is_typeshed_stub = is_typeshed_stub + self.is_stub = is_stub self.indicator = indicator self.patches = patches self.aliases_used = set() # type: Set[str] @@ -785,7 +802,7 @@ def anal_type(self, tp: UnboundType) -> Type: None, self.plugin, self.options, - self.is_typeshed_stub, + self.is_stub, third_pass=True) res = tp.accept(tpan) self.aliases_used = tpan.aliases_used From 0ceafb7c1fd3e0ca93df0584f67779cb95c78d05 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Jun 2018 23:08:25 +0100 Subject: [PATCH 13/29] More equivalent treatment of stub files; minor fixes --- mypy/checkexpr.py | 3 ++- mypy/plugin.py | 2 +- mypy/semanal.py | 18 +++++++++++------- mypy/typeanal.py | 44 ++++++++++++++++++++++++-------------------- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6a2d7a2d5fe8d..9c99c39c9b284 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2084,7 +2084,8 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: if isinstance(tapp.expr, RefExpr) and isinstance(tapp.expr.node, TypeAlias): 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) + 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.safe_apply(tp, item.args, tapp) 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 591ac8dce1d11..3fdd3d74bdf61 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1518,8 +1518,9 @@ def type_analyzer(self, *, tvar_scope, self.plugin, self.options, - self.is_stub_file, + self.is_typeshed_stub_file, allow_tuple_literal=allow_tuple_literal, + allow_unnormalized=self.is_stub_file, allow_unbound_tvars=allow_unbound_tvars, third_pass=third_pass) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) @@ -1615,7 +1616,7 @@ def analyze_simple_literal_type(self, rvalue: Expression) -> Optional[Type]: return None def analyze_alias(self, rvalue: Expression) -> Tuple[Optional[Type], List[str], - Set[str], 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 @@ -1633,8 +1634,9 @@ def analyze_alias(self, rvalue: Expression) -> Tuple[Optional[Type], List[str], self.tvar_scope, self.plugin, self.options, - self.is_stub_file, + self.is_typeshed_stub_file, in_dynamic_func=dynamic, + allow_unnormalized=self.is_stub_file, global_scope=global_scope) typ = None # type: Optional[Type] if res: @@ -1708,8 +1710,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: s.rvalue.analyzed.column = res.column node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, depends_on=list(depends_on), no_args=no_args) - if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias) and rvalue.node.normalized: - node.node.normalized = True + 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, @@ -2742,7 +2744,8 @@ 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 isinstance(expr.base.node, TypeAlias)) or refers_to_class_or_function(expr.base): + elif (isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias) or + refers_to_class_or_function(expr.base)): # Special form -- type application. # Translate index to an unanalyzed type. types = [] # type: List[Type] @@ -2766,7 +2769,8 @@ def visit_index_expr(self, expr: IndexExpr) -> None: expr.base.node.no_args and expr.base.node.target.type.fullname() in nongen_builtins and not expr.base.node.normalized): - self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), + self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), + propose_alt=False), expr) # We also store fine-grained dependencies to correctly re-process nodes # with situations like `L = LongGeneric; x = L[int]()`. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 9c6b109c0e345..9d2bfaf0a4555 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -57,8 +57,9 @@ def analyze_type_alias(node: Expression, tvar_scope: TypeVarScope, plugin: Plugin, options: Options, - is_stub: bool, + is_typeshed_stub: bool, in_dynamic_func: bool = False, + allow_unnormalized: bool = False, global_scope: bool = True) -> Optional[Tuple[Type, Set[str]]]: """Analyze r.h.s. of a (potential) type alias definition. @@ -113,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_stub, - defining_alias=True) + 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) @@ -145,8 +146,9 @@ def __init__(self, tvar_scope: Optional[TypeVarScope], plugin: Plugin, options: Options, - is_stub: bool, *, + is_typeshed_stub: bool, *, allow_tuple_literal: bool = False, + allow_unnormalized: bool = False, allow_unbound_tvars: bool = False, defining_alias: bool = False, third_pass: bool = False) -> None: @@ -159,11 +161,12 @@ def __init__(self, self.allow_tuple_literal = allow_tuple_literal # Positive if we are analyzing arguments of another (outer) type self.nesting_level = 0 + self.allow_unnormalized = allow_unnormalized self.allow_unbound_tvars = allow_unbound_tvars or defining_alias self.defining_alias = defining_alias self.plugin = plugin self.options = options - self.is_stub = is_stub + self.is_typeshed_stub = is_typeshed_stub 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] @@ -201,8 +204,9 @@ 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 self.is_stub): - self.fail(no_subscript_builtin_alias(fullname, propose_alt=not self.defining_alias), 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: @@ -223,14 +227,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: elif fullname == 'typing.Tuple': if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' - if self.options.disallow_any_generics and not self.is_stub: + if self.options.disallow_any_generics and not self.is_typeshed_stub: self.fail(messages.BARE_GENERIC, t) - return self.named_type('builtins.tuple', line=t.line, column=t.column) + 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])]) instance.line = t.line - instance.column = t.column return instance return self.tuple_type(self.anal_array(t.args)) elif fullname == 'typing.Union': @@ -299,10 +302,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # 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 and - not isinstance(sym.node, (FuncDef, Decorator)) and - not (isinstance(sym.node, Var) and sym.node.is_ready) and - not sym.kind in (MODULE_REF, TVAR)) + 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 ... for discussion. @@ -333,12 +336,13 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: instance = Instance(info, self.anal_array(t.args), t.line, t.column) if not t.args and self.options.disallow_any_generics and not self.defining_alias: from_builtins = info.fullname() in nongen_builtins - if not self.is_stub and from_builtins: + if not self.is_typeshed_stub and from_builtins: alternative = nongen_builtins[info.fullname()] self.fail(messages.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), t) - instance.args = [AnyType(TypeOfAny.from_error, line=t.line)] * len(info.type_vars) + any_type = AnyType(TypeOfAny.from_error, line=t.line) else: - instance.args = [AnyType(TypeOfAny.from_omitted_generics, line=t.line)] * len(info.type_vars) + 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: @@ -675,7 +679,7 @@ def __init__(self, api: SemanticAnalyzerCoreInterface, plugin: Plugin, options: Options, - is_stub: bool, + is_typeshed_stub: bool, indicator: Dict[str, bool], patches: List[Tuple[int, Callable[[], None]]]) -> None: self.api = api @@ -685,7 +689,7 @@ def __init__(self, self.note_func = api.note self.options = options self.plugin = plugin - self.is_stub = is_stub + self.is_typeshed_stub = is_typeshed_stub self.indicator = indicator self.patches = patches self.aliases_used = set() # type: Set[str] @@ -802,7 +806,7 @@ def anal_type(self, tp: UnboundType) -> Type: None, self.plugin, self.options, - self.is_stub, + self.is_typeshed_stub, third_pass=True) res = tp.accept(tpan) self.aliases_used = tpan.aliases_used From ab9925c9f544dfad0cb66419021eb89c4158046e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 14 Jun 2018 23:40:30 +0100 Subject: [PATCH 14/29] Minor fixes formatting --- mypy/checkmember.py | 3 +-- mypy/fixup.py | 1 - mypy/literals.py | 4 ++-- mypy/messages.py | 4 ++-- mypy/semanal.py | 6 +++--- mypy/semanal_shared.py | 2 +- mypy/typeanal.py | 12 ++++++------ test-data/unit/check-functions.test | 2 +- test-data/unit/check-generics.test | 2 +- 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a42d66c974bd7..a251848fc975c 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, TypeAlias + ARG_POS, ARG_STAR, ARG_STAR2, Decorator, OverloadedFuncDef, TypeAlias ) from mypy.messages import MessageBuilder from mypy.maptype import map_instance_to_supertype diff --git a/mypy/fixup.py b/mypy/fixup.py index ec3153fdf97a0..f1986394ae74d 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -6,7 +6,6 @@ MypyFile, SymbolNode, SymbolTable, SymbolTableNode, TypeInfo, FuncDef, OverloadedFuncDef, Decorator, Var, TypeVarExpr, ClassDef, Block, TypeAlias, - ) from mypy.types import ( CallableType, Instance, Overloaded, TupleType, TypedDictType, diff --git a/mypy/literals.py b/mypy/literals.py index 7d14113bfc883..44616564a72e7 100644 --- a/mypy/literals.py +++ b/mypy/literals.py @@ -6,9 +6,9 @@ UnicodeExpr, ListExpr, TupleExpr, SetExpr, DictExpr, CallExpr, SliceExpr, CastExpr, ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr, TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension, - GeneratorExpr, BackquoteExpr, TypeVarExpr, NamedTupleExpr, EnumCallExpr, + GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr, TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, - TypeAliasExpr) +) from mypy.visitor import ExpressionVisitor # [Note Literals and literal_hash] diff --git a/mypy/messages.py b/mypy/messages.py index d31cf80e994d0..93844389a3722 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, + 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, reverse_builtin_aliases + CallExpr ) # Constants that represent simple type checker error message, i.e. messages diff --git a/mypy/semanal.py b/mypy/semanal.py index 3fdd3d74bdf61..be5b4f61582db 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1519,9 +1519,9 @@ def type_analyzer(self, *, self.plugin, self.options, self.is_typeshed_stub_file, + allow_unbound_tvars=allow_unbound_tvars, allow_tuple_literal=allow_tuple_literal, allow_unnormalized=self.is_stub_file, - allow_unbound_tvars=allow_unbound_tvars, third_pass=third_pass) tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic()) tpan.global_scope = not self.type and not self.function_stack @@ -1533,8 +1533,8 @@ def anal_type(self, t: Type, *, allow_unbound_tvars: bool = False, third_pass: bool = False) -> Type: a = self.type_analyzer(tvar_scope=tvar_scope, - allow_tuple_literal=allow_tuple_literal, allow_unbound_tvars=allow_unbound_tvars, + allow_tuple_literal=allow_tuple_literal, third_pass=third_pass) typ = t.accept(a) self.add_type_alias_deps(a.aliases_used) @@ -1635,8 +1635,8 @@ def analyze_alias(self, rvalue: Expression) -> Tuple[Optional[Type], List[str], self.plugin, self.options, self.is_typeshed_stub_file, - in_dynamic_func=dynamic, allow_unnormalized=self.is_stub_file, + in_dynamic_func=dynamic, global_scope=global_scope) typ = None # type: Optional[Type] if res: 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/typeanal.py b/mypy/typeanal.py index 9d2bfaf0a4555..c78c2093145c1 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -19,10 +19,10 @@ ) from mypy.nodes import ( - TVAR, 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, TypeAlias, MODULE_REF + Decorator, ImportedName, TypeAlias ) from mypy.tvar_scope import TypeVarScope from mypy.exprtotype import expr_to_unanalyzed_type, TypeTranslationError @@ -58,8 +58,8 @@ def analyze_type_alias(node: Expression, plugin: Plugin, options: Options, is_typeshed_stub: bool, - in_dynamic_func: bool = False, allow_unnormalized: bool = False, + in_dynamic_func: bool = False, global_scope: bool = True) -> Optional[Tuple[Type, Set[str]]]: """Analyze r.h.s. of a (potential) type alias definition. @@ -147,10 +147,10 @@ def __init__(self, plugin: Plugin, options: Options, is_typeshed_stub: bool, *, + defining_alias: bool = False, allow_tuple_literal: bool = False, allow_unnormalized: bool = False, allow_unbound_tvars: bool = False, - defining_alias: bool = False, third_pass: bool = False) -> None: self.api = api self.lookup = api.lookup_qualified @@ -158,12 +158,12 @@ def __init__(self, self.fail_func = api.fail self.note_func = api.note self.tvar_scope = tvar_scope + 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 self.allow_unnormalized = allow_unnormalized self.allow_unbound_tvars = allow_unbound_tvars or defining_alias - self.defining_alias = defining_alias self.plugin = plugin self.options = options self.is_typeshed_stub = is_typeshed_stub @@ -229,7 +229,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) - return self.named_type('builtins.tuple', [], line=t.line, column=t.column) + 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])]) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 9380dae95b8a0..3b900ee5f5fc2 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1613,7 +1613,7 @@ L = Callable[[Arg(name='x', type=int)], int] # ok # I have commented out the following test because I don't know how to expect the "defined here" note part of the error. # M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias E: Unexpected keyword argument "gnome" for "Arg" N = Callable[[Arg(name=None, type=int)], int] # ok -O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable +O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable P = Callable[[mypy_extensions.VarArg(int)], int] # ok Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type" R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name" diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 0862872e375ce..6c5e2432a31ea 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1080,7 +1080,7 @@ main:13: error: Value of type variable "S" of "A" cannot be "str" class A: ... Bad = A[int] # type: ignore -reveal_type(Bad) # E: Revealed type is 'Any' +reveal_type(Bad) # E: Revealed type is 'Any' [out] [case testNoSubscriptionOfBuiltinAliases] From bb42843397b6a36adf0aa0875f744c5104edab87 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Jun 2018 00:46:08 +0100 Subject: [PATCH 15/29] Simplify dependency management --- mypy/nodes.py | 10 ++-------- mypy/semanal.py | 19 +++---------------- mypy/server/astdiff.py | 3 ++- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 9b8591c5965b0..f0b255834be52 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2392,12 +2392,11 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): - __slots__ = ('target', '_fullname', 'alias_tvars', 'depends_on', 'no_args', 'normalized') + __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized') def __init__(self, target: 'mypy.types.Type', fullname: str, *, alias_tvars: Optional[List[str]] = None, - depends_on: Optional[List[str]] = None, no_args: bool = False, normalized: bool = False) -> None: self._fullname = fullname @@ -2405,9 +2404,6 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, if alias_tvars is None: alias_tvars = [] self.alias_tvars = alias_tvars - if depends_on is None: - depends_on = [] - self.depends_on = depends_on self.no_args = no_args self.normalized = normalized @@ -2422,7 +2418,6 @@ def serialize(self) -> JsonDict: 'fullname': self._fullname, 'target': self.target.serialize(), 'alias_tvars': self.alias_tvars, - 'depends_on': self.depends_on, 'no_args': self.no_args, 'normalized': self.normalized } # type: JsonDict @@ -2436,11 +2431,10 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': assert data['.class'] == 'TypeAlias' fullname = data['fullname'] alias_tvars = data['alias_tvars'] - depends_on = data['depends_on'] target = mypy.types.deserialize_type(data['target']) no_args = data['no_args'] normalized = data['normalized'] - return cls(target, fullname, alias_tvars=alias_tvars, depends_on=depends_on, + return cls(target, fullname, alias_tvars=alias_tvars, no_args=no_args, normalized=normalized) diff --git a/mypy/semanal.py b/mypy/semanal.py index be5b4f61582db..60ec1c94c237d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -55,8 +55,8 @@ SetComprehension, DictionaryComprehension, TypeAlias, TypeAliasExpr, YieldExpr, ExecStmt, BackquoteExpr, ImportBase, AwaitExpr, IntExpr, FloatExpr, UnicodeExpr, TempNode, ImportedName, - COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, - get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS, nongen_builtins + COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, nongen_builtins, + get_member_expr_fullname, REVEAL_TYPE, REVEAL_LOCALS ) from mypy.literals import literal from mypy.tvar_scope import TypeVarScope @@ -1686,7 +1686,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: assert node is not None assert node.node is not None self.add_type_alias_deps(depends_on) - depends_on.add(node.node.fullname()) 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 @@ -1708,8 +1707,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args) s.rvalue.analyzed.line = s.line s.rvalue.analyzed.column = res.column - node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, - depends_on=list(depends_on), no_args=no_args) + node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): node.node.normalized = rvalue.node.normalized @@ -2423,11 +2421,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 isinstance(n.node, TypeAlias): - self.add_type_alias_deps([n.node.fullname()]) - def visit_name_expr(self, expr: NameExpr) -> None: n = self.lookup(expr.name, expr) if n: @@ -2435,7 +2428,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 @@ -2644,7 +2636,6 @@ 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: if not n: @@ -2706,7 +2697,6 @@ def visit_member_expr(self, expr: MemberExpr) -> None: type_info = self.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)): if not n: return @@ -2772,9 +2762,6 @@ def visit_index_expr(self, expr: IndexExpr) -> None: self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), propose_alt=False), expr) - # We also store fine-grained dependencies to correctly re-process nodes - # with situations like `L = LongGeneric; x = L[int]()`. - self.add_type_alias_deps(expr.base.node.depends_on) else: # list, dict, set are not directly subscriptable n = self.lookup_type_node(expr.base) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 2a3002dad3ae7..75b4df42f28ed 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -149,7 +149,8 @@ def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> Dict[str, Sna elif isinstance(symbol.node, TypeAlias): result[name] = ('TypeAlias', symbol.node.alias_tvars, - symbol.node.depends_on, + symbol.node.normalized, + symbol.node.no_args, snapshot_optional_type(symbol.node.target)) else: assert symbol.kind != UNBOUND_IMPORTED From e2e166a58e4fa0eecb60ff3d4264995d694b48fc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Jun 2018 01:13:43 +0100 Subject: [PATCH 16/29] Simplify visit_index --- mypy/semanal.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 60ec1c94c237d..2dbed1b1c28ba 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2736,7 +2736,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: expr.index.accept(self) elif (isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias) or refers_to_class_or_function(expr.base)): - # Special form -- type application. + # 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): @@ -2753,17 +2753,15 @@ def visit_index_expr(self, expr: IndexExpr) -> None: types.append(typearg) expr.analyzed = TypeApplication(expr.base, types) expr.analyzed.line = expr.line + # Types list, dict, set are not directly subscriptable, prohibit this. if isinstance(expr.base, RefExpr) and isinstance(expr.base.node, TypeAlias): - # list, dict, set are not directly subscriptable - if (isinstance(expr.base.node.target, Instance) and - expr.base.node.no_args and - expr.base.node.target.type.fullname() in nongen_builtins - and not expr.base.node.normalized): - self.fail(no_subscript_builtin_alias(expr.base.node.target.type.fullname(), - propose_alt=False), - expr) + 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) else: - # list, dict, set are not directly subscriptable 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) From 32236534aaa777efc7bdab1c4eddf84576a645cb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Jun 2018 14:17:50 +0100 Subject: [PATCH 17/29] Add docstrings and comments --- mypy/build.py | 2 + mypy/checkexpr.py | 42 +++++++++++++++++-- mypy/checkmember.py | 12 +++++- mypy/nodes.py | 95 ++++++++++++++++++++++++++++++++++++++++++- mypy/semanal.py | 16 +++++++- mypy/semanal_pass3.py | 3 ++ mypy/typeanal.py | 21 +++++++++- 7 files changed, 183 insertions(+), 8 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 2fcf25de47927..575636d633327 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2834,6 +2834,8 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No 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 for details. typing_mod = graph['typing'].tree assert typing_mod, "The typing module was not parsed" manager.semantic_analyzer.add_builtin_aliases(typing_mod) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 9c99c39c9b284..96705ba110736 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -184,6 +184,7 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: 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. result = self.alias_type_in_runtime(node.target, node.alias_tvars, node.no_args, e) else: # Unknown reference; use any type implicitly to avoid @@ -2080,8 +2081,17 @@ 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, ...]).""" + """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 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, @@ -2092,7 +2102,8 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: else: self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) return AnyType(TypeOfAny.from_error) - + # 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(): @@ -2103,14 +2114,33 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: 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.""" + """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: + + A = reveal_type(List[T]) + reveal_type(A) + + will reveal the same type `builtins.list[Any]`. + """ return self.alias_type_in_runtime(alias.type, alias.tvars, alias.no_args, alias) def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], no_args: bool, ctx: Context) -> 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. + """ if isinstance(target, Instance) and target.invalid: # An invalid alias, error already has been reported return AnyType(TypeOfAny.from_error) + # 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 @@ -2132,6 +2162,12 @@ def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], return Instance(fb_info, []) def safe_apply(self, tp: Type, args: List[Type], ctx: Context) -> Type: + """Safely apply type arguments to a generic callable type. + + 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. + """ if isinstance(tp, CallableType): if len(tp.variables) != len(args): self.msg.incompatible_type_application(len(tp.variables), diff --git a/mypy/checkmember.py b/mypy/checkmember.py index a251848fc975c..5824005ac4ff0 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -17,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,7 +250,16 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, v.info = info if isinstance(vv, TypeAlias) and isinstance(vv.target, Instance): - tp = type_object_type(vv.target.type, builtin_type) + # 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 + # As usual, we first erase any unbound type variables to Any. + target = set_any_tvars(vv.target, vv.alias_tvars, node.line, node.column) + assert isinstance(target, Instance) + tp = type_object_type(target.type, builtin_type) v = Var(name, type=expand_type_by_instance(tp, vv.target)) v.info = info diff --git a/mypy/nodes.py b/mypy/nodes.py index f0b255834be52..5993b60c9d914 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -102,6 +102,8 @@ def get_column(self) -> int: '__package__': '__builtins__.str'} +# These aliases exist because built-in class objects are not subscriptable. +# For example `list[int]` fail at runtime, insteas List[int] should be used. type_aliases = { 'typing.List': 'builtins.list', 'typing.Dict': 'builtins.dict', @@ -1874,8 +1876,16 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarExpr': class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" + # The target type. type = None # type: mypy.types.Type + # 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], no_args: bool) -> None: super().__init__() @@ -2391,7 +2401,87 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): - + """ + A sybmol node representing type alias. + + Type alias is a static concept, in contrast to variables with 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 SemanticAnalyzer.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: + + 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! + + 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 + generics: + + 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 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. + """ __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized') def __init__(self, target: 'mypy.types.Type', fullname: str, @@ -2497,6 +2587,9 @@ 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', diff --git a/mypy/semanal.py b/mypy/semanal.py index 2dbed1b1c28ba..0ed5a70c83b44 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1686,6 +1686,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: assert node is not None 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 @@ -1706,6 +1708,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: if isinstance(s.rvalue, (IndexExpr, CallExpr)): 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 node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, no_args=no_args) if isinstance(rvalue, RefExpr) and isinstance(rvalue.node, TypeAlias): @@ -1744,6 +1747,7 @@ 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. + # 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 @@ -2749,11 +2753,15 @@ def visit_index_expr(self, expr: IndexExpr) -> None: except TypeTranslationError: self.fail('Type expected within [...]', expr) return + # We always allow unbound type variables in InderxExpr, 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 - # Types list, dict, set are not directly subscriptable, prohibit this. + # 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): @@ -2761,6 +2769,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: 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: @@ -3034,6 +3043,9 @@ def builtin_type(self, fully_qualified_name: str) -> Instance: return Instance(node, [AnyType(TypeOfAny.special_form)] * len(node.defn.type_vars)) def add_builtin_aliases(self, tree: MypyFile) -> None: + # For historical reasons, the aliases like `List = list` are not defined + # in typeshed. Instead we need to manually add the corresponding nodes + # on the fly. We explicitly mark these aliases as normalized. assert tree.fullname() == 'typing' for alias, target_name in type_aliases.items(): name = alias.split('.')[-1] @@ -3045,6 +3057,8 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: 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: diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index 0d42417023928..e7b658cf40eda 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -370,6 +370,9 @@ def analyze(self, type: Optional[Type], node: Node, analyzer = self.make_type_analyzer(indicator) type.accept(analyzer) 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: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index c78c2093145c1..2ca2f6659b2d5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -158,11 +158,15 @@ def __init__(self, self.fail_func = api.fail self.note_func = api.note self.tvar_scope = tvar_scope + # 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 unbount type variables (always OK in aliases)? self.allow_unbound_tvars = allow_unbound_tvars or defining_alias self.plugin = plugin self.options = options @@ -335,8 +339,11 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: # Instance with an invalid number of type arguments. instance = Instance(info, self.anal_array(t.args), t.line, t.column) if not t.args and self.options.disallow_any_generics and not self.defining_alias: - from_builtins = info.fullname() in nongen_builtins - if not self.is_typeshed_stub and from_builtins: + # 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) @@ -818,6 +825,16 @@ def anal_type(self, tp: UnboundType) -> Type: 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: From 9459b3a74f0f1fefe28556e67db6a5eec2f98f8c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Jun 2018 14:35:00 +0100 Subject: [PATCH 18/29] Fix typo in a docstring --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 96705ba110736..2bbaf9ddb1bb4 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2122,7 +2122,7 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: A = reveal_type(List[T]) reveal_type(A) - will reveal the same type `builtins.list[Any]`. + will reveal the same type `def (...) -> builtins.list[Any]`. """ return self.alias_type_in_runtime(alias.type, alias.tvars, alias.no_args, alias) From 63d3f5fd22c7136370e68dc339cf3173b18dcd8c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 15 Jun 2018 17:37:30 +0100 Subject: [PATCH 19/29] Preserve alias definition line --- mypy/nodes.py | 14 ++++++++--- mypy/semanal.py | 5 ++-- test-data/unit/fine-grained.test | 42 +++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 5993b60c9d914..74cda7c9febfd 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2481,10 +2481,11 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here 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') + __slots__ = ('target', '_fullname', 'alias_tvars', 'no_args', 'normalized', 'line', 'column') - def __init__(self, target: 'mypy.types.Type', fullname: str, + def __init__(self, target: 'mypy.types.Type', fullname: str, line: int, column: int, *, alias_tvars: Optional[List[str]] = None, no_args: bool = False, @@ -2496,6 +2497,7 @@ def __init__(self, target: 'mypy.types.Type', fullname: str, 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] @@ -2509,7 +2511,9 @@ def serialize(self) -> JsonDict: 'target': self.target.serialize(), 'alias_tvars': self.alias_tvars, 'no_args': self.no_args, - 'normalized': self.normalized + 'normalized': self.normalized, + 'line': self.line, + 'column': self.column } # type: JsonDict return data @@ -2524,7 +2528,9 @@ def deserialize(cls, data: JsonDict) -> 'TypeAlias': target = mypy.types.deserialize_type(data['target']) no_args = data['no_args'] normalized = data['normalized'] - return cls(target, fullname, alias_tvars=alias_tvars, + line = data['line'] + column = data['column'] + return cls(target, fullname, line, column, alias_tvars=alias_tvars, no_args=no_args, normalized=normalized) diff --git a/mypy/semanal.py b/mypy/semanal.py index 0ed5a70c83b44..6085f1010c4f1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1710,7 +1710,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: 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 - node.node = TypeAlias(res, node.node.fullname(), alias_tvars=alias_tvars, no_args=no_args) + 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 @@ -3053,7 +3054,7 @@ def add_builtin_aliases(self, tree: MypyFile) -> None: if n: target = self.named_type_or_none(target_name, []) assert target is not None - alias_node = TypeAlias(target, alias, + 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: diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 39e1d959a288d..3c6a2a925cefc 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -7174,7 +7174,6 @@ def fu() -> int: [out] == - [case testRefreshOptions] [file mypy.ini] [[mypy] @@ -7192,3 +7191,44 @@ x = [] # type: List [builtins fixtures/list.pyi] [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-skip-cache] +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: Name 'A' already defined on line 3 +main:2: error: Invalid type "b.A" From f340632be992a00c560aec389a2ed9254ef02408 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 22 Jun 2018 19:07:14 +0100 Subject: [PATCH 20/29] Code review: part one --- mypy/build.py | 3 ++- mypy/checkexpr.py | 33 +++++++++++++++++++++------------ mypy/nodes.py | 27 +++++++++++++++------------ mypy/semanal.py | 14 +++++++++----- mypy/semanal_pass3.py | 2 +- mypy/typeanal.py | 7 ++++--- 6 files changed, 52 insertions(+), 34 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 575636d633327..e6fe22cd89869 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2835,7 +2835,8 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No 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 for details. + # 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) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 2bbaf9ddb1bb4..ed002e8fe275d 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -185,7 +185,8 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: result = self.analyze_var_ref(node.var, e) elif isinstance(node, TypeAlias): # Something that refers to a type alias appears in runtime context. - result = self.alias_type_in_runtime(node.target, node.alias_tvars, node.no_args, e) + result = self.alias_type_in_runtime_context(node.target, node.alias_tvars, + node.no_args, e) else: # Unknown reference; use any type implicitly to avoid # generating extra type errors. @@ -2084,10 +2085,10 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: """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 first case we need + 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, + 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): @@ -2098,7 +2099,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: tapp.expr.node.no_args, tapp) if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) - return self.safe_apply(tp, item.args, tapp) + return self.apply_type_arguments_to_callable(tp, item.args, tapp) else: self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) return AnyType(TypeOfAny.from_error) @@ -2108,7 +2109,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: if isinstance(tp, (CallableType, Overloaded)): if not tp.is_type_obj(): self.chk.fail(messages.ONLY_CLASS_APPLICATION, tapp) - return self.safe_apply(tp, tapp.types, tapp) + 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) @@ -2117,22 +2118,30 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: """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: + For example, here: A = reveal_type(List[T]) reveal_type(A) - will reveal the same type `def (...) -> builtins.list[Any]`. + 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(alias.type, alias.tvars, alias.no_args, alias) + return self.alias_type_in_runtime_context(alias.type, alias.tvars, alias.no_args, alias) - def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], + def alias_type_in_runtime_context(self, target: Type, alias_tvars: List[str], no_args: bool, ctx: Context) -> 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. + 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 @@ -2148,7 +2157,7 @@ def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], tp = type_object_type(item.type, self.named_type) if no_args: return tp - return self.safe_apply(tp, item.args, ctx) + return self.apply_type_arguments_to_callable(tp, item.args, ctx) else: # This type is invalid in most runtime contexts # and corresponding an error will be reported. @@ -2161,7 +2170,7 @@ def alias_type_in_runtime(self, target: Type, alias_tvars: List[str], fb_info.mro = [fb_info, self.object_type().type] return Instance(fb_info, []) - def safe_apply(self, tp: Type, args: List[Type], ctx: Context) -> Type: + def apply_type_arguments_to_callable(self, tp: Type, args: List[Type], ctx: Context) -> Type: """Safely apply type arguments to a generic callable type. This will first perform type arguments count checks, report the diff --git a/mypy/nodes.py b/mypy/nodes.py index 74cda7c9febfd..226a307c57c8a 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -103,7 +103,7 @@ def get_column(self) -> int: # These aliases exist because built-in class objects are not subscriptable. -# For example `list[int]` fail at runtime, insteas List[int] should be used. +# For example `list[int]` fails at runtime. Instead List[int] should be used. type_aliases = { 'typing.List': 'builtins.list', 'typing.Dict': 'builtins.dict', @@ -2402,10 +2402,10 @@ def __getattribute__(self, attr: str) -> None: class TypeAlias(SymbolNode): """ - A sybmol node representing type alias. + A symbol node representing a type alias. - Type alias is a static concept, in contrast to variables with variables - with types like Type[...]. Namely: + 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 @@ -2419,7 +2419,7 @@ class TypeAlias(SymbolNode): 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 SemanticAnalyzer.check_and_set_up_type_alias + class-valued attributes. See SemanticAnalyzerPass2.check_and_set_up_type_alias for details. Aliases can be generic. Currently, mypy uses unbound type variables for @@ -2428,7 +2428,8 @@ class TypeAlias(SymbolNode): following: 1. An alias targeting a generic class without explicit variables act as - the given class: + 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] @@ -2439,6 +2440,8 @@ class TypeAlias(SymbolNode): x: AA # same as List[Any] x: AA[int] # Error! + C = Callable # Same as Callable[..., 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: @@ -2452,11 +2455,11 @@ 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 - generics: + generic classes: A = List B = A # here A is expanded to List, _not_ List[Any], - # to match the Python runtime behaviour. + # to match the Python runtime behaviour x: B[int] # same as List[int] C = List[A] # this expands to List[List[Any]] @@ -2465,10 +2468,10 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here 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 wrong number of type arguments. + 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. + using the `no_args` flag. See also TypeAliasExpr.no_args. Meaning of other fields: @@ -2476,7 +2479,7 @@ def f(x: B[T]) -> T: ... # without T, Any would be used here 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 + 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 @@ -2595,7 +2598,7 @@ class SymbolTableNode: 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. + are shared by all node kinds. """ __slots__ = ('kind', diff --git a/mypy/semanal.py b/mypy/semanal.py index 6085f1010c4f1..e42fe5737de80 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1705,7 +1705,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: # so we need to replace it with non-explicit Anys res = make_any_non_explicit(res) no_args = isinstance(res, Instance) and not res.args - if isinstance(s.rvalue, (IndexExpr, CallExpr)): + 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 @@ -2754,7 +2754,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: except TypeTranslationError: self.fail('Type expected within [...]', expr) return - # We always allow unbound type variables in InderxExpr, since we + # 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) @@ -3044,9 +3044,13 @@ def builtin_type(self, fully_qualified_name: str) -> Instance: return Instance(node, [AnyType(TypeOfAny.special_form)] * len(node.defn.type_vars)) def add_builtin_aliases(self, tree: MypyFile) -> None: - # For historical reasons, the aliases like `List = list` are not defined - # in typeshed. Instead we need to manually add the corresponding nodes - # on the fly. We explicitly mark these aliases as normalized. + """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] diff --git a/mypy/semanal_pass3.py b/mypy/semanal_pass3.py index e7b658cf40eda..6a554557f2b18 100644 --- a/mypy/semanal_pass3.py +++ b/mypy/semanal_pass3.py @@ -371,7 +371,7 @@ def analyze(self, type: Optional[Type], node: Node, type.accept(analyzer) 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 + # 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) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2ca2f6659b2d5..e9f8332dad9cb 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -166,7 +166,7 @@ def __init__(self, # Should we allow unnormalized types like `list[int]` # (currently allowed in stubs)? self.allow_unnormalized = allow_unnormalized - # Should we accept unbount type variables (always OK in aliases)? + # 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 @@ -311,8 +311,9 @@ def visit_unbound_type_nonoptional(self, t: UnboundType) -> Type: 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 ... for discussion. + # 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) From 490d11aa0f5408453dbf47576a395c5073e5b717 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 22 Jun 2018 20:56:44 +0100 Subject: [PATCH 21/29] Code review: part two --- mypy/checkexpr.py | 30 +++++++++++++++----------- mypy/nodes.py | 5 ++++- mypy/semanal.py | 2 ++ mypy/server/astdiff.py | 2 +- mypy/typeanal.py | 2 +- test-data/unit/check-generics.test | 15 ++++++------- test-data/unit/check-type-aliases.test | 13 +++++++++++ 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 3d6f105f2cc5d..dd8b6ec0c8c42 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -185,8 +185,12 @@ def analyze_ref_expr(self, e: RefExpr, lvalue: bool = False) -> Type: 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) + 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. @@ -2144,10 +2148,13 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: 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) + 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) -> Type: + 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_ @@ -2177,23 +2184,22 @@ class LongName(Generic[T]): ... return tp return self.apply_type_arguments_to_callable(tp, item.args, ctx) else: - # This type is invalid in most runtime contexts - # and corresponding an error will be reported. + if alias_definition: + return AnyType(TypeOfAny.special_form) + # This type is invalid in most runtime contexts. kind = (' to Callable' if isinstance(item, CallableType) else ' to Tuple' if isinstance(item, TupleType) else ' to Union' if isinstance(item, UnionType) else '') - cdef = ClassDef('Type alias' + kind, Block([])) - fb_info = TypeInfo(SymbolTable(), cdef, self.chk.tree.fullname()) - fb_info.bases = [self.object_type()] - fb_info.mro = [fb_info, self.object_type().type] - return Instance(fb_info, []) + self.chk.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) + return AnyType(TypeOfAny.from_error) def apply_type_arguments_to_callable(self, tp: Type, args: List[Type], ctx: Context) -> Type: - """Safely apply type arguments to a generic callable 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. + 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(args): diff --git a/mypy/nodes.py b/mypy/nodes.py index 7b0df86412c75..6470eca7e5abd 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1265,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__() @@ -1282,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): @@ -2447,6 +2449,7 @@ class TypeAlias(SymbolNode): 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 diff --git a/mypy/semanal.py b/mypy/semanal.py index 040246156826d..994ba5d12ebf1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1760,6 +1760,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: 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): diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 3b1ee2758ccf4..d95aa1d49c20e 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -54,7 +54,7 @@ class level -- these are handled at attribute level (say, 'mod.Cls.method' from mypy.nodes import ( SymbolTable, TypeInfo, Var, SymbolNode, Decorator, TypeVarExpr, TypeAlias, - FuncBase, OverloadedFuncDef, FuncItem, MODULE_REF, TYPE_ALIAS, UNBOUND_IMPORTED, TVAR + FuncBase, OverloadedFuncDef, FuncItem, MODULE_REF, UNBOUND_IMPORTED, TVAR ) from mypy.types import ( Type, TypeVisitor, UnboundType, AnyType, NoneTyp, UninhabitedType, diff --git a/mypy/typeanal.py b/mypy/typeanal.py index e9f8332dad9cb..841dd51dad950 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -79,7 +79,7 @@ def analyze_type_alias(node: Expression, node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or - node.fullname == 'typing.Any' or + node.fullname in ('typing.Any', 'typing.Tuple', 'typing.Callable') or isinstance(node.node, TypeAlias)): return None elif isinstance(node, IndexExpr): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 6c5e2432a31ea..6e6b99ac744cb 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -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,18 +1016,18 @@ CA = Callable[[T], int] TA = Tuple[T, int] UA = Union[T, int] -cs = CA + 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() # 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.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[int]() # E: Type application is only supported for generic classes -yy = TA[int]() # E: Type application is only supported for generic classes -zz = UA[int]() # E: Type application is only supported for generic classes +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] diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index cfbfaa068892f..efaf715e7f26b 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -277,3 +277,16 @@ 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] From 26e553bcb97f59fde647fb3fdaec2709ece1c9fd Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 22 Jun 2018 23:28:41 +0100 Subject: [PATCH 22/29] Better name resolution for aliases --- mypy/checkmember.py | 9 +++++++- mypy/semanal.py | 9 ++++++-- test-data/unit/check-type-aliases.test | 29 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index f233d259052a8..c8deb92f25399 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -260,7 +260,7 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, target = set_any_tvars(vv.target, vv.alias_tvars, node.line, node.column) assert isinstance(target, Instance) tp = type_object_type(target.type, builtin_type) - v = Var(name, type=expand_type_by_instance(tp, vv.target)) + v = Var(name, type=expand_type_by_instance(tp, target)) v.info = info if isinstance(v, Var): @@ -481,6 +481,13 @@ 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): + alias = node.node + 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) + if is_decorated: assert isinstance(node.node, Decorator) if node.node.type: diff --git a/mypy/semanal.py b/mypy/semanal.py index 994ba5d12ebf1..1caba0d0175ef 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1715,7 +1715,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: # @@ -2752,9 +2752,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) - if n is not None and (n.kind == MODULE_REF or isinstance(n.node, TypeInfo)): + if n is not None and (n.kind == MODULE_REF or isinstance(n.node, (TypeInfo, + TypeAlias))): if not n: return expr.kind = n.kind diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index efaf715e7f26b..3f0bc58a6c12f 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -290,3 +290,32 @@ 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] From 9af332c981054336bdd64ca8553f5024ee4e5a78 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Jun 2018 00:07:40 +0100 Subject: [PATCH 23/29] Allow type aliases to tuples and Any in runtime context --- mypy/checkexpr.py | 6 +++ mypy/checkmember.py | 26 ++++++++----- test-data/unit/check-type-aliases.test | 53 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index dd8b6ec0c8c42..302d52aa41bfa 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2183,6 +2183,12 @@ class LongName(Generic[T]): ... 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: if alias_definition: return AnyType(TypeOfAny.special_form) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index c8deb92f25399..ef9fa2b20c7c4 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -256,11 +256,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo, # class C: # A = List[int] # x = C.A() <- this is OK - # As usual, we first erase any unbound type variables to Any. - target = set_any_tvars(vv.target, vv.alias_tvars, node.line, node.column) - assert isinstance(target, Instance) - tp = type_object_type(target.type, builtin_type) - v = Var(name, type=expand_type_by_instance(tp, target)) + typ = instance_alias_type(vv, builtin_type) + v = Var(name, type=typ) v.info = info if isinstance(v, Var): @@ -305,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], *, @@ -482,11 +492,7 @@ def analyze_class_attribute_access(itype: Instance, return builtin_type('types.ModuleType') if isinstance(node.node, TypeAlias) and isinstance(node.node.target, Instance): - alias = node.node - 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) + return instance_alias_type(node.node, builtin_type) if is_decorated: assert isinstance(node.node, Decorator) diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 3f0bc58a6c12f..ea907bc455bc9 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -319,3 +319,56 @@ 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] From 2af5c7c66c721d357b8bc0b6bc6cda70547980ae Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Jun 2018 00:11:33 +0100 Subject: [PATCH 24/29] Update tests --- test-data/unit/check-classes.test | 2 +- test-data/unit/deps-types.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ce0f8066e5e96..f21436435a121 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/deps-types.test b/test-data/unit/deps-types.test index 3d53d18cb482a..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] From bf3f973e76890b4638c8d7fcf3009f5c3e1522b6 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Jun 2018 00:20:22 +0100 Subject: [PATCH 25/29] Allow importing names over type aliases --- mypy/semanal.py | 2 +- test-data/unit/check-type-aliases.test | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1caba0d0175ef..41b844c1af47e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1471,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) diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index ea907bc455bc9..d1adfa74c4b0f 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -372,3 +372,25 @@ 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] From 8593d920e14b3ba1dd508413431321a88aa65e77 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Jun 2018 00:30:14 +0100 Subject: [PATCH 26/29] Minor fixes --- test-data/unit/check-classes.test | 4 ++-- test-data/unit/fine-grained.test | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f21436435a121..0f955a5e48a6c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -2244,14 +2244,14 @@ 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] def __init__(self) -> None: self.aa = self.a_int() -reveal_type(C().aa) # E: Revealed type is '__main__.A[builtins.int, builtins.int]' +reveal_type(C().aa) # E: Revealed type is '__main__.A[builtins.int*, builtins.int*]' [out] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 57d328169278f..9d0205bd941ea 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -7240,7 +7240,7 @@ 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-skip-cache] +[case testImportOnTopOfAlias2] from a import A x: A [file a.py] @@ -7257,8 +7257,7 @@ def A(x: str) -> str: pass [builtins fixtures/list.pyi] [out] == -a.py:4: error: Name 'A' already defined on line 3 -main:2: error: Invalid type "b.A" +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 85febc96acbe111dc8dbede0457b48f1e6be0ba5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 23 Jun 2018 01:05:50 +0100 Subject: [PATCH 27/29] One more corner case --- mypy/semanal.py | 4 ++++ test-data/unit/check-type-aliases.test | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 41b844c1af47e..3b8c490058fad 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1480,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) diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index d1adfa74c4b0f..f2483b51dc65d 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -394,3 +394,15 @@ 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] From 41c256166327d4de4622bbed0859a7ffa1a6294c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 25 Jun 2018 15:03:22 +0100 Subject: [PATCH 28/29] Move error formatting to messages.py --- mypy/checkexpr.py | 5 +---- mypy/messages.py | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 302d52aa41bfa..bf36f83985fe6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2193,10 +2193,7 @@ class LongName(Generic[T]): ... if alias_definition: return AnyType(TypeOfAny.special_form) # This type is invalid in most runtime contexts. - kind = (' to Callable' if isinstance(item, CallableType) else - ' to Tuple' if isinstance(item, TupleType) else - ' to Union' if isinstance(item, UnionType) else '') - self.chk.fail('The type alias{} is invalid in runtime context'.format(kind), ctx) + 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: diff --git a/mypy/messages.py b/mypy/messages.py index 762df11c1b1ba..90fe745068f91 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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) From 78d668da134aa0f51530d473f1e6ef457112d544 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 25 Jun 2018 17:11:26 +0100 Subject: [PATCH 29/29] Resolve original definition for type aliases --- mypy/semanal.py | 6 +++++- test-data/unit/check-type-aliases.test | 20 ++++++++++++++++++++ test-data/unit/fixtures/classmethod.pyi | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 3b8c490058fad..bde974c6974d5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3342,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/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index f2483b51dc65d..0a3ddffb13a07 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -406,3 +406,23 @@ from a import A 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/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