Skip to content

Commit 4216545

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 e7bd7ca commit 4216545

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
@@ -347,14 +347,31 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
347347

348348
value = node.parent.value
349349

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

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

@@ -377,26 +394,27 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
377394
)
378395
return
379396

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

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

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

0 commit comments

Comments
 (0)