From 9bdf823d4120231cc738a3965b5e552c25e6d1c9 Mon Sep 17 00:00:00 2001 From: Massimiliano Bruni Date: Sat, 18 Jul 2026 12:26:16 +0200 Subject: [PATCH 1/5] fix(typing): deduplicate unhashable Literal params --- Lib/test/test_typing.py | 5 +++-- Lib/typing.py | 9 +++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 17574c7f03d8e1..19802f9fb6d0e9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2801,8 +2801,9 @@ def test_args(self): self.assertEqual(Literal[1, 2, 3].__args__, (1, 2, 3)) self.assertEqual(Literal[1, 2, 3, 3].__args__, (1, 2, 3)) self.assertEqual(Literal[1, Literal[2], Literal[3, 4]].__args__, (1, 2, 3, 4)) - # Mutable arguments will not be deduplicated - self.assertEqual(Literal[[], []].__args__, ([], [])) + # Mutable arguments will be deduplicated + self.assertEqual(Literal[[], []].__args__, ([],)) + self.assertEqual(Literal[{"a": 1}, {"a": 1}].__args__, ({"a": 1},)) def test_flatten(self): l1 = Literal[Literal[1], Literal[2], Literal[3]] diff --git a/Lib/typing.py b/Lib/typing.py index 933336ff4cf37e..c2ea4823a7c313 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -775,13 +775,10 @@ def open_helper(file: str, mode: MODE) -> str: # There is no '_type_check' call because arguments to Literal[...] are # values, not types. parameters = _flatten_literal_params(parameters) + value_and_type_parameters = list(_value_and_type_iter(parameters)) + deduplicated_parameters = tuple(p for p, _ in _deduplicate(value_and_type_parameters, unhashable_fallback=True)) - try: - parameters = tuple(p for p, _ in _deduplicate(list(_value_and_type_iter(parameters)))) - except TypeError: # unhashable parameters - pass - - return _LiteralGenericAlias(self, parameters) + return _LiteralGenericAlias(self, deduplicated_parameters) @_SpecialForm From 9442d6a86f14c726d00e1d3e9992777be8ac20c5 Mon Sep 17 00:00:00 2001 From: Massimiliano Bruni Date: Sat, 18 Jul 2026 12:43:20 +0200 Subject: [PATCH 2/5] tests: add 2 more test cases --- Lib/test/test_typing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 19802f9fb6d0e9..890b43efb4971a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2804,6 +2804,11 @@ def test_args(self): # Mutable arguments will be deduplicated self.assertEqual(Literal[[], []].__args__, ([],)) self.assertEqual(Literal[{"a": 1}, {"a": 1}].__args__, ({"a": 1},)) + self.assertEqual( + Literal[1, {'a': 'b'}, 2, {'a': 'b'}, 3].__args__, + (1, {'a': 'b'}, 2, 3), + ) + self.assertEqual(Literal[{1}, {1}, {2}, {2}].__args__, ({1}, {2})) def test_flatten(self): l1 = Literal[Literal[1], Literal[2], Literal[3]] From 6ec1018479ab2977382d2f26d4ade8d94bc52406 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:52:12 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-07-18-10-52-10.gh-issue-153896.87oevp.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-18-10-52-10.gh-issue-153896.87oevp.rst diff --git a/Misc/NEWS.d/next/Library/2026-07-18-10-52-10.gh-issue-153896.87oevp.rst b/Misc/NEWS.d/next/Library/2026-07-18-10-52-10.gh-issue-153896.87oevp.rst new file mode 100644 index 00000000000000..217a3d3d272ed0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-18-10-52-10.gh-issue-153896.87oevp.rst @@ -0,0 +1 @@ +Deduplicate unhashable args in :data:`typing.Literal`. From 80caf6813a05a18cdb638e4af9e8ffaf3879afa2 Mon Sep 17 00:00:00 2001 From: Massimiliano Bruni Date: Sat, 18 Jul 2026 13:04:04 +0200 Subject: [PATCH 4/5] chore: mutable -> unhashable --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 890b43efb4971a..c1f340230159db 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2801,7 +2801,7 @@ def test_args(self): self.assertEqual(Literal[1, 2, 3].__args__, (1, 2, 3)) self.assertEqual(Literal[1, 2, 3, 3].__args__, (1, 2, 3)) self.assertEqual(Literal[1, Literal[2], Literal[3, 4]].__args__, (1, 2, 3, 4)) - # Mutable arguments will be deduplicated + # Unhashable arguments will be deduplicated too self.assertEqual(Literal[[], []].__args__, ([],)) self.assertEqual(Literal[{"a": 1}, {"a": 1}].__args__, ({"a": 1},)) self.assertEqual( From 6e95e8464af97a38d3165a64b9d6f090f67d334f Mon Sep 17 00:00:00 2001 From: Massimiliano Bruni Date: Sat, 18 Jul 2026 13:19:25 +0200 Subject: [PATCH 5/5] style: formatting --- Lib/typing.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index c2ea4823a7c313..054420865d7fb5 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -776,7 +776,13 @@ def open_helper(file: str, mode: MODE) -> str: # values, not types. parameters = _flatten_literal_params(parameters) value_and_type_parameters = list(_value_and_type_iter(parameters)) - deduplicated_parameters = tuple(p for p, _ in _deduplicate(value_and_type_parameters, unhashable_fallback=True)) + deduplicated_parameters = tuple( + p + for p, _ in _deduplicate( + value_and_type_parameters, + unhashable_fallback=True, + ) + ) return _LiteralGenericAlias(self, deduplicated_parameters)