Skip to content
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

Incompatible ParamSpecs are not reported as issue #17766

Open
pfaion opened this issue Sep 13, 2024 · 1 comment · May be fixed by #18647
Open

Incompatible ParamSpecs are not reported as issue #17766

pfaion opened this issue Sep 13, 2024 · 1 comment · May be fixed by #18647
Labels
bug mypy got something wrong topic-paramspec PEP 612, ParamSpec, Concatenate topic-type-variables

Comments

@pfaion
Copy link

pfaion commented Sep 13, 2024

Bug Report

Hey everyone, thanks for all the great work on mypy!

I ran into an issue where mypy does not warn if 2 ParamSpecs need to align but they don't. Hard to describe, so here's an example:

To Reproduce

from collections.abc import Callable
from typing import ParamSpec

P = ParamSpec("P")

def foo(cb1: Callable[P, None], cb2: Callable[P, None]) -> None:
    pass
  
def my_callback_1(x: int) -> None:
    pass

def my_callback_2(x: str) -> None:
    pass

foo(my_callback_1, my_callback_2)

Playground link: https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=f2fac01dde7a953c5dec5117428350f6

Expected Behavior

I think mypy should error out on the foo() call since it's not possible to resolve both Callables to a common ParamSpec P.

When I put the same code into VSCode, pylance reports the following error:

Argument of type "(x: str) -> None" cannot be assigned to parameter "cb2" of type "(**P@foo) -> None" in function "foo"
  Type "(x: str) -> None" is not assignable to type "(x: int) -> None"
    Parameter 1: type "int" is incompatible with type "str"
      "int" is not assignable to "str"
Pylance[reportArgumentType](https://github.com/microsoft/pyright/blob/main/docs/configuration.md#reportArgumentType)

Actual Behavior

mypy does not report any issues:

Success: no issues found in 1 source file

Your Environment

See the playground link above, but when I run into this locally, I have:

  • Mypy version used: mypy 1.11.2 (compiled: yes)
  • Mypy command-line flags: --strict
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: Python 3.12.2
@pfaion pfaion added the bug mypy got something wrong label Sep 13, 2024
@pfaion pfaion changed the title Incompatible ParamSpecs are not causing any issue Incompatible ParamSpecs are not reported as issue Sep 13, 2024
@sobolevn sobolevn added topic-paramspec PEP 612, ParamSpec, Concatenate topic-type-variables labels Sep 15, 2024
@sterliakov
Copy link
Collaborator

sterliakov commented Feb 5, 2025

I'm not sure what the right course of action is here.

First of all, P in the snippet can be satisfied by def _(x: Never) -> None, and that's exactly how mypy solves these constraints. Function with such signature can exist, so formally there is no bug.

def foo(cb1: Callable[P, None], cb2: Callable[P, None]) -> Callable[P, None]:
    pass
  
def my_callback_1(x: int) -> None:
    pass

def my_callback_2(x: str) -> None:
    pass

reveal_type(foo(my_callback_1, my_callback_2))  # N: Revealed type is "def (x: Never)"

mypy does detect truly non-overlapping signatures (e.g. def f(x: int) -> None and def g(*, y: str) -> None are rejected in foo(f, g)). It doesn't:(

However, such functions are of rather limited use (assert_never is one of them), and the requested behaviour is more intuitive and useful. This behaviour certainly needs to be formalized first: should that be "all arguments inferred to Never"? "at least one argument inferred to Never"? Something else?

Current spec does not explicitly define incompatible ParamSpec caused by one or more args with constraints satisfied only by bottom type:

Just as with traditional TypeVars, a user may include the same ParamSpec multiple times in the arguments of the same function, to indicate a dependency between multiple arguments. In these cases a type checker may choose to solve to a common behavioral supertype (i.e. a set of parameters for which all of the valid calls are valid in both of the subtypes), but is not obligated to do so.

# Quoted from typing spec
P = ParamSpec("P")

def foo(x: Callable[P, int], y: Callable[P, int]) -> Callable[P, bool]: ...

def x_y(x: int, y: str) -> int: ...
def y_x(y: int, x: str) -> int: ...

foo(x_y, x_y)  # Should return (x: int, y: str) -> bool
               # (a callable with two positional-or-keyword parameters)

foo(x_y, y_x)  # Could return (a: int, b: str, /) -> bool
               # (a callable with two positional-only parameters)
               # This works because both callables have types that are
               # behavioral subtypes of Callable[[int, str], int]


def keyword_only_x(*, x: int) -> int: ...
def keyword_only_y(*, y: int) -> int: ...
foo(keyword_only_x, keyword_only_y) # Rejected

There's nothing technically challenging in changing this behaviour:

diff --git a/mypy/applytype.py b/mypy/applytype.py
index e87bf939c..e443f98db 100644
--- a/mypy/applytype.py
+++ b/mypy/applytype.py
@@ -43,6 +43,10 @@ def get_target_type(
     if isinstance(p_type, UninhabitedType) and tvar.has_default():
         return tvar.default
     if isinstance(tvar, ParamSpecType):
+        if isinstance(p_type, Parameters) and any(isinstance(get_proper_type(p), UninhabitedType) for p in p_type.arg_types):
+            if skip_unsatisfied:
+                return None
+            report_incompatible_typevar_value(callable, type, tvar.name, context)
         return type
     if isinstance(tvar, TypeVarTupleType):
         return type

@sterliakov sterliakov linked a pull request Feb 9, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-paramspec PEP 612, ParamSpec, Concatenate topic-type-variables
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants