Skip to content

mypy doesn't understand that *args: P.args implies that args is a tuple #19663

@randolf-scholz

Description

@randolf-scholz
from typing import Callable

def as_tuple[*Ts](*args: *Ts) -> tuple[*Ts]: return args
def tuple_identity[*Ts](t: tuple[*Ts]) -> tuple[*Ts]: return t
def tuple_identity2[T: tuple](t: T) -> T: return t


def test_paramspec[**P](
    dummy: Callable[P, None],  # ensure P is bound
    /,
    *args: P.args,
    **kwargs: P.kwargs,
) -> None:
    reveal_type(args)                   # N: "P.args`-1" 
    reveal_type( (*args,) )             # N: "builtins.tuple[P.args`-1, ...]"
    reveal_type( tuple(args) )          # N: "builtins.tuple[builtins.object, ...]"
    reveal_type(as_tuple(*args))        # N: "builtins.tuple[P.args`-1, ...]"
    reveal_type(tuple_identity(args))   # N: "builtins.tuple[Never, ...]"
                                        # E: [arg-type]
    reveal_type(tuple_identity2(args))  # N: "P.args`-1" ✅

    if isinstance(args, tuple):
        pass
    else:
        reveal_type(args)  # false negative [warn-unreachable]

https://mypy-play.net/?mypy=latest&python=3.12&flags=warn-unreachable&gist=3eabd8f4ee599e272934c94080f02bd8

Ideally, all the reveal_types should show the same result (or raise errors if considered misuse of ParamSpec1), and the else-branch should trigger an unreachable warning.

Footnotes

  1. For instance, pyright says as_tuple(*args) is illegal. Code sample in pyright playground

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