From 47ebf8d623a28aab51dbabb4cf3bafa920cc585d Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 1 Dec 2025 12:20:56 -0800 Subject: [PATCH 1/3] Fix noncommutative joins with bounded TypeVars Fixes #20344 --- mypy/join.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mypy/join.py b/mypy/join.py index 0822ddbfd89a..a074fa522588 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -297,10 +297,15 @@ def visit_erased_type(self, t: ErasedType) -> ProperType: return self.s def visit_type_var(self, t: TypeVarType) -> ProperType: - if isinstance(self.s, TypeVarType) and self.s.id == t.id: - if self.s.upper_bound == t.upper_bound: - return self.s - return self.s.copy_modified(upper_bound=join_types(self.s.upper_bound, t.upper_bound)) + if isinstance(self.s, TypeVarType): + if self.s.id == t.id: + if self.s.upper_bound == t.upper_bound: + return self.s + return self.s.copy_modified( + upper_bound=join_types(self.s.upper_bound, t.upper_bound) + ) + # Fix non-commutative joins + return get_proper_type(join_types(self.s.upper_bound, t.upper_bound)) else: return self.default(self.s) From 6c6fa8f8d118165ecf224feb23af167f4c1695ed Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 1 Dec 2025 17:58:01 -0800 Subject: [PATCH 2/3] test --- mypy/test/testtypes.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index fc68d9aa6eac..fb5354fe4a14 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -1051,6 +1051,30 @@ def test_join_type_type_type_var(self) -> None: self.assert_join(self.fx.type_a, self.fx.t, self.fx.o) self.assert_join(self.fx.t, self.fx.type_a, self.fx.o) + def test_join_type_var_bounds(self) -> None: + tvar1 = TypeVarType( + "tvar1", + "tvar1", + TypeVarId(-100), + [], + self.fx.o, + AnyType(TypeOfAny.from_omitted_generics), + INVARIANT, + ) + any_type = AnyType(TypeOfAny.special_form) + tvar2 = TypeVarType( + "tvar2", + "tvar2", + TypeVarId(-101), + [], + upper_bound=UnionType([TupleType([any_type], self.fx.std_tuple), TupleType([any_type, any_type], self.fx.std_tuple)]), + default=AnyType(TypeOfAny.from_omitted_generics), + variance=INVARIANT, + ) + + self.assert_join(tvar1, tvar2, self.fx.o) + self.assert_join(tvar2, tvar1, self.fx.o) + # There are additional test cases in check-inference.test. # TODO: Function types + varargs and default args. From 43a70bbf2844d1972c29711b835f3a118f2f563e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 02:09:26 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/test/testtypes.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mypy/test/testtypes.py b/mypy/test/testtypes.py index fb5354fe4a14..f5f4c6797db2 100644 --- a/mypy/test/testtypes.py +++ b/mypy/test/testtypes.py @@ -1067,7 +1067,12 @@ def test_join_type_var_bounds(self) -> None: "tvar2", TypeVarId(-101), [], - upper_bound=UnionType([TupleType([any_type], self.fx.std_tuple), TupleType([any_type, any_type], self.fx.std_tuple)]), + upper_bound=UnionType( + [ + TupleType([any_type], self.fx.std_tuple), + TupleType([any_type, any_type], self.fx.std_tuple), + ] + ), default=AnyType(TypeOfAny.from_omitted_generics), variance=INVARIANT, )