Skip to content

Commit 8a3f8c4

Browse files
authored
Enabling constructor check for class-string variables
1 parent 5914d32 commit 8a3f8c4

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

src/Rules/Classes/InstantiationRule.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Expr\New_;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\Internal\SprintfHelper;
9+
use PHPStan\Reflection\ClassReflection;
910
use PHPStan\Reflection\ParametersAcceptorSelector;
1011
use PHPStan\Reflection\Php\PhpMethodReflection;
1112
use PHPStan\Reflection\ReflectionProvider;
@@ -17,6 +18,7 @@
1718
use PHPStan\Rules\RuleErrorBuilder;
1819
use PHPStan\ShouldNotHappenException;
1920
use PHPStan\Type\Constant\ConstantStringType;
21+
use function array_filter;
2022
use function array_map;
2123
use function array_merge;
2224
use function count;
@@ -245,6 +247,20 @@ private function getClassNames(Node $node, Scope $scope): array
245247

246248
$type = $scope->getType($node->class);
247249

250+
if ($type->isClassString()->yes()) {
251+
$concretes = array_filter(
252+
$type->getClassStringObjectType()->getObjectClassReflections(),
253+
static fn (ClassReflection $classReflection): bool => !$classReflection->isAbstract() && !$classReflection->isInterface(),
254+
);
255+
256+
if (count($concretes) > 0) {
257+
return array_map(
258+
static fn (ClassReflection $classReflection): array => [$classReflection->getName(), true],
259+
$concretes,
260+
);
261+
}
262+
}
263+
248264
return array_merge(
249265
array_map(
250266
static fn (ConstantStringType $type): array => [$type->getValue(), true],

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,4 +511,46 @@ public function testBug11815(): void
511511
$this->analyse([__DIR__ . '/data/bug-11815.php'], []);
512512
}
513513

514+
public function testClassString(): void
515+
{
516+
$this->analyse([__DIR__ . '/data/class-string.php'], [
517+
[
518+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
519+
65,
520+
],
521+
[
522+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
523+
66,
524+
],
525+
[
526+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
527+
67,
528+
],
529+
[
530+
'Parameter #1 $i of class ClassString\C constructor expects int, string given.',
531+
75,
532+
],
533+
[
534+
'Parameter #1 $i of class ClassString\C constructor expects int, string given.',
535+
76,
536+
],
537+
[
538+
'Parameter #1 $i of class ClassString\C constructor expects int, string given.',
539+
77,
540+
],
541+
[
542+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
543+
85,
544+
],
545+
[
546+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
547+
86,
548+
],
549+
[
550+
'Parameter #1 $i of class ClassString\A constructor expects int, string given.',
551+
87,
552+
],
553+
]);
554+
}
555+
514556
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace ClassString;
6+
7+
class A
8+
{
9+
public function __construct(public int $i)
10+
{
11+
}
12+
}
13+
14+
abstract class B
15+
{
16+
public function __construct(public int $i)
17+
{
18+
}
19+
}
20+
21+
class C extends B
22+
{
23+
}
24+
25+
interface D
26+
{
27+
}
28+
29+
class Foo
30+
{
31+
/**
32+
* @return class-string<A>
33+
*/
34+
public static function returnClassStringA(): string
35+
{
36+
return A::class;
37+
}
38+
39+
/**
40+
* @return class-string<B>
41+
*/
42+
public static function returnClassStringB(): string
43+
{
44+
return B::class;
45+
}
46+
47+
/**
48+
* @return class-string<C>
49+
*/
50+
public static function returnClassStringC(): string
51+
{
52+
return C::class;
53+
}
54+
55+
/**
56+
* @return class-string<D>
57+
*/
58+
public static function returnClassStringD(): string
59+
{
60+
return D::class;
61+
}
62+
}
63+
64+
$classString = Foo::returnClassStringA();
65+
$error = new (Foo::returnClassStringA())('O_O');
66+
$error = new ($classString)('O_O');
67+
$error = new $classString('O_O');
68+
69+
$classString = Foo::returnClassStringB();
70+
$ok = new (Foo::returnClassStringB())('O_O');
71+
$ok = new ($classString)('O_O');
72+
$ok = new $classString('O_O');
73+
74+
$classString = Foo::returnClassStringC();
75+
$error = new (Foo::returnClassStringC())('O_O');
76+
$error = new ($classString)('O_O');
77+
$error = new $classString('O_O');
78+
79+
$classString = Foo::returnClassStringD();
80+
$ok = new (Foo::returnClassStringD())('O_O');
81+
$ok = new ($classString)('O_O');
82+
$ok = new $classString('O_O');
83+
84+
$className = A::class;
85+
$error = new ($className)('O_O');
86+
$error = new $className('O_O');
87+
$error = new A('O_O');

0 commit comments

Comments
 (0)