From c1062040a15bc0741ed715173a09efb8cf2ad4b2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 1 Nov 2016 22:41:05 +0100 Subject: [PATCH] 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 -- ------------------------------