Skip to content

Commit 50213b5

Browse files
authored
Add TypeVarTupleExpr node (#12481)
This adds minimal support for a TypeVarTupleExpr node, gated behind the flag to enable incomplete features. It is modeled after paramspec, including the part where we don't support the various arguments that have no behavior defined in PEP646. We also include TypeVarTuple in the typing_extensions stubs for test data and add some very basic semanal tests to verify the basic things work.
1 parent 8b1a810 commit 50213b5

File tree

12 files changed

+134
-7
lines changed

12 files changed

+134
-7
lines changed

mypy/checkexpr.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
3333
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
3434
TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, SymbolNode, PlaceholderNode,
35-
ParamSpecExpr,
35+
ParamSpecExpr, TypeVarTupleExpr,
3636
ArgKind, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, LITERAL_TYPE, REVEAL_TYPE,
3737
)
3838
from mypy.literals import literal
@@ -4186,6 +4186,9 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
41864186
def visit_paramspec_expr(self, e: ParamSpecExpr) -> Type:
41874187
return AnyType(TypeOfAny.special_form)
41884188

4189+
def visit_type_var_tuple_expr(self, e: TypeVarTupleExpr) -> Type:
4190+
return AnyType(TypeOfAny.special_form)
4191+
41894192
def visit_newtype_expr(self, e: NewTypeExpr) -> Type:
41904193
return AnyType(TypeOfAny.special_form)
41914194

mypy/literals.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension,
1010
GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr,
1111
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, AssignmentExpr, ParamSpecExpr,
12-
AssertTypeExpr,
12+
AssertTypeExpr, TypeVarTupleExpr,
1313
)
1414
from mypy.visitor import ExpressionVisitor
1515

@@ -224,6 +224,9 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> None:
224224
def visit_paramspec_expr(self, e: ParamSpecExpr) -> None:
225225
return None
226226

227+
def visit_type_var_tuple_expr(self, e: TypeVarTupleExpr) -> None:
228+
return None
229+
227230
def visit_type_alias_expr(self, e: TypeAliasExpr) -> None:
228231
return None
229232

mypy/nodes.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2233,7 +2233,10 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
22332233

22342234

22352235
class TypeVarLikeExpr(SymbolNode, Expression):
2236-
"""Base class for TypeVarExpr and ParamSpecExpr."""
2236+
"""Base class for TypeVarExpr, ParamSpecExpr and TypeVarTupleExpr.
2237+
2238+
Note that they are constructed by the semantic analyzer.
2239+
"""
22372240

22382241
__slots__ = ('_name', '_fullname', 'upper_bound', 'variance')
22392242

@@ -2339,6 +2342,34 @@ def deserialize(cls, data: JsonDict) -> 'ParamSpecExpr':
23392342
)
23402343

23412344

2345+
class TypeVarTupleExpr(TypeVarLikeExpr):
2346+
"""Type variable tuple expression TypeVarTuple(...)."""
2347+
2348+
__slots__ = ()
2349+
2350+
def accept(self, visitor: ExpressionVisitor[T]) -> T:
2351+
return visitor.visit_type_var_tuple_expr(self)
2352+
2353+
def serialize(self) -> JsonDict:
2354+
return {
2355+
'.class': 'TypeVarTupleExpr',
2356+
'name': self._name,
2357+
'fullname': self._fullname,
2358+
'upper_bound': self.upper_bound.serialize(),
2359+
'variance': self.variance,
2360+
}
2361+
2362+
@classmethod
2363+
def deserialize(cls, data: JsonDict) -> 'TypeVarTupleExpr':
2364+
assert data['.class'] == 'TypeVarTupleExpr'
2365+
return TypeVarTupleExpr(
2366+
data['name'],
2367+
data['fullname'],
2368+
mypy.types.deserialize_type(data['upper_bound']),
2369+
data['variance']
2370+
)
2371+
2372+
23422373
class TypeAliasExpr(Expression):
23432374
"""Type alias expression (rvalue)."""
23442375

