Skip to content

Commit 5500473

Browse files
Rework
1 parent f2663f1 commit 5500473

File tree

6 files changed

+62
-36
lines changed

6 files changed

+62
-36
lines changed

src/Type/Php/IsAFunctionTypeSpecifyingExtension.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
5050
$allowString = !$allowStringType->equals(new ConstantBooleanType(false));
5151

5252
$resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true);
53-
54-
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
55-
if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) {
53+
if ($resultType === null) {
5654
return new SpecifiedTypes([], []);
5755
}
5856

src/Type/Php/IsAFunctionTypeSpecifyingHelper.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Type\UnionType;
1717
use function array_unique;
1818
use function array_values;
19+
use function in_array;
1920

2021
#[AutowiredService]
2122
final class IsAFunctionTypeSpecifyingHelper
@@ -26,7 +27,7 @@ public function determineType(
2627
Type $classType,
2728
bool $allowString,
2829
bool $allowSameClass,
29-
): Type
30+
): ?Type
3031
{
3132
$objectOrClassTypeClassNames = $objectOrClassType->getObjectClassNames();
3233
if ($allowString) {
@@ -36,20 +37,35 @@ public function determineType(
3637
$objectOrClassTypeClassNames = array_values(array_unique($objectOrClassTypeClassNames));
3738
}
3839

39-
return TypeTraverser::map(
40+
$isUncertain = $classType->getConstantStrings() === [];
41+
42+
$resultType = TypeTraverser::map(
4043
$classType,
41-
static function (Type $type, callable $traverse) use ($objectOrClassType, $objectOrClassTypeClassNames, $allowString, $allowSameClass): Type {
44+
static function (Type $type, callable $traverse) use ($objectOrClassType, $objectOrClassTypeClassNames, $allowString, $allowSameClass, &$isUncertain): Type {
4245
if ($type instanceof UnionType || $type instanceof IntersectionType) {
4346
return $traverse($type);
4447
}
4548
if ($type instanceof ConstantStringType) {
46-
if (!$allowSameClass && $objectOrClassTypeClassNames === [$type->getValue()]) {
49+
if (!$allowSameClass) {
4750
// For objectType we cannot be sure since 'Foo' is used for both
4851
// - the Foo class
4952
// - a child of foo class
50-
if ($objectOrClassType->isString()->yes()) {
53+
if (
54+
$objectOrClassTypeClassNames === [$type->getValue()]
55+
&& $objectOrClassType->isString()->yes()
56+
) {
5157
return new NeverType();
5258
}
59+
60+
if (
61+
// For object, as soon as the exact same type is provided
62+
// in the list we cannot be sure of the result
63+
in_array($type->getValue(), $objectOrClassTypeClassNames, true)
64+
// This also occurs for generic class string
65+
|| ($allowString && $objectOrClassTypeClassNames === [] && $objectOrClassType->isSuperTypeOf($type)->yes()))
66+
{
67+
$isUncertain = true;
68+
}
5369
}
5470
if ($allowString) {
5571
return TypeCombinator::union(
@@ -80,6 +96,13 @@ static function (Type $type, callable $traverse) use ($objectOrClassType, $objec
8096
return new ObjectWithoutClassType();
8197
},
8298
);
99+
100+
// prevent false-positives
101+
if ($isUncertain && $resultType->isSuperTypeOf($objectOrClassType)->yes()) {
102+
return null;
103+
}
104+
105+
return $resultType;
83106
}
84107

85108
}

src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,8 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
4545
$allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(true);
4646
$allowString = !$allowStringType->equals(new ConstantBooleanType(false));
4747

48-
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
49-
if ($objectOrClassType instanceof GenericClassStringType && $classType instanceof GenericClassStringType) {
50-
return new SpecifiedTypes([], []);
51-
}
52-
5348
$resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false);
54-
55-
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
56-
if ($resultType->isSuperTypeOf($objectOrClassType)->yes()) {
49+
if (null === $resultType) {
5750
return new SpecifiedTypes([], []);
5851
}
5952

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,10 @@ public function testBug6305(): void
389389
{
390390
$this->treatPhpDocTypesAsCertain = true;
391391
$this->analyse([__DIR__ . '/data/bug-6305.php'], [
392-
// [
393-
// 'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\A\' will always evaluate to true.',
394-
// 11,
395-
// ],
392+
[
393+
'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\A\' will always evaluate to true.',
394+
11,
395+
],
396396
// [
397397
// 'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\B\' will always evaluate to false.',
398398
// 14,
@@ -409,7 +409,17 @@ public function testBug6305b(): void
409409
public function testBug13713(): void
410410
{
411411
$this->treatPhpDocTypesAsCertain = true;
412-
$this->analyse([__DIR__ . '/data/bug-13713.php'], []);
412+
$this->analyse([__DIR__ . '/data/bug-13713.php'], [
413+
[
414+
"Call to function is_subclass_of() with arguments Bug13713\\test, 'stdClass' and false will always evaluate to true.",
415+
12,
416+
],
417+
[
418+
"Call to function is_subclass_of() with arguments class-string<Bug13713\\test>, 'stdClass' and true will always evaluate to true.",
419+
25,
420+
'Because the type is coming from a PHPDoc, you can turn off this check by setting <fg=cyan>treatPhpDocTypesAsCertain: false</> in your <fg=cyan>%configurationFile%</>.',
421+
],
422+
]);
413423
}
414424

415425
public function testBug6698(): void

tests/PHPStan/Rules/Comparison/data/bug-13713.php

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,25 @@
44

55
namespace Bug13713;
66

7-
function debug(object $test): void {
8-
if ($test instanceof \stdClass) {
9-
echo var_export(\is_subclass_of($test, \stdClass::class, false), true) . \PHP_EOL;
7+
function debug(object $object): void {
8+
if ($object instanceof \stdClass) {
9+
echo var_export(\is_subclass_of($object, \stdClass::class, false), true) . \PHP_EOL;
10+
}
11+
if ($object instanceof test) {
12+
echo var_export(\is_subclass_of($object, \stdClass::class, false), true) . \PHP_EOL;
1013
}
1114
}
1215

1316
class test extends \stdClass {}
1417
debug(new test);
18+
19+
/**
20+
* @param class-string<\stdClass> $stdClass
21+
* @param class-string<test> $test
22+
*/
23+
function debugWithClass(string $stdClass, string $test): void {
24+
echo var_export(\is_subclass_of($stdClass, \stdClass::class, true), true) . \PHP_EOL;
25+
echo var_export(\is_subclass_of($test, \stdClass::class, true), true) . \PHP_EOL;
26+
}
27+
28+
debugWithClass(test::class, test::class);

tests/PHPStan/Rules/Comparison/data/bug-6305b.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,15 @@ class B extends A {}
99
$b = mt_rand(0, 1) === 0 ? new B() : new A();
1010

1111
if (is_subclass_of($b, A::class)) {
12-
if (is_subclass_of($b, A::class)) {
13-
echo 'x';
14-
}
1512
}
1613

1714
if (is_subclass_of($b, B::class)) {
18-
if (is_subclass_of($b, B::class)) {
19-
echo 'y';
20-
}
2115
}
2216

2317
$b = mt_rand(0, 1) === 0 ? A::class : B::class;
2418

2519
if (is_subclass_of($b, A::class)) {
26-
if (is_subclass_of($b, A::class)) {
27-
echo 'x';
28-
}
2920
}
3021

3122
if (is_subclass_of($b, B::class)) {
32-
if (is_subclass_of($b, B::class)) {
33-
echo 'y';
34-
}
3523
}

0 commit comments

Comments
 (0)