diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 5ecdf77c6dd1..c2dc27e63ecc 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -426,9 +426,69 @@ 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: +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. 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 + 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] + ... + def activate(cb: CBack[S]) -> S: # Same as Callable[..., S] + ... + 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) + + 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. 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, Optional + from first_example import AliasType + from second_example import Vec + + def fun() -> AliasType: + ... + + T = TypeVar('T') + class NewVec(Generic[T], Vec[T]): + ... + for i, j in NewVec[int](): + ... + + OIntVec = Optional[Vec[int]] + +.. 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 + 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 e9282091d789..a1fabe0b3f5f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -6,7 +6,8 @@ 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, @@ -17,6 +18,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 +1377,55 @@ 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 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.fallback + if isinstance(tp, CallableType): + if len(tp.variables) != len(item.args): + self.msg.incompatible_type_application(len(tp.variables), + len(item.args), item) + return AnyType() + return self.apply_generic_arguments(tp, item.args, item) + elif isinstance(tp, Overloaded): + for it in tp.items(): + if len(it.variables) != len(item.args): + self.msg.incompatible_type_application(len(it.variables), + len(item.args), item) + return AnyType() + 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: + """Replace all type variables of a type alias tp with Any. Basically, this function + finishes what could not be done in method TypeAnalyser.visit_unbound_type() + from typeanal.py. + """ + typ_args = get_typ_args(tp) + new_args = typ_args[:] + for i, arg in enumerate(typ_args): + if isinstance(arg, UnboundType): + sym = None + try: + sym = self.chk.lookup_qualified(arg.name) + except KeyError: + pass + if sym and (sym.kind == UNBOUND_TVAR or sym.kind == BOUND_TVAR): + new_args[i] = AnyType() + else: + new_args[i] = self.replace_tvars_any(arg) + return set_typ_args(tp, new_args, tp.line, tp.column) + def visit_list_expr(self, e: ListExpr) -> Type: """Type check a list expression [...].""" return self.check_lst_expr(e.items, 'builtins.list', '', e) diff --git a/mypy/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/nodes.py b/mypy/nodes.py index e8a6a573087a..79bdaf3f8204 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1732,9 +1732,18 @@ class TypeAliasExpr(Expression): """Type alias expression (rvalue).""" type = None # type: mypy.types.Type - - def __init__(self, type: 'mypy.types.Type') -> None: + # 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', fallback: 'mypy.types.Type' = None, + in_runtime: bool = False) -> None: self.type = type + self.fallback = fallback + 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 2d8f3913a8ff..d4921c6293c5 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 @@ -1173,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) + s.rvalue.analyzed = TypeAliasExpr(res, + fallback=self.alias_fallback(res)) if s.type: # Store type into nodes. for lvalue in s.lvalues: @@ -1211,6 +1214,19 @@ 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' + 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. @@ -2361,7 +2377,16 @@ 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: + # 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, 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. types = [] # type: List[Type] @@ -2375,7 +2400,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 @@ -3051,6 +3076,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 d2f6fbca0cad..27114034f4a3 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1,14 +1,14 @@ """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, 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, TYPE_ALIAS, UNBOUND_IMPORTED, + BOUND_TVAR, UNBOUND_TVAR, TYPE_ALIAS, UNBOUND_IMPORTED, TypeInfo, Context, SymbolTableNode, Var, Expression, IndexExpr, RefExpr ) @@ -40,6 +40,10 @@ def analyze_type_alias(node: Expression, # 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('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 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,10 +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: bool = False) -> None: self.lookup = lookup_func self.lookup_fqn_func = lookup_fqn_func self.fail = fail_func + self.aliasing = aliasing def visit_unbound_type(self, t: UnboundType) -> Type: if t.optional: @@ -141,8 +148,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) + 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, + t.line, t.column) + if exp_len == 0 and act_len == 0: + return override + if act_len != exp_len: + self.fail('Bad number of arguments for type alias, expected: %s, given: %s' + % (exp_len, act_len), t) + return t + return self.replace_alias_tvars(override, all_vars, an_args, t.line, t.column) elif not isinstance(sym.node, TypeInfo): name = sym.fullname if name is None: @@ -153,7 +174,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 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 info = sym.node # type: TypeInfo if len(t.args) > 0 and info.fullname() == 'builtins.tuple': @@ -166,7 +189,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 @@ -181,6 +204,54 @@ def visit_unbound_type(self, t: UnboundType) -> Type: else: return AnyType() + def get_type_var_names(self, tp: Type) -> List[str]: + """Get all type variable names that are present in a generic type alias + in order of textual appearance (recursively, if needed). + """ + tvars = [] # type: List[str] + typ_args = get_typ_args(tp) + 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 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], + newline: int, newcolumn: int) -> Type: + """Replace type variables in a generic type alias tp with substitutions subs + resetting context. Length of subs should be already checked. + """ + typ_args = get_typ_args(tp) + new_args = typ_args[:] + for i, arg in enumerate(typ_args): + 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, newline, newcolumn) + return set_typ_args(tp, new_args, newline, newcolumn) + def visit_any(self, t: AnyType) -> Type: return t @@ -347,7 +418,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 @@ -359,7 +430,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 + 1, t) if not satisfies_upper_bound(arg, TypeVar.upper_bound): self.fail('Type argument "{}" of "{}" must be ' 'a subtype of "{}"'.format( @@ -368,12 +439,16 @@ 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: + self.fail('Type argument {} of "{}" has incompatible value "{}"'.format( + 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 9c80b590cd38..216243d3e123 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) @@ -1524,3 +1524,27 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike name, implicit=True, ) + + +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 + tp.items if not isinstance(tp, CallableType) else + tp.arg_types + [tp.ret_type]) + return typ_args + + +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.""" + if isinstance(tp, Instance): + 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, line, column) + if isinstance(tp, CallableType): + 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 ec44b64f12a5..55a9e9a0f06c 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -505,6 +505,472 @@ type[int] # this was crashing, see #2302 (comment) # E: Type application target [out] +-- Generic type aliases +-- -------------------- + +[case testGenericTypeAliasesBasic] +from typing import TypeVar, Generic +T = TypeVar('T') +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') +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] +# 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') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + ... + +A = Node[T] # Error +B = Node[T, T] +C = Node[T, T, T] # Error +D = Node[T, S] +E = Node[Node[T, T], List[T]] + +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) +y = None # type: E[int] +reveal_type(y) + +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: Type variable "__main__.T" is invalid as target for type alias + +[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*]]' + +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, List +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + self.x = x + def meth(self) -> T: + return self.x + +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, 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, Tuple, Union +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + ... + +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, Union, Any +T = TypeVar('T') +class Node(Generic[T]): + def __init__(self, x: T) -> None: + self.x = x + +UNode = Union[int, Node[T]] +x = 1 # type: UNode[int] + +x + 1 # E: Unsupported left operand type for + (some union) +if not isinstance(x, Node): + x + 1 + +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() # Any simplifies Union to Any now. This test should be updated after #2197 + +[builtins fixtures/isinstance.pyi] + +[case testGenericTypeAliasesTuple] +from typing import TypeVar, Tuple +T = TypeVar('T') + +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 +T = TypeVar('T') +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 typing import TypeVar, List, Tuple +T = TypeVar('T', int, bool) + +Vec = List[Tuple[T, T]] + +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]' + +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" + +[builtins fixtures/list.pyi] + +[case testGenericTypeAliasesImporting] +from typing import TypeVar +from a import Node, TupledNode +T = TypeVar('T') + +n = None # type: TupledNode[int] +n.x = 1 +n.y = (1, 1) +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)) + +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 +T = TypeVar('T') +S = TypeVar('S') +class Node(Generic[T, S]): + def __init__(self, x: T, y: S) -> None: + ... + +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] +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) +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, Union, Tuple, Callable, Any +T = TypeVar('T') + +CA = Callable[[T], int] +TA = Tuple[T, int] +UA = Union[T, int] + +cs = CA[str] + 1 # E: Unsupported left operand type for + ("Type alias to Callable") +reveal_type(cs) # E: Revealed type is 'Any' + +ts = TA[str]() # E: "Type alias to Tuple" not callable +reveal_type(ts) # E: Revealed type is 'Any' + +us = UA[str].x # E: "Type alias to Union" has no attribute "x" +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] +# flags: --show-column-numbers +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] # One error here +SameA = A[T, T] + +x = None # type: SameA[int] +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: 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 -- ------------------------------ 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-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] 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 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