From 5dc0090f01876c20368a2142c5501204d2001cc0 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 27 Oct 2016 23:26:10 +0200 Subject: [PATCH 01/21] Initial crude implementation of GA --- mypy/typeanal.py | 70 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d2f6fbca0cad..99af9217c8d2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -8,7 +8,7 @@ StarType, PartialType, EllipsisType, UninhabitedType, TypeType ) from mypy.nodes import ( - BOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, + BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, IndexExpr, RefExpr ) @@ -79,6 +79,56 @@ def __init__(self, self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func + + def get_unbound_tvar_name(self, t: Type) -> str: + if not isinstance(t, UnboundType): + return None + unbound = t + sym = self.lookup(unbound.name, unbound) + if sym is not None and (sym.kind == UNBOUND_TVAR or sym.kind == BOUND_TVAR): + return unbound.name + return None + + + def unique_vars(self, tvars): + # Get unbound type variables in order of appearance + all_tvars = set(tvars) + new_tvars = [] + for t in tvars: + if t in all_tvars: + new_tvars.append(t) + all_tvars.remove(t) + return new_tvars + + + def get_type_var_names(self, tp: Instance) -> List[str]: + tvars = [] # type: List[str] + if not isinstance(tp, Instance): + return tvars + for arg in tp.args: + tvar = self.get_unbound_tvar_name(arg) + if tvar: + tvars.append(tvar) + elif isinstance(arg, Instance): + subvars = self.get_type_var_names(arg) + if subvars: + tvars.extend(subvars) + return tvars + + + def replace_alias_tvars(self, tp: Instance, vars: List[str], subs: List[Type]) -> None: + if not isinstance(tp, Instance) or not subs: + return AnyType() + new_args = tp.args[:] + for i, arg in enumerate(tp.args): + tvar = self.get_unbound_tvar_name(arg) + if tvar and tvar in vars: + new_args[i] = subs[vars.index(tvar)] + elif isinstance(arg, Instance): + new_args[i] = self.replace_alias_tvars(arg, vars, subs) + return Instance(tp.type, new_args, tp.line) + + def visit_unbound_type(self, t: UnboundType) -> Type: if t.optional: t.optional = False @@ -141,8 +191,22 @@ def visit_unbound_type(self, t: UnboundType) -> Type: item = items[0] return TypeType(item, line=t.line) elif sym.kind == TYPE_ALIAS: - # TODO: Generic type aliases. - return sym.type_override + override = sym.type_override + an_args = self.anal_array(t.args) + found_vars = self.get_type_var_names(override) + all_vars = self.unique_vars(found_vars) + print(all_vars, an_args) + exp_len = len(all_vars) + act_len = len(an_args) + if exp_len > 0 and act_len == 0: + return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len) + 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 self.replace_alias_tvars(override, all_vars, an_args) elif not isinstance(sym.node, TypeInfo): name = sym.fullname if name is None: From f6d862bd70a9ef513d04cb76d8e6aed836ba659c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 28 Oct 2016 09:51:57 +0200 Subject: [PATCH 02/21] Add support for Union, Tuple, Callable --- mypy/semanal.py | 8 +-- mypy/typeanal.py | 128 ++++++++++++++++++++++++++--------------------- 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 2d8f3913a8ff..59afcd8b3351 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1126,7 +1126,8 @@ def visit_block_maybe(self, b: Block) -> None: if b: self.visit_block(b) - def anal_type(self, t: Type, allow_tuple_literal: bool = False) -> Type: + def anal_type(self, t: Type, allow_tuple_literal: bool = False, + aliasing: bool = False) -> Type: if t: if allow_tuple_literal: # Types such as (t1, t2, ...) only allowed in assignment statements. They'll @@ -1143,7 +1144,8 @@ def anal_type(self, t: Type, allow_tuple_literal: bool = False) -> Type: return TupleType(items, self.builtin_type('builtins.tuple'), t.line) a = TypeAnalyser(self.lookup_qualified, self.lookup_fully_qualified, - self.fail) + self.fail, + aliasing=aliasing) return t.accept(a) else: return None @@ -2375,7 +2377,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: except TypeTranslationError: self.fail('Type expected within [...]', expr) return - typearg = self.anal_type(typearg) + typearg = self.anal_type(typearg, aliasing=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 99af9217c8d2..bf74306c16b8 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -39,7 +39,11 @@ def analyze_type_alias(node: Expression, # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). + if isinstance(node, RefExpr): + if node.kind == UNBOUND_TVAR or node.kind == BOUND_TVAR: + fail_func('Invalid type "{}" for aliasing'.format(node.fullname), node) + return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or node.kind == TYPE_ALIAS): @@ -48,7 +52,8 @@ def analyze_type_alias(node: Expression, base = node.base if isinstance(base, RefExpr): if not (isinstance(base.node, TypeInfo) or - base.fullname in type_constructors): + base.fullname in type_constructors or + base.kind == TYPE_ALIAS): return None else: return None @@ -61,7 +66,7 @@ def analyze_type_alias(node: Expression, except TypeTranslationError: fail_func('Invalid type alias', node) return None - analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, fail_func) + analyzer = TypeAnalyser(lookup_func, lookup_fqn_func, fail_func, aliasing=True) return type.accept(analyzer) @@ -74,60 +79,12 @@ class TypeAnalyser(TypeVisitor[Type]): def __init__(self, lookup_func: Callable[[str, Context], SymbolTableNode], lookup_fqn_func: Callable[[str], SymbolTableNode], - fail_func: Callable[[str, Context], None]) -> None: + fail_func: Callable[[str, Context], None], *, + aliasing = False) -> None: self.lookup = lookup_func self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func - - - def get_unbound_tvar_name(self, t: Type) -> str: - if not isinstance(t, UnboundType): - return None - unbound = t - sym = self.lookup(unbound.name, unbound) - if sym is not None and (sym.kind == UNBOUND_TVAR or sym.kind == BOUND_TVAR): - return unbound.name - return None - - - def unique_vars(self, tvars): - # Get unbound type variables in order of appearance - all_tvars = set(tvars) - new_tvars = [] - for t in tvars: - if t in all_tvars: - new_tvars.append(t) - all_tvars.remove(t) - return new_tvars - - - def get_type_var_names(self, tp: Instance) -> List[str]: - tvars = [] # type: List[str] - if not isinstance(tp, Instance): - return tvars - for arg in tp.args: - tvar = self.get_unbound_tvar_name(arg) - if tvar: - tvars.append(tvar) - elif isinstance(arg, Instance): - subvars = self.get_type_var_names(arg) - if subvars: - tvars.extend(subvars) - return tvars - - - def replace_alias_tvars(self, tp: Instance, vars: List[str], subs: List[Type]) -> None: - if not isinstance(tp, Instance) or not subs: - return AnyType() - new_args = tp.args[:] - for i, arg in enumerate(tp.args): - tvar = self.get_unbound_tvar_name(arg) - if tvar and tvar in vars: - new_args[i] = subs[vars.index(tvar)] - elif isinstance(arg, Instance): - new_args[i] = self.replace_alias_tvars(arg, vars, subs) - return Instance(tp.type, new_args, tp.line) - + self.aliasing = aliasing def visit_unbound_type(self, t: UnboundType) -> Type: if t.optional: @@ -193,19 +150,18 @@ def visit_unbound_type(self, t: UnboundType) -> Type: elif sym.kind == TYPE_ALIAS: override = sym.type_override an_args = self.anal_array(t.args) - found_vars = self.get_type_var_names(override) - all_vars = self.unique_vars(found_vars) - print(all_vars, an_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: + # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len) 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 t return self.replace_alias_tvars(override, all_vars, an_args) elif not isinstance(sym.node, TypeInfo): name = sym.fullname @@ -217,7 +173,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type: # as a base class -- however, this will fail soon at runtime so the problem # is pretty minor. return AnyType() - self.fail('Invalid type "{}"'.format(name), t) + # Allow unbount type variables when defining an alias + if not (self.aliasing and sym.kind == UNBOUND_TVAR): + self.fail('Invalid type "{}"'.format(name), t) return t info = sym.node # type: TypeInfo if len(t.args) > 0 and info.fullname() == 'builtins.tuple': @@ -245,6 +203,60 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: return AnyType() + def get_tvar_name(self, t: Type) -> str: + if not isinstance(t, UnboundType): + return None + sym = self.lookup(t.name, t) + if sym is not None and (sym.kind == UNBOUND_TVAR or sym.kind == BOUND_TVAR): + return t.name + return None + + def get_type_var_names(self, tp: Type) -> List[str]: + tvars = [] # type: List[str] + if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): + return tvars + typ_args = (tp.args if isinstance(tp, Instance) else + tp.items if not isinstance(tp, CallableType) else + tp.arg_types + [tp.ret_type]) + for arg in typ_args: + tvar = self.get_tvar_name(arg) + if tvar: + tvars.append(tvar) + else: + subvars = self.get_type_var_names(arg) + if subvars: + tvars.extend(subvars) + # Get unique type variables in order of appearance + all_tvars = set(tvars) + new_tvars = [] + for t in tvars: + if t in all_tvars: + new_tvars.append(t) + all_tvars.remove(t) + return new_tvars + + def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type: + if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)) or not subs: + return tp + typ_args = (tp.args if isinstance(tp, Instance) else + tp.items if not isinstance(tp, CallableType) else + tp.arg_types + [tp.ret_type]) + new_args = typ_args[:] + for i, arg in enumerate(typ_args): + tvar = self.get_tvar_name(arg) + if tvar and tvar in vars: + new_args[i] = subs[vars.index(tvar)] + else: + new_args[i] = self.replace_alias_tvars(arg, vars, subs) + if isinstance(tp, Instance): + return Instance(tp.type, new_args, tp.line) + if isinstance(tp, TupleType): + return tp.copy_modified(items=new_args) + if isinstance(tp, UnionType): + return UnionType.make_union(new_args, tp.line) + if isinstance(tp, CallableType): + return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + def visit_any(self, t: AnyType) -> Type: return t From b0790d40d2e6186afbc1e37e604940f745ba9f5d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 28 Oct 2016 12:42:35 +0200 Subject: [PATCH 03/21] Add runtime behaviour; outline tests --- mypy/checkexpr.py | 38 +++++++++ mypy/semanal.py | 8 +- test-data/unit/check-generics.test | 121 +++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e9282091d789..a38026ce167b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -17,6 +17,7 @@ ConditionalExpr, ComparisonExpr, TempNode, SetComprehension, DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, TypeAliasExpr, BackquoteExpr, ARG_POS, ARG_NAMED, ARG_STAR2, MODULE_REF, + UNBOUND_TVAR, BOUND_TVAR, ) from mypy import nodes import mypy.checker @@ -1375,8 +1376,45 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: return AnyType() def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: + item = alias.type + if isinstance(item, Instance): + item = self.replace_tvars_any(item) + tp = type_object_type(item.type, self.named_type) + else: + return item + if isinstance(tp, CallableType): + return self.apply_generic_arguments(tp, item.args, item) + if isinstance(tp, Overloaded): + return self.apply_generic_arguments2(tp, item.args, item) return AnyType() + def replace_tvars_any(self, tp: Type) -> Type: + if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): + return tp + typ_args = (tp.args if isinstance(tp, Instance) else + tp.items if not isinstance(tp, CallableType) else + tp.arg_types + [tp.ret_type]) + 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 == UNBOUND_TVAR or sym.kind == BOUND_TVAR): + new_args[i] = AnyType() + else: + new_args[i] = self.replace_tvars_any(arg) + if isinstance(tp, Instance): + return Instance(tp.type, new_args, tp.line) + if isinstance(tp, TupleType): + return tp.copy_modified(items=new_args) + if isinstance(tp, UnionType): + return UnionType.make_union(new_args, tp.line) + if isinstance(tp, CallableType): + return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + 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/semanal.py b/mypy/semanal.py index 59afcd8b3351..4987a44dafb2 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2363,7 +2363,13 @@ def visit_unary_expr(self, expr: UnaryExpr) -> None: def visit_index_expr(self, expr: IndexExpr) -> None: expr.base.accept(self) - if refers_to_class_or_function(expr.base): + if isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS: + res = analyze_type_alias(expr, + self.lookup_qualified, + self.lookup_fully_qualified, + self.fail) + expr.analyzed = TypeAliasExpr(res) + elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. types = [] # type: List[Type] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index ec44b64f12a5..f8cbd8fd12be 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -505,6 +505,127 @@ type[int] # this was crashing, see #2302 (comment) # E: Type application target [out] +-- Generic type aliases +-- -------------------- + +[case testGenericTypeAliasesBasic1] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesBasic2] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesBadAliases] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesForAliases] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesAny] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesSubclassing] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesUnion] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesTuple] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesCallable] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesCompex1] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesCompex2] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesImporting] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + +[case testGenericTypeAliasesRuntimeExpressions] +from typing import TypeVar, Generic +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +[out] + + -- Multiple assignment with lists -- ------------------------------ From 7ddd6abd9b4c6a7d5e7d81e521e4a2f4aa24ec9c Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 28 Oct 2016 13:18:48 +0200 Subject: [PATCH 04/21] Formatting + better types --- mypy/checkexpr.py | 2 +- mypy/typeanal.py | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a38026ce167b..834dbf352b76 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1378,7 +1378,7 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: item = alias.type if isinstance(item, Instance): - item = self.replace_tvars_any(item) + item = cast(Instance, self.replace_tvars_any(item)) tp = type_object_type(item.type, self.named_type) else: return item diff --git a/mypy/typeanal.py b/mypy/typeanal.py index bf74306c16b8..5e2e29c2337d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1,6 +1,6 @@ """Semantic analysis of types""" -from typing import Callable, cast, List +from typing import Callable, cast, List, Optional from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, @@ -39,7 +39,6 @@ def analyze_type_alias(node: Expression, # Quickly return None if the expression doesn't look like a type. Note # that we don't support straight string literals as type aliases # (only string literals within index expressions). - if isinstance(node, RefExpr): if node.kind == UNBOUND_TVAR or node.kind == BOUND_TVAR: fail_func('Invalid type "{}" for aliasing'.format(node.fullname), node) @@ -203,14 +202,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: return AnyType() - def get_tvar_name(self, t: Type) -> str: - if not isinstance(t, UnboundType): - return None - sym = self.lookup(t.name, t) - if sym is not None and (sym.kind == UNBOUND_TVAR or sym.kind == BOUND_TVAR): - return t.name - return None - def get_type_var_names(self, tp: Type) -> List[str]: tvars = [] # type: List[str] if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): @@ -235,6 +226,14 @@ def get_type_var_names(self, tp: Type) -> List[str]: all_tvars.remove(t) return new_tvars + 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 == UNBOUND_TVAR or sym.kind == BOUND_TVAR): + return t.name + return None + def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type: if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)) or not subs: return tp From 633e3d1574de0257717ea284c92ab9b324b551e8 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 28 Oct 2016 16:46:05 +0200 Subject: [PATCH 05/21] Add some tests --- test-data/unit/check-generics.test | 194 ++++++++++++++++++++++++++--- test-data/unit/fixtures/list.pyi | 1 + 2 files changed, 178 insertions(+), 17 deletions(-) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index f8cbd8fd12be..96ecc200094b 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -508,25 +508,163 @@ type[int] # this was crashing, see #2302 (comment) # E: Type application target -- Generic type aliases -- -------------------- -[case testGenericTypeAliasesBasic1] +[case testGenericTypeAliasesBasic] from typing import TypeVar, Generic T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: ... +IntNode = Node[int, S] +IntIntNode = Node[int, int] +SameNode = Node[T, T] + +n = Node(1, 1) # type: IntIntNode +n1 = Node(1, 'a') # type: IntIntNode # E: Argument 2 to "Node" has incompatible type "str"; expected "int" + +m = Node(1, 1) # type: IntNode +m1 = Node('x', 1) # type: IntNode # E: Argument 1 to "Node" has incompatible type "str"; expected "int" +m2 = Node(1, 1) # type: IntNode[str] # E: Argument 2 to "Node" has incompatible type "int"; expected "str" + +s = Node(1, 1) # type: SameNode[int] +reveal_type(s) # E: Revealed type is '__main__.Node[builtins.int, builtins.int]' +s1 = Node(1, 'x') # type: SameNode[int] # E: Argument 2 to "Node" has incompatible type "str"; expected "int" + [out] [case testGenericTypeAliasesBasic2] from typing import TypeVar, Generic T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: ... +IntNode = Node[int, S] +IntIntNode = Node[int, int] +SameNode = Node[T, T] + +def output_bad() -> IntNode[str]: + return Node(1, 1) # Eroor - bad return type, see out + +def input(x: IntNode[str]) -> None: + pass +input(Node(1, 's')) +input(Node(1, 1)) # E: Argument 2 to "Node" has incompatible type "int"; expected "str" + +def output() -> IntNode[str]: + return Node(1, 'x') +reveal_type(output()) # E: Revealed type is '__main__.Node[builtins.int, builtins.str]' + +def func(x: IntNode[T]) -> IntNode[T]: + return x +reveal_type(func) # E: Revealed type is 'def [T] (x: __main__.Node[builtins.int, T`-1]) -> __main__.Node[builtins.int, T`-1]' + +func(1) # E: Argument 1 to "func" has incompatible type "int"; expected Node[int, None] +func(Node('x', 1)) # E: Argument 1 to "Node" has incompatible type "str"; expected "int" +reveal_type(func(Node(1, 'x'))) # E: Revealed type is '__main__.Node[builtins.int, builtins.str*]' + +def func2(x: SameNode[T]) -> SameNode[T]: + return x +reveal_type(func2) # E: Revealed type is 'def [T] (x: __main__.Node[T`-1, T`-1]) -> __main__.Node[T`-1, T`-1]' + +func2(Node(1, 'x')) # E: Cannot infer type argument 1 of "func2" +y = func2(Node('x', 'x')) +reveal_type(y) # E: Revealed type is '__main__.Node[builtins.str*, builtins.str*]' + +def wrap(x: T) -> IntNode[T]: + return Node(1, x) + +z = None # type: str +reveal_type(wrap(z)) # E: Revealed type is '__main__.Node[builtins.int, builtins.str*]' + [out] +main: note: In function "output_bad": +main:13: error: Argument 2 to "Node" has incompatible type "int"; expected "str" +main: note: At top level: + +[case testGenericTypeAliasesWrongAliases] +from typing import TypeVar, Generic, List, Callable, Tuple, Union +T = TypeVar('T') +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + ... + +A = Node[T] # E: Type application has too few types (2 expected) +B = Node[T, T] +C = Node[T, T, T] # E: Type application has too many types (2 expected) +D = Node[T, S] +E = Node[Node[T, T], List[T]] + +# Errors for F, G, H are not reported if those aliases left used +F = Node[List[T, T], S] # E: "list" expects 1 type argument, but 2 given +f = None # type: F +G = Callable[..., List[T, T]] # E: "list" expects 1 type argument, but 2 given +g = None # type: G[int] +H = Union[int, Tuple[T, Node[T]]] # E: "Node" expects 2 type arguments, but 1 given +h = None # type: H +h1 = None # type: H[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 + +x = None # type: D[int, str] +reveal_type(x) # E: Revealed type is '__main__.Node[builtins.int, builtins.str]' +y = None # type: E[int] +reveal_type(y) # E: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' + +[builtins fixtures/list.pyi] + +[case testGenericTypeAliasesForAliases] +from typing import TypeVar, Generic, List, Union +T = TypeVar('T') +S = TypeVar('S') + +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + pass + +ListedNode = Node[List[T], List[S]] +Second = ListedNode[int, T] +Third = Union[int, Second[str]] + +def f2(x: T) -> Second[T]: + return Node([1], [x]) +reveal_type(f2('a')) # E: Revealed type is '__main__.Node[builtins.list[builtins.int], builtins.list[builtins.str*]]' -[case testGenericTypeAliasesBadAliases] +def f3() -> Third: + return Node([1], ['x']) +reveal_type(f3()) # E: Revealed type is 'Union[builtins.int, __main__.Node[builtins.list[builtins.int], builtins.list[builtins.str]]]' + +[builtins fixtures/list.pyi] + +[case testGenericTypeAliasesAny] +from typing import TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + self.x = x + self.y = y + +IntNode = Node[int, S] +AnyNode = Node[S, T] + +def output() -> IntNode[str]: + return Node(1, 'x') +x = output() # type: IntNode # This is OK (implicit Any) + +y = None # type: IntNode +y.x = 1 +y.x = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +y.y = 1 # Both are OK (implicit Any) +y.y = 'x' + +z = Node(1, 'x') # type: AnyNode +reveal_type(z) # E: Revealed type is '__main__.Node[Any, Any]' + +[out] + +[case testGenericTypeAliasesAcessingMethods] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -535,7 +673,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesForAliases] +[case testGenericTypeAliasesSubclassing] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -544,7 +682,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesAny] +[case testGenericTypeAliasesSubclassingBad] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -553,7 +691,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesSubclassing] +[case testGenericTypeAliasesUnion] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -562,8 +700,8 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesUnion] -from typing import TypeVar, Generic +[case testGenericTypeAliasesOptional] +from typing import TypeVar, Generic, Optional T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: @@ -589,7 +727,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesCompex1] +[case testGenericTypeAliasesCompex] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -598,7 +736,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesCompex2] +[case testGenericTypeAliasesImporting] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -607,7 +745,7 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesImporting] +[case testGenericTypeAliasesImporting2] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -616,11 +754,33 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesRuntimeExpressions] +[case testGenericTypeAliasesRuntimeExpressionsInstance] from typing import TypeVar, Generic T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + ... + +IntIntNode = Node[int, int] +IntIntNode(1, 1) +IntIntNode(1, 'a') # E: Argument 2 has incompatible type "str"; expected "int" + +SameNode = Node[T, T] +a = SameNode(1, 'x') +reveal_type(a) # E: Revealed type is '__main__.Node[Any, Any]' +b = SameNode[int](1, 1) +reveal_type(b) # E: Revealed type is '__main__.Node[builtins.int*, builtins.int*]' +SameNode[int](1, 'x') # E: Argument 2 to "Node" has incompatible type "str"; expected "int" + +[out] + +[case testGenericTypeAliasesRuntimeExpressionsOther] +from typing import TypeVar, Generic +T = TypeVar('T') +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: ... [out] diff --git a/test-data/unit/fixtures/list.pyi b/test-data/unit/fixtures/list.pyi index 2f9893d727bb..9413cf760513 100644 --- a/test-data/unit/fixtures/list.pyi +++ b/test-data/unit/fixtures/list.pyi @@ -9,6 +9,7 @@ class object: def __init__(self): pass class type: pass +class ellipsis: pass class list(Iterable[T], Generic[T]): @overload From 07baa51396a6ee3e84d505f8a865201674e881f5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 00:47:47 +0200 Subject: [PATCH 06/21] More tests, better error reporting --- mypy/checkexpr.py | 8 +- mypy/nodes.py | 4 +- mypy/semanal.py | 2 +- test-data/unit/check-generics.test | 121 +++++++++++++++++++++-------- 4 files changed, 99 insertions(+), 36 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 834dbf352b76..52692bd65cc9 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1377,11 +1377,15 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: item = alias.type + if isinstance(item, (Instance, TupleType, UnionType, CallableType)): + item = self.replace_tvars_any(item) if isinstance(item, Instance): - item = cast(Instance, self.replace_tvars_any(item)) tp = type_object_type(item.type, self.named_type) else: - return item + if alias.line > 0: + self.chk.fail('Invalid type alias in runtime expression: {}' + .format(item), alias) + return AnyType() if isinstance(tp, CallableType): return self.apply_generic_arguments(tp, item.args, item) if isinstance(tp, Overloaded): diff --git a/mypy/nodes.py b/mypy/nodes.py index e8a6a573087a..edb836dad108 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1732,9 +1732,11 @@ class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" type = None # type: mypy.types.Type + line = None # type: int - def __init__(self, type: 'mypy.types.Type') -> None: + def __init__(self, type: 'mypy.types.Type', line: int = -1) -> None: self.type = type + self.line = line def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) diff --git a/mypy/semanal.py b/mypy/semanal.py index 4987a44dafb2..d76994c6832d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2368,7 +2368,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: self.lookup_qualified, self.lookup_fully_qualified, self.fail) - expr.analyzed = TypeAliasExpr(res) + expr.analyzed = TypeAliasExpr(res, line=expr.line) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 96ecc200094b..75195564d176 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -665,34 +665,76 @@ reveal_type(z) # E: Revealed type is '__main__.Node[Any, Any]' [out] [case testGenericTypeAliasesAcessingMethods] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, List T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: - ... + self.x = x + def meth(self) -> T: + return self.x -[out] +ListedNode = Node[List[T]] +l = None # type: ListedNode[int] +l.x.append(1) +l.meth().append(1) +reveal_type(l.meth()) # E: Revealed type is 'builtins.list*[builtins.int]' +l.meth().append('x') # E: Argument 1 to "append" of "list" has incompatible type "str"; expected "int" + +ListedNode[str]([]).x = 1 # E: Incompatible types in assignment (expression has type "int", variable has type List[str]) + +[builtins fixtures/list.pyi] [case testGenericTypeAliasesSubclassing] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Tuple, List T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: ... +TupledNode = Node[Tuple[T, T]] + +class D(Generic[T], TupledNode[T]): + ... +class L(Generic[T], List[TupledNode[T]]): + ... + +def f_bad(x: T) -> D[T]: + return D(1) # Error, see out + +L[int]().append(Node((1, 1))) +L[int]().append(5) # E: Argument 1 to "append" of "list" has incompatible type "int"; expected Node[Tuple[int, int]] + +x = D((1, 1)) # type: D[int] +y = D(5) # type: D[int] # E: Argument 1 to "D" has incompatible type "int"; expected "Tuple[int, int]" + +def f(x: T) -> D[T]: + return D((x, x)) +reveal_type(f('a')) # E: Revealed type is '__main__.D[builtins.str*]' + +[builtins fixtures/list.pyi] [out] +main: note: In function "f_bad": +main:15: error: Argument 1 to "D" has incompatible type "int"; expected "Tuple[T, T]" +main: note: At top level: [case testGenericTypeAliasesSubclassingBad] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Tuple, Union T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: ... -[out] +TupledNode = Node[Tuple[T, T]] +UNode = Union[int, Node[T]] + +class C(TupledNode): ... # Same as TupledNode[Any] +class D(TupledNode[T]): ... # E: Invalid type "__main__.T" +class E(Generic[T], UNode[T]): ... # E: Invalid base class + +[builtins fixtures/list.pyi] [case testGenericTypeAliasesUnion] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Union T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: @@ -710,7 +752,7 @@ class Node(Generic[T]): [out] [case testGenericTypeAliasesTuple] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Tuple T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: @@ -719,7 +761,7 @@ class Node(Generic[T]): [out] [case testGenericTypeAliasesCallable] -from typing import TypeVar, Generic +from typing import TypeVar, Generic, Callable T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: @@ -727,25 +769,31 @@ class Node(Generic[T]): [out] -[case testGenericTypeAliasesCompex] -from typing import TypeVar, Generic -T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: - ... +[case testGenericTypeAliasesPEPBasedExample] +from typing import TypeVar, List, Tuple +T = TypeVar('T', int, bool) -[out] +Vec = List[Tuple[T, T]] -[case testGenericTypeAliasesImporting] -from typing import TypeVar, Generic -T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: - ... +vec = [] # type: Vec[bool] +vec.append('x') # E: Argument 1 to "append" of "list" has incompatible type "str"; expected "Tuple[bool, bool]" +reveal_type(vec[0]) # E: Revealed type is 'Tuple[builtins.bool, builtins.bool]' -[out] +def fun1(v: Vec[T]) -> T: + return v[0][0] +def fun2(v: Vec[T], scale: T) -> Vec[T]: + return v + +reveal_type(fun1([(1, 1)])) # E: Revealed type is 'builtins.int*' +fun1(1) # E: Argument 1 to "fun1" has incompatible type "int"; expected List[Tuple[int, int]] +fun1([(1, 'x')]) # E: Cannot infer type argument 1 of "fun1" + +reveal_type(fun2([(1, 1)], 1)) # E: Revealed type is 'builtins.list[Tuple[builtins.int*, builtins.int*]]' +fun2([('x', 'x')], 'x') # E: Type argument 1 of "fun2" has incompatible value "str" -[case testGenericTypeAliasesImporting2] +[builtins fixtures/list.pyi] + +[case testGenericTypeAliasesImporting] from typing import TypeVar, Generic T = TypeVar('T') class Node(Generic[T]): @@ -762,9 +810,9 @@ class Node(Generic[T, S]): def __init__(self, x: T, y: S) -> None: ... -IntIntNode = Node[int, int] -IntIntNode(1, 1) -IntIntNode(1, 'a') # E: Argument 2 has incompatible type "str"; expected "int" +IntNode = Node[int, T] +IntNode[int](1, 1) +IntNode[int](1, 'a') # E: Argument 2 to "Node" has incompatible type "str"; expected "int" SameNode = Node[T, T] a = SameNode(1, 'x') @@ -776,12 +824,21 @@ SameNode[int](1, 'x') # E: Argument 2 to "Node" has incompatible type "str"; exp [out] [case testGenericTypeAliasesRuntimeExpressionsOther] -from typing import TypeVar, Generic +from typing import TypeVar, Union, Tuple, Callable, Any T = TypeVar('T') -S = TypeVar('S') -class Node(Generic[T, S]): - def __init__(self, x: T, y: S) -> None: - ... + +CA = Callable[[T], int] +TA = Tuple[T, int] +UA = Union[T, int] + +cs = CA[str]() # E: Invalid type alias in runtime expression: def (builtins.str) -> builtins.int +reveal_type(cs) # E: Revealed type is 'Any' + +ts = TA[str]() # E: Invalid type alias in runtime expression: Tuple[builtins.str, builtins.int] +reveal_type(ts) # E: Revealed type is 'Any' + +us = UA[str]() # E: Invalid type alias in runtime expression: Union[builtins.str, builtins.int] +reveal_type(us) # E: Revealed type is 'Any' [out] From 56b7ae75963caeb45f87e6d44a98e9e76dc6b258 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 01:27:12 +0200 Subject: [PATCH 07/21] Add tests, comments, and doctrings --- mypy/checkexpr.py | 7 +++++++ mypy/typeanal.py | 11 +++++++++++ test-data/unit/check-generics.test | 31 +++++++++++++++++++++++++----- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 52692bd65cc9..fcf30d15a85a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1376,12 +1376,15 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: return AnyType() def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: + """ Get type of a type alias (could be generic) in a runtime expression.""" item = alias.type if isinstance(item, (Instance, TupleType, UnionType, CallableType)): item = self.replace_tvars_any(item) if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) else: + # TODO: Better error reporting, need to find line for + # unsubscribed generic aliases if alias.line > 0: self.chk.fail('Invalid type alias in runtime expression: {}' .format(item), alias) @@ -1393,6 +1396,10 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: return AnyType() def replace_tvars_any(self, tp: Type) -> Type: + """ Replace all unbound type variables with Any if an alias is used in + a runtime expression. Basically, this function finishes what could not be done + in similar funtion from typeanal.py. + """ if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): return tp typ_args = (tp.args if isinstance(tp, Instance) else diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 5e2e29c2337d..5f1ddc27f247 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -158,6 +158,8 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if exp_len == 0 and act_len == 0: return override if act_len != exp_len: + # TODO: Detect wrong type variable numer for unused aliases + # (although it could be difficult at this stage, see comment below) self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) return t @@ -203,6 +205,9 @@ def visit_unbound_type(self, t: UnboundType) -> Type: 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). + """ tvars = [] # type: List[str] if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): return tvars @@ -235,6 +240,9 @@ def get_tvar_name(self, t: Type) -> Optional[str]: return None def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type: + """ Replace type variables in a generic type alias tp with substitutions subs. + Length of subs should be already checked. + """ if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)) or not subs: return tp typ_args = (tp.args if isinstance(tp, Instance) else @@ -244,9 +252,12 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty for i, arg in enumerate(typ_args): tvar = self.get_tvar_name(arg) 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) + # Create a copy with type vars replaced. if isinstance(tp, Instance): return Instance(tp.type, new_args, tp.line) if isinstance(tp, TupleType): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 75195564d176..e7fe60bc8ad5 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -794,13 +794,34 @@ fun2([('x', 'x')], 'x') # E: Type argument 1 of "fun2" has incompatible value "s [builtins fixtures/list.pyi] [case testGenericTypeAliasesImporting] -from typing import TypeVar, Generic +from typing import TypeVar +from a import Node, TupledNode T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: - ... -[out] +n = None # type: TupledNode[int] +n.x = 1 +n.y = (1, 1) +n.y = 'x' # E: Bad type + +def f(x: Node[T, T]) -> TupledNode[T]: + return Node(x.x, (x.x, x.x)) + +f(1) # E: Argument 1 to "f" has incompatible type "int"; expected Node[None, None] +f(Node(1, 'x')) # E: Cannot infer type argument 1 of "f" +reveal_type(Node('x', 'x')) # E: Revealed type is 'a.Node[builtins.str*, builtins.str*]' + +[file a.py] +from typing import TypeVar, Generic, Tuple +T = TypeVar('T') +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + self.x = x + self.y = y + +TupledNode = Node[T, Tuple[T, T]] + +[builtins fixtures/list.pyi] [case testGenericTypeAliasesRuntimeExpressionsInstance] from typing import TypeVar, Generic From cbcb2c0758c5217f92dd28368c241edd72170279 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 12:45:26 +0200 Subject: [PATCH 08/21] Add even more tests, add documentation --- docs/source/kinds_of_types.rst | 55 ++++++++++++++++++++++++++++-- mypy/semanal.py | 2 ++ test-data/unit/check-generics.test | 42 +++++++++++++++++------ test-data/unit/check-optional.test | 25 +++++++++++++- 4 files changed, 109 insertions(+), 15 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 5ecdf77c6dd1..9981e182096d 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -426,9 +426,58 @@ assigning the type to a variable: def f() -> AliasType: ... -A type alias does not create a new type. It's just a shorthand notation -for another type -- it's equivalent to the target type. Type aliases -can be imported from modules like any names. +Type aliases can be generic, in this case they could be used in two variants: +Subscribed aliases are equivalent to original types with substituted type variables, +number of type arguments must match the number of free type variables +in generic type alias. Unsubscribed aliases are treated as original types with free +vaiables replacec with ``Any``. Examples (following `PEP 484 +`_): + +.. code-block:: python + + from typing import TypeVar, Iterable, Tuple + T = TypeVar('T', int, float, complex) + + Vec = Iterable[Tuple[T, T]] + + def inproduct(v: Vec[T]) -> T: + return sum(x*y for x, y in v) + + def dilate(v: Vec[T], scale: T) -> Vec[T]: + return ((x * scale, y * scale) for x, y in v) + + v1: Vec[int] = [] # Same as Iterable[Tuple[int, int]] + v2: Vec = [] # Same as Iterable[Tuple[Any, Any]] + v3: Vec[int, int] = [] # Error: Invalid alias, too many type arguments! + +Type aliases can be imported from modules like any names. Following previous examples: + +.. code-block:: python + + from typing import TypeVar, Generic + from first_example import AliasType + from secon_example import Vec + + T = TypeVar('T') + class NewVec(Generic[T], Vec[T]): + ... + + for i, j in NewVec[int](): + ... + + def fun() -> AliasType: + ... + +.. note:: + + A type alias does not create a new type. It's just a shorthand notation for + another type -- it's equivalent to the target type. For generic type aliases + this means that variance of type variables used for alias definition does not + allpy to aliases. Parameterized generic alias is treated simply as an original + type with corresponding type variables substituted. Accordingly, type checking + happens when a type alias is used. Invalid aliases (like e.g. + ``Callable[..., List[T, T]]``) might not always be flagged by mypy if they are + left unused. .. _newtypes: diff --git a/mypy/semanal.py b/mypy/semanal.py index d76994c6832d..4bc19a011d2a 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2364,6 +2364,8 @@ def visit_unary_expr(self, expr: UnaryExpr) -> None: def visit_index_expr(self, expr: IndexExpr) -> None: expr.base.accept(self) if isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS: + # Special form -- subcribing 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, diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index e7fe60bc8ad5..2074e5a2e0b2 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -734,22 +734,42 @@ class E(Generic[T], UNode[T]): ... # E: Invalid base class [builtins fixtures/list.pyi] [case testGenericTypeAliasesUnion] -from typing import TypeVar, Generic, Union +from typing import TypeVar, Generic, Union, Any T = TypeVar('T') class Node(Generic[T]): def __init__(self, x: T) -> None: - ... + self.x = x -[out] +UNode = Union[int, Node[T]] +x = 1 # type: UNode[int] -[case testGenericTypeAliasesOptional] -from typing import TypeVar, Generic, Optional -T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: - ... +x + 1 # E: Unsupported left operand type for + (some union) +if not isinstance(x, Node): + x + 1 -[out] +if not isinstance(x, int): + x.x = 1 + x.x = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +def f(x: T) -> UNode[T]: + if 1: + return Node(x) + else: + return 1 + +reveal_type(f(1)) # E: Revealed type is 'Union[builtins.int, __main__.Node[builtins.int*]]' + +TNode = Union[T, Node[int]] +s = 1 # type: TNode[str] # E: Incompatible types in assignment (expression has type "int", variable has type "Union[str, Node[int]]") + +if not isinstance(s, str): + s.x = 1 + +z = None # type: TNode # Same as TNode[Any] +z.x +z.foo() # E: Some element of union has no attribute "foo" + +[builtins fixtures/isinstance.pyi] [case testGenericTypeAliasesTuple] from typing import TypeVar, Generic, Tuple @@ -801,7 +821,7 @@ T = TypeVar('T') n = None # type: TupledNode[int] n.x = 1 n.y = (1, 1) -n.y = 'x' # E: Bad type +n.y = 'x' # E: Incompatible types in assignment (expression has type "str", variable has type "Tuple[int, int]") def f(x: Node[T, T]) -> TupledNode[T]: return Node(x.x, (x.x, x.x)) diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index d33c4c8442ee..b0f0503f5306 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -498,7 +498,6 @@ else: reveal_type(x) # E: Revealed type is 'Union[builtins.str, builtins.int, builtins.None]' [builtins fixtures/ops.pyi] - [case testWarnNoReturnWorksWithStrictOptional] # flags: --warn-no-return def f() -> None: @@ -509,3 +508,27 @@ def g() -> int: [out] main: note: In function "g": main:5: note: Missing return statement + +[case testGenericTypeAliasesOptional] +from typing import TypeVar, Generic, Optional +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + self.x = x + +ONode = Optional[Node[T]] +def f(x: T) -> ONode[T]: + if 1 > 0: + return Node(x) + else: + return None + +x = None # type: ONode[int] +x = f(1) +x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" + +x.x = 1 # E: Some element of union has no attribute "x" +if x is not None: + x.x = 1 # OK here + +[builtins fixtures/ops.pyi] From 5c92b125bd47b90b3ff7537a881c6c072edbf4ab Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 13:43:27 +0200 Subject: [PATCH 09/21] Last tests --- docs/source/kinds_of_types.rst | 16 +++++++----- test-data/unit/check-generics.test | 41 ++++++++++++++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 9981e182096d..9df41a70ccd5 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -450,23 +450,27 @@ vaiables replacec with ``Any``. Examples (following `PEP 484 v2: Vec = [] # Same as Iterable[Tuple[Any, Any]] v3: Vec[int, int] = [] # Error: Invalid alias, too many type arguments! -Type aliases can be imported from modules like any names. Following previous examples: +Type aliases can be imported from modules like any names. Aliases can target another +aliases (although building complex chains of aliases is not recommended, this +impedes code readability, thus defeating the purpose of using aliases). +Following previous examples: .. code-block:: python - from typing import TypeVar, Generic + from typing import TypeVar, Generic, Optional from first_example import AliasType - from secon_example import Vec + from second_example import Vec + + def fun() -> AliasType: + ... T = TypeVar('T') class NewVec(Generic[T], Vec[T]): ... - for i, j in NewVec[int](): ... - def fun() -> AliasType: - ... + OIntVec = Optional[Vec[int]] .. note:: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 2074e5a2e0b2..c9ff94bd3cfb 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -772,13 +772,26 @@ z.foo() # E: Some element of union has no attribute "foo" [builtins fixtures/isinstance.pyi] [case testGenericTypeAliasesTuple] -from typing import TypeVar, Generic, Tuple +from typing import TypeVar, Tuple T = TypeVar('T') -class Node(Generic[T]): - def __init__(self, x: T) -> None: - ... -[out] +SameTP = Tuple[T, T] +IntTP = Tuple[int, T] + +def f1(x: T) -> SameTP[T]: + return x, x + +a, b, c = f1(1) # E: Need more than 2 values to unpack (3 expected) +x, y = f1(1) +reveal_type(x) # E: Revealed type is 'builtins.int' + +def f2(x: IntTP[T]) -> IntTP[T]: + return x + +f2((1, 2, 3)) # E: Argument 1 to "f2" has incompatible type "Tuple[int, int, int]"; expected "Tuple[int, None]" +reveal_type(f2((1, 'x'))) # E: Revealed type is 'Tuple[builtins.int, builtins.str*]' + +[builtins fixtures/for.pyi] [case testGenericTypeAliasesCallable] from typing import TypeVar, Generic, Callable @@ -787,6 +800,24 @@ class Node(Generic[T]): def __init__(self, x: T) -> None: ... +BadC = Callable[T] # E: Invalid function type + +C = Callable[..., T] +C2 = Callable[[T, T], Node[T]] + +def make_cb(x: T) -> C[T]: + return lambda *args: x + +reveal_type(make_cb(1)) # E: Revealed type is 'def (*Any, **Any) -> builtins.int*' + +def use_cb(arg: T, cb: C2[T]) -> Node[T]: + return cb(arg, arg) + +use_cb(1, 1) # E: Argument 2 to "use_cb" has incompatible type "int"; expected Callable[[int, int], Node[int]] +my_cb = None # type: C2[int] +use_cb('x', my_cb) # E: Argument 2 to "use_cb" has incompatible type Callable[[int, int], Node[int]]; expected Callable[[str, str], Node[str]] +reveal_type(use_cb(1, my_cb)) # E: Revealed type is '__main__.Node[builtins.int]' + [out] [case testGenericTypeAliasesPEPBasedExample] From 13e1fff39e79e2e7ed8c297a497ab88374253c1d Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 14:32:06 +0200 Subject: [PATCH 10/21] Corrections due to upstream refactoring --- mypy/checkexpr.py | 18 ++++++++++++++---- mypy/typeanal.py | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fcf30d15a85a..942a58573f9a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1383,16 +1383,26 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) else: - # TODO: Better error reporting, need to find line for - # unsubscribed generic aliases + # TODO: Better error reporting: need to find line for + # unsubscribed generic aliases, that are invalid at runtime. if alias.line > 0: self.chk.fail('Invalid type alias in runtime expression: {}' .format(item), alias) return AnyType() 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() return self.apply_generic_arguments(tp, item.args, item) - if isinstance(tp, Overloaded): - return self.apply_generic_arguments2(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() + return Overloaded([self.apply_generic_arguments(it, item.args, item) + for it in tp.items()]) return AnyType() def replace_tvars_any(self, tp: Type) -> Type: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 5f1ddc27f247..36156db41c4e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -159,7 +159,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return override if act_len != exp_len: # TODO: Detect wrong type variable numer for unused aliases - # (although it could be difficult at this stage, see comment below) + # (it is difficult at this stage, see comment below, line 187 atm) self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) return t From ee1c25cf454f0d51182d2c0846a1a5692adcc924 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 29 Oct 2016 16:25:03 +0200 Subject: [PATCH 11/21] Add few more examples to docs --- docs/source/kinds_of_types.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 9df41a70ccd5..4d4bf5593156 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -435,9 +435,12 @@ vaiables replacec with ``Any``. Examples (following `PEP 484 .. code-block:: python - from typing import TypeVar, Iterable, Tuple + from typing import TypeVar, Iterable, Tuple, Union, Callable T = TypeVar('T', int, float, complex) + TInt = Tuple[T, int] + UInt = Union[T, int] + CBack = Callable[..., T] Vec = Iterable[Tuple[T, T]] def inproduct(v: Vec[T]) -> T: From 2edcf484361049b65d9409efa077c2147f186e56 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 31 Oct 2016 00:07:56 +0100 Subject: [PATCH 12/21] Do not substitute type variables if they are in runtime expression, report same error as without alias --- mypy/checkexpr.py | 3 ++- mypy/nodes.py | 5 ++++- mypy/semanal.py | 2 +- test-data/unit/check-generics.test | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 942a58573f9a..db9264bfe62e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1378,7 +1378,8 @@ 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.""" item = alias.type - if isinstance(item, (Instance, TupleType, UnionType, CallableType)): + if (isinstance(item, (Instance, TupleType, UnionType, CallableType)) + and not alias.runtime): item = self.replace_tvars_any(item) if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) diff --git a/mypy/nodes.py b/mypy/nodes.py index edb836dad108..49f02c051a17 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1733,10 +1733,13 @@ class TypeAliasExpr(Expression): type = None # type: mypy.types.Type line = None # type: int + runtime = False # type: bool - def __init__(self, type: 'mypy.types.Type', line: int = -1) -> None: + def __init__(self, type: 'mypy.types.Type', line: int = -1, + runtime: bool = False) -> None: self.type = type self.line = line + self.runtime = runtime def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) diff --git a/mypy/semanal.py b/mypy/semanal.py index 4bc19a011d2a..f9b5ad9d02ef 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2370,7 +2370,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: self.lookup_qualified, self.lookup_fully_qualified, self.fail) - expr.analyzed = TypeAliasExpr(res, line=expr.line) + expr.analyzed = TypeAliasExpr(res, line=expr.line, runtime=True) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index c9ff94bd3cfb..dfd1d94d8568 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -887,6 +887,7 @@ IntNode[int](1, 1) IntNode[int](1, 'a') # E: Argument 2 to "Node" has incompatible type "str"; expected "int" SameNode = Node[T, T] +ff = SameNode[T](1, 1) # E: Need type annotation for variable a = SameNode(1, 'x') reveal_type(a) # E: Revealed type is '__main__.Node[Any, Any]' b = SameNode[int](1, 1) From 64fc96ee7c7a57dae8047557d6a3f62ae9896529 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 1 Nov 2016 18:41:41 +0100 Subject: [PATCH 13/21] First part of response to comments (bigger things) --- mypy/checkexpr.py | 29 +++++++---------------------- mypy/nodes.py | 6 +++--- mypy/semanal.py | 20 ++++++++++++++++++-- mypy/typeanal.py | 24 ++++-------------------- mypy/types.py | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index db9264bfe62e..35e4e5ccd17e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -6,7 +6,9 @@ Type, AnyType, CallableType, Overloaded, NoneTyp, Void, TypeVarDef, TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, - true_only, false_only, is_named_instance, function_type + true_only, false_only, is_named_instance, function_type, + get_typ_args, set_typ_args + ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -1378,18 +1380,12 @@ 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.""" item = alias.type - if (isinstance(item, (Instance, TupleType, UnionType, CallableType)) - and not alias.runtime): + if not alias.runtime: item = self.replace_tvars_any(item) if isinstance(item, Instance): tp = type_object_type(item.type, self.named_type) else: - # TODO: Better error reporting: need to find line for - # unsubscribed generic aliases, that are invalid at runtime. - if alias.line > 0: - self.chk.fail('Invalid type alias in runtime expression: {}' - .format(item), alias) - return AnyType() + return alias.fback if isinstance(tp, CallableType): if len(tp.variables) != len(item.args): self.msg.incompatible_type_application(len(tp.variables), @@ -1411,11 +1407,7 @@ def replace_tvars_any(self, tp: Type) -> Type: a runtime expression. Basically, this function finishes what could not be done in similar funtion from typeanal.py. """ - if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): - return tp - typ_args = (tp.args if isinstance(tp, Instance) else - tp.items if not isinstance(tp, CallableType) else - tp.arg_types + [tp.ret_type]) + typ_args = get_typ_args(tp) new_args = typ_args[:] for i, arg in enumerate(typ_args): if isinstance(arg, UnboundType): @@ -1428,14 +1420,7 @@ def replace_tvars_any(self, tp: Type) -> Type: new_args[i] = AnyType() else: new_args[i] = self.replace_tvars_any(arg) - if isinstance(tp, Instance): - return Instance(tp.type, new_args, tp.line) - if isinstance(tp, TupleType): - return tp.copy_modified(items=new_args) - if isinstance(tp, UnionType): - return UnionType.make_union(new_args, tp.line) - if isinstance(tp, CallableType): - return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + return set_typ_args(tp, new_args) def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" diff --git a/mypy/nodes.py b/mypy/nodes.py index 49f02c051a17..c71c3235b607 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1732,13 +1732,13 @@ class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" type = None # type: mypy.types.Type - line = None # type: int + fback = None # type: mypy.types.Type runtime = False # type: bool - def __init__(self, type: 'mypy.types.Type', line: int = -1, + def __init__(self, type: 'mypy.types.Type', fback: 'mypy.types.Type' = None, runtime: bool = False) -> None: self.type = type - self.line = line + self.fback = fback self.runtime = runtime def accept(self, visitor: NodeVisitor[T]) -> T: diff --git a/mypy/semanal.py b/mypy/semanal.py index f9b5ad9d02ef..9b62ebe46a0d 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1175,7 +1175,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: node.kind = TYPE_ALIAS node.type_override = res if isinstance(s.rvalue, IndexExpr): - s.rvalue.analyzed = TypeAliasExpr(res) + s.rvalue.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res)) if s.type: # Store type into nodes. for lvalue in s.lvalues: @@ -1213,6 +1213,20 @@ 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). + """ + + 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{}'.format(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 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. @@ -2370,7 +2384,7 @@ def visit_index_expr(self, expr: IndexExpr) -> None: self.lookup_qualified, self.lookup_fully_qualified, self.fail) - expr.analyzed = TypeAliasExpr(res, line=expr.line, runtime=True) + expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), runtime=True) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. @@ -3061,6 +3075,8 @@ def visit_decorator(self, dec: Decorator) -> None: def visit_assignment_stmt(self, s: AssignmentStmt) -> None: self.analyze(s.type) + if isinstance(s.rvalue, IndexExpr) and isinstance(s.rvalue.analyzed, TypeAliasExpr): + self.analyze(s.rvalue.analyzed.type) super().visit_assignment_stmt(s) def visit_cast_expr(self, e: CastExpr) -> None: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 36156db41c4e..b3a70839a2c5 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, - StarType, PartialType, EllipsisType, UninhabitedType, TypeType + StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args ) from mypy.nodes import ( BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -209,11 +209,7 @@ def get_type_var_names(self, tp: Type) -> List[str]: in order of textual appearance (recursively, if needed). """ tvars = [] # type: List[str] - if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): - return tvars - typ_args = (tp.args if isinstance(tp, Instance) else - tp.items if not isinstance(tp, CallableType) else - tp.arg_types + [tp.ret_type]) + typ_args = get_typ_args(tp) for arg in typ_args: tvar = self.get_tvar_name(arg) if tvar: @@ -243,11 +239,7 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty """ Replace type variables in a generic type alias tp with substitutions subs. Length of subs should be already checked. """ - if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)) or not subs: - return tp - typ_args = (tp.args if isinstance(tp, Instance) else - tp.items if not isinstance(tp, CallableType) else - tp.arg_types + [tp.ret_type]) + typ_args = get_typ_args(tp) new_args = typ_args[:] for i, arg in enumerate(typ_args): tvar = self.get_tvar_name(arg) @@ -257,15 +249,7 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty else: # ...recursively, if needed. new_args[i] = self.replace_alias_tvars(arg, vars, subs) - # Create a copy with type vars replaced. - if isinstance(tp, Instance): - return Instance(tp.type, new_args, tp.line) - if isinstance(tp, TupleType): - return tp.copy_modified(items=new_args) - if isinstance(tp, UnionType): - return UnionType.make_union(new_args, tp.line) - if isinstance(tp, CallableType): - return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + return set_typ_args(tp, new_args) def visit_any(self, t: AnyType) -> Type: return t diff --git a/mypy/types.py b/mypy/types.py index 9c80b590cd38..c6377ce656d6 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1524,3 +1524,22 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike name, implicit=True, ) + +def get_typ_args(tp: Type) -> List[Type]: + if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): + return [] + typ_args = (tp.args if isinstance(tp, Instance) else + tp.items if not isinstance(tp, CallableType) else + tp.arg_types + [tp.ret_type]) + return typ_args + +def set_typ_args(tp: Type, args: List[Type]) -> Type: + if isinstance(tp, Instance): + return Instance(tp.type, new_args, tp.line) + if isinstance(tp, TupleType): + return tp.copy_modified(items=new_args) + if isinstance(tp, UnionType): + return UnionType.make_union(new_args, tp.line) + if isinstance(tp, CallableType): + return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + return tp From 8c6411104f4a39246114167b8f90e46c2cd36713 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 1 Nov 2016 20:57:00 +0100 Subject: [PATCH 14/21] Update tests; some formatting --- mypy/types.py | 4 +++- test-data/unit/check-generics.test | 15 +++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mypy/types.py b/mypy/types.py index c6377ce656d6..eeb74badf980 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1525,6 +1525,7 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike implicit=True, ) + def get_typ_args(tp: Type) -> List[Type]: if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): return [] @@ -1533,7 +1534,8 @@ def get_typ_args(tp: Type) -> List[Type]: tp.arg_types + [tp.ret_type]) return typ_args -def set_typ_args(tp: Type, args: List[Type]) -> Type: + +def set_typ_args(tp: Type, new_args: List[Type]) -> Type: if isinstance(tp, Instance): return Instance(tp.type, new_args, tp.line) if isinstance(tp, TupleType): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index dfd1d94d8568..a9d86ebc0fd8 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -592,17 +592,14 @@ class Node(Generic[T, S]): def __init__(self, x: T, y: S) -> None: ... -A = Node[T] # E: Type application has too few types (2 expected) +A = Node[T] # E: "Node" expects 2 type arguments, but 1 given B = Node[T, T] -C = Node[T, T, T] # E: Type application has too many types (2 expected) +C = Node[T, T, T] # E: "Node" expects 2 type arguments, but 3 given D = Node[T, S] E = Node[Node[T, T], List[T]] -# Errors for F, G, H are not reported if those aliases left used F = Node[List[T, T], S] # E: "list" expects 1 type argument, but 2 given -f = None # type: F G = Callable[..., List[T, T]] # E: "list" expects 1 type argument, but 2 given -g = None # type: G[int] H = Union[int, Tuple[T, Node[T]]] # E: "Node" expects 2 type arguments, but 1 given h = None # type: H h1 = None # type: H[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 @@ -612,6 +609,8 @@ reveal_type(x) # E: Revealed type is '__main__.Node[builtins.int, builtins.str]' y = None # type: E[int] reveal_type(y) # E: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' +X = T # E: Invalid type "__main__.T" for aliasing + [builtins fixtures/list.pyi] [case testGenericTypeAliasesForAliases] @@ -904,13 +903,13 @@ CA = Callable[[T], int] TA = Tuple[T, int] UA = Union[T, int] -cs = CA[str]() # E: Invalid type alias in runtime expression: def (builtins.str) -> builtins.int +cs = CA[str] + 1 # E: Unsupported left operand type for + ("Type alias to Callable") reveal_type(cs) # E: Revealed type is 'Any' -ts = TA[str]() # E: Invalid type alias in runtime expression: Tuple[builtins.str, builtins.int] +ts = TA[str]() # E: "Type alias to Tuple" not callable reveal_type(ts) # E: Revealed type is 'Any' -us = UA[str]() # E: Invalid type alias in runtime expression: Union[builtins.str, builtins.int] +us = UA[str].x # E: "Type alias to Union" has no attribute "x" reveal_type(us) # E: Revealed type is 'Any' [out] From c1062040a15bc0741ed715173a09efb8cf2ad4b2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 1 Nov 2016 22:41:05 +0100 Subject: [PATCH 15/21] Second part of response to comments: minor things (but many); Two more tests --- docs/source/kinds_of_types.rst | 21 +++++++++------- mypy/checkexpr.py | 15 +++++++---- mypy/nodes.py | 8 +++--- mypy/semanal.py | 5 ++-- mypy/typeanal.py | 10 +++----- mypy/types.py | 6 +++-- test-data/unit/check-generics.test | 40 +++++++++++++++++++++++++++++- 7 files changed, 76 insertions(+), 29 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 4d4bf5593156..a60e802a48ec 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -427,10 +427,10 @@ assigning the type to a variable: ... Type aliases can be generic, in this case they could be used in two variants: -Subscribed aliases are equivalent to original types with substituted type variables, +Subscripted aliases are equivalent to original types with substituted type variables, number of type arguments must match the number of free type variables in generic type alias. Unsubscribed aliases are treated as original types with free -vaiables replacec with ``Any``. Examples (following `PEP 484 +variables replaced with ``Any``. Examples (following `PEP 484 `_): .. code-block:: python @@ -438,11 +438,17 @@ vaiables replacec with ``Any``. Examples (following `PEP 484 from typing import TypeVar, Iterable, Tuple, Union, Callable T = TypeVar('T', int, float, complex) - TInt = Tuple[T, int] + TInt = Tuple[int, T] UInt = Union[T, int] CBack = Callable[..., T] Vec = Iterable[Tuple[T, T]] + def response(query: str) -> UInt[str]: # Same as Union[str, int] + ... + def activate(cb: CBack[T]) -> T: # Same as Callable[..., T] + ... + table_entry: TInt # Same as Tuple[int, Any] + def inproduct(v: Vec[T]) -> T: return sum(x*y for x, y in v) @@ -479,12 +485,9 @@ Following previous examples: A type alias does not create a new type. It's just a shorthand notation for another type -- it's equivalent to the target type. For generic type aliases - this means that variance of type variables used for alias definition does not - allpy to aliases. Parameterized generic alias is treated simply as an original - type with corresponding type variables substituted. Accordingly, type checking - happens when a type alias is used. Invalid aliases (like e.g. - ``Callable[..., List[T, T]]``) might not always be flagged by mypy if they are - left unused. + this means that variance or constraints of type variables used for alias + definition don't apply to aliases. Parameterized generic alias is treated + simply as an original type with corresponding type variables substituted. .. _newtypes: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 35e4e5ccd17e..128299c626bc 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1378,13 +1378,18 @@ def visit_type_application(self, tapp: TypeApplication) -> Type: return AnyType() def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: - """ Get type of a type alias (could be generic) in a runtime expression.""" + """Get type of a type alias (could be generic) in a runtime expression.""" item = alias.type - if not alias.runtime: + 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) 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.fback if isinstance(tp, CallableType): if len(tp.variables) != len(item.args): @@ -1403,9 +1408,9 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type: return AnyType() def replace_tvars_any(self, tp: Type) -> Type: - """ Replace all unbound type variables with Any if an alias is used in - a runtime expression. Basically, this function finishes what could not be done - in similar funtion from typeanal.py. + """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[:] diff --git a/mypy/nodes.py b/mypy/nodes.py index c71c3235b607..fd9aeb2471f5 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1733,13 +1733,15 @@ class TypeAliasExpr(Expression): type = None # type: mypy.types.Type fback = None # type: mypy.types.Type - runtime = False # type: bool + # 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', fback: 'mypy.types.Type' = None, - runtime: bool = False) -> None: + in_runtime: bool = False) -> None: self.type = type self.fback = fback - self.runtime = runtime + self.in_runtime = in_runtime def accept(self, visitor: NodeVisitor[T]) -> T: return visitor.visit_type_alias_expr(self) diff --git a/mypy/semanal.py b/mypy/semanal.py index 9b62ebe46a0d..7b0bd3a23db3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1217,7 +1217,6 @@ 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). """ - kind = (' to Callable' if isinstance(tp, CallableType) else ' to Tuple' if isinstance(tp, TupleType) else ' to Union' if isinstance(tp, UnionType) else '') @@ -2378,13 +2377,13 @@ def visit_unary_expr(self, expr: UnaryExpr) -> None: def visit_index_expr(self, expr: IndexExpr) -> None: expr.base.accept(self) if isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS: - # Special form -- subcribing a generic 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.fail) - expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), runtime=True) + expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), in_runtime=True) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index b3a70839a2c5..cf38ed91471d 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -79,7 +79,7 @@ def __init__(self, lookup_func: Callable[[str, Context], SymbolTableNode], lookup_fqn_func: Callable[[str], SymbolTableNode], fail_func: Callable[[str, Context], None], *, - aliasing = False) -> None: + aliasing: bool = False) -> None: self.lookup = lookup_func self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func @@ -158,8 +158,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if exp_len == 0 and act_len == 0: return override if act_len != exp_len: - # TODO: Detect wrong type variable numer for unused aliases - # (it is difficult at this stage, see comment below, line 187 atm) self.fail('Bad number of arguments for type alias, expected: %s, given: %s' % (exp_len, act_len), t) return t @@ -174,7 +172,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: # as a base class -- however, this will fail soon at runtime so the problem # is pretty minor. return AnyType() - # Allow unbount type variables when defining an alias + # Allow unbound type variables when defining an alias if not (self.aliasing and sym.kind == UNBOUND_TVAR): self.fail('Invalid type "{}"'.format(name), t) return t @@ -205,7 +203,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: 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 + """Get all type variable names that are present in a generic type alias in order of textual appearance (recursively, if needed). """ tvars = [] # type: List[str] @@ -236,7 +234,7 @@ def get_tvar_name(self, t: Type) -> Optional[str]: return None def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type: - """ Replace type variables in a generic type alias tp with substitutions subs. + """Replace type variables in a generic type alias tp with substitutions subs. Length of subs should be already checked. """ typ_args = get_typ_args(tp) diff --git a/mypy/types.py b/mypy/types.py index eeb74badf980..5e9f4fa91cae 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1527,6 +1527,7 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike def get_typ_args(tp: Type) -> List[Type]: + """Get all type arguments from a parameterizable Type.""" if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)): return [] typ_args = (tp.args if isinstance(tp, Instance) else @@ -1536,12 +1537,13 @@ def get_typ_args(tp: Type) -> List[Type]: def set_typ_args(tp: Type, new_args: List[Type]) -> Type: + """Return a copy of a parameterizable Type with arguments set to new_args.""" if isinstance(tp, Instance): - return Instance(tp.type, new_args, tp.line) + return Instance(tp.type, new_args, tp.line, tp.column) if isinstance(tp, TupleType): return tp.copy_modified(items=new_args) if isinstance(tp, UnionType): - return UnionType.make_union(new_args, tp.line) + return UnionType.make_simplified_union(new_args, tp.line, tp.column) if isinstance(tp, CallableType): return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) return tp diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index a9d86ebc0fd8..eef01e17873e 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -766,7 +766,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # E: Some element of union has no attribute "foo" +z.foo() # Any simplyfies Union to Any [builtins fixtures/isinstance.pyi] @@ -914,6 +914,44 @@ reveal_type(us) # E: Revealed type is 'Any' [out] +[case testGenericTypeAliasesTypeVarBinding] +from typing import TypeVar, Generic, List +T = TypeVar('T') +S = TypeVar('S') + +class A(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: ... + +class B(Generic[T, S]): + def __init__(self, x: List[T], y: List[S]) -> None: ... + +SameA = A[T, T] +SameB = B[T, T] + +class C(Generic[T]): + a = None # type: SameA[T] + b = SameB[T]([], []) + +reveal_type(C[int]().a) # E: Revealed type is '__main__.A[builtins.int*, builtins.int*]' +reveal_type(C[str]().b) # E: Revealed type is '__main__.B[builtins.str*, builtins.str*]' + +[builtins fixtures/list.pyi] + +[case testGenericTypeAliasesTypeVarConstraints] +from typing import TypeVar, Generic +T = TypeVar('T', int, list) +S = TypeVar('S', int, list) + +class A(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: ... + +BadA = A[str, T] # E: Bad ... +SameA = A[T, T] + +x = None # type: SameA[int] +y = None # type: SameA[str] # E: Bad ... + +[builtins fixtures/list.pyi] -- Multiple assignment with lists -- ------------------------------ From feb94133351ce819e9eb2055b1fae29fcaf02fe5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 2 Nov 2016 00:31:08 +0100 Subject: [PATCH 16/21] Fix last bits --- docs/source/kinds_of_types.rst | 6 +++--- mypy/typeanal.py | 16 +++++++++------- mypy/types.py | 11 +++++++---- test-data/unit/check-generics.test | 10 +++++++--- 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index a60e802a48ec..739820224645 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -485,9 +485,9 @@ Following previous examples: A type alias does not create a new type. It's just a shorthand notation for another type -- it's equivalent to the target type. For generic type aliases - this means that variance or constraints of type variables used for alias - definition don't apply to aliases. Parameterized generic alias is treated - simply as an original type with corresponding type variables substituted. + this means that variance of type variables used for alias definition does not + apply to aliases. Parameterized generic alias is treated simply as an original + type with corresponding type variables substituted. .. _newtypes: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index cf38ed91471d..ae9a7bc4ecc4 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -154,14 +154,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) + return self.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) + return self.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: @@ -233,9 +234,10 @@ def get_tvar_name(self, t: Type) -> Optional[str]: return t.name return None - def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type: - """Replace type variables in a generic type alias tp with substitutions subs. - Length of subs should be already checked. + 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[:] @@ -246,8 +248,8 @@ def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Ty new_args[i] = subs[vars.index(tvar)] else: # ...recursively, if needed. - new_args[i] = self.replace_alias_tvars(arg, vars, subs) - return set_typ_args(tp, new_args) + 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 diff --git a/mypy/types.py b/mypy/types.py index 5e9f4fa91cae..171ab50388b3 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1536,14 +1536,17 @@ def get_typ_args(tp: Type) -> List[Type]: return typ_args -def set_typ_args(tp: Type, new_args: List[Type]) -> Type: +def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> Type: """Return a copy of a parameterizable Type with arguments set to new_args.""" + line = line if line > 0 else tp.line + column = column if column > 0 else tp.column if isinstance(tp, Instance): - return Instance(tp.type, new_args, tp.line, tp.column) + return Instance(tp.type, new_args, line, column) if isinstance(tp, TupleType): return tp.copy_modified(items=new_args) if isinstance(tp, UnionType): - return UnionType.make_simplified_union(new_args, tp.line, tp.column) + return UnionType.make_simplified_union(new_args, line, column) if isinstance(tp, CallableType): - return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1]) + return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1], + line=line, column=column) return tp diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index eef01e17873e..1403e6b15afb 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -601,7 +601,7 @@ E = Node[Node[T, T], List[T]] F = Node[List[T, T], S] # E: "list" expects 1 type argument, but 2 given G = Callable[..., List[T, T]] # E: "list" expects 1 type argument, but 2 given H = Union[int, Tuple[T, Node[T]]] # E: "Node" expects 2 type arguments, but 1 given -h = None # type: H +h = None # type: H # E: "Node" expects 2 type arguments, but 1 given h1 = None # type: H[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 x = None # type: D[int, str] @@ -945,13 +945,17 @@ S = TypeVar('S', int, list) class A(Generic[T, S]): def __init__(self, x: T, y: S) -> None: ... -BadA = A[str, T] # E: Bad ... +BadA = A[str, T] # This error is reported twice (but it actually looks useful) SameA = A[T, T] x = None # type: SameA[int] -y = None # type: SameA[str] # E: Bad ... +y = None # type: SameA[str] # E: Invalid type argument value for "A" [builtins fixtures/list.pyi] +[out] +main:8: error: Invalid type argument value for "A" +main:8: error: Type argument 1 of "A" has incompatible value "str" + -- Multiple assignment with lists -- ------------------------------ From d402a683ef19a012bba4a0c8777e7e81d46aa289 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 2 Nov 2016 00:53:30 +0100 Subject: [PATCH 17/21] Minor correction to docs --- docs/source/kinds_of_types.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 739820224645..eee6c6b9c806 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -429,19 +429,17 @@ assigning the type to a variable: Type aliases can be generic, in this case they could be used in two variants: Subscripted aliases are equivalent to original types with substituted type variables, number of type arguments must match the number of free type variables -in generic type alias. Unsubscribed aliases are treated as original types with free +in generic type alias. Unsubscripted aliases are treated as original types with free variables replaced with ``Any``. Examples (following `PEP 484 `_): .. code-block:: python from typing import TypeVar, Iterable, Tuple, Union, Callable - T = TypeVar('T', int, float, complex) - - TInt = Tuple[int, T] - UInt = Union[T, int] - CBack = Callable[..., T] - Vec = Iterable[Tuple[T, T]] + S = TypeVar('S') + TInt = Tuple[int, S] + UInt = Union[S, int] + CBack = Callable[..., S] def response(query: str) -> UInt[str]: # Same as Union[str, int] ... @@ -449,6 +447,9 @@ variables replaced with ``Any``. Examples (following `PEP 484 ... table_entry: TInt # Same as Tuple[int, Any] + T = TypeVar('T', int, float, complex) + Vec = Iterable[Tuple[T, T]] + def inproduct(v: Vec[T]) -> T: return sum(x*y for x, y in v) From 8cea1560d36cb47fce19bb2146f690a7775b5b14 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 2 Nov 2016 01:04:23 +0100 Subject: [PATCH 18/21] Yet another tiny correction to docs --- docs/source/kinds_of_types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index eee6c6b9c806..1bba8647e3aa 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -443,7 +443,7 @@ variables replaced with ``Any``. Examples (following `PEP 484 def response(query: str) -> UInt[str]: # Same as Union[str, int] ... - def activate(cb: CBack[T]) -> T: # Same as Callable[..., T] + def activate(cb: CBack[S]) -> S: # Same as Callable[..., S] ... table_entry: TInt # Same as Tuple[int, Any] From 382c53ef6accfe1572d988b51e4af17fe710a3ec Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 3 Nov 2016 01:44:41 +0100 Subject: [PATCH 19/21] Improving error messages (+column numbers); main part --- mypy/exprtotype.py | 6 ++-- mypy/fastparse.py | 1 + mypy/typeanal.py | 17 ++++++---- mypy/types.py | 2 +- test-data/unit/check-generics.test | 42 ++++++++++++++++-------- test-data/unit/check-newtype.test | 2 +- test-data/unit/check-typevar-values.test | 6 ++-- 7 files changed, 48 insertions(+), 28 deletions(-) diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index 764c716b1f96..293454ebe0ab 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -20,11 +20,11 @@ def expr_to_unanalyzed_type(expr: Expression) -> Type: """ if isinstance(expr, NameExpr): name = expr.name - return UnboundType(name, line=expr.line) + return UnboundType(name, line=expr.line, column=expr.column) elif isinstance(expr, MemberExpr): fullname = get_member_expr_fullname(expr) if fullname: - return UnboundType(fullname, line=expr.line) + return UnboundType(fullname, line=expr.line, column=expr.column) else: raise TypeTranslationError() elif isinstance(expr, IndexExpr): @@ -42,7 +42,7 @@ def expr_to_unanalyzed_type(expr: Expression) -> Type: raise TypeTranslationError() elif isinstance(expr, ListExpr): return TypeList([expr_to_unanalyzed_type(t) for t in expr.items], - line=expr.line) + line=expr.line, column=expr.column) elif isinstance(expr, (StrExpr, BytesExpr)): # Parse string literal type. try: diff --git a/mypy/fastparse.py b/mypy/fastparse.py index f68d1b0279d0..fdb748162147 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -432,6 +432,7 @@ def visit_Assign(self, n: ast35.Assign) -> AssignmentStmt: typ = parse_type_comment(n.type_comment, n.lineno) elif new_syntax: typ = TypeConverter(line=n.lineno).visit(n.annotation) # type: ignore + typ.column = n.annotation.col_offset if n.value is None: # always allow 'x: int' rvalue = TempNode(AnyType()) # type: Expression else: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index ae9a7bc4ecc4..a338fb443de6 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -188,7 +188,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: # checked only later, since we do not always know the # 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) + instance = Instance(info, self.anal_array(t.args), t.line, t.column) tup = info.tuple_type if tup is None: return instance @@ -417,7 +417,7 @@ def visit_instance(self, t: Instance) -> None: t.args = [AnyType() for _ in info.type_vars] elif info.defn.type_vars: # Check type argument values. - for arg, TypeVar in zip(t.args, info.defn.type_vars): + for (i, arg), TypeVar in zip(enumerate(t.args), info.defn.type_vars): if TypeVar.values: if isinstance(arg, TypeVarType): arg_values = arg.values @@ -429,7 +429,7 @@ def visit_instance(self, t: Instance) -> None: else: arg_values = [arg] self.check_type_var_values(info, arg_values, - TypeVar.values, t) + TypeVar.values, i, t) if not satisfies_upper_bound(arg, TypeVar.upper_bound): self.fail('Type argument "{}" of "{}" must be ' 'a subtype of "{}"'.format( @@ -438,12 +438,17 @@ def visit_instance(self, t: Instance) -> None: arg.accept(self) def check_type_var_values(self, type: TypeInfo, actuals: List[Type], - valids: List[Type], context: Context) -> None: + valids: List[Type], arg_number: int, context: Context) -> None: for actual in actuals: if (not isinstance(actual, AnyType) and not any(is_same_type(actual, value) for value in valids)): - self.fail('Invalid type argument value for "{}"'.format( - type.name()), context) + if len(actuals) > 1 or not isinstance(actual, Instance): + self.fail('Invalid type argument value for "{}"'.format( + type.name()), context) + else: + # print(context.column) + self.fail('Type argument {} of "{}" has incompatible value "{}"'.format( + arg_number + 1, type.name(), actual.type.name()), context) def visit_callable_type(self, t: CallableType) -> None: t.ret_type.accept(self) diff --git a/mypy/types.py b/mypy/types.py index 171ab50388b3..80cd7a235e37 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -880,7 +880,7 @@ def make_simplified_union(items: List[Type], line: int = -1, column: int = -1) - items[i] = true_or_false(ti) simplified_set = [items[i] for i in range(len(items)) if i not in removed] - return UnionType.make_union(simplified_set) + return UnionType.make_union(simplified_set, line, column) def length(self) -> int: return len(self.items) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 1403e6b15afb..fc86c89063be 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -585,6 +585,7 @@ main:13: error: Argument 2 to "Node" has incompatible type "int"; expected "str" main: note: At top level: [case testGenericTypeAliasesWrongAliases] +# flags: --show-column-numbers --fast-parser --python-version 3.6 from typing import TypeVar, Generic, List, Callable, Tuple, Union T = TypeVar('T') S = TypeVar('S') @@ -592,26 +593,37 @@ class Node(Generic[T, S]): def __init__(self, x: T, y: S) -> None: ... -A = Node[T] # E: "Node" expects 2 type arguments, but 1 given +A = Node[T] # Error B = Node[T, T] -C = Node[T, T, T] # E: "Node" expects 2 type arguments, but 3 given +C = Node[T, T, T] # Error D = Node[T, S] E = Node[Node[T, T], List[T]] -F = Node[List[T, T], S] # E: "list" expects 1 type argument, but 2 given -G = Callable[..., List[T, T]] # E: "list" expects 1 type argument, but 2 given -H = Union[int, Tuple[T, Node[T]]] # E: "Node" expects 2 type arguments, but 1 given -h = None # type: H # E: "Node" expects 2 type arguments, but 1 given -h1 = None # type: H[int, str] # E: Bad number of arguments for type alias, expected: 1, given: 2 +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 x = None # type: D[int, str] -reveal_type(x) # E: Revealed type is '__main__.Node[builtins.int, builtins.str]' +reveal_type(x) y = None # type: E[int] -reveal_type(y) # E: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' +reveal_type(y) -X = T # E: Invalid type "__main__.T" for aliasing +X = T # Error [builtins fixtures/list.pyi] +[out] +main:9:4: error: "Node" expects 2 type arguments, but 1 given +main:11:4: error: "Node" expects 2 type arguments, but 3 given +main:15:9: error: "list" expects 1 type argument, but 2 given +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:22: error: Revealed type is '__main__.Node[builtins.int, builtins.str]' +main:24: error: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' +main:26:4: error: Invalid type "__main__.T" for aliasing [case testGenericTypeAliasesForAliases] from typing import TypeVar, Generic, List, Union @@ -938,6 +950,7 @@ reveal_type(C[str]().b) # E: Revealed type is '__main__.B[builtins.str*, builtin [builtins fixtures/list.pyi] [case testGenericTypeAliasesTypeVarConstraints] +# flags: --show-column-numbers from typing import TypeVar, Generic T = TypeVar('T', int, list) S = TypeVar('S', int, list) @@ -945,16 +958,17 @@ S = TypeVar('S', int, list) class A(Generic[T, S]): def __init__(self, x: T, y: S) -> None: ... -BadA = A[str, T] # This error is reported twice (but it actually looks useful) +BadA = A[str, T] # One error here SameA = A[T, T] x = None # type: SameA[int] -y = None # type: SameA[str] # E: Invalid type argument value for "A" +y = None # type: SameA[str] # Two errors here, for both args of A [builtins fixtures/list.pyi] [out] -main:8: error: Invalid type argument value for "A" -main:8: error: Type argument 1 of "A" has incompatible value "str" +main:9:7: error: Type argument 1 of "A" has incompatible value "str" +main:13:8: error: Type argument 1 of "A" has incompatible value "str" +main:13:8: error: Type argument 2 of "A" has incompatible value "str" -- Multiple assignment with lists diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test index 68bc6dd024ca..1c5c181b5809 100644 --- a/test-data/unit/check-newtype.test +++ b/test-data/unit/check-newtype.test @@ -277,8 +277,8 @@ A = NewType('A', T) B = NewType('B', List[T]) [builtins fixtures/list.pyi] [out] -main:3: error: Invalid type "__main__.T" main:3: error: Argument 2 to NewType(...) must be subclassable (got T?) +main:3: error: Invalid type "__main__.T" main:4: error: Invalid type "__main__.T" [case testNewTypeWithNewTypeFails] diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 238be6e5e918..605e8c011359 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -235,7 +235,7 @@ X = TypeVar('X', int, str) class A(Generic[X]): pass a = None # type: A[int] b = None # type: A[str] -d = None # type: A[object] # E: Invalid type argument value for "A" +d = None # type: A[object] # E: Type argument 1 of "A" has incompatible value "object" c = None # type: A[Any] [case testConstructGenericTypeWithTypevarValuesAndTypeInference] @@ -356,8 +356,8 @@ Y = TypeVar('Y', int, str) class C(Generic[X, Y]): pass a = None # type: C[A, int] b = None # type: C[B, str] -c = None # type: C[int, int] # E: Invalid type argument value for "C" -d = None # type: C[A, A] # E: Invalid type argument value for "C" +c = None # type: C[int, int] # E: Type argument 1 of "C" has incompatible value "int" +d = None # type: C[A, A] # E: Type argument 2 of "C" has incompatible value "A" [case testCallGenericFunctionUsingMultipleTypevarsWithValues] from typing import TypeVar From 863c5767ae8f47234d629c241503b907087f083f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 3 Nov 2016 09:51:46 +0100 Subject: [PATCH 20/21] Remaining part of response to comments (minor thigs) --- docs/source/kinds_of_types.rst | 4 ++-- mypy/checkexpr.py | 7 +++---- mypy/nodes.py | 8 +++++--- mypy/semanal.py | 8 +++++--- mypy/typeanal.py | 9 +++++---- mypy/types.py | 2 -- test-data/unit/check-generics.test | 8 ++++---- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 1bba8647e3aa..c2dc27e63ecc 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -487,8 +487,8 @@ Following previous examples: A type alias does not create a new type. It's just a shorthand notation for another type -- it's equivalent to the target type. For generic type aliases this means that variance of type variables used for alias definition does not - apply to aliases. Parameterized generic alias is treated simply as an original - type with corresponding type variables substituted. + apply to aliases. A parameterized generic alias is treated simply as an original + type with the corresponding type variables substituted. .. _newtypes: diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 128299c626bc..a1fabe0b3f5f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -7,8 +7,7 @@ TupleType, Instance, TypeVarId, TypeVarType, ErasedType, UnionType, PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, true_only, false_only, is_named_instance, function_type, - get_typ_args, set_typ_args - + get_typ_args, set_typ_args, ) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, @@ -1390,7 +1389,7 @@ 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 alias.fback + return alias.fallback if isinstance(tp, CallableType): if len(tp.variables) != len(item.args): self.msg.incompatible_type_application(len(tp.variables), @@ -1425,7 +1424,7 @@ def replace_tvars_any(self, tp: Type) -> Type: new_args[i] = AnyType() else: new_args[i] = self.replace_tvars_any(arg) - return set_typ_args(tp, new_args) + return set_typ_args(tp, new_args, tp.line, tp.column) def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" diff --git a/mypy/nodes.py b/mypy/nodes.py index fd9aeb2471f5..79bdaf3f8204 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1732,15 +1732,17 @@ class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" type = None # type: mypy.types.Type - fback = 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', fback: 'mypy.types.Type' = None, + def __init__(self, type: 'mypy.types.Type', fallback: 'mypy.types.Type' = None, in_runtime: bool = False) -> None: self.type = type - self.fback = fback + self.fallback = fallback self.in_runtime = in_runtime def accept(self, visitor: NodeVisitor[T]) -> T: diff --git a/mypy/semanal.py b/mypy/semanal.py index 7b0bd3a23db3..d4921c6293c5 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1175,7 +1175,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: node.kind = TYPE_ALIAS node.type_override = res if isinstance(s.rvalue, IndexExpr): - s.rvalue.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res)) + s.rvalue.analyzed = TypeAliasExpr(res, + fallback=self.alias_fallback(res)) if s.type: # Store type into nodes. for lvalue in s.lvalues: @@ -1220,7 +1221,7 @@ def alias_fallback(self, tp: Type) -> Instance: 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{}'.format(kind), Block([])) + 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] @@ -2383,7 +2384,8 @@ def visit_index_expr(self, expr: IndexExpr) -> None: self.lookup_qualified, self.lookup_fully_qualified, self.fail) - expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), in_runtime=True) + expr.analyzed = TypeAliasExpr(res, fallback=self.alias_fallback(res), + in_runtime=True) elif refers_to_class_or_function(expr.base): # Special form -- type application. # Translate index to an unanalyzed type. diff --git a/mypy/typeanal.py b/mypy/typeanal.py index a338fb443de6..2f9924e10c4f 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -5,7 +5,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, - StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args + StarType, PartialType, EllipsisType, UninhabitedType, TypeType, get_typ_args, set_typ_args, ) from mypy.nodes import ( BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, @@ -41,7 +41,8 @@ def analyze_type_alias(node: Expression, # (only string literals within index expressions). if isinstance(node, RefExpr): if node.kind == UNBOUND_TVAR or node.kind == BOUND_TVAR: - fail_func('Invalid type "{}" for aliasing'.format(node.fullname), node) + fail_func('Type variable "{}" is invalid as target for type alias'.format( + node.fullname), node) return None if not (isinstance(node.node, TypeInfo) or node.fullname == 'typing.Any' or @@ -429,7 +430,7 @@ def visit_instance(self, t: Instance) -> None: else: arg_values = [arg] self.check_type_var_values(info, arg_values, - TypeVar.values, i, t) + TypeVar.values, i + 1, t) if not satisfies_upper_bound(arg, TypeVar.upper_bound): self.fail('Type argument "{}" of "{}" must be ' 'a subtype of "{}"'.format( @@ -448,7 +449,7 @@ def check_type_var_values(self, type: TypeInfo, actuals: List[Type], else: # print(context.column) self.fail('Type argument {} of "{}" has incompatible value "{}"'.format( - arg_number + 1, type.name(), actual.type.name()), context) + arg_number, type.name(), actual.type.name()), context) def visit_callable_type(self, t: CallableType) -> None: t.ret_type.accept(self) diff --git a/mypy/types.py b/mypy/types.py index 80cd7a235e37..216243d3e123 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1538,8 +1538,6 @@ def get_typ_args(tp: Type) -> List[Type]: def set_typ_args(tp: Type, new_args: List[Type], line: int = -1, column: int = -1) -> Type: """Return a copy of a parameterizable Type with arguments set to new_args.""" - line = line if line > 0 else tp.line - column = column if column > 0 else tp.column if isinstance(tp, Instance): return Instance(tp.type, new_args, line, column) if isinstance(tp, TupleType): diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index fc86c89063be..55a9e9a0f06c 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -623,7 +623,7 @@ 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:22: error: Revealed type is '__main__.Node[builtins.int, builtins.str]' main:24: error: Revealed type is '__main__.Node[__main__.Node[builtins.int, builtins.int], builtins.list[builtins.int]]' -main:26:4: error: Invalid type "__main__.T" for aliasing +main:26:4: error: Type variable "__main__.T" is invalid as target for type alias [case testGenericTypeAliasesForAliases] from typing import TypeVar, Generic, List, Union @@ -778,7 +778,7 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # Any simplyfies Union to Any +z.foo() # Any simplifies Union to Any now. This test should be updated after #2197 [builtins fixtures/isinstance.pyi] @@ -967,8 +967,8 @@ y = None # type: SameA[str] # Two errors here, for both args of A [builtins fixtures/list.pyi] [out] main:9:7: error: Type argument 1 of "A" has incompatible value "str" -main:13:8: error: Type argument 1 of "A" has incompatible value "str" -main:13:8: error: Type argument 2 of "A" has incompatible value "str" +main:13: error: Type argument 1 of "A" has incompatible value "str" +main:13: error: Type argument 2 of "A" has incompatible value "str" -- Multiple assignment with lists From 544ff66468f3900ae107f2233737a48b8891d9f7 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 3 Nov 2016 10:16:34 +0100 Subject: [PATCH 21/21] Remove debugging print() --- mypy/typeanal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 2f9924e10c4f..27114034f4a3 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -447,7 +447,6 @@ def check_type_var_values(self, type: TypeInfo, actuals: List[Type], self.fail('Invalid type argument value for "{}"'.format( type.name()), context) else: - # print(context.column) self.fail('Type argument {} of "{}" has incompatible value "{}"'.format( arg_number, type.name(), actual.type.name()), context)