mypy/semanal.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
typing_extensions_aliases,
7979
EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
8080
ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, implicit_module_attrs,
81-
MatchStmt, FuncBase
81+
MatchStmt, FuncBase, TypeVarTupleExpr
8282
)
8383
from mypy.patterns import (
8484
AsPattern, OrPattern, ValuePattern, SequencePattern,
@@ -2074,6 +2074,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
20742074
special_form = True
20752075
elif self.process_paramspec_declaration(s):
20762076
special_form = True
2077+
elif self.process_typevartuple_declaration(s):
2078+
special_form = True
20772079
# * type constructors
20782080
elif self.analyze_namedtuple_assign(s):
20792081
special_form = True
@@ -3332,6 +3334,43 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
33323334
self.add_symbol(name, call.analyzed, s)
33333335
return True
33343336

3337+
def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool:
3338+
"""Checks if s declares a TypeVarTuple; if yes, store it in symbol table.
3339+
3340+
Return True if this looks like a TypeVarTuple (maybe with errors), otherwise return False.
3341+
"""
3342+
call = self.get_typevarlike_declaration(
3343+
s, ("typing_extensions.TypeVarTuple", "typing.TypeVarTuple")
3344+
)
3345+
if not call:
3346+
return False
3347+
3348+
if len(call.args) > 1:
3349+
self.fail(
3350+
"Only the first argument to TypeVarTuple has defined semantics",
3351+
s,
3352+
)
3353+
3354+
if not self.options.enable_incomplete_features:
3355+
self.fail('"TypeVarTuple" is not supported by mypy yet', s)
3356+
return False
3357+
3358+
name = self.extract_typevarlike_name(s, call)
3359+
if name is None:
3360+
return False
3361+
3362+
# PEP 646 does not specify the behavior of variance, constraints, or bounds.
3363+
if not call.analyzed:
3364+
typevartuple_var = TypeVarTupleExpr(
3365+
name, self.qualified_name(name), self.object_type(), INVARIANT
3366+
)
3367+
typevartuple_var.line = call.line
3368+
call.analyzed = typevartuple_var
3369+
else:
3370+
assert isinstance(call.analyzed, TypeVarTupleExpr)
3371+
self.add_symbol(name, call.analyzed, s)
3372+
return True
3373+
33353374
def basic_new_typeinfo(self, name: str,
33363375
basetype_or_fallback: Instance,
33373376
line: int) -> TypeInfo:

mypy/strconv.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,18 @@ def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> str:
496496
a += ['UpperBound({})'.format(o.upper_bound)]
497497
return self.dump(a, o)
498498

499+
def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> str:
500+
import mypy.types
501+
502+
a: List[Any] = []
503+
if o.variance == mypy.nodes.COVARIANT:
504+
a += ['Variance(COVARIANT)']
505+
if o.variance == mypy.nodes.CONTRAVARIANT:
506+
a += ['Variance(CONTRAVARIANT)']
507+
if not mypy.types.is_named_instance(o.upper_bound, 'builtins.object'):
508+
a += ['UpperBound({})'.format(o.upper_bound)]
509+
return self.dump(a, o)
510+
499511
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> str:
500512
return 'TypeAliasExpr({})'.format(o.type)
501513

mypy/test/testtransform.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None:
3838
options = parse_options(src, testcase, 1)
3939
options.use_builtins_fixtures = True
4040
options.semantic_analysis_only = True
41+
options.enable_incomplete_features = True
4142
options.show_traceback = True
4243
result = build.build(sources=[BuildSource('main', None, src)],
4344
options=options,
@@ -54,8 +55,10 @@ def test_transform(testcase: DataDrivenTestCase) -> None:
5455
# path.
5556
# TODO the test is not reliable
5657
if (not f.path.endswith((os.sep + 'builtins.pyi',
58+
'typing_extensions.pyi',
5759
'typing.pyi',
58-
'abc.pyi'))
60+
'abc.pyi',
61+
'sys.pyi'))
5962
and not os.path.basename(f.path).startswith('_')
6063
and not os.path.splitext(
6164
os.path.basename(f.path))[0].endswith('_')):

mypy/treetransform.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension,
2121
DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr,
2222
YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, AssignmentExpr,
23-
OverloadPart, EnumCallExpr, REVEAL_TYPE, GDEF
23+
OverloadPart, EnumCallExpr, REVEAL_TYPE, GDEF, TypeVarTupleExpr
2424
)
2525
from mypy.types import Type, FunctionLike, ProperType
2626
from mypy.traverser import TraverserVisitor
@@ -515,6 +515,11 @@ def visit_paramspec_expr(self, node: ParamSpecExpr) -> ParamSpecExpr:
515515
node.name, node.fullname, self.type(node.upper_bound), variance=node.variance
516516
)
517517

