Skip to content

Commit 0ea510c

Browse files
authored
pep612: add semanal for paramspec (#9339)
Co-authored-by: hauntsaninja <>
1 parent 2f291f2 commit 0ea510c

13 files changed

+171
-38
lines changed

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
erase_def_to_union_or_bound, erase_to_union_or_bound, coerce_to_literal,
5151
try_getting_str_literals_from_type, try_getting_int_literals_from_type,
5252
tuple_fallback, is_singleton_type, try_expanding_enum_to_union,
53-
true_only, false_only, function_type, TypeVarExtractor, custom_special_method,
53+
true_only, false_only, function_type, get_type_vars, custom_special_method,
5454
is_literal_type_like,
5555
)
5656
from mypy import message_registry
@@ -5328,7 +5328,7 @@ def detach_callable(typ: CallableType) -> CallableType:
53285328

53295329
appear_map = {} # type: Dict[str, List[int]]
53305330
for i, inner_type in enumerate(type_list):
5331-
typevars_available = inner_type.accept(TypeVarExtractor())
5331+
typevars_available = get_type_vars(inner_type)
53325332
for var in typevars_available:
53335333
if var.fullname not in appear_map:
53345334
appear_map[var.fullname] = []
@@ -5338,7 +5338,7 @@ def detach_callable(typ: CallableType) -> CallableType:
53385338
for var_name, appearances in appear_map.items():
53395339
used_type_var_names.add(var_name)
53405340

5341-
all_type_vars = typ.accept(TypeVarExtractor())
5341+
all_type_vars = get_type_vars(typ)
53425342
new_variables = []
53435343
for var in set(all_type_vars):
53445344
if var.fullname not in used_type_var_names:

mypy/checkexpr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
3232
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
3333
TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, SymbolNode, PlaceholderNode,
34+
ParamSpecExpr,
3435
ARG_POS, ARG_OPT, ARG_NAMED, ARG_STAR, ARG_STAR2, LITERAL_TYPE, REVEAL_TYPE,
3536
)
3637
from mypy.literals import literal
@@ -3973,6 +3974,9 @@ def visit_temp_node(self, e: TempNode) -> Type:
39733974
def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
39743975
return AnyType(TypeOfAny.special_form)
39753976

3977+
def visit_paramspec_expr(self, e: ParamSpecExpr) -> Type:
3978+
return AnyType(TypeOfAny.special_form)
3979+
39763980
def visit_newtype_expr(self, e: NewTypeExpr) -> Type:
39773981
return AnyType(TypeOfAny.special_form)
39783982

mypy/literals.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
ConditionalExpr, EllipsisExpr, YieldFromExpr, YieldExpr, RevealExpr, SuperExpr,
99
TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension,
1010
GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr,
11-
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, AssignmentExpr,
11+
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, AssignmentExpr, ParamSpecExpr
1212
)
1313
from mypy.visitor import ExpressionVisitor
1414

@@ -213,6 +213,9 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> None:
213213
def visit_type_var_expr(self, e: TypeVarExpr) -> None:
214214
return None
215215

216+
def visit_paramspec_expr(self, e: ParamSpecExpr) -> None:
217+
return None
218+
216219
def visit_type_alias_expr(self, e: TypeAliasExpr) -> None:
217220
return None
218221

mypy/nodes.py

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,23 +2043,10 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
20432043
CONTRAVARIANT = 2 # type: Final[int]
20442044

20452045

2046-
class TypeVarExpr(SymbolNode, Expression):
2047-
"""Type variable expression TypeVar(...).
2048-
2049-
This is also used to represent type variables in symbol tables.
2050-
2051-
A type variable is not valid as a type unless bound in a TypeVarScope.
2052-
That happens within:
2053-
2054-
1. a generic class that uses the type variable as a type argument or
2055-
2. a generic function that refers to the type variable in its signature.
2056-
"""
2057-
2046+
class TypeVarLikeExpr(SymbolNode, Expression):
2047+
"""Base class for TypeVarExpr and ParamSpecExpr."""
20582048
_name = ''
20592049
_fullname = ''
2060-
# Value restriction: only types in the list are valid as values. If the
2061-
# list is empty, there is no restriction.
2062-
values = None # type: List[mypy.types.Type]
20632050
# Upper bound: only subtypes of upper_bound are valid as values. By default
20642051
# this is 'object', meaning no restriction.
20652052
upper_bound = None # type: mypy.types.Type
@@ -2069,14 +2056,12 @@ class TypeVarExpr(SymbolNode, Expression):
20692056
# variable.
20702057
variance = INVARIANT
20712058

