Skip to content

subscripting a class with a TypeVarTuple followed by a defaulted ParamSpec reports "Can only replace ParamSpec" and infers Any #22375

Open

Description

@DerekLaymn

Bug Report

PEP 696 and the typing spec (section "Defaults following TypeVarTuple") allow a ParamSpec with a default after a TypeVarTuple. The spec's example also gives the TypeVarTuple a default; the result below is the same with and without it. mypy accepts the class definition, but subscripting it gives an error: the last type argument is taken as the argument for the ParamSpec, and the result is Foo[*tuple[Any, ...], Any].

To Reproduce

from collections.abc import Callable
from typing import assert_type


class Foo[*Ts, **P = [float, bool]]:
    def args(self) -> tuple[*Ts]:
        raise NotImplementedError

    def fn(self) -> Callable[P, None]:
        raise NotImplementedError


foo = Foo[int, str]()
assert_type(foo.args(), tuple[int, str])
assert_type(foo.fn(), Callable[[float, bool], None])

bar = Foo[int, str, [bytes]]()
assert_type(bar.args(), tuple[int, str])
assert_type(bar.fn(), Callable[[bytes], None])

The old-style spelling (Ts = TypeVarTuple("Ts"), P = ParamSpec("P", default=[float, bool]), class Foo(Generic[Unpack[Ts], P]), a parameter annotated Foo[int, str]) gives the same message on the subscript.

Actual Behavior

t.py:13: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str"  [misc]
t.py:14: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]"  [assert-type]
t.py:15: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[float, bool], None]"  [assert-type]
t.py:17: error: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "str"  [misc]
t.py:18: error: Expression is of type "tuple[Any, ...]", not "tuple[int, str]"  [assert-type]
t.py:19: error: Expression is of type "def (*Any, **Any) -> None", not "Callable[[bytes], None]"  [assert-type]

With a default on the TypeVarTuple too (class Foo[*Ts = *tuple[int, str], **P = [float, bool]], the example from the spec) the result is the same. This is also what the typing conformance suite shows for mypy on generics_defaults.py, lines 203 to 205.

Expected Behavior

No errors. Foo[int, str] is Ts = (int, str), P = [float, bool] and Foo[int, str, [bytes]] is Ts = (int, str), P = [bytes], as in the spec. A ParamSpec argument has to be a list, ... or another ParamSpec, so str cannot be one and there is no ambiguity with the TypeVarTuple. pyright 1.1.414 and ty 0.0.97 accept the program.

Note: CPython 3.13.16, 3.14.8 and 3.15.0 raise TypeError: Type parameter with a default follows TypeVarTuple when this class is created (python/typing#2211, a runtime bug), so the snippet is for type checking only.

My Environment

  • Mypy version used: 2.4.0, 2.4.1 and master 2.5.0+dev.879da562
  • Mypy command-line flags: --python-version 3.13 --no-incremental
  • Python version used: 3.13.16
  • OS: Linux

mypy-play: https://mypy-play.net/?gist=250c90e8db7f8b3279d619ca6d36c422

Activity

  1. added a commit that references this issue on Nov 5, 2026
    c5d4fb9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions