diff --git a/src/Type/CallableTypeHelper.php b/src/Type/CallableTypeHelper.php index 2b292914c95..6c6c561cff4 100644 --- a/src/Type/CallableTypeHelper.php +++ b/src/Type/CallableTypeHelper.php @@ -79,7 +79,7 @@ public static function isParametersAcceptorSuperTypeOf( $isSuperType = $theirParameter->getType()->accepts($ourParameterType, $strictTypes); $isSuperType = new IsSuperTypeOfResult($isSuperType->result, $isSuperType->reasons); } else { - $isSuperType = $theirParameter->getType()->isSuperTypeOf($ourParameterType); + $isSuperType = $ourParameterType->isSuperTypeOf($theirParameter->getType()); } if ($isSuperType->maybe()) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-11285.php b/tests/PHPStan/Analyser/nsrt/bug-11285.php new file mode 100644 index 00000000000..452c22d99e7 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11285.php @@ -0,0 +1,58 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11285; + +use function PHPStan\Testing\assertType; + +/** + * @template T + * @template U + */ +class Templated +{ + + /** + * @param callable(T|U): void $callback + */ + public function compare(callable $callback): void { + + } +} + +class ClassA {} +class ClassB {} + + +class Test +{ + /** + * @param Templated|Templated $t + */ + public function broken( + Templated $t, + ): void + { + $t->compare( + static function (ClassA|ClassB $crate): void { + assertType('Bug11285\\ClassA|Bug11285\\ClassB', $crate); + } + ); + } + + /** + * @param Templated $t + */ + public function working( + Templated $t, + ): void + { + $t->compare( + static function (ClassA|ClassB $crate): void { + assertType('Bug11285\\ClassA|Bug11285\\ClassB', $crate); + } + ); + } + +} diff --git a/tests/PHPStan/Type/CallableTypeTest.php b/tests/PHPStan/Type/CallableTypeTest.php index 898b5f891bd..21d7bb4d256 100644 --- a/tests/PHPStan/Type/CallableTypeTest.php +++ b/tests/PHPStan/Type/CallableTypeTest.php @@ -44,12 +44,12 @@ public static function dataIsSuperTypeOf(): array [ new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - TrinaryLogic::createMaybe(), + TrinaryLogic::createYes(), ], [ new CallableType([new NativeParameterReflection('foo', false, new IntegerType(), PassedByReference::createNo(), false, null)], new MixedType(), false), new CallableType([new NativeParameterReflection('foo', false, new MixedType(), PassedByReference::createNo(), false, null)], new MixedType(), false), - TrinaryLogic::createYes(), + TrinaryLogic::createMaybe(), ], [ new CallableType([ @@ -432,8 +432,8 @@ public function testIsSuperTypeOfKeepsLazyReasonsOfMaybeParameter(): void $accepting = new CallableType([new NativeParameterReflection('x', false, $acceptingParameterType, PassedByReference::createNo(), false, null)], new VoidType()); $passed = new CallableType([new NativeParameterReflection('x', false, $passedParameterType, PassedByReference::createNo(), false, null)], new VoidType()); - // the passed parameter's isSuperTypeOf() is maybe, with a lazy reason from its array{b: int} member - $parameterResult = $passedParameterType->isSuperTypeOf($acceptingParameterType); + // the accepting parameter's isSuperTypeOf() is maybe, with a lazy reason from the passed array{b: int} member + $parameterResult = $acceptingParameterType->isSuperTypeOf($passedParameterType); $this->assertTrue($parameterResult->maybe()); $this->assertNotSame([], $parameterResult->lazyReasons); @@ -442,7 +442,7 @@ public function testIsSuperTypeOfKeepsLazyReasonsOfMaybeParameter(): void $reasons = $result->getReasons(); $this->assertCount(2, $reasons); $this->assertSame('Type array{a: int}|array{b: int} of parameter #1 $x of passed callable needs to be same or wider than parameter type array{a: int, b?: int} of accepting callable.', $reasons[0]); - $this->assertStringStartsWith('Sealed array shapes array{b: int} and array{a: int, b?: int} cannot be intersected.', $reasons[1]); + $this->assertStringStartsWith('Sealed array shapes array{a: int, b?: int} and array{b: int} cannot be intersected.', $reasons[1]); } } diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index c4da5fd93ee..244d3389119 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -19,6 +19,7 @@ use PHPStan\Generics\FunctionsAssertType\C; use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\Callables\SimpleImpurePoint; +use PHPStan\Reflection\Php\DummyParameter; use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\Accessory\AccessoryArrayListType; @@ -3244,6 +3245,36 @@ public static function dataUnion(): iterable ConstantArrayType::class, 'array{}', ]; + yield [ + [ + new CallableType([ + new DummyParameter('callback', new ObjectType('stdClass'), false, null, false, null), + ], new VoidType(), false), + new CallableType([ + new DummyParameter('callback', new UnionType([ + new ObjectType('stdClass'), + new ObjectType('Exception'), + ]), false, null, false, null), + ], new VoidType(), false), + ], + CallableType::class, + 'callable(Exception|stdClass): void', + ]; + yield [ + [ + new CallableType([ + new DummyParameter('callback', new ObjectType('stdClass'), false, null, false, null), + ], new VoidType(), false), + new CallableType([ + new DummyParameter('callback', new UnionType([ + new IntegerType(), + new ObjectType('Exception'), + ]), false, null, false, null), + ], new VoidType(), false), + ], + UnionType::class, + '(callable(Exception|int): void)|(callable(stdClass): void)', + ]; } /** diff --git a/turbo-ext/src/CallableTypeHelper.cpp b/turbo-ext/src/CallableTypeHelper.cpp index c78d5a30c79..f09524e448b 100644 --- a/turbo-ext/src/CallableTypeHelper.cpp +++ b/turbo-ext/src/CallableTypeHelper.cpp @@ -127,7 +127,7 @@ class CallableTypeHelper if (UNEXPECTED(accepts.isUndef())) return zv::Val(); isSuperType = resultFrom(accepts.raw()); } else { - isSuperType = callObject(theirParameterType.raw(), PT_LC("issupertypeof"), 1, ourParameterType.raw()); + isSuperType = callObject(ourParameterType.raw(), PT_LC("issupertypeof"), 1, theirParameterType.raw()); } if (UNEXPECTED(isSuperType.isUndef())) return zv::Val();