From a27dec535e3eb1ed8dab1625e592bce5ab9a7972 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 27 Feb 2023 18:22:58 +0000 Subject: [PATCH] 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`. --- mypy/meet.py | 17 +++++++++++++- test-data/unit/pythoneval.test | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/mypy/meet.py b/mypy/meet.py index d99e1a92d2eb..3214b4b43975 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -342,7 +342,22 @@ def _is_overlapping_types(left: Type, right: Type) -> bool: left_possible = get_possible_variants(left) right_possible = get_possible_variants(right) - # We start by checking multi-variant types like Unions first. We also perform + # First handle special cases relating to PEP 612: + # - comparing a `Parameters` to a `Parameters` + # - comparing a `Parameters` to a `ParamSpecType` + # - comparing a `ParamSpecType` to a `ParamSpecType` + # + # These should all always be considered overlapping equality checks. + # These need to be done before we move on to other TypeVarLike comparisons. + if isinstance(left, (Parameters, ParamSpecType)) and isinstance( + right, (Parameters, ParamSpecType) + ): + return True + # A `Parameters` does not overlap with anything else, however + if isinstance(left, Parameters) or isinstance(right, Parameters): + return False + + # Now move on to checking multi-variant types like Unions. We also perform # the same logic if either type happens to be a TypeVar/ParamSpec/TypeVarTuple. # # Handling the TypeVarLikes now lets us simulate having them bind to the corresponding diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test index fbbaecbba241..a3413e071184 100644 --- a/test-data/unit/pythoneval.test +++ b/test-data/unit/pythoneval.test @@ -1924,3 +1924,46 @@ _testStarUnpackNestedUnderscore.py:10: error: List item 0 has incompatible type _testStarUnpackNestedUnderscore.py:10: error: List item 1 has incompatible type "int"; expected "str" _testStarUnpackNestedUnderscore.py:11: note: Revealed type is "builtins.list[builtins.str]" _testStarUnpackNestedUnderscore.py:16: note: Revealed type is "builtins.list[builtins.object]" + +[case testStrictEqualitywithParamSpec] +# flags: --strict-equality +from typing import Generic +from typing_extensions import Concatenate, ParamSpec + +P = ParamSpec("P") + +class Foo(Generic[P]): ... +class Bar(Generic[P]): ... + +def bad(foo: Foo[[int]], bar: Bar[[int]]) -> bool: + return foo == bar + +def good1(foo1: Foo[[int]], foo2: Foo[[str]]) -> bool: + return foo1 == foo2 + +def good2(foo1: Foo[[int, str]], foo2: Foo[[int, bytes]]) -> bool: + return foo1 == foo2 + +def good3(foo1: Foo[[int]], foo2: Foo[[int, int]]) -> bool: + return foo1 == foo2 + +def good4(foo1: Foo[[int]], foo2: Foo[[int]]) -> bool: + return foo1 == foo2 + +def good5(foo1: Foo[[int]], foo2: Foo[[bool]]) -> bool: + return foo1 == foo2 + +def good6(foo1: Foo[[int, int]], foo2: Foo[[bool, bool]]) -> bool: + return foo1 == foo2 + +def good7(foo1: Foo[[int]], foo2: Foo[P], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +def good8(foo1: Foo[P], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +def good9(foo1: Foo[Concatenate[int, P]], foo2: Foo[[int, str, bytes]], *args: P.args, **kwargs: P.kwargs) -> bool: + return foo1 == foo2 + +[out] +_testStrictEqualitywithParamSpec.py:11: error: Non-overlapping equality check (left operand type: "Foo[[int]]", right operand type: "Bar[[int]]")