Skip to content

Add TypeVarTupleExpr node #12481

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
DictionaryComprehension, ComplexExpr, EllipsisExpr, StarExpr, AwaitExpr, YieldExpr,
YieldFromExpr, TypedDictExpr, PromoteExpr, NewTypeExpr, NamedTupleExpr, TypeVarExpr,
TypeAliasExpr, BackquoteExpr, EnumCallExpr, TypeAlias, SymbolNode, PlaceholderNode,
ParamSpecExpr,
ParamSpecExpr, TypeVarTupleExpr,
ArgKind, ARG_POS, ARG_NAMED, ARG_STAR, ARG_STAR2, LITERAL_TYPE, REVEAL_TYPE,
)
from mypy.literals import literal
Expand Down Expand Up @@ -4186,6 +4186,9 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> Type:
def visit_paramspec_expr(self, e: ParamSpecExpr) -> Type:
return AnyType(TypeOfAny.special_form)

def visit_type_var_tuple_expr(self, e: TypeVarTupleExpr) -> Type:
return AnyType(TypeOfAny.special_form)

def visit_newtype_expr(self, e: NewTypeExpr) -> Type:
return AnyType(TypeOfAny.special_form)

Expand Down
5 changes: 4 additions & 1 deletion mypy/literals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TypeApplication, LambdaExpr, ListComprehension, SetComprehension, DictionaryComprehension,
GeneratorExpr, BackquoteExpr, TypeVarExpr, TypeAliasExpr, NamedTupleExpr, EnumCallExpr,
TypedDictExpr, NewTypeExpr, PromoteExpr, AwaitExpr, TempNode, AssignmentExpr, ParamSpecExpr,
AssertTypeExpr,
AssertTypeExpr, TypeVarTupleExpr,
)
from mypy.visitor import ExpressionVisitor

Expand Down Expand Up @@ -224,6 +224,9 @@ def visit_type_var_expr(self, e: TypeVarExpr) -> None:
def visit_paramspec_expr(self, e: ParamSpecExpr) -> None:
return None

def visit_type_var_tuple_expr(self, e: TypeVarTupleExpr) -> None:
return None

def visit_type_alias_expr(self, e: TypeAliasExpr) -> None:
return None

Expand Down
33 changes: 32 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2233,7 +2233,10 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:


class TypeVarLikeExpr(SymbolNode, Expression):
"""Base class for TypeVarExpr and ParamSpecExpr."""
"""Base class for TypeVarExpr, ParamSpecExpr and TypeVarTupleExpr.

Note that they are constructed by the semantic analyzer.
"""

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

Expand Down Expand Up @@ -2339,6 +2342,34 @@ def deserialize(cls, data: JsonDict) -> 'ParamSpecExpr':
)


class TypeVarTupleExpr(TypeVarLikeExpr):
Copy link
Collaborator

@97littleleaf11 97littleleaf11 Mar 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docstring for this and also modify the docstring of TypeVarLikeExpr

"""Type variable tuple expression TypeVarTuple(...)."""

__slots__ = ()

def accept(self, visitor: ExpressionVisitor[T]) -> T:
return visitor.visit_type_var_tuple_expr(self)

def serialize(self) -> JsonDict:
return {
'.class': 'TypeVarTupleExpr',
'name': self._name,
'fullname': self._fullname,
'upper_bound': self.upper_bound.serialize(),
'variance': self.variance,
}

@classmethod
def deserialize(cls, data: JsonDict) -> 'TypeVarTupleExpr':
assert data['.class'] == 'TypeVarTupleExpr'
return TypeVarTupleExpr(
data['name'],
data['fullname'],
mypy.types.deserialize_type(data['upper_bound']),
data['variance']
)


class TypeAliasExpr(Expression):
"""Type alias expression (rvalue)."""

