diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 88c83cac563..27af7bea08c 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = '28c29b8'; + public const EXPECTED_EXTENSION_VERSION = '19e694d'; private static bool $active = false; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 16a2ed918c4..d52efe09254 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -2094,6 +2094,10 @@ public function tryRemove(Type $typeToRemove): ?Type // time, without rebuilding the subtracted union once per member. $membersToRemove = []; foreach ($typeToRemove->getTypes() as $member) { + if ($member->isSuperTypeOf($this)->yes()) { + return new NeverType(); + } + $isSuperTypeOfMember = $this->isSuperTypeOf($member); if ($isSuperTypeOfMember->yes()) { $membersToRemove[] = $member; diff --git a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php index 08f06c6f97c..6511c2bbc41 100644 --- a/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php +++ b/tests/PHPStan/Rules/Exceptions/MissingCheckedExceptionInFunctionThrowsRuleTest.php @@ -83,4 +83,15 @@ public function testConditionalThrows(): void ]); } + public function testBug15392(): void + { + require_once __DIR__ . '/data/bug-15392.php'; + $this->analyse([__DIR__ . '/data/bug-15392.php'], [ + [ + 'Function Bug15392\\multiCatchLeaksOtherSubtype() throws checked exception Bug15392\\SubB but it\'s missing from the PHPDoc @throws tag.', + 84, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Exceptions/data/bug-15392.php b/tests/PHPStan/Rules/Exceptions/data/bug-15392.php new file mode 100644 index 00000000000..797d0424b32 --- /dev/null +++ b/tests/PHPStan/Rules/Exceptions/data/bug-15392.php @@ -0,0 +1,87 @@ +tryRemove(new $objectClass(\Error::class))), $view($subjects['dateTimeInterface']->tryRemove(new $objectClass(\DateTime::class))), $view($subjects['dateTimeInterface']->tryRemove(new $objectClass(\DateTimeImmutable::class)))]; + // a sealed type minus a union: a member that is a supertype of the whole + // type removes everything, whatever the other members are + $sealedUnion = static fn (\PHPStan\Type\Type ...$members): \PHPStan\Type\Type => new \PHPStan\Type\UnionType($members); + $r['sealed tryRemove union'] = [ + $view($subjects['enum']->tryRemove($sealedUnion(new $objectClass($objectEnum), $others['stdClass']))), + $view($subjects['enum']->tryRemove($sealedUnion($others['stdClass'], new $objectClass($objectEnum)))), + $view($subjects['enum']->tryRemove($sealedUnion(new $objectClass(\UnitEnum::class), $others['exception']))), + $view($subjects['enum']->tryRemove($sealedUnion($others['caseFoo'], new $objectClass(\UnitEnum::class)))), + $view($subjects['enum']->tryRemove($sealedUnion($others['caseFoo'], $others['stdClass']))), + $view($subjects['enumMinusFoo']->tryRemove($sealedUnion(new $objectClass($objectEnum), $others['stdClass']))), + $view($subjects['enumWithReflection']->tryRemove($sealedUnion(new $objectClass($objectEnum), $others['stdClass']))), + $view(\PHPStan\Type\TypeCombinator::remove($subjects['enum'], $sealedUnion(new $objectClass($objectEnum), $others['stdClass']))), + ]; $r['object equals object'] = [(new $objectClass(\stdClass::class))->equals(new $objectClass(\stdClass::class)), (new $objectClass(\stdClass::class))->equals(new $objectClass('stdclass')), (new $objectClass(\stdClass::class))->equals(new $genericClass(\stdClass::class, [])), (new $genericClass(\stdClass::class, []))->equals(new $objectClass(\stdClass::class)), (new $caseClass($objectEnum, 'FOO'))->equals(new $caseClass($objectEnum, 'FOO')), (new $caseClass($objectEnum, 'FOO'))->equals(new $objectClass($objectEnum))]; // the constructors by named arguments $r['object named'] = $view(new $objectClass(className: \stdClass::class, classReflection: null));