Skip to content

Commit cf09fa0

Browse files
authored
Merge branch refs/heads/1.12.x into 2.1.x
2 parents 975c37c + 031f34e commit cf09fa0

9 files changed

+254
-9
lines changed

src/Rules/Comparison/ImpossibleCheckTypeFunctionCallRule.php

-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Rules\Rule;
99
use PHPStan\Rules\RuleErrorBuilder;
1010
use function sprintf;
11-
use function strtolower;
1211

1312
/**
1413
* @implements Rule<Node\Expr\FuncCall>
@@ -37,9 +36,6 @@ public function processNode(Node $node, Scope $scope): array
3736
}
3837

3938
$functionName = (string) $node->name;
40-
if (strtolower($functionName) === 'is_a') {
41-
return [];
42-
}
4339
$isAlways = $this->impossibleCheckTypeHelper->findSpecifiedType($scope, $node);
4440
if ($isAlways === null) {
4541
return [];

src/Type/Php/IsAFunctionTypeSpecifyingExtension.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
4747
$allowStringType = isset($node->getArgs()[2]) ? $scope->getType($node->getArgs()[2]->value) : new ConstantBooleanType(false);
4848
$allowString = !$allowStringType->equals(new ConstantBooleanType(false));
4949

50+
$resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true);
51+
52+
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
53+
if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) {
54+
return new SpecifiedTypes([], []);
55+
}
56+
5057
return $this->typeSpecifier->create(
5158
$node->getArgs()[0]->value,
52-
$this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, true),
59+
$resultType,
5360
$context,
5461
$scope,
5562
);

src/Type/Php/IsSubclassOfFunctionTypeSpecifyingExtension.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,16 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
4848
return new SpecifiedTypes([], []);
4949
}
5050

51+
$resultType = $this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false);
52+
53+
// prevent false-positives in IsAFunctionTypeSpecifyingHelper
54+
if ($classType->getConstantStrings() === [] && $resultType->isSuperTypeOf($objectOrClassType)->yes()) {
55+
return new SpecifiedTypes([], []);
56+
}
57+
5158
return $this->typeSpecifier->create(
5259
$node->getArgs()[0]->value,
53-
$this->isAFunctionTypeSpecifyingHelper->determineType($objectOrClassType, $classType, $allowString, false),
60+
$resultType,
5461
$context,
5562
$scope,
5663
);

tests/PHPStan/Analyser/TypeSpecifierTest.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -1134,9 +1134,7 @@ public function dataCondition(): iterable
11341134
new Arg(new Variable('stringOrNull')),
11351135
new Arg(new Expr\ConstFetch(new Name('false'))),
11361136
]),
1137-
[
1138-
'$object' => 'object',
1139-
],
1137+
[],
11401138
[],
11411139
],
11421140
[

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

+37
Original file line numberDiff line numberDiff line change
@@ -971,4 +971,41 @@ public function testAlwaysTruePregMatch(): void
971971
$this->analyse([__DIR__ . '/data/always-true-preg-match.php'], []);
972972
}
973973

974+
public function testBug3979(): void
975+
{
976+
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
977+
$this->treatPhpDocTypesAsCertain = true;
978+
$this->analyse([__DIR__ . '/data/bug-3979.php'], []);
979+
}
980+
981+
public function testBug8464(): void
982+
{
983+
if (PHP_VERSION_ID < 80000) {
984+
$this->markTestSkipped('Test requires PHP 8.0.');
985+
}
986+
987+
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
988+
$this->treatPhpDocTypesAsCertain = true;
989+
$this->analyse([__DIR__ . '/data/bug-8464.php'], []);
990+
}
991+
992+
public function testBug8954(): void
993+
{
994+
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
995+
$this->treatPhpDocTypesAsCertain = true;
996+
$this->analyse([__DIR__ . '/data/bug-8954.php'], []);
997+
}
998+
999+
public function testBugPR3404(): void
1000+
{
1001+
$this->checkAlwaysTrueCheckTypeFunctionCall = true;
1002+
$this->treatPhpDocTypesAsCertain = true;
1003+
$this->analyse([__DIR__ . '/data/bug-pr-3404.php'], [
1004+
[
1005+
'Call to function is_a() with arguments BugPR3404\Location, \'BugPR3404\\\\Location\' and true will always evaluate to true.',
1006+
21,
1007+
],
1008+
]);
1009+
}
1010+
9741011
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace Bug3979;
4+
5+
class A { }
6+
class B extends A { }
7+
class C { }
8+
9+
/**
10+
* @param mixed $value
11+
* @param string $class_type
12+
*/
13+
function check_class($value, $class_type): bool
14+
{
15+
if (!is_string($value) || !class_exists($value) ||
16+
($class_type && !is_subclass_of($value, $class_type)))
17+
return false;
18+
return true;
19+
}
20+
21+
var_dump(check_class("B", "A")); // true
22+
var_dump(check_class("C", "A")); // false
23+
24+
/**
25+
* @param class-string $value
26+
* @param string $class_type
27+
*/
28+
function check_class2($value, $class_type): bool
29+
{
30+
if (is_a($value, $class_type, true)) {
31+
return true;
32+
}
33+
return false;
34+
}
35+
36+
/**
37+
* @param class-string|object $value
38+
* @param string $class_type
39+
*/
40+
function check_class3($value, $class_type): bool
41+
{
42+
if (is_a($value, $class_type, true)) {
43+
return true;
44+
}
45+
return false;
46+
}
47+
48+
/**
49+
* @param class-string|object $value
50+
* @param string $class_type
51+
*/
52+
function check_class4($value, $class_type): bool
53+
{
54+
if (is_subclass_of($value, $class_type, true)) {
55+
return true;
56+
}
57+
return false;
58+
}
59+
60+
/**
61+
* @param object $value
62+
* @param string $class_type
63+
*/
64+
function check_class5($value, $class_type): bool
65+
{
66+
if (is_a($value, $class_type, true)) {
67+
return true;
68+
}
69+
return false;
70+
}
71+
72+
/**
73+
* @param object $value
74+
* @param string $class_type
75+
*/
76+
function check_class6($value, $class_type): bool
77+
{
78+
if (is_subclass_of($value, $class_type, true)) {
79+
return true;
80+
}
81+
return false;
82+
}
83+
84+
/**
85+
* @param object $value
86+
* @param class-string $class_type
87+
*/
88+
function check_class7($value, $class_type): bool
89+
{
90+
if (is_a($value, $class_type, true)) {
91+
return true;
92+
}
93+
return false;
94+
}
95+
96+
/**
97+
* @param object $value
98+
* @param class-string $class_type
99+
*/
100+
function check_class8($value, $class_type): bool
101+
{
102+
if (is_subclass_of($value, $class_type, true)) {
103+
return true;
104+
}
105+
return false;
106+
}
107+
108+
/**
109+
* @param class-string $value
110+
* @param class-string $class_type
111+
*/
112+
function check_class9($value, $class_type): bool
113+
{
114+
if (is_a($value, $class_type, true)) {
115+
return true;
116+
}
117+
return false;
118+
}
119+
120+
/**
121+
* @param class-string $value
122+
* @param class-string $class_type
123+
*/
124+
function check_class10($value, $class_type): bool
125+
{
126+
if (is_subclass_of($value, $class_type, true)) {
127+
return true;
128+
}
129+
return false;
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug8464;
4+
5+
final class ObjectUtil
6+
{
7+
/**
8+
* @param class-string $type
9+
*/
10+
public static function instanceOf(mixed $object, string $type): bool
11+
{
12+
return \is_object($object)
13+
&& (
14+
$object::class === $type ||
15+
is_subclass_of($object, $type)
16+
);
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8954;
4+
5+
/**
6+
* @template U
7+
* @template V
8+
*
9+
* @param ?class-string<U> $class
10+
* @param class-string<V> $expected
11+
*
12+
* @return ?class-string<V>
13+
*/
14+
function ensureSubclassOf(?string $class, string $expected): ?string {
15+
if ($class === null) {
16+
return $class;
17+
}
18+
19+
if (!class_exists($class)) {
20+
throw new \Exception("Class “{$class}” does not exist.");
21+
}
22+
23+
if (!is_subclass_of($class, $expected)) {
24+
throw new \Exception("Class “{$class}” is not a subclass of “{$expected}”.");
25+
}
26+
27+
return $class;
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php // lint >= 8.0
2+
3+
namespace BugPR3404;
4+
5+
interface Location
6+
{
7+
8+
}
9+
10+
/** @return class-string<Location> */
11+
function aaa(): string
12+
{
13+
14+
}
15+
16+
function (Location $l): void {
17+
if (is_a($l, aaa(), true)) {
18+
// might not always be true. $l might be one subtype of Location, aaa() might return a name of a different subtype of Location
19+
}
20+
21+
if (is_a($l, Location::class, true)) {
22+
// always true
23+
}
24+
};

0 commit comments

Comments
 (0)