Expand Down
41 changes: 40 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
typing_extensions_aliases,
EnumCallExpr, RUNTIME_PROTOCOL_DECOS, FakeExpression, Statement, AssignmentExpr,
ParamSpecExpr, EllipsisExpr, TypeVarLikeExpr, implicit_module_attrs,
MatchStmt, FuncBase
MatchStmt, FuncBase, TypeVarTupleExpr
)
from mypy.patterns import (
AsPattern, OrPattern, ValuePattern, SequencePattern,
Expand Down Expand Up @@ -2074,6 +2074,8 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
special_form = True
elif self.process_paramspec_declaration(s):
special_form = True
elif self.process_typevartuple_declaration(s):
special_form = True
# * type constructors
elif self.analyze_namedtuple_assign(s):
special_form = True
Expand Down Expand Up @@ -3332,6 +3334,43 @@ def process_paramspec_declaration(self, s: AssignmentStmt) -> bool:
self.add_symbol(name, call.analyzed, s)
return True

def process_typevartuple_declaration(self, s: AssignmentStmt) -> bool:
"""Checks if s declares a TypeVarTuple; if yes, store it in symbol table.

Return True if this looks like a TypeVarTuple (maybe with errors), otherwise return False.
"""
call = self.get_typevarlike_declaration(
s, ("typing_extensions.TypeVarTuple", "typing.TypeVarTuple")
)
if not call:
return False

if len(call.args) > 1:
self.fail(
"Only the first argument to TypeVarTuple has defined semantics",
s,
)

if not self.options.enable_incomplete_features:
self.fail('"TypeVarTuple" is not supported by mypy yet', s)
return False

name = self.extract_typevarlike_name(s, call)
if name is None:
return False

# PEP 646 does not specify the behavior of variance, constraints, or bounds.
if not call.analyzed:
typevartuple_var = TypeVarTupleExpr(
name, self.qualified_name(name), self.object_type(), INVARIANT
)
typevartuple_var.line = call.line
call.analyzed = typevartuple_var
else:
assert isinstance(call.analyzed, TypeVarTupleExpr)
self.add_symbol(name, call.analyzed, s)
return True

def basic_new_typeinfo(self, name: str,
basetype_or_fallback: Instance,
line: int) -> TypeInfo:
Expand Down
12 changes: 12 additions & 0 deletions mypy/strconv.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,18 @@ def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> str:
a += ['UpperBound({})'.format(o.upper_bound)]
return self.dump(a, o)

def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> str:
import mypy.types

a: List[Any] = []
if o.variance == mypy.nodes.COVARIANT:
a += ['Variance(COVARIANT)']
if o.variance == mypy.nodes.CONTRAVARIANT:
a += ['Variance(CONTRAVARIANT)']
if not mypy.types.is_named_instance(o.upper_bound, 'builtins.object'):
a += ['UpperBound({})'.format(o.upper_bound)]
return self.dump(a, o)

def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> str:
return 'TypeAliasExpr({})'.format(o.type)

Expand Down
5 changes: 4 additions & 1 deletion mypy/test/testtransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def test_transform(testcase: DataDrivenTestCase) -> None:
options = parse_options(src, testcase, 1)
options.use_builtins_fixtures = True
options.semantic_analysis_only = True
options.enable_incomplete_features = True
options.show_traceback = True
result = build.build(sources=[BuildSource('main', None, src)],
options=options,
Expand All @@ -54,8 +55,10 @@ def test_transform(testcase: DataDrivenTestCase) -> None:
# path.
# TODO the test is not reliable
if (not f.path.endswith((os.sep + 'builtins.pyi',
'typing_extensions.pyi',
'typing.pyi',
'abc.pyi'))
'abc.pyi',
'sys.pyi'))
and not os.path.basename(f.path).startswith('_')
and not os.path.splitext(
os.path.basename(f.path))[0].endswith('_')):
Expand Down
7 changes: 6 additions & 1 deletion mypy/treetransform.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension,
DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr,
YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, AssignmentExpr,
OverloadPart, EnumCallExpr, REVEAL_TYPE, GDEF
OverloadPart, EnumCallExpr, REVEAL_TYPE, GDEF, TypeVarTupleExpr
)
from mypy.types import Type, FunctionLike, ProperType
from mypy.traverser import TraverserVisitor
Expand Down Expand Up @@ -515,6 +515,11 @@ def visit_paramspec_expr(self, node: ParamSpecExpr) -> ParamSpecExpr:
node.name, node.fullname, self.type(node.upper_bound), variance=node.variance
)