2072-
def __init__(self, name: str, fullname: str,
2073-
values: List['mypy.types.Type'],
2074-
upper_bound: 'mypy.types.Type',
2075-
variance: int = INVARIANT) -> None:
2059+
def __init__(
2060+
self, name: str, fullname: str, upper_bound: 'mypy.types.Type', variance: int = INVARIANT
2061+
) -> None:
20762062
super().__init__()
20772063
self._name = name
20782064
self._fullname = fullname
2079-
self.values = values
20802065
self.upper_bound = upper_bound
20812066
self.variance = variance
20822067

@@ -2088,6 +2073,29 @@ def name(self) -> str:
20882073
def fullname(self) -> str:
20892074
return self._fullname
20902075

2076+
2077+
class TypeVarExpr(TypeVarLikeExpr):
2078+
"""Type variable expression TypeVar(...).
2079+
2080+
This is also used to represent type variables in symbol tables.
2081+
2082+
A type variable is not valid as a type unless bound in a TypeVarScope.
2083+
That happens within:
2084+
2085+
1. a generic class that uses the type variable as a type argument or
2086+
2. a generic function that refers to the type variable in its signature.
2087+
"""
2088+
# Value restriction: only types in the list are valid as values. If the
2089+
# list is empty, there is no restriction.
2090+
values = None # type: List[mypy.types.Type]
2091+
2092+
def __init__(self, name: str, fullname: str,
2093+
values: List['mypy.types.Type'],
2094+
upper_bound: 'mypy.types.Type',
2095+
variance: int = INVARIANT) -> None:
2096+
super().__init__(name, fullname, upper_bound, variance)
2097+
self.values = values
2098+
20912099
def accept(self, visitor: ExpressionVisitor[T]) -> T:
20922100
return visitor.visit_type_var_expr(self)
20932101

@@ -2110,6 +2118,30 @@ def deserialize(cls, data: JsonDict) -> 'TypeVarExpr':
21102118
data['variance'])
21112119

21122120

2121+
class ParamSpecExpr(TypeVarLikeExpr):
2122+
def accept(self, visitor: ExpressionVisitor[T]) -> T:
2123+
return visitor.visit_paramspec_expr(self)
2124+
2125+
def serialize(self) -> JsonDict:
2126+
return {
2127+
'.class': 'ParamSpecExpr',
2128+
'name': self._name,
2129+
'fullname': self._fullname,
2130+
'upper_bound': self.upper_bound.serialize(),
2131+
'variance': self.variance,
2132+
}
2133+
2134+
@classmethod
2135+
def deserialize(cls, data: JsonDict) -> 'ParamSpecExpr':
2136+
assert data['.class'] == 'ParamSpecExpr'
2137+
return ParamSpecExpr(
2138+
data['name'],
2139+
data['fullname'],
2140+
mypy.types.deserialize_type(data['upper_bound']),
2141+
data['variance']
2142+
)
2143+
2144+
21132145
class TypeAliasExpr(Expression):
21142146
"""Type alias expression (rvalue)."""
21152147

