Skip to content

Commit c106204

Browse files
committed
Second part of response to comments: minor things (but many); Two more tests
1 parent 8c64111 commit c106204

File tree

7 files changed

+76
-29
lines changed

7 files changed

+76
-29
lines changed

docs/source/kinds_of_types.rst

+12-9
Original file line numberDiff line numberDiff line change
@@ -427,22 +427,28 @@ assigning the type to a variable:
427427
...
428428
429429
Type aliases can be generic, in this case they could be used in two variants:
430-
Subscribed aliases are equivalent to original types with substituted type variables,
430+
Subscripted aliases are equivalent to original types with substituted type variables,
431431
number of type arguments must match the number of free type variables
432432
in generic type alias. Unsubscribed aliases are treated as original types with free
433-
vaiables replacec with ``Any``. Examples (following `PEP 484
433+
variables replaced with ``Any``. Examples (following `PEP 484
434434
<https://www.python.org/dev/peps/pep-0484/#type-aliases>`_):
435435

436436
.. code-block:: python
437437
438438
from typing import TypeVar, Iterable, Tuple, Union, Callable
439439
T = TypeVar('T', int, float, complex)
440440
441-
TInt = Tuple[T, int]
441+
TInt = Tuple[int, T]
442442
UInt = Union[T, int]
443443
CBack = Callable[..., T]
444444
Vec = Iterable[Tuple[T, T]]
445445
446+
def response(query: str) -> UInt[str]: # Same as Union[str, int]
447+
...
448+
def activate(cb: CBack[T]) -> T: # Same as Callable[..., T]
449+
...
450+
table_entry: TInt # Same as Tuple[int, Any]
451+
446452
def inproduct(v: Vec[T]) -> T:
447453
return sum(x*y for x, y in v)
448454
@@ -479,12 +485,9 @@ Following previous examples:
479485

480486
A type alias does not create a new type. It's just a shorthand notation for
481487
another type -- it's equivalent to the target type. For generic type aliases
482-
this means that variance of type variables used for alias definition does not
483-
allpy to aliases. Parameterized generic alias is treated simply as an original
484-
type with corresponding type variables substituted. Accordingly, type checking
485-
happens when a type alias is used. Invalid aliases (like e.g.
486-
``Callable[..., List[T, T]]``) might not always be flagged by mypy if they are
487-
left unused.
488+
this means that variance or constraints of type variables used for alias
489+
definition don't apply to aliases. Parameterized generic alias is treated
490+
simply as an original type with corresponding type variables substituted.
488491

489492
.. _newtypes:
490493

mypy/checkexpr.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -1378,13 +1378,18 @@ def visit_type_application(self, tapp: TypeApplication) -> Type:
13781378
return AnyType()
13791379

13801380
def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type:
1381-
""" Get type of a type alias (could be generic) in a runtime expression."""
1381+
"""Get type of a type alias (could be generic) in a runtime expression."""
13821382
item = alias.type
1383-
if not alias.runtime:
1383+
if not alias.in_runtime:
1384+
# We don't replace TypeVar's with Any for alias used as Alias[T](42).
13841385
item = self.replace_tvars_any(item)
13851386
if isinstance(item, Instance):
1387+
# Normally we get a callable type (or overloaded) with .is_type_obj() true
1388+
# representing the class's constructor
13861389
tp = type_object_type(item.type, self.named_type)
13871390
else:
1391+
# This type is invalid in most runtime contexts
1392+
# and corresponding an error will be reported.
13881393
return alias.fback
13891394
if isinstance(tp, CallableType):
13901395
if len(tp.variables) != len(item.args):
@@ -1403,9 +1408,9 @@ def visit_type_alias_expr(self, alias: TypeAliasExpr) -> Type:
14031408
return AnyType()
14041409

14051410
def replace_tvars_any(self, tp: Type) -> Type:
1406-
""" Replace all unbound type variables with Any if an alias is used in
1407-
a runtime expression. Basically, this function finishes what could not be done
1408-
in similar funtion from typeanal.py.
1411+
"""Replace all type variables of a type alias tp with Any. Basically, this function
1412+
finishes what could not be done in method TypeAnalyser.visit_unbound_type()
1413+
from typeanal.py.
14091414
"""
14101415
typ_args = get_typ_args(tp)
14111416
new_args = typ_args[:]

mypy/nodes.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1733,13 +1733,15 @@ class TypeAliasExpr(Expression):
17331733

17341734
type = None # type: mypy.types.Type
17351735
fback = None # type: mypy.types.Type
1736-
runtime = False # type: bool
1736+
# This type alias is subscripted in a runtime expression like Alias[int](42)
1737+
# (not in a type context like type annotation or base class).
1738+
in_runtime = False # type: bool
17371739

17381740
def __init__(self, type: 'mypy.types.Type', fback: 'mypy.types.Type' = None,
1739-
runtime: bool = False) -> None:
1741+
in_runtime: bool = False) -> None:
17401742
self.type = type
17411743
self.fback = fback
1742-
self.runtime = runtime
1744+
self.in_runtime = in_runtime
17431745

17441746
def accept(self, visitor: NodeVisitor[T]) -> T:
17451747
return visitor.visit_type_alias_expr(self)

mypy/semanal.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1217,7 +1217,6 @@ def alias_fallback(self, tp: Type) -> Instance:
12171217
"""Make a dummy Instance with no methods. It is used as a fallback type
12181218
to detect errors for non-Instance aliases (i.e. Unions, Tuples, Callables).
12191219
"""
1220-
12211220
kind = (' to Callable' if isinstance(tp, CallableType) else
12221221
' to Tuple' if isinstance(tp, TupleType) else
12231222
' to Union' if isinstance(tp, UnionType) else '')
@@ -2378,13 +2377,13 @@ def visit_unary_expr(self, expr: UnaryExpr) -> None:
23782377
def visit_index_expr(self, expr: IndexExpr) -> None:
23792378
expr.base.accept(self)
23802379
if isinstance(expr.base, RefExpr) and expr.base.kind == TYPE_ALIAS:
2381-
# Special form -- subcribing a generic type alias.
2380+
# Special form -- subscripting a generic type alias.
23822381
# Perform the type substitution and create a new alias.
23832382
res = analyze_type_alias(expr,
23842383
self.lookup_qualified,
23852384
self.lookup_fully_qualified,
23862385
self.fail)
2387-
expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), runtime=True)
2386+
expr.analyzed = TypeAliasExpr(res, fback=self.alias_fallback(res), in_runtime=True)
23882387
elif refers_to_class_or_function(expr.base):
23892388
# Special form -- type application.
23902389
# Translate index to an unanalyzed type.

mypy/typeanal.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def __init__(self,
7979
lookup_func: Callable[[str, Context], SymbolTableNode],
8080
lookup_fqn_func: Callable[[str], SymbolTableNode],
8181
fail_func: Callable[[str, Context], None], *,
82-
aliasing = False) -> None:
82+
aliasing: bool = False) -> None:
8383
self.lookup = lookup_func
8484
self.lookup_fqn_func = lookup_fqn_func
8585
self.fail = fail_func
@@ -158,8 +158,6 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
158158
if exp_len == 0 and act_len == 0:
159159
return override
160160
if act_len != exp_len:
161-
# TODO: Detect wrong type variable numer for unused aliases
162-
# (it is difficult at this stage, see comment below, line 187 atm)
163161
self.fail('Bad number of arguments for type alias, expected: %s, given: %s'
164162
% (exp_len, act_len), t)
165163
return t
@@ -174,7 +172,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
174172
# as a base class -- however, this will fail soon at runtime so the problem
175173
# is pretty minor.
176174
return AnyType()
177-
# Allow unbount type variables when defining an alias
175+
# Allow unbound type variables when defining an alias
178176
if not (self.aliasing and sym.kind == UNBOUND_TVAR):
179177
self.fail('Invalid type "{}"'.format(name), t)
180178
return t
@@ -205,7 +203,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
205203
return AnyType()
206204

207205
def get_type_var_names(self, tp: Type) -> List[str]:
208-
""" Get all type variable names that are present in a generic type alias
206+
"""Get all type variable names that are present in a generic type alias
209207
in order of textual appearance (recursively, if needed).
210208
"""
211209
tvars = [] # type: List[str]
@@ -236,7 +234,7 @@ def get_tvar_name(self, t: Type) -> Optional[str]:
236234
return None
237235

238236
def replace_alias_tvars(self, tp: Type, vars: List[str], subs: List[Type]) -> Type:
239-
""" Replace type variables in a generic type alias tp with substitutions subs.
237+
"""Replace type variables in a generic type alias tp with substitutions subs.
240238
Length of subs should be already checked.
241239
"""
242240
typ_args = get_typ_args(tp)

mypy/types.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,7 @@ def function_type(func: mypy.nodes.FuncBase, fallback: Instance) -> FunctionLike
15271527

15281528

15291529
def get_typ_args(tp: Type) -> List[Type]:
1530+
"""Get all type arguments from a parameterizable Type."""
15301531
if not isinstance(tp, (Instance, UnionType, TupleType, CallableType)):
15311532
return []
15321533
typ_args = (tp.args if isinstance(tp, Instance) else
@@ -1536,12 +1537,13 @@ def get_typ_args(tp: Type) -> List[Type]:
15361537

15371538

15381539
def set_typ_args(tp: Type, new_args: List[Type]) -> Type:
1540+
"""Return a copy of a parameterizable Type with arguments set to new_args."""
15391541
if isinstance(tp, Instance):
1540-
return Instance(tp.type, new_args, tp.line)
1542+
return Instance(tp.type, new_args, tp.line, tp.column)
15411543
if isinstance(tp, TupleType):
15421544
return tp.copy_modified(items=new_args)
15431545
if isinstance(tp, UnionType):
1544-
return UnionType.make_union(new_args, tp.line)
1546+
return UnionType.make_simplified_union(new_args, tp.line, tp.column)
15451547
if isinstance(tp, CallableType):
15461548
return tp.copy_modified(arg_types=new_args[:-1], ret_type=new_args[-1])
15471549
return tp

test-data/unit/check-generics.test

+39-1
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ if not isinstance(s, str):
766766

767767
z = None # type: TNode # Same as TNode[Any]
768768
z.x
769-
z.foo() # E: Some element of union has no attribute "foo"
769+
z.foo() # Any simplyfies Union to Any
770770

771771
[builtins fixtures/isinstance.pyi]
772772

@@ -914,6 +914,44 @@ reveal_type(us) # E: Revealed type is 'Any'
914914

915915
[out]
916916

917+
[case testGenericTypeAliasesTypeVarBinding]
918+
from typing import TypeVar, Generic, List
919+
T = TypeVar('T')
920+
S = TypeVar('S')
921+
922+
class A(Generic[T, S]):
923+
def __init__(self, x: T, y: S) -> None: ...
924+
925+
class B(Generic[T, S]):
926+
def __init__(self, x: List[T], y: List[S]) -> None: ...
927+
928+
SameA = A[T, T]
929+
SameB = B[T, T]
930+
931+
class C(Generic[T]):
932+
a = None # type: SameA[T]
933+
b = SameB[T]([], [])
934+
935+
reveal_type(C[int]().a) # E: Revealed type is '__main__.A[builtins.int*, builtins.int*]'
936+
reveal_type(C[str]().b) # E: Revealed type is '__main__.B[builtins.str*, builtins.str*]'
937+
938+
[builtins fixtures/list.pyi]
939+
940+
[case testGenericTypeAliasesTypeVarConstraints]
941+
from typing import TypeVar, Generic
942+
T = TypeVar('T', int, list)
943+
S = TypeVar('S', int, list)
944+
945+
class A(Generic[T, S]):
946+
def __init__(self, x: T, y: S) -> None: ...
947+
948+
BadA = A[str, T] # E: Bad ...
949+
SameA = A[T, T]
950+
951+
x = None # type: SameA[int]
952+
y = None # type: SameA[str] # E: Bad ...
953+
954+
[builtins fixtures/list.pyi]
917955

918956
-- Multiple assignment with lists
919957
-- ------------------------------

0 commit comments

Comments
 (0)