518+
def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr:
519+
return TypeVarTupleExpr(
520+
node.name, node.fullname, self.type(node.upper_bound), variance=node.variance
521+
)
522+
518523
def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr:
519524
return TypeAliasExpr(node.node)
520525

mypy/visitor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
165165
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
166166
pass
167167

168+
@abstractmethod
169+
def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> T:
170+
pass
171+
168172
@abstractmethod
169173
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
170174
pass
@@ -590,6 +594,9 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
590594
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
591595
pass
592596

597+
def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> T:
598+
pass
599+
593600
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
594601
pass
595602

mypyc/irbuild/visitor.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
NamedTupleExpr, NewTypeExpr, NonlocalDecl, OverloadedFuncDef, PrintStmt, RaiseStmt,
1818
RevealExpr, SetExpr, SliceExpr, StarExpr, SuperExpr, TryStmt, TypeAliasExpr, TypeApplication,
1919
TypeVarExpr, TypedDictExpr, UnicodeExpr, WithStmt, YieldFromExpr, YieldExpr, ParamSpecExpr,
20-
MatchStmt
20+
MatchStmt, TypeVarTupleExpr
2121
)
2222

2323
from mypyc.ir.ops import Value
@@ -315,6 +315,9 @@ def visit_type_var_expr(self, o: TypeVarExpr) -> Value:
315315
def visit_paramspec_expr(self, o: ParamSpecExpr) -> Value:
316316
assert False, "can't compile analysis-only expressions"
317317

318+
def visit_type_var_tuple_expr(self, o: TypeVarTupleExpr) -> Value:
319+
assert False, "can't compile analysis-only expressions"
320+
318321
def visit_typeddict_expr(self, o: TypedDictExpr) -> Value:
319322
assert False, "can't compile analysis-only expressions"
320323

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ TypeAlias: _SpecialForm
2929
TypeGuard: _SpecialForm
3030
Never: _SpecialForm
3131

32+
TypeVarTuple: _SpecialForm
3233
Unpack: _SpecialForm
3334

3435
# Fallback type for all typed dicts (does not exist at runtime).

test-data/unit/semanal-errors.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,3 +1460,13 @@ heterogenous_tuple: Tuple[Unpack[Tuple[int, str]]]
14601460
homogenous_tuple: Tuple[Unpack[Tuple[int, ...]]]
14611461
bad: Tuple[Unpack[int]] # E: builtins.int cannot be unpacked (must be tuple or TypeVarTuple)
14621462
[builtins fixtures/tuple.pyi]
1463+
1464+
[case testTypeVarTuple]
1465+
from typing_extensions import TypeVarTuple
1466+
1467+
TVariadic = TypeVarTuple('TVariadic')
1468+
TP = TypeVarTuple('?') # E: String argument 1 "?" to TypeVarTuple(...) does not match variable name "TP"
1469+
TP2: int = TypeVarTuple('TP2') # E: Cannot declare the type of a TypeVar or similar construct
1470+
TP3 = TypeVarTuple() # E: Too few arguments for TypeVarTuple()
1471+
TP4 = TypeVarTuple('TP4', 'TP4') # E: Only the first argument to TypeVarTuple has defined semantics
1472+
TP5 = TypeVarTuple(t='TP5') # E: TypeVarTuple() expects a string literal as first argument

test-data/unit/semanal-types.test

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,3 +1548,13 @@ MypyFile:1(
15481548
AssignmentStmt:2(
15491549
NameExpr(P* [__main__.P])
15501550
ParamSpecExpr:2()))
1551+
1552+
[case testTypeVarTuple]
1553+
from typing_extensions import TypeVarTuple
1554+
TV = TypeVarTuple("TV")
1555+
[out]
1556+
MypyFile:1(
1557+
ImportFrom:1(typing_extensions, [TypeVarTuple])
1558+
AssignmentStmt:2(
1559+
NameExpr(TV* [__main__.TV])
1560+
TypeVarTupleExpr:2()))

0 commit comments

Comments
 (0)