From 8be6c5b951c4c13eea61aabacd1ef4cbbb1a8397 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 1 Nov 2026 11:59:27 -0500 Subject: [PATCH] Fix duplicate argument checks for normalized Unicode names --- AUTHORS.txt | 1 + parso/python/errors.py | 15 +++++++++------ test/test_python_errors.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 451918f..385b068 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -55,5 +55,6 @@ Tim Gates (@timgates42) Batuhan Taskaya (@isidentical) Jocelyn Boullier (@Kazy) +Rudy Celekli (@rudycelekli) Note: (@user) means a github user name. diff --git a/parso/python/errors.py b/parso/python/errors.py index b498af3..ba4ea9b 100644 --- a/parso/python/errors.py +++ b/parso/python/errors.py @@ -3,6 +3,7 @@ import sys import warnings import re +import unicodedata from contextlib import contextmanager from parso.normalizer import Normalizer, NormalizerConfig, Issue, Rule @@ -942,14 +943,15 @@ def is_issue(self, node): else: # Is a keyword argument. kw_only = True if first.type == 'name': - if first.value in arg_set: + name = unicodedata.normalize('NFKC', first.value) + if name in arg_set: # f(x=1, x=2) message = "keyword argument repeated" if self._normalizer.version >= (3, 9): - message += ": {}".format(first.value) + message += ": {}".format(name) self.add_issue(first, message=message) else: - arg_set.add(first.value) + arg_set.add(name) else: if kw_unpacking_only: # f(**x, y) @@ -978,13 +980,14 @@ def is_issue(self, node): default_only = False continue - if p.name.value in param_names: + name = unicodedata.normalize('NFKC', p.name.value) + if name in param_names: if sys.version_info[:2] < (3, 15): message = "duplicate argument '%s' in function definition" else: message = "duplicate parameter '%s' in function definition" - self.add_issue(p.name, message=message % p.name.value) - param_names.add(p.name.value) + self.add_issue(p.name, message=message % name) + param_names.add(name) if not star_seen: if p.default is None and not p.star_count: diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 56795f2..a453ba3 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -456,6 +456,38 @@ def test_forbidden_name(template, target): assert _get_error_list(template.format(target=target), version="3") +@pytest.mark.parametrize('code', [ + 'def f(K, K): pass', + 'def f(K, K): pass', + 'def f(a, a): pass', + 'def f(K, *, K): pass', + 'lambda K, K: None', + 'lambda a, a: None', + 'f(K=1, K=2)', + 'f(K=1, K=2)', + 'f(a=1, a=2)', +]) +def test_nfkc_duplicate_arguments(code): + wanted, line_nr = _get_actual_exception(code) + errors = _get_error_list(code) + + error, = errors + assert error.message in wanted + assert error.start_pos[0] == line_nr + assert parso.parse(code).get_code() == code + + +@pytest.mark.parametrize('code', [ + 'def f(K, k): pass', + 'def f(K, K2): pass', + 'lambda K, k: None', + 'f(K=1, k=2)', +]) +def test_distinct_unicode_arguments(code): + compile(code, '', 'exec') + assert not _get_error_list(code) + + def test_repeated_kwarg(): # python 3.9+ shows which argument is repeated assert (