@@ -350,10 +350,14 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
350
350
351
351
# Composition: direct object creation (self.x = P())
352
352
if isinstance (value , nodes .Call ):
353
+ inferred_types = utils .infer_node (node )
354
+ element_types = extract_element_types (inferred_types )
355
+
356
+ # Resolve nodes to actual class definitions
357
+ resolved_types = resolve_to_class_def (element_types )
358
+
353
359
current = set (parent .compositions_type [node .attrname ])
354
- parent .compositions_type [node .attrname ] = list (
355
- current | utils .infer_node (node )
356
- )
360
+ parent .compositions_type [node .attrname ] = list (current | resolved_types )
357
361
return
358
362
359
363
# Composition: comprehensions with object creation (self.x = [P() for ...])
@@ -367,13 +371,15 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
367
371
368
372
# If the element is a Call (object creation), it's composition
369
373
if isinstance (element , nodes .Call ):
370
- element_type = safe_infer (element )
371
- if element_type :
372
- current = set (parent .compositions_type [node .attrname ])
373
- parent .compositions_type [node .attrname ] = list (
374
- current | {element_type }
375
- )
376
- return
374
+ inferred_types = utils .infer_node (node )
375
+ element_types = extract_element_types (inferred_types )
376
+
377
+ # Resolve nodes to actual class definitions
378
+ resolved_types = resolve_to_class_def (element_types )
379
+
380
+ current = set (parent .compositions_type [node .attrname ])
381
+ parent .compositions_type [node .attrname ] = list (current | resolved_types )
382
+ return
377
383
378
384
# Not a composition, pass to next handler
379
385
super ().handle (node , parent )
@@ -391,10 +397,14 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
391
397
392
398
# Aggregation: direct assignment (self.x = x)
393
399
if isinstance (value , nodes .Name ):
400
+ inferred_types = utils .infer_node (node )
401
+ element_types = extract_element_types (inferred_types )
402
+
403
+ # Resolve nodes to actual class definitions
404
+ resolved_types = resolve_to_class_def (element_types )
405
+
394
406
current = set (parent .aggregations_type [node .attrname ])
395
- parent .aggregations_type [node .attrname ] = list (
396
- current | utils .infer_node (node )
397
- )
407
+ parent .aggregations_type [node .attrname ] = list (current | resolved_types )
398
408
return
399
409
400
410
# Aggregation: comprehensions without object creation (self.x = [existing_obj for ...])
@@ -409,13 +419,15 @@ def handle(self, node: nodes.AssignAttr, parent: nodes.ClassDef) -> None:
409
419
410
420
# If the element is a Name, it means it's an existing object, so it's aggregation
411
421
if isinstance (element , nodes .Name ):
412
- element_type = safe_infer (element )
413
- if element_type :
414
- current = set (parent .aggregations_type [node .attrname ])
415
- parent .aggregations_type [node .attrname ] = list (
416
- current | {element_type }
417
- )
418
- return
422
+ inferred_types = utils .infer_node (node )
423
+ element_types = extract_element_types (inferred_types )
424
+
425
+ # Resolve nodes to actual class definitions
426
+ resolved_types = resolve_to_class_def (element_types )
427
+
428
+ current = set (parent .aggregations_type [node .attrname ])
429
+ parent .aggregations_type [node .attrname ] = list (current | resolved_types )
430
+ return
419
431
420
432
# Not an aggregation, pass to next handler
421
433
super ().handle (node , parent )
0 commit comments