Skip to content

ParamSpec with __init__ somehow transforms keyword arguments into positional #16412

Closed
@sliedes

Description

@sliedes

Consider this code (playground)

from typing import Callable, ParamSpec, TypeVar

_ParamsT = ParamSpec("_ParamsT")
_T = TypeVar("_T")


def smoke_testable(*args: _ParamsT.args, **kwargs: _ParamsT.kwargs) -> Callable[[Callable[_ParamsT, _T]], type[_T]]:
    assert args == (), args

    def decorator(cls: Callable[_ParamsT, _T]) -> type[_T]:
        assert isinstance(cls, type), type(cls)
        cls._smoke_testable_kwargs = kwargs
        return cls

    return decorator


@smoke_testable(name="bob", size=512, flt=0.5)  # Argument 1 has incompatible type "type[SomeClass]"; expected "Callable[[str, int, float], SomeClass]"
# @smoke_testable(size=512, name="bob", flt=0.5)  # OK
# @smoke_testable(name=512, size="bob", flt=0.5)  # Argument 1 has incompatible type "type[SomeClass]"; expected "Callable[[int, str, float], SomeClass]"
class SomeClass:
    def __init__(self, size: int, name: str, flt: float) -> None:
        pass

Mypy gives this error message for the decorator line (which has correct kwargs, but in an order different from __init__):

Argument 1 has incompatible type "type[SomeClass]"; expected "Callable[[str, int, float], SomeClass]" [arg-type]

This seems clearly wrong; all the arguments to smoke_testable were kwargs, and their order should not matter. If I change the order in smoke_testable to the same as __init__, there is no error (line 2).

The third line should give an error (because it assigns an int to a str kwarg and a str to an int kwarg), and it does. However, the error message is surprising:

Argument 1 has incompatible type "type[SomeClass]"; expected "Callable[[int, str, float], SomeClass]"

As far as I can tell, the class is a Callable[[int, str, float], SomeClass].

Expected Behavior

The first decorator line should produce no error (it currently does).
The second decorator line should produce no error, and it currently does not.
The third decorator line should produce an error, but the error message should not claim that SomeClass is not Callable[[int, str, float], SomeClass].

Your Environment

  • Mypy version used: 1.6.1
  • Mypy command-line flags: --strict
  • Python version used: 3.11

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