Skip to content

Commit 4d8843a

Browse files
worksbyfridaypylint-backport[bot]
authored andcommitted
Fix undefined-variable false positive with metaclass in nested class (#10853)
When an imported name was used as a `metaclass=` argument inside a nested class within a method, pylint incorrectly flagged subsequent uses of that name at module level as `undefined-variable`. Root cause: `_check_classdef_metaclasses()` found the name in enclosing scopes and `_check_metaclasses()` popped it directly from `to_consume`, making it completely unresolvable. Subsequent references to the same name triggered `undefined-variable`. Fix: use `NamesConsumer.mark_as_consumed()` instead of raw dict pop. This properly moves the name from `to_consume` to `consumed`, so: 1. The import is not flagged as unused (original intent) 2. Later references find the name in `consumed` and resolve correctly Closes #10823 (cherry picked from commit f56e41a)
1 parent 88e1ab7 commit 4d8843a

3 files changed

Lines changed: 34 additions & 8 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix ``undefined-variable`` false positive when a name used as a ``metaclass``
2+
argument in a nested class is referenced again later in the module.
3+
4+
Closes #10823

pylint/checkers/variables.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3387,27 +3387,29 @@ def _check_imports(self, not_consumed: Consumption) -> None:
33873387

33883388
def _check_metaclasses(self, node: nodes.Module | nodes.FunctionDef) -> None:
33893389
"""Update consumption analysis for metaclasses."""
3390-
consumed: list[tuple[Consumption, str]] = []
3390+
consumed: list[tuple[NamesConsumer, str, list[nodes.NodeNG]]] = []
33913391

33923392
for child_node in node.get_children():
33933393
if isinstance(child_node, nodes.ClassDef):
33943394
consumed.extend(self._check_classdef_metaclasses(child_node, node))
33953395

3396-
# Pop the consumed items, in order to avoid having
3397-
# unused-import and unused-variable false positives
3398-
for scope_locals, name in consumed:
3399-
scope_locals.pop(name, None)
3396+
# Mark the consumed items properly so they move from to_consume
3397+
# to consumed, avoiding unused-import/unused-variable false positives
3398+
# while still allowing subsequent references to resolve.
3399+
for consumer, name, found_nodes in consumed:
3400+
if name in consumer.to_consume:
3401+
consumer.mark_as_consumed(name, found_nodes)
34003402

34013403
def _check_classdef_metaclasses(
34023404
self,
34033405
klass: nodes.ClassDef,
34043406
parent_node: nodes.Module | nodes.FunctionDef,
3405-
) -> list[tuple[Consumption, str]]:
3407+
) -> list[tuple[NamesConsumer, str, list[nodes.NodeNG]]]:
34063408
if not klass._metaclass:
34073409
# Skip if this class doesn't use explicitly a metaclass, but inherits it from ancestors
34083410
return []
34093411

3410-
consumed: list[tuple[Consumption, str]] = []
3412+
consumed: list[tuple[NamesConsumer, str, list[nodes.NodeNG]]] = []
34113413
metaclass = klass.metaclass()
34123414
name = ""
34133415
match klass._metaclass:
@@ -3433,7 +3435,7 @@ def _check_classdef_metaclasses(
34333435
found_nodes = scope_locals.get(name, [])
34343436
for found_node in found_nodes:
34353437
if found_node.lineno <= klass.lineno:
3436-
consumed.append((scope_locals, name))
3438+
consumed.append((to_consume, name, found_nodes))
34373439
found = True
34383440
break
34393441
# Check parent scope
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Tests for undefined-variable false positive when a name is used as a metaclass
2+
in a nested class inside a method, and then used again at module level.
3+
4+
https://github.com/pylint-dev/pylint/issues/10823
5+
"""
6+
# pylint: disable=too-few-public-methods,missing-class-docstring,missing-function-docstring
7+
# pylint: disable=unused-variable,pointless-statement
8+
9+
import abc
10+
11+
12+
class Test:
13+
def test1(self):
14+
class A(metaclass=abc.ABCMeta):
15+
pass
16+
17+
18+
# This should NOT trigger undefined-variable — abc was imported at module level
19+
# and consumed by the metaclass usage, but it should still be resolvable.
20+
abc.ABCMeta

0 commit comments

Comments
 (0)