mypy/semanal.py

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
nongen_builtins, get_member_expr_fullname, REVEAL_TYPE,
7777
REVEAL_LOCALS, is_final_node, TypedDictExpr, type_aliases_target_versions,
7878
EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
79+
ParamSpecExpr
7980
)
8081
from mypy.tvar_scope import TypeVarScope
8182
from mypy.typevars import fill_typevars
@@ -1921,6 +1922,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
19211922
# * type variable definition
19221923
elif self.process_typevar_declaration(s):
19231924
special_form = True
1925+
elif self.process_paramspec_declaration(s):
1926+
special_form = True
19241927
# * type constructors
19251928
elif self.analyze_namedtuple_assign(s):
19261929
special_form = True
@@ -2836,7 +2839,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
28362839
Return True if this looks like a type variable declaration (but maybe
28372840
with errors), otherwise return False.
28382841
"""
2839-
call = self.get_typevar_declaration(s)
2842+
call = self.get_typevarlike_declaration(s, ("typing.TypeVar",))
28402843
if not call:
28412844
return False
28422845

@@ -2847,7 +2850,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
28472850
return False
28482851

28492852
name = lvalue.name
2850-
if not self.check_typevar_name(call, name, s):
2853+
if not self.check_typevarlike_name(call, name, s):
28512854
return False
28522855

28532856
# Constraining types
@@ -2907,24 +2910,31 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
29072910
self.add_symbol(name, call.analyzed, s)
29082911
return True
29092912

2910-
def check_typevar_name(self, call: CallExpr, name: str, context: Context) -> bool:
2913+
def check_typevarlike_name(self, call: CallExpr, name: str, context: Context) -> bool:
2914+
"""Checks that the name of a TypeVar or ParamSpec matches its variable."""
29112915
name = unmangle(name)
2916+
assert isinstance(call.callee, RefExpr)
2917+
typevarlike_type = (
2918+
call.callee.name if isinstance(call.callee, NameExpr) else call.callee.fullname
2919+
)
29122920
if len(call.args) < 1:
2913-
self.fail("Too few arguments for TypeVar()", context)
2921+
self.fail("Too few arguments for {}()".format(typevarlike_type), context)
29142922
return False
29152923
if (not isinstance(call.args[0], (StrExpr, BytesExpr, UnicodeExpr))
29162924
or not call.arg_kinds[0] == ARG_POS):
2917-
self.fail("TypeVar() expects a string literal as first argument", context)
2925+
self.fail("{}() expects a string literal as first argument".format(typevarlike_type),
2926+
context)
29182927
return False
29192928
elif call.args[0].value != name:
2920-
msg = "String argument 1 '{}' to TypeVar(...) does not match variable name '{}'"
2921-
self.fail(msg.format(call.args[0].value, name), context)
2929+
msg = "String argument 1 '{}' to {}(...) does not match variable name '{}'"
2930+
self.fail(msg.format(call.args[0].value, typevarlike_type, name), context)
29222931
return False
29232932
return True
29242933

2925-
def get_typevar_declaration(self, s: AssignmentStmt) -> Optional[CallExpr]:
2926-
"""Returns the TypeVar() call expression if `s` is a type var declaration
2927-
or None otherwise.
2934+
def get_typevarlike_declaration(self, s: AssignmentStmt,
2935+
typevarlike_types: Tuple[str, ...]) -> Optional[CallExpr]:
2936+
"""Returns the call expression if `s` is a declaration of `typevarlike_type`
2937+
(TypeVar or ParamSpec), or None otherwise.
29282938
"""
29292939
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], NameExpr):
29302940
return None
@@ -2934,7 +2944,7 @@ def get_typevar_declaration(self, s: AssignmentStmt) -> Optional[CallExpr]:
29342944
callee = call.callee
29352945
if not isinstance(callee, RefExpr):
29362946
return None
2937-
if callee.fullname != 'typing.TypeVar':
2947+
if callee.fullname not in typevarlike_types:
29382948
return None
29392949
return call
29402950

@@ -3021,6 +3031,41 @@ def process_typevar_parameters(self, args: List[Expression],
30213031
variance = INVARIANT
30223032
return variance, upper_bound
30233033

3034+
def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
3035+
"""Checks if s declares a ParamSpec; if yes, store it in symbol table.
3036+
3037+
Return True if this looks like a ParamSpec (maybe with errors), otherwise return False.
3038+
3039+
In the future, ParamSpec may accept bounds and variance arguments, in which
3040+
case more aggressive sharing of code with process_typevar_declaration should be pursued.
3041+
"""
3042+
call = self.get_typevarlike_declaration(
3043+
s, ("typing_extensions.ParamSpec", "typing.ParamSpec")
3044+
)
3045+
if not call:
3046+
return False
3047+
3048+
lvalue = s.lvalues[0]
3049+
assert isinstance(lvalue, NameExpr)
3050+
if s.type:
3051+
self.fail("Cannot declare the type of a parameter specification", s)
3052+
return False
3053+
3054+
name = lvalue.name
3055+
if not self.check_typevarlike_name(call, name, s):
3056+
return False
3057+
3058+
# PEP 612 reserves the right to define bound, covariant and contravariant arguments to
3059+
# ParamSpec in a later PEP. If and when that happens, we should do something
3060+
# on the lines of process_typevar_parameters
3061+
paramspec_var = ParamSpecExpr(
3062+
name, self.qualified_name(name), self.object_type(), INVARIANT
3063+
)
3064+
paramspec_var.line = call.line
3065+
call.analyzed = paramspec_var
3066+
self.add_symbol(name, call.analyzed, s)
3067+
return True
3068+
30243069
def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance) -> TypeInfo:
30253070
class_def = ClassDef(name, Block([]))
30263071
if self.is_func_scope() and not self.type:

