From a58a971130afeff73b0b49491c1ab488d4733b57 Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Wed, 2 Apr 2025 18:12:41 +0200 Subject: [PATCH 1/6] fix: check for type strict equality to include union and non-union mismatch --- mypy/messages.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 25c4ed68ccb5..25b1908a8ea7 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2439,7 +2439,11 @@ def generate_incompatible_tuple_error( error_cnt = 0 notes: list[str] = [] for i, (lhs_t, rhs_t) in enumerate(zip(lhs_types, rhs_types)): - if not is_subtype(lhs_t, rhs_t): + # If `lhs_t` is a subtype of `rhs_t`, ensure it's not strictly equal + # to capture both union and non-union mismatches. + # Example: is_subtype(str, str | None) -> True, but this should still be an error + # as is_same_type(str, str | None) -> False + if not is_subtype(lhs_t, rhs_t) or not is_same_type(lhs_t, rhs_t): if error_cnt < 3: notes.append( "Expression tuple item {} has type {}; {} expected; ".format( From 220a2f422b3abb46a7820cb8681b7e1bb3874191 Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Fri, 4 Apr 2025 12:14:29 +0200 Subject: [PATCH 2/6] test: union and non-union type mismatch case --- test-data/unit/check-classes.test | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 65a6a0c9c0a8..7c182c9d6470 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1665,6 +1665,45 @@ a.f = a.f # E: Property "f" defined in "A" is read-only a.f.x # E: "int" has no attribute "x" [builtins fixtures/property.pyi] +[case testPropertyWithTupleUnionAndNonUnionMismatchReturnValueType] +from typing import Tuple, Union +class A: + a: str + b: str + c: str + d: str + e: str + f: str + g: Union[str, int] + h: Union[str, float] + i: Union[str, None] + j: Union[str, None] + k: Union[str, None] + l: Union[str, None] + + @property + def x(self) -> Tuple[str, str, str, str, str, str, str, str, str, str, str, str]: + return ( + self.a, + self.b, + self.c, + self.d, + self.e, + self.f, + self.g, + self.h, + self.i, + self.j, + self.k, + self.l, + ) +[out] +main:18: error: Incompatible return value type (6 tuple items are incompatible; 3 items are omitted) +main:18: note: Expression tuple item 6 has type "Union[str, int]"; "str" expected; +main:18: note: Expression tuple item 7 has type "Union[str, float]"; "str" expected; +main:18: note: Expression tuple item 8 has type "Optional[str]"; "str" expected; +[builtins fixtures/property.pyi] + -- Descriptors -- ----------- From 11a6c13ee1a5e6efe44836f1068bdce72aafcd1e Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Fri, 4 Apr 2025 16:20:49 +0200 Subject: [PATCH 3/6] refactor: tuple types subtype check --- mypy/messages.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 25b1908a8ea7..2e07d7f63498 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -2439,11 +2439,7 @@ def generate_incompatible_tuple_error( error_cnt = 0 notes: list[str] = [] for i, (lhs_t, rhs_t) in enumerate(zip(lhs_types, rhs_types)): - # If `lhs_t` is a subtype of `rhs_t`, ensure it's not strictly equal - # to capture both union and non-union mismatches. - # Example: is_subtype(str, str | None) -> True, but this should still be an error - # as is_same_type(str, str | None) -> False - if not is_subtype(lhs_t, rhs_t) or not is_same_type(lhs_t, rhs_t): + if not is_subtype(rhs_t, lhs_t): if error_cnt < 3: notes.append( "Expression tuple item {} has type {}; {} expected; ".format( From 9109962f59f2b4b1bb60d9ef312cb4e3685a5263 Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Fri, 4 Apr 2025 18:09:21 +0200 Subject: [PATCH 4/6] test: wider expected return value --- test-data/unit/check-classes.test | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7c182c9d6470..b0509566a4c7 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1704,6 +1704,44 @@ main:18: note: Expression tuple item 7 has type "Union[str, float]"; "str" expec main:18: note: Expression tuple item 8 has type "Optional[str]"; "str" expected; [builtins fixtures/property.pyi] +[case testPropertyWithTupleUnionAndNonUnionMismatchReturnValueType_wider_expeced_return] +from typing import Tuple, Union +class A: + a: str + b: str + c: str + d: str + e: str + f: str + g: str + h: str + i: str + j: str + k: str + l: Union[float, int] + + @property + def x(self) -> Tuple[Union[str, int], Union[str, float], int, Union[str, None], Union[str, None], Union[str, None], str, str, str, str, str, str]: + return ( + self.a, + self.b, + self.c, + self.d, + self.e, + self.f, + self.g, + self.h, + self.i, + self.j, + self.k, + self.l, + ) +[out] +main:18: error: Incompatible return value type (2 tuple items are incompatible) +main:18: note: Expression tuple item 2 has type "str"; "int" expected; +main:18: note: Expression tuple item 11 has type "Union[float, int]"; "str" expected; +[builtins fixtures/property.pyi] + -- Descriptors -- ----------- From 57177bf9aa146a564f272a1ae32d581dc9468902 Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Fri, 4 Apr 2025 19:45:37 +0200 Subject: [PATCH 5/6] test: better case names --- test-data/unit/check-classes.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index b0509566a4c7..29544a317840 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1665,7 +1665,7 @@ a.f = a.f # E: Property "f" defined in "A" is read-only a.f.x # E: "int" has no attribute "x" [builtins fixtures/property.pyi] -[case testPropertyWithTupleUnionAndNonUnionMismatchReturnValueType] +[case testPropertyReturnTypeMismatchUnion] from typing import Tuple, Union class A: a: str @@ -1704,7 +1704,7 @@ main:18: note: Expression tuple item 7 has type "Union[str, float]"; "str" expec main:18: note: Expression tuple item 8 has type "Optional[str]"; "str" expected; [builtins fixtures/property.pyi] -[case testPropertyWithTupleUnionAndNonUnionMismatchReturnValueType_wider_expeced_return] +[case testPropertyTupleReturnTypeMismatchUnionWiderExpected] from typing import Tuple, Union class A: a: str From e36a0c75cb676bd91503553ecc6b37329673f4f6 Mon Sep 17 00:00:00 2001 From: Thomas Mattone Date: Sun, 6 Apr 2025 00:18:45 +0200 Subject: [PATCH 6/6] refactor: move test --- test-data/unit/check-classes.test | 77 ------------------------------- test-data/unit/check-tuples.test | 77 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 29544a317840..65a6a0c9c0a8 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1665,83 +1665,6 @@ a.f = a.f # E: Property "f" defined in "A" is read-only a.f.x # E: "int" has no attribute "x" [builtins fixtures/property.pyi] -[case testPropertyReturnTypeMismatchUnion] -from typing import Tuple, Union -class A: - a: str - b: str - c: str - d: str - e: str - f: str - g: Union[str, int] - h: Union[str, float] - i: Union[str, None] - j: Union[str, None] - k: Union[str, None] - l: Union[str, None] - - @property - def x(self) -> Tuple[str, str, str, str, str, str, str, str, str, str, str, str]: - return ( - self.a, - self.b, - self.c, - self.d, - self.e, - self.f, - self.g, - self.h, - self.i, - self.j, - self.k, - self.l, - ) -[out] -main:18: error: Incompatible return value type (6 tuple items are incompatible; 3 items are omitted) -main:18: note: Expression tuple item 6 has type "Union[str, int]"; "str" expected; -main:18: note: Expression tuple item 7 has type "Union[str, float]"; "str" expected; -main:18: note: Expression tuple item 8 has type "Optional[str]"; "str" expected; -[builtins fixtures/property.pyi] - -[case testPropertyTupleReturnTypeMismatchUnionWiderExpected] -from typing import Tuple, Union -class A: - a: str - b: str - c: str - d: str - e: str - f: str - g: str - h: str - i: str - j: str - k: str - l: Union[float, int] - - @property - def x(self) -> Tuple[Union[str, int], Union[str, float], int, Union[str, None], Union[str, None], Union[str, None], str, str, str, str, str, str]: - return ( - self.a, - self.b, - self.c, - self.d, - self.e, - self.f, - self.g, - self.h, - self.i, - self.j, - self.k, - self.l, - ) -[out] -main:18: error: Incompatible return value type (2 tuple items are incompatible) -main:18: note: Expression tuple item 2 has type "str"; "int" expected; -main:18: note: Expression tuple item 11 has type "Union[float, int]"; "str" expected; -[builtins fixtures/property.pyi] - -- Descriptors -- ----------- diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index d675a35c4aae..3424d053fe42 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1607,6 +1607,83 @@ t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3 [builtins fixtures/tuple.pyi] +[case testPropertyLongTupleReturnTypeMismatchUnion] +from typing import Tuple, Union +class A: + a: str + b: str + c: str + d: str + e: str + f: str + g: Union[str, int] + h: Union[str, float] + i: Union[str, None] + j: Union[str, None] + k: Union[str, None] + l: Union[str, None] + + @property + def x(self) -> Tuple[str, str, str, str, str, str, str, str, str, str, str, str]: + return ( + self.a, + self.b, + self.c, + self.d, + self.e, + self.f, + self.g, + self.h, + self.i, + self.j, + self.k, + self.l, + ) +[out] +main:18: error: Incompatible return value type (6 tuple items are incompatible; 3 items are omitted) +main:18: note: Expression tuple item 6 has type "Union[str, int]"; "str" expected; +main:18: note: Expression tuple item 7 has type "Union[str, float]"; "str" expected; +main:18: note: Expression tuple item 8 has type "Optional[str]"; "str" expected; +[builtins fixtures/property.pyi] + +[case testPropertyLongTupleReturnTypeMismatchUnionWiderExpected] +from typing import Tuple, Union +class A: + a: str + b: str + c: str + d: str + e: str + f: str + g: str + h: str + i: str + j: str + k: str + l: Union[float, int] + + @property + def x(self) -> Tuple[Union[str, int], Union[str, float], int, Union[str, None], Union[str, None], Union[str, None], str, str, str, str, str, str]: + return ( + self.a, + self.b, + self.c, + self.d, + self.e, + self.f, + self.g, + self.h, + self.i, + self.j, + self.k, + self.l, + ) +[out] +main:18: error: Incompatible return value type (2 tuple items are incompatible) +main:18: note: Expression tuple item 2 has type "str"; "int" expected; +main:18: note: Expression tuple item 11 has type "Union[float, int]"; "str" expected; +[builtins fixtures/property.pyi] + [case testTupleWithStarExpr] from typing import Tuple, List points = (1, "test") # type: Tuple[int, str]