From 282467cdb6f76b3490e776b744b3b1e2701cc55c Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Nov 2025 13:48:08 -0500 Subject: [PATCH 1/4] [invalid-name] Fix FP for exclusive assignment of objects --- doc/whatsnew/fragments/10745.false_positive | 4 ++++ pylint/checkers/base/name_checker/checker.py | 11 ++++------- .../invalid/invalid_name/invalid_name_module_level.py | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 doc/whatsnew/fragments/10745.false_positive diff --git a/doc/whatsnew/fragments/10745.false_positive b/doc/whatsnew/fragments/10745.false_positive new file mode 100644 index 0000000000..3915e3b405 --- /dev/null +++ b/doc/whatsnew/fragments/10745.false_positive @@ -0,0 +1,4 @@ +Fix a false positive for ``invalid-name`` on an UPPER_CASED name inside an +``if`` branch that assigns an object. + +Closes #10745 diff --git a/pylint/checkers/base/name_checker/checker.py b/pylint/checkers/base/name_checker/checker.py index 4530e78822..919f9cad4a 100644 --- a/pylint/checkers/base/name_checker/checker.py +++ b/pylint/checkers/base/name_checker/checker.py @@ -513,18 +513,15 @@ def visit_assignname( # pylint: disable=too-many-branches,too-many-statements else: node_type = "variable" iattrs = tuple(node.frame().igetattr(node.name)) + attrs = tuple(node.frame().getattr(node.name)) if ( util.Uninferable in iattrs and self._name_regexps["const"].match(node.name) is not None ): return - if ( - util.Uninferable not in iattrs - and len(iattrs) > 1 - and all( - astroid.are_exclusive(*combo) - for combo in itertools.combinations(iattrs, 2) - ) + if len(attrs) > 1 and all( + astroid.are_exclusive(*combo) + for combo in itertools.combinations(attrs, 2) ): node_type = "const" if not self._meets_exception_for_non_consts( diff --git a/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py index 99323d1119..12996d7931 100644 --- a/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py +++ b/tests/functional/i/invalid/invalid_name/invalid_name_module_level.py @@ -44,6 +44,12 @@ def A(): # [invalid-name] other_const = [3] +if CONST: + ANOTHER_CONST = A() +else: + ANOTHER_CONST = 5 + + from importlib.metadata import PackageNotFoundError from importlib.metadata import version From 30c857a9824c2fc444d02389da60bd3514c996cd Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Nov 2025 15:00:54 -0500 Subject: [PATCH 2/4] Add comment, add micro-optimization --- pylint/checkers/base/name_checker/checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pylint/checkers/base/name_checker/checker.py b/pylint/checkers/base/name_checker/checker.py index 919f9cad4a..9b3ed0ed1f 100644 --- a/pylint/checkers/base/name_checker/checker.py +++ b/pylint/checkers/base/name_checker/checker.py @@ -513,12 +513,14 @@ def visit_assignname( # pylint: disable=too-many-branches,too-many-statements else: node_type = "variable" iattrs = tuple(node.frame().igetattr(node.name)) - attrs = tuple(node.frame().getattr(node.name)) if ( util.Uninferable in iattrs and self._name_regexps["const"].match(node.name) is not None ): return + # Do the exclusive assignment analysis on attrs, not iattrs. + # iattrs locations could be anywhere (inference result). + attrs = tuple(node.frame().getattr(node.name)) if len(attrs) > 1 and all( astroid.are_exclusive(*combo) for combo in itertools.combinations(attrs, 2) From 9f5ac2ef130b03370d46842b73c936eab0b0960f Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Nov 2025 15:05:44 -0500 Subject: [PATCH 3/4] wordlist --- custom_dict.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_dict.txt b/custom_dict.txt index 77df598cf8..d705ad8c96 100644 --- a/custom_dict.txt +++ b/custom_dict.txt @@ -144,6 +144,7 @@ gv hashable hmac html +iattrs idgeneratormixin ifexpr igetattr From da1dd72358903c64a8e5a3444aec8f15d90eee69 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Sat, 15 Nov 2025 15:41:52 -0500 Subject: [PATCH 4/4] wordlist --- custom_dict.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_dict.txt b/custom_dict.txt index d705ad8c96..9428ac6d79 100644 --- a/custom_dict.txt +++ b/custom_dict.txt @@ -24,6 +24,7 @@ asynccontextmanager attr attrib attrname +attrs backport BaseChecker basename