Skip to content

Commit 5d4ab20

Browse files
committed
Try to fix the failing tests by processing instance attributes and class level attributes separate
1 parent ba56957 commit 5d4ab20

File tree

1 file changed

+46
-17
lines changed

1 file changed

+46
-17
lines changed

pylint/pyreverse/inspector.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ def visit_classdef(self, node: nodes.ClassDef) -> None:
186186
self.compositions_handler.handle(assignattr, node)
187187
self.handle_assignattr_type(assignattr, node)
188188

189+
# Process class attributes
190+
for local_nodes in node.locals.values():
191+
for local_node in local_nodes:
192+
if isinstance(local_node, nodes.AssignName) and isinstance(
193+
local_node.parent, nodes.Assign
194+
):
195+
self.compositions_handler.handle(local_node, node)
196+
189197
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
190198
"""Visit an astroid.Function node.
191199
@@ -307,7 +315,9 @@ def set_next(
307315
pass
308316

309317
@abstractmethod
310-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
318+
def handle(
319+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
320+
) -> None:
311321
pass
312322

313323

@@ -332,21 +342,29 @@ def set_next(
332342
return handler
333343

334344
@abstractmethod
335-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
345+
def handle(
346+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
347+
) -> None:
336348
if self._next_handler:
337349
self._next_handler.handle(node, parent)
338350

339351

340352
class CompositionsHandler(AbstractRelationshipHandler):
341353
"""Handle composition relationships where parent creates child objects."""
342354

343-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
355+
def handle(
356+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
357+
) -> None:
358+
# If the node is not part of an assignment, pass to next handler
344359
if not isinstance(node.parent, (nodes.AnnAssign, nodes.Assign)):
345360
super().handle(node, parent)
346361
return
347362

348363
value = node.parent.value
349364

365+
# Extract the name to handle both AssignAttr and AssignName nodes
366+
name = node.attrname if isinstance(node, nodes.AssignAttr) else node.name
367+
350368
# Composition: direct object creation (self.x = P())
351369
if isinstance(value, nodes.Call):
352370
inferred_types = utils.infer_node(node)
@@ -355,8 +373,8 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
355373
# Resolve nodes to actual class definitions
356374
resolved_types = resolve_to_class_def(element_types)
357375

358-
current = set(parent.compositions_type[node.attrname])
359-
parent.compositions_type[node.attrname] = list(current | resolved_types)
376+
current = set(parent.compositions_type[name])
377+
parent.compositions_type[name] = list(current | resolved_types)
360378
return
361379

362380
# Composition: comprehensions with object creation (self.x = [P() for ...])
@@ -376,8 +394,8 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
376394
# Resolve nodes to actual class definitions
377395
resolved_types = resolve_to_class_def(element_types)
378396

379-
current = set(parent.compositions_type[node.attrname])
380-
parent.compositions_type[node.attrname] = list(current | resolved_types)
397+
current = set(parent.compositions_type[name])
398+
parent.compositions_type[name] = list(current | resolved_types)
381399
return
382400

383401
# Not a composition, pass to next handler
@@ -387,13 +405,19 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
387405
class AggregationsHandler(AbstractRelationshipHandler):
388406
"""Handle aggregation relationships where parent receives child objects."""
389407

390-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
408+
def handle(
409+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
410+
) -> None:
411+
# If the node is not part of an assignment, pass to next handler
391412
if not isinstance(node.parent, (nodes.AnnAssign, nodes.Assign)):
392413
super().handle(node, parent)
393414
return
394415

395416
value = node.parent.value
396417

418+
# Extract the name to handle both AssignAttr and AssignName nodes
419+
name = node.attrname if isinstance(node, nodes.AssignAttr) else node.name
420+
397421
# Aggregation: direct assignment (self.x = x)
398422
if isinstance(value, nodes.Name):
399423
inferred_types = utils.infer_node(node)
@@ -402,8 +426,8 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
402426
# Resolve nodes to actual class definitions
403427
resolved_types = resolve_to_class_def(element_types)
404428

405-
current = set(parent.aggregations_type[node.attrname])
406-
parent.aggregations_type[node.attrname] = list(current | resolved_types)
429+
current = set(parent.aggregations_type[name])
430+
parent.aggregations_type[name] = list(current | resolved_types)
407431
return
408432

409433
# Aggregation: comprehensions without object creation (self.x = [existing_obj for ...])
@@ -423,8 +447,8 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
423447
# Resolve nodes to actual class definitions
424448
resolved_types = resolve_to_class_def(element_types)
425449

426-
current = set(parent.aggregations_type[node.attrname])
427-
parent.aggregations_type[node.attrname] = list(current | resolved_types)
450+
current = set(parent.aggregations_type[name])
451+
parent.aggregations_type[name] = list(current | resolved_types)
428452
return
429453

430454
# Not an aggregation, pass to next handler
@@ -434,7 +458,12 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
434458
class AssociationsHandler(AbstractRelationshipHandler):
435459
"""Handle regular association relationships."""
436460

437-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
461+
def handle(
462+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
463+
) -> None:
464+
# Extract the name to handle both AssignAttr and AssignName nodes
465+
name = node.attrname if isinstance(node, nodes.AssignAttr) else node.name
466+
438467
# Type annotation only (x: P) -> Association
439468
# BUT only if there's no actual assignment (to avoid duplicates)
440469
if isinstance(node.parent, nodes.AnnAssign) and node.parent.value is None:
@@ -444,18 +473,18 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
444473
# Resolve nodes to actual class definitions
445474
resolved_types = resolve_to_class_def(element_types)
446475

447-
current = set(parent.associations_type[node.attrname])
448-
parent.associations_type[node.attrname] = list(current | resolved_types)
476+
current = set(parent.associations_type[name])
477+
parent.associations_type[name] = list(current | resolved_types)
449478
return
450479

451480
# Everything else is also association (fallback)
452-
current = set(parent.associations_type[node.attrname])
481+
current = set(parent.associations_type[name])
453482
inferred_types = utils.infer_node(node)
454483
element_types = extract_element_types(inferred_types)
455484

456485
# Resolve Name nodes to actual class definitions
457486
resolved_types = resolve_to_class_def(element_types)
458-
parent.associations_type[node.attrname] = list(current | resolved_types)
487+
parent.associations_type[name] = list(current | resolved_types)
459488

460489

461490
def resolve_to_class_def(types: set[nodes.NodeNG]) -> set[nodes.ClassDef]:

0 commit comments

Comments
 (0)