Skip to content

Commit f2663f1

Browse files
Avoid false error on is_subclass_of
1 parent acf7f97 commit f2663f1

File tree

7 files changed

+79
-13
lines changed

7 files changed

+79
-13
lines changed

src/Type/Php/IsAFunctionTypeSpecifyingHelper.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,18 @@ public function determineType(
3838

3939
return TypeTraverser::map(
4040
$classType,
41-
static function (Type $type, callable $traverse) use ($objectOrClassTypeClassNames, $allowString, $allowSameClass): Type {
41+
static function (Type $type, callable $traverse) use ($objectOrClassType, $objectOrClassTypeClassNames, $allowString, $allowSameClass): Type {
4242
if ($type instanceof UnionType || $type instanceof IntersectionType) {
4343
return $traverse($type);
4444
}
4545
if ($type instanceof ConstantStringType) {
4646
if (!$allowSameClass && $objectOrClassTypeClassNames === [$type->getValue()]) {
47-
return new NeverType();
47+
// For objectType we cannot be sure since 'Foo' is used for both
48+
// - the Foo class
49+
// - a child of foo class
50+
if ($objectOrClassType->isString()->yes()) {
51+
return new NeverType();
52+
}
4853
}
4954
if ($allowString) {
5055
return TypeCombinator::union(

src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
5353
$resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false);
5454

5555
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
56-
if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) {
56+
if ($resultType->isSuperTypeOf($objectOrClassType)->yes()) {
5757
return new SpecifiedTypes([], []);
5858
}
5959

tests/PHPStan/Analyser/nsrt/bug-6305.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ class B extends A {}
1515
}
1616

1717
if (is_subclass_of($b, B::class)) {
18-
assertType('*NEVER*', $b);
18+
assertType('Bug6305Types\B', $b); // Could be NEVER
1919
}

tests/PHPStan/Analyser/nsrt/is-subclass-of.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
function (Bar $a, Bar $b, Bar $c, Bar $d) {
66
if (is_subclass_of($a, Bar::class)) {
7-
\PHPStan\Testing\assertType('*NEVER*', $a);
7+
\PHPStan\Testing\assertType('IsSubclassOf\Bar', $a); // Can still be a Bar child
88
}
99

1010
if (is_subclass_of($b, Foo::class)) {

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -389,17 +389,29 @@ 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-
],
396-
[
397-
'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\B\' will always evaluate to false.',
398-
14,
399-
],
392+
// [
393+
// 'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\A\' will always evaluate to true.',
394+
// 11,
395+
// ],
396+
// [
397+
// 'Call to function is_subclass_of() with Bug6305\B and \'Bug6305\\\B\' will always evaluate to false.',
398+
// 14,
399+
// ],
400400
]);
401401
}
402402

403+
public function testBug6305b(): void
404+
{
405+
$this->treatPhpDocTypesAsCertain = true;
406+
$this->analyse([__DIR__ . '/data/bug-6305b.php'], []);
407+
}
408+
409+
public function testBug13713(): void
410+
{
411+
$this->treatPhpDocTypesAsCertain = true;
412+
$this->analyse([__DIR__ . '/data/bug-13713.php'], []);
413+
}
414+
403415
public function testBug6698(): void
404416
{
405417
$this->treatPhpDocTypesAsCertain = true;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug13713;
6+
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;
10+
}
11+
}
12+
13+
class test extends \stdClass {}
14+
debug(new test);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Bug6305b;
4+
5+
class A {}
6+
7+
class B extends A {}
8+
9+
$b = mt_rand(0, 1) === 0 ? new B() : new A();
10+
11+
if (is_subclass_of($b, A::class)) {
12+
if (is_subclass_of($b, A::class)) {
13+
echo 'x';
14+
}
15+
}
16+
17+
if (is_subclass_of($b, B::class)) {
18+
if (is_subclass_of($b, B::class)) {
19+
echo 'y';
20+
}
21+
}
22+
23+
$b = mt_rand(0, 1) === 0 ? A::class : B::class;
24+
25+
if (is_subclass_of($b, A::class)) {
26+
if (is_subclass_of($b, A::class)) {
27+
echo 'x';
28+
}
29+
}
30+
31+
if (is_subclass_of($b, B::class)) {
32+
if (is_subclass_of($b, B::class)) {
33+
echo 'y';
34+
}
35+
}

0 commit comments

Comments
 (0)