Skip to content

Commit 5dd2a04

Browse files
committed
Fix undefined-variable false positive with metaclass in nested class
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
1 parent 4fb4079 commit 5dd2a04

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
@@ -3400,27 +3400,29 @@ def _check_imports(self, not_consumed: Consumption) -> None:
34003400

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

34053405
for child_node in node.get_children():
34063406
if isinstance(child_node, nodes.ClassDef):
34073407
consumed.extend(self._check_classdef_metaclasses(child_node, node))
34083408

3409-
# Pop the consumed items, in order to avoid having
3410-
# unused-import and unused-variable false positives
3411-
for scope_locals, name in consumed:
3412-
scope_locals.pop(name, None)
3409+
# Mark the consumed items properly so they move from to_consume
3410+
# to consumed, avoiding unused-import/unused-variable false positives
3411+
# while still allowing subsequent references to resolve.
3412+
for consumer, name, found_nodes in consumed:
3413+
if name in consumer.to_consume:
3414+
consumer.mark_as_consumed(name, found_nodes)
34133415

34143416
def _check_classdef_metaclasses(
34153417
self,
34163418
klass: nodes.ClassDef,
34173419
parent_node: nodes.Module | nodes.FunctionDef,
3418-
) -> list[tuple[Consumption, str]]:
3420+
) -> list[tuple[NamesConsumer, str, list[nodes.NodeNG]]]:
34193421
if not klass._metaclass:
34203422
# Skip if this class doesn't use explicitly a metaclass, but inherits it from ancestors
34213423
return []
34223424

3423-
consumed: list[tuple[Consumption, str]] = []
3425+
consumed: list[tuple[NamesConsumer, str, list[nodes.NodeNG]]] = []
34243426
metaclass = klass.metaclass()
34253427
name = ""
34263428
match klass._metaclass:
@@ -3446,7 +3448,7 @@ def _check_classdef_metaclasses(
34463448
found_nodes = scope_locals.get(name, [])
34473449
for found_node in found_nodes:
34483450
if found_node.lineno <= klass.lineno:
3449-
consumed.append((scope_locals, name))
3451+
consumed.append((to_consume, name, found_nodes))
34503452
found = True
34513453
break
34523454
# 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)