Repository: python/mypy

Issue:
Crash involving `ParamSpec`, `Concatenate` and `overloads`
**Crash Report**

The following snippet of code causes a mypy crash:

```python
from typing import ParamSpec, TypeVar, Concatenate, Callable, overload

P = ParamSpec("P")
R = TypeVar("R")
S = TypeVar("S")

@overload
def bar(x: Callable[Concatenate[S, P], R]) -> str: ...
@overload
def bar(x: Callable[P, R]) -> int: ...
def bar(x: Callable[..., R]) -> str | int: ...
```

Discovered in https://github.com/python/typeshed/pull/7771.


**Mypy output**

```
Traceback (most recent call last):
  File "/opt/python3.9/lib/python3.9/runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/opt/python3.9/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "mypy/checker.py", line 431, in accept
  File "mypy/nodes.py", line 581, in accept
  File "mypy/checker.py", line 464, in visit_overloaded_func_def
  File "mypy/checker.py", line 491, in _visit_overloaded_func_def
  File "mypy/checker.py", line 573, in check_overlapping_overloads
  File "mypy/checker.py", line 6118, in is_unsafe_overlapping_overload_signatures
  File "mypy/subtypes.py", line 982, in is_callable_compatible
  File "mypy/subtypes.py", line 1051, in are_parameters_compatible
  File "mypy/subtypes.py", line 1178, in are_args_compatible
  File "mypy/subtypes.py", line 1183, in new_is_compat
  File "mypy/checker.py", line 6603, in is_overlapping_types_no_promote
  File "mypy/meet.py", line 335, in is_overlapping_types
  File "mypy/subtypes.py", line 982, in is_callable_compatible
  File "mypy/subtypes.py", line 1039, in are_parameters_compatible
  File "mypy/subtypes.py", line 1037, in _incompatible
  File "mypy/meet.py", line 181, in _is_overlapping_types
  File "mypy/meet.py", line 401, in is_overlapping_types
AssertionError: 
main.py:7: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.960
main.py:7: : note: use --pdb to drop into pdb
```

Note that this is a very similar traceback to #12322, but I have no idea if it's the same issue, since #12322 doesn't have a minimal repro.

**To Reproduce**

https://mypy-play.net/?mypy=latest&python=3.10&flags=show-traceback&gist=13ea15dacce1cac389fbd26c5fca569f

**Your Environment**

- Mypy version used: 0.960
- Python version used: 3.10



Discussion:

Resolving patch:
diff --git a/mypy/meet.py b/mypy/meet.py
--- a/mypy/meet.py
+++ b/mypy/meet.py
@@ -6,7 +6,7 @@
     TupleType, TypedDictType, ErasedType, UnionType, PartialType, DeletedType,
     UninhabitedType, TypeType, TypeOfAny, Overloaded, FunctionLike, LiteralType,
     ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeGuardedType,
-    ParamSpecType, Parameters, UnpackType, TypeVarTupleType,
+    ParamSpecType, Parameters, UnpackType, TypeVarTupleType, TypeVarLikeType
 )
 from mypy.subtypes import is_equivalent, is_subtype, is_callable_compatible, is_proper_subtype
 from mypy.erasetype import erase_type
@@ -117,8 +117,8 @@ def get_possible_variants(typ: Type) -> List[Type]:
     If this function receives any other type, we return a list containing just that
     original type. (E.g. pretend the type was contained within a singleton union).
 
-    The only exception is regular TypeVars: we return a list containing that TypeVar's
-    upper bound.
+    The only current exceptions are regular TypeVars and ParamSpecs. For these "TypeVarLike"s,
+    we return a list containing that TypeVarLike's upper bound.
 
     This function is useful primarily when checking to see if two types are overlapping:
     the algorithm to check if two unions are overlapping is fundamentally the same as
@@ -134,6 +134,8 @@ def get_possible_variants(typ: Type) -> List[Type]:
             return typ.values
         else:
             return [typ.upper_bound]
+    elif isinstance(typ, ParamSpecType):
+        return [typ.upper_bound]
     elif isinstance(typ, UnionType):
         return list(typ.items)
     elif isinstance(typ, Overloaded):