mypy/strconv.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,17 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> str:
467467
a += ['UpperBound({})'.format(o.upper_bound)]
468468
return self.dump(a, o)
469469

470+
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> str:
471+
import mypy.types
472+
a = [] # type: List[Any]
473+
if o.variance == mypy.nodes.COVARIANT:
474+
a += ['Variance(COVARIANT)']
475+
if o.variance == mypy.nodes.CONTRAVARIANT:
476+
a += ['Variance(CONTRAVARIANT)']
477+
if not mypy.types.is_named_instance(o.upper_bound, 'builtins.object'):
478+
a += ['UpperBound({})'.format(o.upper_bound)]
479+
return self.dump(a, o)
480+
470481
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> str:
471482
return 'TypeAliasExpr({})'.format(o.type)
472483

mypy/treetransform.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
ConditionalExpr, DictExpr, SetExpr, NameExpr, IntExpr, StrExpr, BytesExpr,
1616
UnicodeExpr, FloatExpr, CallExpr, SuperExpr, MemberExpr, IndexExpr,
1717
SliceExpr, OpExpr, UnaryExpr, LambdaExpr, TypeApplication, PrintStmt,
18-
SymbolTable, RefExpr, TypeVarExpr, NewTypeExpr, PromoteExpr,
18+
SymbolTable, RefExpr, TypeVarExpr, ParamSpecExpr, NewTypeExpr, PromoteExpr,
1919
ComparisonExpr, TempNode, StarExpr, Statement, Expression,
2020
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension,
2121
DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr,
@@ -498,6 +498,11 @@ def visit_type_var_expr(self, node: TypeVarExpr) -> TypeVarExpr:
498498
self.types(node.values),
499499
self.type(node.upper_bound), variance=node.variance)
500500

501+
def visit_paramspec_expr(self, node: ParamSpecExpr) -> ParamSpecExpr:
502+
return ParamSpecExpr(
503+
node.name, node.fullname, self.type(node.upper_bound), variance=node.variance
504+
)
505+
501506
def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr:
502507
return TypeAliasExpr(node.node)
503508

mypy/visitor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T:
155155
def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
156156
pass
157157

158+
@abstractmethod
159+
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
160+
pass
161+
158162
@abstractmethod
159163
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
160164
pass
@@ -529,6 +533,9 @@ def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T:
529533
def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
530534
pass
531535

536+
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
537+
pass
538+
532539
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
533540
pass
534541

mypyc/irbuild/visitor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
FloatExpr, GeneratorExpr, GlobalDecl, LambdaExpr, ListComprehension, SetComprehension,
1717
NamedTupleExpr, NewTypeExpr, NonlocalDecl, OverloadedFuncDef, PrintStmt, RaiseStmt,
1818
RevealExpr, SetExpr, SliceExpr, StarExpr, SuperExpr, TryStmt, TypeAliasExpr, TypeApplication,
19-
TypeVarExpr, TypedDictExpr, UnicodeExpr, WithStmt, YieldFromExpr, YieldExpr
19+
TypeVarExpr, TypedDictExpr, UnicodeExpr, WithStmt, YieldFromExpr, YieldExpr, ParamSpecExpr
2020
)
2121

2222
from mypyc.ir.ops import Value
@@ -309,6 +309,9 @@ def visit_type_application(self, o: TypeApplication) -> Value:
309309
def visit_type_var_expr(self, o: TypeVarExpr) -> Value:
310310
assert False, "can't compile analysis-only expressions"
311311

312+
def visit_paramspec_expr(self, o: ParamSpecExpr) -> Value:
313+
assert False, "can't compile analysis-only expressions"
314+
312315
def visit_typeddict_expr(self, o: TypedDictExpr) -> Value:
313316
assert False, "can't compile analysis-only expressions"
314317

test-data/unit/lib-stub/typing.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ClassVar = 0
2424
Final = 0
2525
NoReturn = 0
2626
NewType = 0
27+
ParamSpec = 0
2728

2829
T = TypeVar('T')
2930
T_co = TypeVar('T_co', covariant=True)

test-data/unit/lib-stub/typing_extensions.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Literal: _SpecialForm = ...
2121

2222
Annotated: _SpecialForm = ...
2323

24+
ParamSpec: _SpecialForm
25+
Concatenate: _SpecialForm
2426

2527
# Fallback type for all typed dicts (does not exist at runtime).
2628
class _TypedDict(Mapping[str, object]):

0 commit comments

Comments
 (0)