Skip to content

Commit a27dec5

Browse files
AlexWaygoodkoogoro
authored andcommitted
Fix --strict-equality crash for instances of a class generic over a ParamSpec (#14792)
Fixes #14783. Running mypy on this snippet of code currently causes a crash if you have the `--strict-equality` option enabled: ```python from typing import Generic, ParamSpec P = ParamSpec("P") class Foo(Generic[P]): ... def checker(foo1: Foo[[int]], foo2: Foo[[str]]) -> None: foo1 == foo2 ``` This is because the overlapping-equality logic in `meet.py` currently does not account for the fact that `left` and `right` might both be instances of `mypy.types.Parameters`, leading to this assertion being tripped: https://github.com/python/mypy/blob/800e8ffdf17de9fc641fefff46389a940f147eef/mypy/meet.py#L519 This PR attempts to add the necessary logic to `meet.py` to handle instances of `mypy.types.Parameters`.
1 parent c201658 commit a27dec5

File tree

2 files changed

+59
-1
lines changed

2 files changed

+59
-1
lines changed

Diff for: mypy/meet.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,22 @@ def _is_overlapping_types(left: Type, right: Type) -> bool:
342342
left_possible = get_possible_variants(left)
343343
right_possible = get_possible_variants(right)
344344

345-
# We start by checking multi-variant types like Unions first. We also perform
345+
# First handle special cases relating to PEP 612:
346+
# - comparing a `Parameters` to a `Parameters`
347+
# - comparing a `Parameters` to a `ParamSpecType`
348+
# - comparing a `ParamSpecType` to a `ParamSpecType`
349+
#
350+
# These should all always be considered overlapping equality checks.
351+
# These need to be done before we move on to other TypeVarLike comparisons.
352+
if isinstance(left, (Parameters, ParamSpecType)) and isinstance(
353+
right, (Parameters, ParamSpecType)
354+
):
355+
return True
356+
# A `Parameters` does not overlap with anything else, however
357+
if isinstance(left, Parameters) or isinstance(right, Parameters):
358+
return False
359+
360+
# Now move on to checking multi-variant types like Unions. We also perform
346361
# the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple.
347362
#
348363
# Handling the TypeVarLikes now lets us simulate having them bind to the corresponding

Diff for: test-data/unit/pythoneval.test

+43
Original file line numberDiff line numberDiff line change
@@ -1924,3 +1924,46 @@ _testStarUnpackNestedUnderscore.py:10: error: List item 0 has incompatible type
19241924
_testStarUnpackNestedUnderscore.py:10: error: List item 1 has incompatible type "int"; expected "str"
19251925
_testStarUnpackNestedUnderscore.py:11: note: Revealed type is "builtins.list[builtins.str]"
19261926
_testStarUnpackNestedUnderscore.py:16: note: Revealed type is "builtins.list[builtins.object]"
1927+
1928+
[case testStrictEqualitywithParamSpec]
1929+
# flags: --strict-equality
1930+
from typing import Generic
1931+
from typing_extensions import Concatenate, ParamSpec
1932+
1933+
P = ParamSpec("P")
1934+
1935+
class Foo(Generic[P]): ...
1936+
class Bar(Generic[P]): ...
1937+
1938+
def bad(foo: Foo[[int]], bar: Bar[[int]]) -> bool:
1939+
return foo == bar
1940+
1941+
def good1(foo1: Foo[[int]], foo2: Foo[[str]]) -> bool:
1942+
return foo1 == foo2
1943+
1944+
def good2(foo1: Foo[[int, str]], foo2: Foo[[int, bytes]]) -> bool:
1945+
return foo1 == foo2
1946+
1947+
def good3(foo1: Foo[[int]], foo2: Foo[[int, int]]) -> bool:
1948+
return foo1 == foo2
1949+
1950+
def good4(foo1: Foo[[int]], foo2: Foo[[int]]) -> bool:
1951+
return foo1 == foo2
1952+
1953+
def good5(foo1: Foo[[int]], foo2: Foo[[bool]]) -> bool:
1954+
return foo1 == foo2
1955+
1956+
def good6(foo1: Foo[[int, int]], foo2: Foo[[bool, bool]]) -> bool:
1957+
return foo1 == foo2
1958+
1959+
def good7(foo1: Foo[[int]], foo2: Foo[P], *args: P.args, **kwargs: P.kwargs) -> bool:
1960+
return foo1 == foo2
1961+
1962+
def good8(foo1: Foo[P], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool:
1963+
return foo1 == foo2
1964+
1965+
def good9(foo1: Foo[Concatenate[int, P]], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool:
1966+
return foo1 == foo2
1967+
1968+
[out]
1969+
_testStrictEqualitywithParamSpec.py:11: error: Non-overlapping equality check (left operand type: "Foo[[int]]", right operand type: "Bar[[int]]")

0 commit comments

Comments
 (0)