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