@@ -244,36 +246,36 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
     right_possible = get_possible_variants(right)
 
     # We start by checking multi-variant types like Unions first. We also perform
-    # the same logic if either type happens to be a TypeVar.
+    # the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple.
     #
-    # Handling the TypeVars now lets us simulate having them bind to the corresponding
+    # Handling the TypeVarLikes now lets us simulate having them bind to the corresponding
     # type -- if we deferred these checks, the "return-early" logic of the other
     # checks will prevent us from detecting certain overlaps.
     #
-    # If both types are singleton variants (and are not TypeVars), we've hit the base case:
+    # If both types are singleton variants (and are not TypeVarLikes), we've hit the base case:
     # we skip these checks to avoid infinitely recursing.
 
-    def is_none_typevar_overlap(t1: Type, t2: Type) -> bool:
+    def is_none_typevarlike_overlap(t1: Type, t2: Type) -> bool:
         t1, t2 = get_proper_types((t1, t2))
-        return isinstance(t1, NoneType) and isinstance(t2, TypeVarType)
+        return isinstance(t1, NoneType) and isinstance(t2, TypeVarLikeType)
 
     if prohibit_none_typevar_overlap:
-        if is_none_typevar_overlap(left, right) or is_none_typevar_overlap(right, left):
+        if is_none_typevarlike_overlap(left, right) or is_none_typevarlike_overlap(right, left):
             return False
 
     if (len(left_possible) > 1 or len(right_possible) > 1
-            or isinstance(left, TypeVarType) or isinstance(right, TypeVarType)):
+            or isinstance(left, TypeVarLikeType) or isinstance(right, TypeVarLikeType)):
         for l in left_possible:
             for r in right_possible:
                 if _is_overlapping_types(l, r):
                     return True
         return False
 
-    # Now that we've finished handling TypeVars, we're free to end early
+    # Now that we've finished handling TypeVarLikes, we're free to end early
     # if one one of the types is None and we're running in strict-optional mode.
     # (None only overlaps with None in strict-optional mode).
     #
-    # We must perform this check after the TypeVar checks because
+    # We must perform this check after the TypeVarLike checks because
     # a TypeVar could be bound to None, for example.
 
     if state.strict_optional and isinstance(left, NoneType) != isinstance(right, NoneType):


Test patch:
diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test
--- a/test-data/unit/check-overloading.test
+++ b/test-data/unit/check-overloading.test
@@ -6506,3 +6506,33 @@ if True:
         @overload
         def f3(g: D) -> D: ...
 def f3(g): ...  # E: Name "f3" already defined on line 32
+
+[case testOverloadingWithParamSpec]
+from typing import TypeVar, Callable, Any, overload
+from typing_extensions import ParamSpec, Concatenate
+
+P = ParamSpec("P")
+R = TypeVar("R")
+
+@overload
+def func(x: Callable[Concatenate[Any, P], R]) -> Callable[P, R]: ...  # E: Overloaded function signatures 1 and 2 overlap with incompatible return types
+@overload
+def func(x: Callable[P, R]) -> Callable[Concatenate[str, P], R]: ...
+def func(x: Callable[..., R]) -> Callable[..., R]: ...
+
+def foo(arg1: str, arg2: int) -> bytes: ...
+reveal_type(func(foo))  # N: Revealed type is "def (arg2: builtins.int) -> builtins.bytes"
+
+def bar() -> int: ...
+reveal_type(func(bar))  # N: Revealed type is "def (builtins.str) -> builtins.int"
+
+baz: Callable[[str, str], str] = lambda x, y: 'baz'
+reveal_type(func(baz))  # N: Revealed type is "def (builtins.str) -> builtins.str"
+
+eggs = lambda: 'eggs'
+reveal_type(func(eggs))  # N: Revealed type is "def (builtins.str) -> builtins.str"
+
+spam: Callable[..., str] = lambda x, y: 'baz'
+reveal_type(func(spam))  # N: Revealed type is "def (*Any, **Any) -> Any"
+
+[builtins fixtures/paramspec.pyi]