|
65 | 65 | use Psalm\Type\Union; |
66 | 66 |
|
67 | 67 | use function array_intersect_key; |
| 68 | +use function array_keys; |
68 | 69 | use function array_merge; |
| 70 | +use function array_search; |
69 | 71 | use function count; |
70 | 72 | use function is_string; |
71 | 73 |
|
@@ -435,7 +437,19 @@ private static function refine( |
435 | 437 | && ($codebase->classExists($existing_var_type_part->value) |
436 | 438 | || $codebase->interfaceExists($existing_var_type_part->value)) |
437 | 439 | ) { |
438 | | - $existing_var_type_part = $existing_var_type_part->addIntersectionType($new_type_part); |
| 440 | + $intersected_type_part = $new_type_part; |
| 441 | + |
| 442 | + if ($existing_var_type_part instanceof TGenericObject |
| 443 | + && !$new_type_part instanceof TGenericObject |
| 444 | + ) { |
| 445 | + $intersected_type_part = self::inferTemplateParamsFromParent( |
| 446 | + $codebase, |
| 447 | + $new_type_part, |
| 448 | + $existing_var_type_part, |
| 449 | + ); |
| 450 | + } |
| 451 | + |
| 452 | + $existing_var_type_part = $existing_var_type_part->addIntersectionType($intersected_type_part); |
439 | 453 | $acceptable_atomic_types[] = $existing_var_type_part; |
440 | 454 | } |
441 | 455 |
|
@@ -598,6 +612,80 @@ private static function filterTypeWithAnother( |
598 | 612 | return null; |
599 | 613 | } |
600 | 614 |
|
| 615 | + /** |
| 616 | + |
| 617 | + * (`@extends Parent`, `@implements Iterator[P]`) gives the child those arguments: |
| 618 | + * `Iterator[pure]` narrowed to `Child` is a `Child[pure]`. |
| 619 | + * |
| 620 | + * @psalm-capabilities read-props |
| 621 | + */ |
| 622 | + private static function inferTemplateParamsFromParent( |
| 623 | + Codebase $codebase, |
| 624 | + TNamedObject $child, |
| 625 | + TGenericObject $parent, |
| 626 | + ): TNamedObject { |
| 627 | + if (!$codebase->classlike_storage_provider->has($child->value) |
| 628 | + || !$codebase->classlike_storage_provider->has($parent->value) |
| 629 | + ) { |
| 630 | + return $child; |
| 631 | + } |
| 632 | + |
| 633 | + $child_storage = $codebase->classlike_storage_provider->get($child->value); |
| 634 | + $parent_storage = $codebase->classlike_storage_provider->get($parent->value); |
| 635 | + |
| 636 | + $extended_params = $child_storage->template_extended_params[$parent_storage->name] ?? null; |
| 637 | + |
| 638 | + if ($child_storage->template_types === null |
| 639 | + || $child_storage->template_types === [] |
| 640 | + || $parent_storage->template_types === null |
| 641 | + || $parent_storage->template_types === [] |
| 642 | + || $extended_params === null |
| 643 | + ) { |
| 644 | + return $child; |
| 645 | + } |
| 646 | + |
| 647 | + $parent_template_names = array_keys($parent_storage->template_types); |
| 648 | + $type_params = []; |
| 649 | + |
| 650 | + foreach (array_keys($child_storage->template_types) as $template_name) { |
| 651 | + $inferred = null; |
| 652 | + |
| 653 | + foreach ($extended_params as $parent_template_name => $extended_type) { |
| 654 | + $offset = array_search($parent_template_name, $parent_template_names, true); |
| 655 | + |
| 656 | + if ($offset === false || !isset($parent->type_params[$offset]) || !$extended_type->isSingle()) { |
| 657 | + continue; |
| 658 | + } |
| 659 | + |
| 660 | + $extended_atomic = $extended_type->getSingleAtomic(); |
| 661 | + |
| 662 | + if ($extended_atomic instanceof TTemplateParam |
| 663 | + && $extended_atomic->param_name === $template_name |
| 664 | + && $extended_atomic->defining_class === $child_storage->name |
| 665 | + ) { |
| 666 | + $inferred = $parent->type_params[$offset]; |
| 667 | + break; |
| 668 | + } |
| 669 | + } |
| 670 | + |
| 671 | + if ($inferred === null) { |
| 672 | + // a template not passed straight to the parent: nothing is known about it |
| 673 | + return $child; |
| 674 | + } |
| 675 | + |
| 676 | + $type_params[] = $inferred; |
| 677 | + } |
| 678 | + |
| 679 | + return new TGenericObject( |
| 680 | + $child->value, |
| 681 | + $type_params, |
| 682 | + false, |
| 683 | + $child->is_static, |
| 684 | + $child->extra_types, |
| 685 | + $child->from_docblock, |
| 686 | + ); |
| 687 | + } |
| 688 | + |
601 | 689 | private static function filterAtomicWithAnother( |
602 | 690 | Atomic &$type_1_atomic, |
603 | 691 | Atomic $type_2_atomic, |
@@ -662,6 +750,10 @@ private static function filterAtomicWithAnother( |
662 | 750 | && ($codebase->interfaceExists($type_1_atomic->value) |
663 | 751 | || $codebase->interfaceExists($type_2_atomic->value)) |
664 | 752 | ) { |
| 753 | + if ($type_1_atomic instanceof TGenericObject && !$type_2_atomic instanceof TGenericObject) { |
| 754 | + $type_2_atomic = self::inferTemplateParamsFromParent($codebase, $type_2_atomic, $type_1_atomic); |
| 755 | + } |
| 756 | + |
665 | 757 | return $type_2_atomic->addIntersectionType($type_1_atomic); |
666 | 758 | } |
667 | 759 |
|
|
0 commit comments