Skip to content

Commit ec76cca

Browse files
authored
pep612: more semantic analysis for paramspec (#9422)
Linking #8645 Like #9339, much of this is in #9250. However, this PR is a little less self contained, in that it causes several future TODOs. A lot of the changes here are necessitated by changing the type of CallableType.variables to Sequence[TypeVarLikeDef]. This is nice, because it informs me where I need to make changes in the future / integrates a little bit better with existing type var stuff.
1 parent e959952 commit ec76cca

20 files changed

+361
-181
lines changed

mypy/applytype.py

+55-43
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,55 @@
44
import mypy.sametypes
55
from mypy.expandtype import expand_type
66
from mypy.types import (
7-
Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType, get_proper_types
7+
Type, TypeVarId, TypeVarType, CallableType, AnyType, PartialType, get_proper_types,
8+
TypeVarDef, TypeVarLikeDef, ProperType
89
)
910
from mypy.nodes import Context
1011

1112

13+
def get_target_type(
14+
tvar: TypeVarLikeDef,
15+
type: ProperType,
16+
callable: CallableType,
17+
report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None],
18+
context: Context,
19+
skip_unsatisfied: bool
20+
) -> Optional[Type]:
21+
# TODO(shantanu): fix for ParamSpecDef
22+
assert isinstance(tvar, TypeVarDef)
23+
values = get_proper_types(tvar.values)
24+
if values:
25+
if isinstance(type, AnyType):
26+
return type
27+
if isinstance(type, TypeVarType) and type.values:
28+
# Allow substituting T1 for T if every allowed value of T1
29+
# is also a legal value of T.
30+
if all(any(mypy.sametypes.is_same_type(v, v1) for v in values)
31+
for v1 in type.values):
32+
return type
33+
matching = []
34+
for value in values:
35+
if mypy.subtypes.is_subtype(type, value):
36+
matching.append(value)
37+
if matching:
38+
best = matching[0]
39+
# If there are more than one matching value, we select the narrowest
40+
for match in matching[1:]:
41+
if mypy.subtypes.is_subtype(match, best):
42+
best = match
43+
return best
44+
if skip_unsatisfied:
45+
return None
46+
report_incompatible_typevar_value(callable, type, tvar.name, context)
47+
else:
48+
upper_bound = tvar.upper_bound
49+
if not mypy.subtypes.is_subtype(type, upper_bound):
50+
if skip_unsatisfied:
51+
return None
52+
report_incompatible_typevar_value(callable, type, tvar.name, context)
53+
return type
54+
55+
1256
def apply_generic_arguments(
1357
callable: CallableType, orig_types: Sequence[Optional[Type]],
1458
report_incompatible_typevar_value: Callable[[CallableType, Type, str, Context], None],
@@ -29,52 +73,20 @@ def apply_generic_arguments(
2973
# Check that inferred type variable values are compatible with allowed
3074
# values and bounds. Also, promote subtype values to allowed values.
3175
types = get_proper_types(orig_types)
32-
for i, type in enumerate(types):
76+
77+
# Create a map from type variable id to target type.
78+
id_to_type = {} # type: Dict[TypeVarId, Type]
79+
80+
for tvar, type in zip(tvars, types):
3381
assert not isinstance(type, PartialType), "Internal error: must never apply partial type"
34-
values = get_proper_types(callable.variables[i].values)
3582
if type is None:
3683
continue
37-
if values:
38-
if isinstance(type, AnyType):
39-
continue
40-
if isinstance(type, TypeVarType) and type.values:
41-
# Allow substituting T1 for T if every allowed value of T1
42-
# is also a legal value of T.
43-
if all(any(mypy.sametypes.is_same_type(v, v1) for v in values)
44-
for v1 in type.values):
45-
continue
46-
matching = []
47-
for value in values:
48-
if mypy.subtypes.is_subtype(type, value):
49-
matching.append(value)
50-
if matching:
51-
best = matching[0]
52-
# If there are more than one matching value, we select the narrowest
53-
for match in matching[1:]:
54-
if mypy.subtypes.is_subtype(match, best):
55-
best = match
56-
types[i] = best
57-
else:
58-
if skip_unsatisfied:
59-
types[i] = None
60-
else:
61-
report_incompatible_typevar_value(callable, type, callable.variables[i].name,
62-
context)
63-
else:
64-
upper_bound = callable.variables[i].upper_bound
65-
if not mypy.subtypes.is_subtype(type, upper_bound):
66-
if skip_unsatisfied:
67-
types[i] = None
68-
else:
69-
report_incompatible_typevar_value(callable, type, callable.variables[i].name,
70-
context)
7184

72-
# Create a map from type variable id to target type.
73-
id_to_type = {} # type: Dict[TypeVarId, Type]
74-
for i, tv in enumerate(tvars):
75-
typ = types[i]
76-
if typ:
77-
id_to_type[tv.id] = typ
85+
target_type = get_target_type(
86+
tvar, type, callable, report_incompatible_typevar_value, context, skip_unsatisfied
87+
)
88+
if target_type is not None:
89+
id_to_type[tvar.id] = target_type
7890

7991
# Apply arguments to argument types.
8092
arg_types = [expand_type(at, id_to_type) for at in callable.arg_types]

mypy/checker.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1389,15 +1389,14 @@ def expand_typevars(self, defn: FuncItem,
13891389
typ: CallableType) -> List[Tuple[FuncItem, CallableType]]:
13901390
# TODO use generator
13911391
subst = [] # type: List[List[Tuple[TypeVarId, Type]]]
1392-
tvars = typ.variables or []
1393-
tvars = tvars[:]
1392+
tvars = list(typ.variables) or []
13941393
if defn.info:
13951394
# Class type variables
13961395
tvars += defn.info.defn.type_vars or []
1396+
# TODO(shantanu): audit for paramspec
13971397
for tvar in tvars:
1398-
if tvar.values:
1399-
subst.append([(tvar.id, value)
1400-
for value in tvar.values])
1398+
if isinstance(tvar, TypeVarDef) and tvar.values:
1399+
subst.append([(tvar.id, value) for value in tvar.values])
14011400
# Make a copy of the function to check for each combination of
14021401
# value restricted type variables. (Except when running mypyc,
14031402
# where we need one canonical version of the function.)

mypy/checkexpr.py

+2
Original file line numberDiff line numberDiff line change
@@ -4311,6 +4311,8 @@ def merge_typevars_in_callables_by_name(
43114311
for tvdef in target.variables:
43124312
name = tvdef.fullname
43134313
if name not in unique_typevars:
4314+
# TODO(shantanu): fix for ParamSpecDef
4315+
assert isinstance(tvdef, TypeVarDef)
43144316
unique_typevars[name] = TypeVarType(tvdef)
43154317
variables.append(tvdef)
43164318
rename[tvdef.id] = unique_typevars[name]

mypy/checkmember.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"""Type checking of attribute access"""
22

3-
from typing import cast, Callable, Optional, Union, List
3+
from typing import cast, Callable, Optional, Union, Sequence
44
from typing_extensions import TYPE_CHECKING
55

66
from mypy.types import (
7-
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef,
8-
Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
7+
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike,
8+
TypeVarLikeDef, Overloaded, TypeVarType, UnionType, PartialType, TypeOfAny, LiteralType,
99
DeletedType, NoneType, TypeType, has_type_vars, get_proper_type, ProperType
1010
)
1111
from mypy.nodes import (
@@ -676,7 +676,7 @@ def analyze_class_attribute_access(itype: Instance,
676676
name: str,
677677
mx: MemberContext,
678678
override_info: Optional[TypeInfo] = None,
679-
original_vars: Optional[List[TypeVarDef]] = None
679+
original_vars: Optional[Sequence[TypeVarLikeDef]] = None
680680
) -> Optional[Type]:
681681
"""Analyze access to an attribute on a class object.
682682
@@ -839,7 +839,7 @@ def analyze_enum_class_attribute_access(itype: Instance,
839839
def add_class_tvars(t: ProperType, isuper: Optional[Instance],
840840
is_classmethod: bool,
841841
original_type: Type,
842-
original_vars: Optional[List[TypeVarDef]] = None) -> Type:
842+
original_vars: Optional[Sequence[TypeVarLikeDef]] = None) -> Type:
843843
"""Instantiate type variables during analyze_class_attribute_access,
844844
e.g T and Q in the following:
845845
@@ -883,7 +883,7 @@ class B(A[str]): pass
883883
assert isuper is not None
884884
t = cast(CallableType, expand_type_by_instance(t, isuper))
885885
freeze_type_vars(t)
886-
return t.copy_modified(variables=tvars + t.variables)
886+
return t.copy_modified(variables=list(tvars) + list(t.variables))
887887
elif isinstance(t, Overloaded):
888888
return Overloaded([cast(CallableType, add_class_tvars(item, isuper,
889889
is_classmethod, original_type,

mypy/expandtype.py

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def freshen_function_type_vars(callee: F) -> F:
4040
tvdefs = []
4141
tvmap = {} # type: Dict[TypeVarId, Type]
4242
for v in callee.variables:
43+
# TODO(shantanu): fix for ParamSpecDef
44+
assert isinstance(v, TypeVarDef)
4345
tvdef = TypeVarDef.new_unification_variable(v)
4446
tvdefs.append(tvdef)
4547
tvmap[v.id] = TypeVarType(tvdef)

mypy/fixup.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from mypy.types import (
1212
CallableType, Instance, Overloaded, TupleType, TypedDictType,
1313
TypeVarType, UnboundType, UnionType, TypeVisitor, LiteralType,
14-
TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny)
14+
TypeType, NOT_READY, TypeAliasType, AnyType, TypeOfAny, TypeVarDef
15+
)
1516
from mypy.visitor import NodeVisitor
1617
from mypy.lookup import lookup_fully_qualified
1718

@@ -183,10 +184,11 @@ def visit_callable_type(self, ct: CallableType) -> None:
183184
if ct.ret_type is not None:
184185
ct.ret_type.accept(self)
185186
for v in ct.variables:
186-
if v.values:
187-
for val in v.values:
188-
val.accept(self)
189-
v.upper_bound.accept(self)
187+
if isinstance(v, TypeVarDef):
188+
if v.values:
189+
for val in v.values:
190+
val.accept(self)
191+
v.upper_bound.accept(self)
190192
for arg in ct.bound_args:
191193
if arg:
192194
arg.accept(self)

mypy/messages.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from mypy.errors import Errors
2222
from mypy.types import (
2323
Type, CallableType, Instance, TypeVarType, TupleType, TypedDictType, LiteralType,
24-
UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType,
24+
UnionType, NoneType, AnyType, Overloaded, FunctionLike, DeletedType, TypeType, TypeVarDef,
2525
UninhabitedType, TypeOfAny, UnboundType, PartialType, get_proper_type, ProperType,
2626
get_proper_types
2727
)
@@ -1862,16 +1862,20 @@ def [T <: int] f(self, x: int, y: T) -> None
18621862
if tp.variables:
18631863
tvars = []
18641864
for tvar in tp.variables:
1865-
upper_bound = get_proper_type(tvar.upper_bound)
1866-
if (isinstance(upper_bound, Instance) and
1867-
upper_bound.type.fullname != 'builtins.object'):
1868-
tvars.append('{} <: {}'.format(tvar.name, format_type_bare(upper_bound)))
1869-
elif tvar.values:
1870-
tvars.append('{} in ({})'
1871-
.format(tvar.name, ', '.join([format_type_bare(tp)
1872-
for tp in tvar.values])))
1865+
if isinstance(tvar, TypeVarDef):
1866+
upper_bound = get_proper_type(tvar.upper_bound)
1867+
if (isinstance(upper_bound, Instance) and
1868+
upper_bound.type.fullname != 'builtins.object'):
1869+
tvars.append('{} <: {}'.format(tvar.name, format_type_bare(upper_bound)))
1870+
elif tvar.values:
1871+
tvars.append('{} in ({})'
1872+
.format(tvar.name, ', '.join([format_type_bare(tp)
1873+
for tp in tvar.values])))
1874+
else:
1875+
tvars.append(tvar.name)
18731876
else:
1874-
tvars.append(tvar.name)
1877+
# For other TypeVarLikeDefs, just use the repr
1878+
tvars.append(repr(tvar))
18751879
s = '[{}] {}'.format(', '.join(tvars), s)
18761880
return 'def {}'.format(s)
18771881

mypy/nodes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2079,7 +2079,7 @@ class TypeVarExpr(TypeVarLikeExpr):
20792079
20802080
This is also used to represent type variables in symbol tables.
20812081
2082-
A type variable is not valid as a type unless bound in a TypeVarScope.
2082+
A type variable is not valid as a type unless bound in a TypeVarLikeScope.
20832083
That happens within:
20842084
20852085
1. a generic class that uses the type variable as a type argument or

mypy/plugin.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class C: pass
126126
from mypy.nodes import (
127127
Expression, Context, ClassDef, SymbolTableNode, MypyFile, CallExpr
128128
)
129-
from mypy.tvar_scope import TypeVarScope
129+
from mypy.tvar_scope import TypeVarLikeScope
130130
from mypy.types import Type, Instance, CallableType, TypeList, UnboundType, ProperType
131131
from mypy.messages import MessageBuilder
132132
from mypy.options import Options
@@ -265,7 +265,7 @@ def fail(self, msg: str, ctx: Context, serious: bool = False, *,
265265

266266
@abstractmethod
267267
def anal_type(self, t: Type, *,
268-
tvar_scope: Optional[TypeVarScope] = None,
268+
tvar_scope: Optional[TypeVarLikeScope] = None,
269269
allow_tuple_literal: bool = False,
270270
allow_unbound_tvars: bool = False,
271271
report_invalid_types: bool = True,

mypy/plugins/default.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from mypy.plugins.common import try_getting_str_literals
1111
from mypy.types import (
1212
Type, Instance, AnyType, TypeOfAny, CallableType, NoneType, TypedDictType,
13-
TypeVarType, TPDICT_FB_NAMES, get_proper_type, LiteralType
13+
TypeVarDef, TypeVarType, TPDICT_FB_NAMES, get_proper_type, LiteralType
1414
)
1515
from mypy.subtypes import is_subtype
1616
from mypy.typeops import make_simplified_union
@@ -204,6 +204,7 @@ def typed_dict_get_signature_callback(ctx: MethodSigContext) -> CallableType:
204204
# Tweak the signature to include the value type as context. It's
205205
# only needed for type inference since there's a union with a type
206206
# variable that accepts everything.
207+
assert isinstance(signature.variables[0], TypeVarDef)
207208
tv = TypeVarType(signature.variables[0])
208209
return signature.copy_modified(
209210
arg_types=[signature.arg_types[0],
@@ -269,6 +270,7 @@ def typed_dict_pop_signature_callback(ctx: MethodSigContext) -> CallableType:
269270
# Tweak the signature to include the value type as context. It's
270271
# only needed for type inference since there's a union with a type
271272
# variable that accepts everything.
273+
assert isinstance(signature.variables[0], TypeVarDef)
272274
tv = TypeVarType(signature.variables[0])
273275
typ = make_simplified_union([value_type, tv])
274276
return signature.copy_modified(

0 commit comments

Comments
 (0)