From d07259ade278dcf0f0e2f4c6bdcef21a3e4c1187 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 10 Jun 2017 21:15:06 +0200 Subject: [PATCH 01/13] Start reworking type aliases --- mypy/semanal.py | 52 +++++++++++++---------------- test-data/unit/check-inference.test | 2 +- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b4adfb85f3cb..776103cd9f31 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1517,26 +1517,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: allow_tuple_literal = isinstance(s.lvalues[-1], (TupleExpr, ListExpr)) s.type = self.anal_type(s.type, allow_tuple_literal=allow_tuple_literal) else: - # For simple assignments, allow binding type aliases. - # Also set the type if the rvalue is a simple literal. + # Set the type if the rvalue is a simple literal. if (s.type is None and len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)): if s.lvalues[0].is_def: s.type = self.analyze_simple_literal_type(s.rvalue) - res = analyze_type_alias(s.rvalue, - self.lookup_qualified, - self.lookup_fully_qualified, - self.tvar_scope, - self.fail, allow_unnormalized=True) - if res and (not isinstance(res, Instance) or res.args): - # TODO: What if this gets reassigned? - name = s.lvalues[0] - node = self.lookup(name.name, name) - node.kind = TYPE_ALIAS - node.type_override = res - if isinstance(s.rvalue, IndexExpr): - s.rvalue.analyzed = TypeAliasExpr(res, - fallback=self.alias_fallback(res)) if s.type: # Store type into nodes. for lvalue in s.lvalues: @@ -1592,24 +1577,35 @@ def alias_fallback(self, tp: Type) -> Instance: def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed.""" - # For now, type aliases only work at the top level of a module. - if (len(s.lvalues) == 1 and not self.is_func_scope() and not self.type - and not s.type): + # For now, type aliases are not created at class scope, + # instead they are treated as class valued members. + if len(s.lvalues) == 1 and not s.type and (not self.type or self.is_func_scope()): lvalue = s.lvalues[0] if isinstance(lvalue, NameExpr): if not lvalue.is_def: # Only a definition can create a type alias, not regular assignment. return rvalue = s.rvalue - if isinstance(rvalue, RefExpr): - node = rvalue.node - if isinstance(node, TypeInfo): - # TODO: We should record the fact that this is a variable - # that refers to a type, rather than making this - # just an alias for the type. - sym = self.lookup_type_node(rvalue) - if sym: - self.globals[lvalue.name] = sym + res = analyze_type_alias(rvalue, + self.lookup_qualified, + self.lookup_fully_qualified, + self.tvar_scope, + self.fail, allow_unnormalized=True) + if res: + # TODO: What if this gets reassigned? + node = self.lookup(lvalue.name, lvalue) + if isinstance(res, Instance) and not res.args: + node.node = res.type + if isinstance(rvalue, RefExpr): + sym = self.lookup_type_node(rvalue) + if sym: + node.normalized = sym.normalized + return + node.kind = TYPE_ALIAS + node.type_override = res + if isinstance(s.rvalue, IndexExpr): + s.rvalue.analyzed = TypeAliasExpr(res, + fallback=self.alias_fallback(res)) def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 42cd312c0531..2a268be513af 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -148,7 +148,7 @@ class B: pass import typing def f() -> None: a = A - a(A()) # E: Too many arguments + a(A()) # E: Too many arguments for "A" a() t = a # type: type From 34de9ae7f6352e4958d58983c38fceb31ec51f37 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 10 Jun 2017 23:39:34 +0200 Subject: [PATCH 02/13] Fix another alias issue --- mypy/nodes.py | 8 +++++++- mypy/semanal.py | 14 ++++++++++---- mypy/typeanal.py | 23 +++++------------------ 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index a5cb96007750..558c9f168443 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2254,6 +2254,7 @@ class SymbolTableNode: mod_id = '' # type: Optional[str] # If this not None, override the type of the 'node' attribute. type_override = None # type: Optional[mypy.types.Type] + alias_tvars = None # type: Optional[List[str]] # If False, this name won't be imported via 'from import *'. # This has no effect on names within classes. module_public = True @@ -2265,13 +2266,15 @@ class SymbolTableNode: def __init__(self, kind: int, node: Optional[SymbolNode], mod_id: str = None, typ: 'mypy.types.Type' = None, - module_public: bool = True, normalized: bool = False) -> None: + module_public: bool = True, normalized: bool = False, + alias_tvars: Optional[List[str]] = None) -> None: self.kind = kind self.node = node self.type_override = typ self.mod_id = mod_id self.module_public = module_public self.normalized = normalized + self.alias_tvars = alias_tvars @property def fullname(self) -> Optional[str]: @@ -2329,6 +2332,7 @@ def serialize(self, prefix: str, name: str) -> JsonDict: 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 @@ -2347,6 +2351,8 @@ def deserialize(cls, data: JsonDict) -> 'SymbolTableNode': 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'] if 'module_public' in data: stnode.module_public = data['module_public'] return stnode diff --git a/mypy/semanal.py b/mypy/semanal.py index 776103cd9f31..1da22a3d3a6f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1352,7 +1352,8 @@ def visit_import_from(self, imp: ImportFrom) -> None: self.cur_mod_id, node.type_override, module_public=module_public, - normalized=node.normalized) + normalized=node.normalized, + alias_tvars=node.alias_tvars) self.add_symbol(imported_id, symbol, imp) elif module and not missing: # Missing attribute. @@ -1404,7 +1405,8 @@ def normalize_type_alias(self, node: SymbolTableNode, normalized = True if normalized: node = SymbolTableNode(node.kind, node.node, - node.mod_id, node.type_override, normalized=True) + node.mod_id, node.type_override, + normalized=True, alias_tvars=node.alias_tvars) return node def add_fixture_note(self, fullname: str, ctx: Context) -> None: @@ -1448,7 +1450,8 @@ def visit_import_all(self, i: ImportAll) -> None: self.add_symbol(name, SymbolTableNode(node.kind, node.node, self.cur_mod_id, node.type_override, - normalized=node.normalized), i) + normalized=node.normalized, + alias_tvars=node.alias_tvars), i) else: # Don't add any dummy symbols for 'from x import *' if 'x' is unknown. pass @@ -1594,7 +1597,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: if res: # TODO: What if this gets reassigned? node = self.lookup(lvalue.name, lvalue) - if isinstance(res, Instance) and not res.args: + if isinstance(res, Instance) and not res.args and isinstance(rvalue, RefExpr): node.node = res.type if isinstance(rvalue, RefExpr): sym = self.lookup_type_node(rvalue) @@ -1603,6 +1606,9 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: return node.kind = TYPE_ALIAS node.type_override = res + node.alias_tvars = [name for (name, _) in + res.accept(TypeVariableQuery(self.lookup_qualified, + self.tvar_scope))] if isinstance(s.rvalue, IndexExpr): s.rvalue.analyzed = TypeAliasExpr(res, fallback=self.alias_fallback(res)) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 3b90464c3367..32cad2d3f3d2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -207,9 +207,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return UninhabitedType(is_noreturn=True) elif sym.kind == TYPE_ALIAS: override = sym.type_override + all_vars = sym.alias_tvars assert override is not None an_args = self.anal_array(t.args) - all_vars = self.get_type_var_names(override) exp_len = len(all_vars) act_len = len(an_args) if exp_len > 0 and act_len == 0: @@ -273,22 +273,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: return AnyType() - def get_type_var_names(self, tp: Type) -> List[str]: - """Get all type variable names that are present in a generic type alias - in order of textual appearance (recursively, if needed). - """ - return [name for name, _ - in tp.accept(TypeVariableQuery(self.lookup, self.tvar_scope, - include_callables=True, include_bound_tvars=True))] - - def get_tvar_name(self, t: Type) -> Optional[str]: - if not isinstance(t, UnboundType): - return None - sym = self.lookup(t.name, t) - if sym is not None and sym.kind == TVAR: - return t.name - return None - def replace_alias_tvars(self, 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 @@ -297,7 +281,10 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type], typ_args = get_typ_args(tp) new_args = typ_args[:] for i, arg in enumerate(typ_args): - tvar = self.get_tvar_name(arg) + if isinstance(arg, (UnboundType, TypeVarType)): + tvar = arg.name + else: + tvar = None if tvar and tvar in vars: # Perform actual substitution... new_args[i] = subs[vars.index(tvar)] From 6765a089ac26d01518742ed593837346c4bca0b7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 00:09:59 +0200 Subject: [PATCH 03/13] Add some tests --- test-data/unit/check-generics.test | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 33106feca98d..12dac46fbf84 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -885,6 +885,52 @@ TupledNode = Node[T, Tuple[T, T]] [builtins fixtures/list.pyi] +[case testGenericTypeAliasesImportingWithoutTypeVar] +from typing import Tuple +from lib import Transform + +def int_tf(m: int) -> Transform[int, str]: + def transform(i: int, pos: int) -> Tuple[int, str]: + pass + return transform + +[file lib.py] +from typing import Callable, TypeVar, Tuple + +T = TypeVar('T') +R = TypeVar('R') + +Transform = Callable[[T, int], Tuple[T, R]] +[out] + +[case testGenericTypeAliasesImportingWithoutTypeVarError] +from a import Alias +x: Alias[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 + +[file a.py] +from typing import TypeVar, List +T = TypeVar('T') + +Alias = List[List[T]] +[builtins fixtures/list.pyi] +[out] + +[case testTypeAliasesResultingInPlainInstance] +from typing import Optional, Union + +O = Optional[int] +U = Union[int] + +x: O +y: U + +reveal_type(x) # E: Revealed type is 'builtins.int' +reveal_type(y) # E: Revealed type is 'builtins.int' + +U[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 +[out] + [case testGenericTypeAliasesRuntimeExpressionsInstance] from typing import TypeVar, Generic T = TypeVar('T') From 8dc40d651d7de5016f8b085e98dcc4c4f2180c3f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 11 Jun 2017 01:56:50 +0200 Subject: [PATCH 04/13] Prohibit reassigning aliases --- mypy/semanal.py | 11 +++++--- test-data/unit/check-type-aliases.test | 39 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 1da22a3d3a6f..c7b2bfb8212d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1585,9 +1585,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: if len(s.lvalues) == 1 and not s.type and (not self.type or self.is_func_scope()): lvalue = s.lvalues[0] if isinstance(lvalue, NameExpr): - if not lvalue.is_def: - # Only a definition can create a type alias, not regular assignment. - return rvalue = s.rvalue res = analyze_type_alias(rvalue, self.lookup_qualified, @@ -1595,8 +1592,14 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: self.tvar_scope, self.fail, allow_unnormalized=True) if res: - # TODO: What if this gets reassigned? node = self.lookup(lvalue.name, lvalue) + if not lvalue.is_def: + # Only a definition can create a type alias, not regular assignment. + if node and node.kind == TYPE_ALIAS or isinstance(node.node, TypeInfo): + self.fail('Cannot assign multiple types to name "{}"' + ' without an explicit "Type[...]" annotation' + .format(lvalue.name), lvalue) + return if isinstance(res, Instance) and not res.args and isinstance(rvalue, RefExpr): node.node = res.type if isinstance(rvalue, RefExpr): diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 20022cccecea..5517f6fd6340 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -56,6 +56,45 @@ from typing import Union U = Union[int, str] [builtins fixtures/tuple.pyi] +[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 +[out] + +[case testProhibitReassigningSubscriptedAliases] +from typing import Callable +A = Callable[[], float] +A = Callable[[], int] # E: Cannot assign multiple types to name "A" without an explicit "Type[...]" annotation \ + # E: Value of type "int" is not indexable + # the second error is because of `Callable = 0` in lib-stub/typing.pyi +[builtins fixtures/list.pyi] +[out] + +[case testProhibitReassigningGenericAliases] +from typing import TypeVar, Union, Tuple +T = TypeVar('T') + +A = Tuple[T, T] +A = Union[T, int] # E: Cannot assign multiple types to name "A" without an explicit "Type[...]" annotation \ + # E: Value of type "int" is not indexable + # the second error is because of `Union = 0` in lib-stub/typing.pyi +[out] + +[case testProhibitUsingVariablesAsTypesAndAllowAliasesAsTypes] +from typing import TypeVar, Sequence, Type +T = TypeVar('T') + +A: Type[float] = int +x: A # E: Invalid type "__main__.A" +def bad(tp: A) -> None: # E: Invalid type "__main__.A" + pass + +Alias = int +GenAlias = Sequence[T] +def fun(x: Alias) -> GenAlias[int]: pass +[out] + [case testTypeAliasInBuiltins] def f(x: bytes): pass bytes From 50056c4fe9601f2ab66e5bf118a59e89d7d85afb Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 22 Jun 2017 23:26:53 +0200 Subject: [PATCH 05/13] Factor out common code, fix runtime aliases and deserialization; add a test --- mypy/checkexpr.py | 25 ++-------------- mypy/fixup.py | 3 ++ mypy/nodes.py | 5 ++-- mypy/semanal.py | 40 ++++++++++++++----------- mypy/treetransform.py | 3 +- mypy/typeanal.py | 47 +++++++++++++++--------------- test-data/unit/check-generics.test | 20 +++++++++++++ 7 files changed, 78 insertions(+), 65 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 0101f8eec107..207d866dc0f5 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4,7 +4,7 @@ from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional from mypy.errors import report_internal_error -from mypy.typeanal import has_any_from_unimported_type +from mypy.typeanal import has_any_from_unimported_type, replace_alias_tvars from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef, TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, @@ -1730,7 +1730,8 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: item = alias.type if not alias.in_runtime: # We don't replace TypeVar's with Any for alias used as Alias[T](42). - item = self.replace_tvars_any(item) + item = replace_alias_tvars(item, alias.tvars, [AnyType()] * len(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 @@ -1755,26 +1756,6 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: for it in tp.items()]) return AnyType() - def replace_tvars_any(self, tp: Type) -> Type: - """Replace all type variables of a type alias tp with Any. Basically, this function - finishes what could not be done in method TypeAnalyser.visit_unbound_type() - from typeanal.py. - """ - typ_args = get_typ_args(tp) - new_args = typ_args[:] - for i, arg in enumerate(typ_args): - if isinstance(arg, UnboundType): - sym = None - try: - sym = self.chk.lookup_qualified(arg.name) - except KeyError: - pass - if sym and (sym.kind == TVAR): - new_args[i] = AnyType() - else: - new_args[i] = self.replace_tvars_any(arg) - return set_typ_args(tp, new_args, tp.line, tp.column) - 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 681b4d929f38..330deec92570 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -88,6 +88,9 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None: if stnode is not None: value.node = stnode.node value.type_override = stnode.type_override + if not value.type_override and self.quick_and_dirty: + value.type_override = Instance(stale_info(), []) + value.alias_tvars = stnode.alias_tvras or [] elif not self.quick_and_dirty: assert stnode is not None, "Could not find cross-ref %s" % (cross_ref,) else: diff --git a/mypy/nodes.py b/mypy/nodes.py index 8defd2bc4aa1..6e5dffb7b589 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1808,11 +1808,12 @@ class TypeAliasExpr(Expression): # (not in a type context like type annotation or base class). in_runtime = False # type: bool - def __init__(self, type: 'mypy.types.Type', fallback: 'mypy.types.Type' = None, - in_runtime: bool = False) -> None: + def __init__(self, type: 'mypy.types.Type', tvars: List[str], + fallback: 'mypy.types.Type' = None, in_runtime: bool = False) -> None: 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) diff --git a/mypy/semanal.py b/mypy/semanal.py index 457102ca660a..10661be29bdc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1598,6 +1598,20 @@ def alias_fallback(self, tp: Type) -> Instance: fb_info.mro = [fb_info, self.object_type().type] return Instance(fb_info, []) + def analyze_alias(self, rvalue: Expression, + allow_unnormalized: bool) -> Tuple[Optional[Type], List[str]]: + res = analyze_type_alias(rvalue, + self.lookup_qualified, + self.lookup_fully_qualified, + self.tvar_scope, + self.fail, self.plugin, allow_unnormalized=True) + if res: + alias_tvars = [name for (name, _) in + res.accept(TypeVariableQuery(self.lookup_qualified, self.tvar_scope))] + else: + alias_tvars = [] + return res, alias_tvars + def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed.""" # For now, type aliases are not created at class scope, @@ -1606,11 +1620,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: lvalue = s.lvalues[0] if isinstance(lvalue, NameExpr): rvalue = s.rvalue - res = analyze_type_alias(rvalue, - self.lookup_qualified, - self.lookup_fully_qualified, - self.tvar_scope, - self.fail, self.plugin, allow_unnormalized=True) + res, alias_tvars = self.analyze_alias(rvalue, allow_unnormalized=True) if res: node = self.lookup(lvalue.name, lvalue) if not lvalue.is_def: @@ -1629,12 +1639,12 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: return node.kind = TYPE_ALIAS node.type_override = res - node.alias_tvars = [name for (name, _) in - res.accept(TypeVariableQuery(self.lookup_qualified, - self.tvar_scope))] + node.alias_tvars = alias_tvars if isinstance(s.rvalue, IndexExpr): - s.rvalue.analyzed = TypeAliasExpr(res, + s.rvalue.analyzed = TypeAliasExpr(res, node.alias_tvars, fallback=self.alias_fallback(res)) + s.rvalue.analyzed.line = s.rvalue.line + s.rvalue.analyzed.column = s.rvalue.column def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, @@ -3124,15 +3134,11 @@ def visit_index_expr(self, expr: IndexExpr) -> None: 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 = analyze_type_alias(expr, - self.lookup_qualified, - self.lookup_fully_qualified, - self.tvar_scope, - self.fail, - self.plugin, - allow_unnormalized=self.is_stub_file) - expr.analyzed = TypeAliasExpr(res, fallback=self.alias_fallback(res), + res, alias_tvars = self.analyze_alias(expr, allow_unnormalized=self.is_stub_file) + expr.analyzed = TypeAliasExpr(res, alias_tvars, fallback=self.alias_fallback(res), in_runtime=True) + expr.analyzed.line = expr.line + expr.analyzed.column = expr.column elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 170be48da9c5..c7debd826715 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -477,7 +477,8 @@ 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) + 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) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index f047016a2df8..47a1cca81cde 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -221,15 +221,15 @@ def visit_unbound_type(self, t: UnboundType) -> Type: 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, ...] - return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len, - t.line, t.column) + return replace_alias_tvars(override, all_vars, [AnyType()] * exp_len, + t.line, t.column) if exp_len == 0 and act_len == 0: return override if act_len != exp_len: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) return t - return self.replace_alias_tvars(override, all_vars, an_args, t.line, t.column) + return replace_alias_tvars(override, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): name = sym.fullname if name is None: @@ -280,26 +280,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: return AnyType() - def replace_alias_tvars(self, 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 - resetting context. Length of subs should be already checked. - """ - typ_args = get_typ_args(tp) - new_args = typ_args[:] - for i, arg in enumerate(typ_args): - if isinstance(arg, (UnboundType, TypeVarType)): - tvar = arg.name - else: - tvar = None - if tvar and tvar in vars: - # Perform actual substitution... - new_args[i] = subs[vars.index(tvar)] - else: - # ...recursively, if needed. - new_args[i] = self.replace_alias_tvars(arg, vars, subs, newline, newcolumn) - return set_typ_args(tp, new_args, newline, newcolumn) - def visit_any(self, t: AnyType) -> Type: return t @@ -688,6 +668,27 @@ def visit_type_type(self, t: TypeType) -> None: TypeVarList = List[Tuple[str, TypeVarExpr]] +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 + resetting context. Length of subs should be already checked. + """ + typ_args = get_typ_args(tp) + new_args = typ_args[:] + for i, arg in enumerate(typ_args): + if isinstance(arg, (UnboundType, TypeVarType)): + tvar = arg.name + else: + tvar = None + if tvar and tvar in vars: + # Perform actual substitution... + new_args[i] = subs[vars.index(tvar)] + else: + # ...recursively, if needed. + new_args[i] = replace_alias_tvars(arg, vars, subs, newline, newcolumn) + return set_typ_args(tp, new_args, newline, newcolumn) + + def remove_dups(tvars: Iterable[T]) -> List[T]: # Get unique elements in order of appearance all_tvars = set() # type: Set[T] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 12dac46fbf84..6138962f7989 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1047,6 +1047,26 @@ reveal_type(BadGenList()) # E: Revealed type is 'builtins.list[Any]' [builtins fixtures/list.pyi] [out] +[case testImportedTypeAliasInRuntimeContext] +from m import Alias + +n = Alias[int]([1]) +reveal_type(n) # E: Revealed type is 'm.Node[builtins.list*[builtins.int]]' +bad = Alias[str]([1]) # E: List item 0 has incompatible type "int" + +n2 = Alias([1]) # Same as Node[List[Any]] +reveal_type(n2) # E: Revealed type is 'm.Node[builtins.list*[Any]]' +[file m.py] +from typing import TypeVar, Generic, List +T = TypeVar('T') + +class Node(Generic[T]): + def __init__(self, x: T) -> None: + self.x = x + +Alias = Node[List[T]] +[builtins fixtures/list.pyi] +[out] -- Simplified declaration of generics -- ---------------------------------- From b2212dd51889b251a438cca349a1834b652bb38f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 22 Jun 2017 23:35:36 +0200 Subject: [PATCH 06/13] Fix typo --- mypy/fixup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/fixup.py b/mypy/fixup.py index 330deec92570..48fb085d158a 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -90,7 +90,7 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None: value.type_override = stnode.type_override if not value.type_override and self.quick_and_dirty: value.type_override = Instance(stale_info(), []) - value.alias_tvars = stnode.alias_tvras or [] + 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: From 598a5111cc3ce12fc7804b0d8569486459e29ad3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 29 Jun 2017 12:46:48 +0200 Subject: [PATCH 07/13] Don't create aliases at function scope, create variables instead --- mypy/semanal.py | 7 ++++--- test-data/unit/check-inference.test | 2 +- test-data/unit/check-typevar-values.test | 9 ++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index c7b2bfb8212d..ac06a624c1c7 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1580,9 +1580,10 @@ def alias_fallback(self, tp: Type) -> Instance: def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed.""" - # For now, type aliases are not created at class scope, - # instead they are treated as class valued members. - if len(s.lvalues) == 1 and not s.type and (not self.type or self.is_func_scope()): + # Type aliases are created only at module scope, at class and function scopes + # assignments create class valued members and local variables with type object types. + if (len(s.lvalues) == 1 and not self.is_func_scope() and not self.type + and not s.type): lvalue = s.lvalues[0] if isinstance(lvalue, NameExpr): rvalue = s.rvalue diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 2a268be513af..42cd312c0531 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -148,7 +148,7 @@ class B: pass import typing def f() -> None: a = A - a(A()) # E: Too many arguments for "A" + a(A()) # E: Too many arguments a() t = a # type: type diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 36df2235a209..fa0453e43c8d 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -500,14 +500,13 @@ from typing import TypeVar, List class C: T = TypeVar('T', bound=int) def f(self, x: T) -> T: - L = List[C.T] # a valid type alias - l: L = [] - reveal_type(l) # E: Revealed type is 'builtins.list[T`-1]' + L = List[C.T] # this creates a variable, not an alias + reveal_type(L) # E: Revealed type is 'Overload(def () -> builtins.list[T`-1], def (x: typing.Iterable[T`-1]) -> builtins.list[T`-1])' y: C.T = x - l.append(x) + L().append(x) C.T # E: Type variable "C.T" cannot be used as an expression A = C.T # E: Type variable "C.T" cannot be used as an expression - return l[0] + return L()[0] [builtins fixtures/list.pyi] From f90dabfdf977bf788348b50f0cea89093eb03ea7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 29 Jun 2017 12:55:11 +0200 Subject: [PATCH 08/13] Fix broken merge --- test-data/unit/check-generics.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 6138962f7989..d22e3e9be8d0 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -924,7 +924,7 @@ U = Union[int] x: O y: U -reveal_type(x) # E: Revealed type is 'builtins.int' +reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]' reveal_type(y) # E: Revealed type is 'builtins.int' U[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 From 8c35be03c2bf92c1b43017b97ab61e28ae5aba10 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 4 Jul 2017 18:06:18 +0200 Subject: [PATCH 09/13] Fix strict-optional self-test :-) --- mypy/typeanal.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 39a462347527..50583492e63a 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -217,10 +217,14 @@ def visit_unbound_type(self, t: UnboundType) -> Type: all_vars = sym.alias_tvars assert override is not None an_args = self.anal_array(t.args) - exp_len = len(all_vars) + 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 replace_alias_tvars(override, all_vars, [AnyType()] * exp_len, t.line, t.column) if exp_len == 0 and act_len == 0: @@ -229,6 +233,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) return t + assert all_vars is not None return replace_alias_tvars(override, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): name = sym.fullname @@ -678,7 +683,7 @@ def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], new_args = typ_args[:] for i, arg in enumerate(typ_args): if isinstance(arg, (UnboundType, TypeVarType)): - tvar = arg.name + tvar = arg.name # type: Optional[str] else: tvar = None if tvar and tvar in vars: From 5cc19535043851448c6d5ae5dfb086369a37f096 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 7 Jul 2017 00:28:55 +0200 Subject: [PATCH 10/13] First part of review comments --- mypy/semanal.py | 3 +- mypy/typeanal.py | 2 +- test-data/unit/check-generics.test | 55 ++++++++++++++++++++++++++ test-data/unit/check-incremental.test | 22 +++++++++++ test-data/unit/check-type-aliases.test | 1 + 5 files changed, 81 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index b0b89a24d593..480e53e5abea 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1646,7 +1646,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed.""" # Type aliases are created only at module scope, at class and function scopes # assignments create class valued members and local variables with type object types. - if (len(s.lvalues) == 1 and not self.is_func_scope() and not self.type + if (len(s.lvalues) == 1 and not self.is_func_scope() and + not (self.type and isinstance(s.rvalue, NameExpr) and s.lvalues[0].is_def) and not s.type): lvalue = s.lvalues[0] if isinstance(lvalue, NameExpr): diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 24ef2beeab87..899dccef253a 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -242,7 +242,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if act_len != exp_len: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) - return t + return AnyType() assert all_vars is not None return replace_alias_tvars(override, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index d22e3e9be8d0..d9d8a0618fe6 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -894,6 +894,8 @@ def int_tf(m: int) -> Transform[int, str]: pass return transform +var: Transform[int, str] +reveal_type(var) # E: Revealed type is 'def (builtins.int, builtins.int) -> Tuple[builtins.int, builtins.str]' [file lib.py] from typing import Callable, TypeVar, Tuple @@ -906,6 +908,7 @@ Transform = Callable[[T, int], Tuple[T, R]] [case testGenericTypeAliasesImportingWithoutTypeVarError] from a import Alias x: Alias[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 +reveal_type(x) # E: Revealed type is 'Any' [file a.py] from typing import TypeVar, List @@ -915,6 +918,38 @@ Alias = List[List[T]] [builtins fixtures/list.pyi] [out] +[case testGenericAliasWithTypeVarsFromDifferentModules] +from mod import Alias, TypeVar + +S = TypeVar('S') +NewAlias = Alias[int, int, S, S] +class C: pass + +x: NewAlias[str] +reveal_type(x) # E: Revealed type is 'builtins.list[Tuple[builtins.int, builtins.int, builtins.str, builtins.str]]' +y: Alias[int, str, C, C] +reveal_type(y) # E: Revealed type is 'builtins.list[Tuple[builtins.int, builtins.str, __main__.C, __main__.C]]' + +[file mod.py] +from typing import TypeVar, List, Tuple +import a +import b +T = TypeVar('T') +Alias = List[Tuple[T, a.T, b.T, b.B.T]] + +[file a.py] +from typing import TypeVar +T = TypeVar('T') + +[file b.py] +from typing import TypeVar + +T = TypeVar('T') +class B: + T = TypeVar('T') +[builtins fixtures/list.pyi] +[out] + [case testTypeAliasesResultingInPlainInstance] from typing import Optional, Union @@ -931,6 +966,26 @@ U[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 [out] +[case testAliasesInClassBodyNormalVsSubscripted] +from typing import Union, Type, Iterable + +class A: pass +class B(A): pass +class C: + a = A # This is a variable + 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") + c = int + def f(self, x: a) -> None: pass # E: Invalid type "__main__.C.a" + def g(self, x: b) -> None: pass + def h(self, x: c) -> None: pass # E: Invalid type "__main__.C.c" + x: b + reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' +[out] + [case testGenericTypeAliasesRuntimeExpressionsInstance] from typing import TypeVar, Generic T = TypeVar('T') diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 7fd17acd3041..7ac5add3e707 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -1232,6 +1232,28 @@ def C() -> str: [out2] tmp/m1.py:3: error: Argument 1 to "accepts_int" has incompatible type "str"; expected "int" +[case testIncrementalStoresAliasTypeVars] +import a + +[file mod.py] +from typing import TypeVar, Union +T = TypeVar('T') +Alias = Union[int, T] +x: Alias[str] + +[file a.py] +from mod import Alias, x + +[file a.py.2] +from mod import Alias, x + +reveal_type(x) +y: Alias[int] +reveal_type(y) +[out2] +tmp/a.py:3: error: Revealed type is 'Union[builtins.int, builtins.str]' +tmp/a.py:5: error: Revealed type is 'Union[builtins.int, builtins.int]' + [case testIncrementalSilentImportsWithBlatantError] # cmd: mypy -m main # flags: --follow-imports=skip diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 5517f6fd6340..ab4da0011129 100644 --- a/test-data/unit/check-type-aliases.test +++ b/test-data/unit/check-type-aliases.test @@ -86,6 +86,7 @@ from typing import TypeVar, Sequence, Type T = TypeVar('T') A: Type[float] = int +A = float # OK x: A # E: Invalid type "__main__.A" def bad(tp: A) -> None: # E: Invalid type "__main__.A" pass From 7e7c1b70e1cfff4f09e200bc2d3b298497891239 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 7 Jul 2017 01:22:40 +0200 Subject: [PATCH 11/13] Second part of review comments --- mypy/fixup.py | 2 -- mypy/nodes.py | 2 ++ mypy/semanal.py | 19 ++++++++++++++++--- test-data/unit/check-generics.test | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/mypy/fixup.py b/mypy/fixup.py index 17b703c52c36..5cb1188072cc 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -88,8 +88,6 @@ def visit_symbol_table(self, symtab: SymbolTable) -> None: if stnode is not None: value.node = stnode.node value.type_override = stnode.type_override - if not value.type_override and self.quick_and_dirty: - value.type_override = Instance(stale_info(), []) 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,) diff --git a/mypy/nodes.py b/mypy/nodes.py index 9da56cbf93e6..6cc75f5b8f22 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2268,6 +2268,8 @@ class SymbolTableNode: mod_id = '' # type: Optional[str] # If this not None, override the type of the 'node' attribute. type_override = None # type: Optional[mypy.types.Type] + # For generic aliases this stores the (qualified) names of type variables. + # (For example see testGenericAliasWithTypeVarsFromDifferentModules.) alias_tvars = None # type: Optional[List[str]] # If False, this name won't be imported via 'from import *'. # This has no effect on names within classes. diff --git a/mypy/semanal.py b/mypy/semanal.py index 480e53e5abea..e46423cb7dfd 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1626,6 +1626,11 @@ def alias_fallback(self, tp: Type) -> Instance: def analyze_alias(self, rvalue: Expression, allow_unnormalized: bool) -> Tuple[Optional[Type], List[str]]: + """Check if 'rvalue' represents a valid type allowed for aliasing + (e.g. not a type variable). If yes, return the corresponding type and a list of + qualified type variable names for generic aliases. + If 'allow_unnormalized' is True, allow types like builtins.list[T]. + """ res = analyze_type_alias(rvalue, self.lookup_qualified, self.lookup_fully_qualified, @@ -1643,9 +1648,13 @@ def analyze_alias(self, rvalue: Expression, return res, alias_tvars def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: - """Check if assignment creates a type alias and set it up as needed.""" - # Type aliases are created only at module scope, at class and function scopes - # assignments create class valued members and local variables with type object types. + """Check if assignment creates a type alias and set it up as needed. + For simple aliases like L = List we use a simpler mechanism, just copying TypeInfo. + For subscripted (including generic) aliases we mark corresponding node as TYPE_ALIAS + and store the corresponding type in node.override and in rvalue.analyzed. + """ + # Type aliases are created only at module scope and class scope (for subscripted types), + # at function scope assignments always create local variables with type object types. if (len(s.lvalues) == 1 and not self.is_func_scope() and not (self.type and isinstance(s.rvalue, NameExpr) and s.lvalues[0].is_def) and not s.type): @@ -1668,6 +1677,8 @@ 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) 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 if isinstance(rvalue, RefExpr): sym = self.lookup_type_node(rvalue) @@ -1678,6 +1689,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: node.type_override = res node.alias_tvars = alias_tvars if isinstance(s.rvalue, IndexExpr): + # We only need this for subscripted aliases, since simple aliases + # are already processed using aliasing TypeInfo's above. s.rvalue.analyzed = TypeAliasExpr(res, node.alias_tvars, fallback=self.alias_fallback(res)) s.rvalue.analyzed.line = s.rvalue.line diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index d9d8a0618fe6..8cbdb7e17059 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -935,7 +935,7 @@ from typing import TypeVar, List, Tuple import a import b T = TypeVar('T') -Alias = List[Tuple[T, a.T, b.T, b.B.T]] +Alias = List[Tuple[T, a.T, b.T, b.B.T]] # alias_tvars here will be ['T', 'a.T', 'b.T', 'b.B.T'] [file a.py] from typing import TypeVar From efe63424713bc182fcecf100509995f212bd6cb3 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 7 Jul 2017 01:43:24 +0200 Subject: [PATCH 12/13] Minor fix; reduce nesting in check_and_set_up_type_alias --- mypy/semanal.py | 78 +++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index e46423cb7dfd..8462b2b820eb 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1655,46 +1655,48 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """ # Type aliases are created only at module scope and class scope (for subscripted types), # at function scope assignments always create local variables with type object types. + lvalue = s.lvalues[0] + if not isinstance(lvalue, NameExpr): + return if (len(s.lvalues) == 1 and not self.is_func_scope() and - not (self.type and isinstance(s.rvalue, NameExpr) and s.lvalues[0].is_def) + not (self.type and isinstance(s.rvalue, NameExpr) and lvalue.is_def) and not s.type): - lvalue = s.lvalues[0] - if isinstance(lvalue, NameExpr): - rvalue = s.rvalue - res, alias_tvars = self.analyze_alias(rvalue, allow_unnormalized=True) - if res: - node = self.lookup(lvalue.name, lvalue) - if not lvalue.is_def: - # Only a definition can create a type alias, not regular assignment. - if node and node.kind == TYPE_ALIAS or isinstance(node.node, TypeInfo): - self.fail('Cannot assign multiple types to name "{}"' - ' without an explicit "Type[...]" annotation' - .format(lvalue.name), lvalue) - return - check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, - context=s) - # 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 - if isinstance(rvalue, RefExpr): - 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(s.rvalue, IndexExpr): - # We only need this for subscripted aliases, since simple aliases - # are already processed using aliasing TypeInfo's above. - s.rvalue.analyzed = TypeAliasExpr(res, node.alias_tvars, - fallback=self.alias_fallback(res)) - s.rvalue.analyzed.line = s.rvalue.line - s.rvalue.analyzed.column = s.rvalue.column + rvalue = s.rvalue + res, alias_tvars = self.analyze_alias(rvalue, allow_unnormalized=True) + if not res: + return + node = self.lookup(lvalue.name, lvalue) + if not lvalue.is_def: + # Only a definition can create a type alias, not regular assignment. + if node and node.kind == TYPE_ALIAS or isinstance(node.node, TypeInfo): + self.fail('Cannot assign multiple types to name "{}"' + ' without an explicit "Type[...]" annotation' + .format(lvalue.name), lvalue) + return + check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, + context=s) + # 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 + if isinstance(rvalue, RefExpr): + 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): + # 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 def analyze_lvalue(self, lval: Lvalue, nested: bool = False, add_global: bool = False, From 91bda8435c24ef883e518c5df8d71da5b166be76 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 7 Jul 2017 10:42:51 +0200 Subject: [PATCH 13/13] Better type on error; shorten docstring; minor refactoring --- mypy/checkexpr.py | 7 ++----- mypy/semanal.py | 4 ++-- mypy/typeanal.py | 13 +++++++++---- test-data/unit/check-generics.test | 5 +++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4315dd44de06..e09b512e2dae 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4,9 +4,7 @@ from typing import cast, Dict, Set, List, Tuple, Callable, Union, Optional from mypy.errors import report_internal_error -from mypy.typeanal import ( - has_any_from_unimported_type, check_for_explicit_any, replace_alias_tvars -) +from mypy.typeanal import has_any_from_unimported_type, check_for_explicit_any, set_any_tvars from mypy.types import ( Type, AnyType, CallableType, Overloaded, NoneTyp, TypeVarDef, TupleType, TypedDictType, Instance, TypeVarType, ErasedType, UnionType, @@ -1739,8 +1737,7 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: item = alias.type if not alias.in_runtime: # We don't replace TypeVar's with Any for alias used as Alias[T](42). - item = replace_alias_tvars(item, alias.tvars, [AnyType()] * len(alias.tvars), - alias.line, alias.column) + 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 diff --git a/mypy/semanal.py b/mypy/semanal.py index 8462b2b820eb..bcbe495e1c93 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1650,8 +1650,8 @@ def analyze_alias(self, rvalue: Expression, def check_and_set_up_type_alias(self, s: AssignmentStmt) -> None: """Check if assignment creates a type alias and set it up as needed. For simple aliases like L = List we use a simpler mechanism, just copying TypeInfo. - For subscripted (including generic) aliases we mark corresponding node as TYPE_ALIAS - and store the corresponding type in node.override and in rvalue.analyzed. + For subscripted (including generic) aliases the resulting types are stored + in rvalue.analyzed. """ # Type aliases are created only at module scope and class scope (for subscripted types), # at function scope assignments always create local variables with type object types. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 899dccef253a..fe95a5f14605 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -234,15 +234,14 @@ def visit_unbound_type(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 - any_type = AnyType(from_omitted_generics=True, line=t.line, column=t.column) - return replace_alias_tvars(override, all_vars, [any_type] * exp_len, - t.line, t.column) + return set_any_tvars(override, all_vars, t.line, t.column) if exp_len == 0 and act_len == 0: return override if act_len != exp_len: self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) - return AnyType() + return set_any_tvars(override, all_vars or [], + t.line, t.column, implicit=False) assert all_vars is not None return replace_alias_tvars(override, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): @@ -724,6 +723,12 @@ def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type], return set_typ_args(tp, new_args, newline, newcolumn) +def set_any_tvars(tp: Type, vars: List[str], + newline: int, newcolumn: int, implicit: bool = True) -> Type: + any_type = AnyType(from_omitted_generics=implicit, line=newline, column=newcolumn) + return replace_alias_tvars(tp, vars, [any_type] * len(vars), newline, newcolumn) + + def remove_dups(tvars: Iterable[T]) -> List[T]: # Get unique elements in order of appearance all_tvars = set() # type: Set[T] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 8cbdb7e17059..bb05e0318012 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -604,7 +604,7 @@ F = Node[List[T, T], S] # Error G = Callable[..., List[T, T]] # Error H = Union[int, Tuple[T, Node[T]]] # Error h: H # Error -h1: H[int, str] # Error +h1: H[int, str] # Two errors here, wrong number of args for H, and for Node x = None # type: D[int, str] reveal_type(x) @@ -622,6 +622,7 @@ main:16:18: error: "list" expects 1 type argument, but 2 given main:17:24: error: "Node" expects 2 type arguments, but 1 given main:18:3: error: "Node" expects 2 type arguments, but 1 given main:19:4: error: Bad number of arguments for type alias, expected: 1, given: 2 +main:19:4: error: "Node" expects 2 type arguments, but 1 given main:22:0: error: Revealed type is '__main__.Node[builtins.int, builtins.str]' main:24:0: error: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' main:26:4: error: Type variable "__main__.T" is invalid as target for type alias @@ -908,7 +909,7 @@ Transform = Callable[[T, int], Tuple[T, R]] [case testGenericTypeAliasesImportingWithoutTypeVarError] from a import Alias x: Alias[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 -reveal_type(x) # E: Revealed type is 'Any' +reveal_type(x) # E: Revealed type is 'builtins.list[builtins.list[Any]]' [file a.py] from typing import TypeVar, List