Skip to content

Commit

Permalink
Second part of response to comments: minor things (but many); Two mor…
Browse files Browse the repository at this point in the history
…e tests
  • Loading branch information
ilevkivskyi committed Nov 1, 2016
1 parent 8c64111 commit c106204
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 29 deletions.
21 changes: 12 additions & 9 deletions docs/source/kinds_of_types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -427,22 +427,28 @@ 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
<https://www.python.org/dev/peps/pep-0484/#type-aliases>`_):

.. code-block:: python
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)
Expand Down Expand Up @@ -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:

Expand Down
15 changes: 10 additions & 5 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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[:]
Expand Down
8 changes: 5 additions & 3 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 2 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 '')
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 4 additions & 6 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
40 changes: 39 additions & 1 deletion test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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
-- ------------------------------
Expand Down

0 comments on commit c106204

Please sign in to comment.