diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index cc5de1f8b03..793c3198b2e 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -888,33 +888,37 @@ private static function intersectAtomicTypes( } if (null === $intersection_atomic) { - if (AtomicTypeComparator::isContainedBy( - $codebase, - $type_2_atomic, - $type_1_atomic, - $allow_interface_equality, - $allow_float_int_equality, - )) { - $intersection_atomic = $type_2_atomic; - $wider_type = $type_1_atomic; - $intersection_performed = true; - } elseif (AtomicTypeComparator::isContainedBy( - $codebase, - $type_1_atomic, - $type_2_atomic, - $allow_interface_equality, - $allow_float_int_equality, - )) { - $intersection_atomic = $type_1_atomic; - $wider_type = $type_2_atomic; - $intersection_performed = true; - } + try { + if (AtomicTypeComparator::isContainedBy( + $codebase, + $type_2_atomic, + $type_1_atomic, + $allow_interface_equality, + $allow_float_int_equality, + )) { + $intersection_atomic = $type_2_atomic; + $wider_type = $type_1_atomic; + $intersection_performed = true; + } elseif (AtomicTypeComparator::isContainedBy( + $codebase, + $type_1_atomic, + $type_2_atomic, + $allow_interface_equality, + $allow_float_int_equality, + )) { + $intersection_atomic = $type_1_atomic; + $wider_type = $type_2_atomic; + $intersection_performed = true; + } - if ($intersection_atomic - && !self::hasIntersection($type_1_atomic) - && !self::hasIntersection($type_2_atomic) - ) { - return $intersection_atomic; + if ($intersection_atomic + && !self::hasIntersection($type_1_atomic) + && !self::hasIntersection($type_2_atomic) + ) { + return $intersection_atomic; + } + } catch (InvalidArgumentException $e) { + // Ignore non-existing classes during initial scan } } diff --git a/tests/NativeIntersectionsTest.php b/tests/NativeIntersectionsTest.php index 5d7c2a0367c..538119bbf5b 100644 --- a/tests/NativeIntersectionsTest.php +++ b/tests/NativeIntersectionsTest.php @@ -51,6 +51,64 @@ function test(A&B $in): void { 'ignored_issues' => [], 'php_version' => '8.1', ], + 'nativeTypeIntersectionAsClassProperty' => [ + 'code' => 'intersection = new C(); + } + } + ', + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], + 'nativeTypeIntersectionAsClassPropertyUsingProcessedInterfaces' => [ + 'code' => 'other = new AB(); + } + } + ', + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], + 'nativeTypeIntersectionAsClassPropertyUsingUnprocessedInterfaces' => [ + 'code' => 'other = new StringableJson(); + } + } + ', + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], ]; } @@ -136,6 +194,22 @@ function foo (A&B $test): A&B { 'ignored_issues' => [], 'php_version' => '8.0', ], + 'nativeTypeIntersectionAsClassPropertyUsingUnknownInterfaces' => [ + 'code' => 'other = new \Example\Unknown\AB(); + } + } + ', + // @todo decide whether a fall-back should be implemented, that allows to by-pass this failure (opt-in config) + // `UndefinedClass - src/somefile.php:3:33 - Class, interface or enum named Example\Unknown\B does not exist` + 'error_message' => 'UndefinedClass', + 'ignored_issues' => [], + 'php_version' => '8.1', + ], ]; } }