Skip to content

Commit 70492f6

Browse files
committed
Try to fix the failing tests by processing instance attributes and class level attributes separate
1 parent 5852435 commit 70492f6

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
@@ -187,6 +187,14 @@ def visit_classdef(self, node: nodes.ClassDef) -> None:
187187
self.compositions_handler.handle(assignattr, node)
188188
self.handle_assignattr_type(assignattr, node)
189189

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

310318
@abstractmethod
311-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
319+
def handle(
320+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
321+
) -> None:
312322
pass
313323

314324

@@ -333,21 +343,29 @@ def set_next(
333343
return handler
334344

335345
@abstractmethod
336-
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
346+
def handle(
347+
self, node: nodes.AssignAttr | nodes.AssignName, parent: nodes.ClassDef
348+
) -> None:
337349
if self._next_handler:
338350
self._next_handler.handle(node, parent)
339351

340352

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

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

349364
value = node.parent.value
350365

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

359-
current = set(parent.compositions_type[node.attrname])
360-
parent.compositions_type[node.attrname] = list(current | resolved_types)
377+
current = set(parent.compositions_type[name])
378+
parent.compositions_type[name] = list(current | resolved_types)
361379
return
362380

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

380-
current = set(parent.compositions_type[node.attrname])
381-
parent.compositions_type[node.attrname] = list(current | resolved_types)
398+
current = set(parent.compositions_type[name])
399+
parent.compositions_type[name] = list(current | resolved_types)
382400
return
383401

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

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

396417
value = node.parent.value
397418

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

406-
current = set(parent.aggregations_type[node.attrname])
407-
parent.aggregations_type[node.attrname] = list(current | resolved_types)
430+
current = set(parent.aggregations_type[name])
431+
parent.aggregations_type[name] = list(current | resolved_types)
408432
return
409433

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

427-
current = set(parent.aggregations_type[node.attrname])
428-
parent.aggregations_type[node.attrname] = list(current | resolved_types)
451+
current = set(parent.aggregations_type[name])
452+
parent.aggregations_type[name] = list(current | resolved_types)
429453
return
430454

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

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

448-
current = set(parent.associations_type[node.attrname])
449-
parent.associations_type[node.attrname] = list(current | resolved_types)
477+
current = set(parent.associations_type[name])
478+
parent.associations_type[name] = list(current | resolved_types)
450479
return
451480

452481
# Everything else is also association (fallback)
453-
current = set(parent.associations_type[node.attrname])
482+
current = set(parent.associations_type[name])
454483
inferred_types = utils.infer_node(node)
455484
element_types = extract_element_types(inferred_types)
456485

457486
# Resolve Name nodes to actual class definitions
458487
resolved_types = resolve_to_class_def(element_types)
459-
parent.associations_type[node.attrname] = list(current | resolved_types)
488+
parent.associations_type[name] = list(current | resolved_types)
460489

461490

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

0 commit comments

Comments
 (0)