diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index f6678d283..302f45cb6 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -2066,11 +2066,22 @@ class AssertionReconciler extends \Psalm\Type\Reconciler $atomic_comparison_results ); - if ($atomic_contained_by || $atomic_comparison_results->type_coerced) { + if ($atomic_contained_by) { $has_local_match = true; - if ($atomic_comparison_results->type_coerced) { - $matching_atomic_types[] = $existing_type_part; + + if ($atomic_comparison_results->type_coerced + && get_class($new_type_part) === Type\Atomic\TNamedObject::class + && $existing_type_part instanceof Type\Atomic\TGenericObject + ) { + // this is a hack - it's not actually rigorous, as the params may be different + $matching_atomic_types[] = new Type\Atomic\TGenericObject( + $new_type_part->value, + $existing_type_part->type_params + ); } + } elseif ($atomic_comparison_results->type_coerced) { + $has_local_match = true; + $matching_atomic_types[] = $existing_type_part; } if (($new_type_part instanceof Type\Atomic\TGenericObject diff --git a/tests/Template/ClassTemplateExtendsTest.php b/tests/Template/ClassTemplateExtendsTest.php index 2aaa29eff..62bbb596b 100644 --- a/tests/Template/ClassTemplateExtendsTest.php +++ b/tests/Template/ClassTemplateExtendsTest.php @@ -2424,6 +2424,41 @@ class ClassTemplateExtendsTest extends TestCase return new Right(new B()); }' ], + 'refineGenericWithInstanceof' => [ + ' + */ + class Some implements Maybe { + /** @var T */ + private $value; + + /** @psalm-param T $value */ + public function __construct($value) { + $this->value = $value; + } + + /** @psalm-return T */ + public function extract() { return $this->value; } + } + + /** + * @psalm-return Maybe + */ + function repository(): Maybe { + return new Some(5); + } + + $maybe = repository(); + + if ($maybe instanceof Some) { + $anInt = $maybe->extract(); + }' + ], ]; }