Skip to content

Commit ff9deb3

Browse files
authored
Correctly handle runtime type applications of variadic types (#16240)
This adds some missing pieces to runtime type application handling for both `TypeVarTuple` and `ParamSpec`. Everything is straightforward (maybe a bit hacky, but we already import `typeanal` in `checkexpr` for similar purposes, e.g. type aliases in runtime context). Fixes #14799
1 parent 940fceb commit ff9deb3

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

mypy/checkexpr.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from mypy.maptype import map_instance_to_supertype
2929
from mypy.meet import is_overlapping_types, narrow_declared_type
3030
from mypy.message_registry import ErrorMessage
31-
from mypy.messages import MessageBuilder
31+
from mypy.messages import MessageBuilder, format_type
3232
from mypy.nodes import (
3333
ARG_NAMED,
3434
ARG_POS,
@@ -116,10 +116,12 @@
116116
from mypy.type_visitor import TypeTranslator
117117
from mypy.typeanal import (
118118
check_for_explicit_any,
119+
fix_instance,
119120
has_any_from_unimported_type,
120121
instantiate_type_alias,
121122
make_optional_type,
122123
set_any_tvars,
124+
validate_instance,
123125
)
124126
from mypy.typeops import (
125127
callable_type,
@@ -166,10 +168,12 @@
166168
TypeVarLikeType,
167169
TypeVarTupleType,
168170
TypeVarType,
171+
UnboundType,
169172
UninhabitedType,
170173
UnionType,
171174
UnpackType,
172175
find_unpack_in_list,
176+
flatten_nested_tuples,
173177
flatten_nested_unions,
174178
get_proper_type,
175179
get_proper_types,
@@ -4637,15 +4641,35 @@ class C(Generic[T, Unpack[Ts]]): ...
46374641
similar to how it is done in other places using split_with_prefix_and_suffix().
46384642
"""
46394643
vars = t.variables
4644+
args = flatten_nested_tuples(args)
4645+
4646+
# TODO: this logic is duplicated with semanal_typeargs.
4647+
for tv, arg in zip(t.variables, args):
4648+
if isinstance(tv, ParamSpecType):
4649+
if not isinstance(
4650+
get_proper_type(arg), (Parameters, ParamSpecType, AnyType, UnboundType)
4651+
):
4652+
self.chk.fail(
4653+
"Can only replace ParamSpec with a parameter types list or"
4654+
f" another ParamSpec, got {format_type(arg, self.chk.options)}",
4655+
ctx,
4656+
)
4657+
return [AnyType(TypeOfAny.from_error)] * len(vars)
4658+
46404659
if not vars or not any(isinstance(v, TypeVarTupleType) for v in vars):
46414660
return list(args)
4661+
assert t.is_type_obj()
4662+
info = t.type_object()
4663+
# We reuse the logic from semanal phase to reduce code duplication.
4664+
fake = Instance(info, args, line=ctx.line, column=ctx.column)
4665+
if not validate_instance(fake, self.chk.fail):
4666+
fix_instance(
4667+
fake, self.chk.fail, self.chk.note, disallow_any=False, options=self.chk.options
4668+
)
4669+
args = list(fake.args)
46424670

46434671
prefix = next(i for (i, v) in enumerate(vars) if isinstance(v, TypeVarTupleType))
46444672
suffix = len(vars) - prefix - 1
4645-
if len(args) < len(vars) - 1:
4646-
self.msg.incompatible_type_application(len(vars), len(args), ctx)
4647-
return [AnyType(TypeOfAny.from_error)] * len(vars)
4648-
46494673
tvt = vars[prefix]
46504674
assert isinstance(tvt, TypeVarTupleType)
46514675
start, middle, end = split_with_prefix_and_suffix(tuple(args), prefix, suffix)

test-data/unit/check-parameter-specification.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1977,6 +1977,19 @@ g(cb, y='a', x=0) # E: Argument "y" to "g" has incompatible type "str"; expecte
19771977
# E: Argument "x" to "g" has incompatible type "int"; expected "str"
19781978
[builtins fixtures/paramspec.pyi]
19791979

1980+
[case testParamSpecBadRuntimeTypeApplication]
1981+
from typing import ParamSpec, TypeVar, Generic, Callable
1982+
1983+
R = TypeVar("R")
1984+
P = ParamSpec("P")
1985+
class C(Generic[P, R]):
1986+
x: Callable[P, R]
1987+
1988+
bad = C[int, str]() # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int"
1989+
reveal_type(bad) # N: Revealed type is "__main__.C[Any, Any]"
1990+
reveal_type(bad.x) # N: Revealed type is "def (*Any, **Any) -> Any"
1991+
[builtins fixtures/paramspec.pyi]
1992+
19801993
[case testParamSpecNoCrashOnUnificationAlias]
19811994
import mod
19821995
[file mod.pyi]

test-data/unit/check-typevar-tuple.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,3 +1845,23 @@ def foo2(func: Callable[[Unpack[Args]], T], *args: Unpack[Args2]) -> T:
18451845
def foo3(func: Callable[[int, Unpack[Args2]], T], *args: Unpack[Args2]) -> T:
18461846
return submit(func, 1, *args)
18471847
[builtins fixtures/tuple.pyi]
1848+
1849+
[case testTypeVarTupleRuntimeTypeApplication]
1850+
from typing import Generic, TypeVar, Tuple
1851+
from typing_extensions import Unpack, TypeVarTuple
1852+
1853+
T = TypeVar("T")
1854+
S = TypeVar("S")
1855+
Ts = TypeVarTuple("Ts")
1856+
class C(Generic[T, Unpack[Ts], S]): ...
1857+
1858+
Ints = Tuple[int, int]
1859+
x = C[Unpack[Ints]]()
1860+
reveal_type(x) # N: Revealed type is "__main__.C[builtins.int, builtins.int]"
1861+
1862+
y = C[Unpack[Tuple[int, ...]]]()
1863+
reveal_type(y) # N: Revealed type is "__main__.C[builtins.int, Unpack[builtins.tuple[builtins.int, ...]], builtins.int]"
1864+
1865+
z = C[int]() # E: Bad number of arguments, expected: at least 2, given: 1
1866+
reveal_type(z) # N: Revealed type is "__main__.C[Any, Unpack[builtins.tuple[Any, ...]], Any]"
1867+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)