Skip to content

Commit d3677ea

Browse files
committed
Enhance composition and aggregation handling in AST node processing so that the differentation between aggregation and composition is correct according to UMLEnhance composition and aggregation handling in AST node processing so that the differentation between aggregation and composition is correct according to UML
1 parent 376d1b5 commit d3677ea

File tree

1 file changed

+44
-17
lines changed

1 file changed

+44
-17
lines changed

pylint/pyreverse/inspector.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -346,14 +346,31 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
346346

347347
value = node.parent.value
348348

349-
# Composition: parent creates child (self.x = P())
349+
# Composition: direct object creation (self.x = P())
350350
if isinstance(value, nodes.Call):
351351
current = set(parent.compositions_type[node.attrname])
352352
parent.compositions_type[node.attrname] = list(
353353
current | utils.infer_node(node)
354354
)
355355
return
356356

357+
# Composition: comprehensions with object creation (self.x = [P() for ...])
358+
if isinstance(
359+
value, (nodes.ListComp, nodes.DictComp, nodes.SetComp, nodes.GeneratorExp)
360+
):
361+
if isinstance(value, nodes.DictComp):
362+
element = value.value
363+
else:
364+
element = value.elt
365+
366+
# If the element is a Call (object creation), it's composition
367+
if isinstance(element, nodes.Call):
368+
current = set(parent.compositions_type[node.attrname])
369+
parent.compositions_type[node.attrname] = list(
370+
current | utils.infer_node(node)
371+
)
372+
return
373+
357374
# Not a composition, pass to next handler
358375
super().handle(node, parent)
359376

@@ -376,26 +393,27 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
376393
)
377394
return
378395

379-
# Aggregation: comprehensions (self.x = [P() for ...])
396+
# Aggregation: comprehensions without object creation (self.x = [existing_obj for ...])
380397
if isinstance(
381398
value, (nodes.ListComp, nodes.DictComp, nodes.SetComp, nodes.GeneratorExp)
382399
):
383400
if isinstance(value, nodes.DictComp):
384-
element_type = safe_infer(value.value)
401+
element = value.value
385402
else:
386-
element_type = safe_infer(value.elt)
387-
if element_type:
388-
current = set(parent.aggregations_type[node.attrname])
389-
parent.aggregations_type[node.attrname] = list(current | {element_type})
390-
return
391-
392-
# Type annotation only (x: P) defaults to aggregation
393-
if isinstance(node.parent, nodes.AnnAssign) and node.parent.value is None:
394-
current = set(parent.aggregations_type[node.attrname])
395-
parent.aggregations_type[node.attrname] = list(
396-
current | utils.infer_node(node)
397-
)
398-
return
403+
element = value.elt
404+
405+
# If the element is NOT a Call (no object creation), it's aggregation
406+
if not isinstance(element, nodes.Call):
407+
if isinstance(value, nodes.DictComp):
408+
element_type = safe_infer(value.value)
409+
else:
410+
element_type = safe_infer(value.elt)
411+
if element_type:
412+
current = set(parent.aggregations_type[node.attrname])
413+
parent.aggregations_type[node.attrname] = list(
414+
current | {element_type}
415+
)
416+
return
399417

400418
# Not an aggregation, pass to next handler
401419
super().handle(node, parent)
@@ -405,7 +423,16 @@ class AssociationsHandler(AbstractAssociationHandler):
405423
"""Handle regular association relationships."""
406424

407425
def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
408-
# Everything else is a regular association
426+
# Type annotation only (x: P) -> Association
427+
# BUT only if there's no actual assignment (to avoid duplicates)
428+
if isinstance(node.parent, nodes.AnnAssign) and node.parent.value is None:
429+
current = set(parent.associations_type[node.attrname])
430+
parent.associations_type[node.attrname] = list(
431+
current | utils.infer_node(node)
432+
)
433+
return
434+
435+
# Everything else is also association (fallback)
409436
current = set(parent.associations_type[node.attrname])
410437
parent.associations_type[node.attrname] = list(current | utils.infer_node(node))
411438

0 commit comments

Comments
 (0)