def visit_type_var_tuple_expr(self, node: TypeVarTupleExpr) -> TypeVarTupleExpr:
return TypeVarTupleExpr(
node.name, node.fullname, self.type(node.upper_bound), variance=node.variance
)

def visit_type_alias_expr(self, node: TypeAliasExpr) -> TypeAliasExpr:
return TypeAliasExpr(node.node)

Expand Down
7 changes: 7 additions & 0 deletions mypy/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
pass

@abstractmethod
def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> T:
pass

@abstractmethod
def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
pass
Expand Down Expand Up @@ -590,6 +594,9 @@ def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T:
def visit_paramspec_expr(self, o: 'mypy.nodes.ParamSpecExpr') -> T:
pass

def visit_type_var_tuple_expr(self, o: 'mypy.nodes.TypeVarTupleExpr') -> T:
pass

def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T:
pass

Expand Down
5 changes: 4 additions & 1 deletion mypyc/irbuild/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
NamedTupleExpr, NewTypeExpr, NonlocalDecl, OverloadedFuncDef, PrintStmt, RaiseStmt,
RevealExpr, SetExpr, SliceExpr, StarExpr, SuperExpr, TryStmt, TypeAliasExpr, TypeApplication,
TypeVarExpr, TypedDictExpr, UnicodeExpr, WithStmt, YieldFromExpr, YieldExpr, ParamSpecExpr,
MatchStmt
MatchStmt, TypeVarTupleExpr
)

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

def visit_type_var_tuple_expr(self, o: TypeVarTupleExpr) -> Value:
assert False, "can't compile analysis-only expressions"

def visit_typeddict_expr(self, o: TypedDictExpr) -> Value:
assert False, "can't compile analysis-only expressions"

Expand Down
1 change: 1 addition & 0 deletions test-data/unit/lib-stub/typing_extensions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ TypeAlias: _SpecialForm
TypeGuard: _SpecialForm
Never: _SpecialForm

TypeVarTuple: _SpecialForm
Unpack: _SpecialForm

# Fallback type for all typed dicts (does not exist at runtime).
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1460,3 +1460,13 @@ heterogenous_tuple: Tuple[Unpack[Tuple[int, str]]]
homogenous_tuple: Tuple[Unpack[Tuple[int, ...]]]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add at least one AST dump test case to test-data/unit/semanal-types.test.

bad: Tuple[Unpack[int]] # E: builtins.int cannot be unpacked (must be tuple or TypeVarTuple)
[builtins fixtures/tuple.pyi]

[case testTypeVarTuple]
from typing_extensions import TypeVarTuple

TVariadic = TypeVarTuple('TVariadic')
TP = TypeVarTuple('?') # E: String argument 1 "?" to TypeVarTuple(...) does not match variable name "TP"
TP2: int = TypeVarTuple('TP2') # E: Cannot declare the type of a TypeVar or similar construct
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test calling TypeVarTuple with no args, too many args, and keyword args (all should be disallowed).

TP3 = TypeVarTuple() # E: Too few arguments for TypeVarTuple()
TP4 = TypeVarTuple('TP4', 'TP4') # E: Only the first argument to TypeVarTuple has defined semantics
TP5 = TypeVarTuple(t='TP5') # E: TypeVarTuple() expects a string literal as first argument
10 changes: 10 additions & 0 deletions test-data/unit/semanal-types.test
Original file line number Diff line number Diff line change
Expand Up @@ -1548,3 +1548,13 @@ MypyFile:1(
AssignmentStmt:2(
NameExpr(P* [__main__.P])
ParamSpecExpr:2()))

[case testTypeVarTuple]
from typing_extensions import TypeVarTuple
TV = TypeVarTuple("TV")
[out]
MypyFile:1(
ImportFrom:1(typing_extensions, [TypeVarTuple])
AssignmentStmt:2(
NameExpr(TV* [__main__.TV])
TypeVarTupleExpr:2()))