Skip to content

Commit 9def6e9

Browse files
Rule
1 parent 23947f1 commit 9def6e9

4 files changed

Lines changed: 141 additions & 0 deletions

File tree

src/Rules/Classes/InstantiationRule.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@
2424
use PHPStan\Rules\RestrictedUsage\RewrittenDeclaringClassMethodReflection;
2525
use PHPStan\Rules\Rule;
2626
use PHPStan\Rules\RuleErrorBuilder;
27+
use PHPStan\Rules\RuleLevelHelper;
2728
use PHPStan\ShouldNotHappenException;
2829
use PHPStan\Type\Constant\ConstantStringType;
30+
use PHPStan\Type\ErrorType;
31+
use PHPStan\Type\ObjectWithoutClassType;
32+
use PHPStan\Type\StringType;
33+
use PHPStan\Type\Type;
34+
use PHPStan\Type\UnionType;
35+
use PHPStan\Type\VerbosityLevel;
2936
use function array_filter;
3037
use function array_map;
3138
use function array_merge;
@@ -48,6 +55,7 @@ public function __construct(
4855
private ReflectionProvider $reflectionProvider,
4956
private FunctionCallParametersCheck $check,
5057
private ClassNameCheck $classCheck,
58+
private RuleLevelHelper $ruleLevelHelper,
5159
private ConsistentConstructorHelper $consistentConstructorHelper,
5260
#[AutowiredParameter(ref: '%tips.discoveringSymbols%')]
5361
private bool $discoveringSymbolsTip,
@@ -62,13 +70,51 @@ public function getNodeType(): string
6270

6371
public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataEmitter $scope): array
6472
{
73+
if ($node->class instanceof Node\Expr) {
74+
$errors = $this->checkClassNameExprType($node->class, $scope);
75+
if ($errors !== []) {
76+
return $errors;
77+
}
78+
}
79+
6580
$errors = [];
6681
foreach ($this->getClassNames($node, $scope) as [$class, $isName]) {
6782
$errors = array_merge($errors, $this->checkClassName($class, $isName, $node, $scope));
6883
}
6984
return $errors;
7085
}
7186

87+
/**
88+
* @return list<IdentifierRuleError>
89+
*/
90+
private function checkClassNameExprType(Node\Expr $class, Scope $scope): array
91+
{
92+
$acceptedType = new UnionType([new StringType(), new ObjectWithoutClassType()]);
93+
$typeResult = $this->ruleLevelHelper->findTypeToCheck(
94+
$scope,
95+
$class,
96+
'',
97+
static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(),
98+
);
99+
100+
$foundType = $typeResult->getType();
101+
if ($foundType instanceof ErrorType) {
102+
// Unknown classes and mixed are reported elsewhere (e.g. "Instantiated class X not found.").
103+
return [];
104+
}
105+
106+
if ($acceptedType->isSuperTypeOf($foundType)->yes()) {
107+
return [];
108+
}
109+
110+
return [
111+
RuleErrorBuilder::message(sprintf(
112+
'Cannot instantiate class using %s.',
113+
$foundType->describe(VerbosityLevel::typeOnly()),
114+
))->identifier('new.nonObject')->build(),
115+
];
116+
}
117+
72118
/**
73119
* @param Node\Expr\New_ $node
74120
* @return list<IdentifierRuleError>

tests/PHPStan/Rules/Classes/ForbiddenNameCheckExtensionRuleTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ protected function getRule(): Rule
5353
$reflectionProvider,
5454
$container,
5555
),
56+
new RuleLevelHelper(
57+
$reflectionProvider,
58+
checkNullables: true,
59+
checkThisOnly: false,
60+
checkUnionTypes: true,
61+
checkExplicitMixed: false,
62+
checkImplicitMixed: false,
63+
checkBenevolentUnionTypes: false,
64+
discoveringSymbolsTip: true,
65+
),
5666
new ConsistentConstructorHelper(),
5767
discoveringSymbolsTip: true,
5868
);

tests/PHPStan/Rules/Classes/InstantiationRuleTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ protected function getRule(): Rule
5555
$reflectionProvider,
5656
$container,
5757
),
58+
new RuleLevelHelper(
59+
$reflectionProvider,
60+
checkNullables: true,
61+
checkThisOnly: false,
62+
checkUnionTypes: true,
63+
checkExplicitMixed: $this->checkExplicitMixed,
64+
checkImplicitMixed: false,
65+
checkBenevolentUnionTypes: false,
66+
discoveringSymbolsTip: true,
67+
),
5868
new ConsistentConstructorHelper(),
5969
discoveringSymbolsTip: true,
6070
);
@@ -682,4 +692,34 @@ public function testBug14499(): void
682692
$this->analyse([__DIR__ . '/data/bug-14499.php'], []);
683693
}
684694

695+
public function testInstantiationWithNonObjectType(): void
696+
{
697+
$this->analyse([__DIR__ . '/data/instantiation-non-object.php'], [
698+
[
699+
'Cannot instantiate class using int.',
700+
31,
701+
],
702+
[
703+
'Cannot instantiate class using int.',
704+
35,
705+
],
706+
[
707+
'Cannot instantiate class using float.',
708+
36,
709+
],
710+
[
711+
'Cannot instantiate class using bool.',
712+
37,
713+
],
714+
[
715+
'Cannot instantiate class using int|string.',
716+
38,
717+
],
718+
[
719+
'Cannot instantiate class using array<int, string>.',
720+
44,
721+
],
722+
]);
723+
}
724+
685725
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace InstantiationNonObject;
4+
5+
function get_class_name(): int
6+
{
7+
return 123;
8+
}
9+
10+
class Foo
11+
{
12+
}
13+
14+
/**
15+
* @param class-string $classString
16+
* @param class-string<Foo> $classStringOfFoo
17+
*/
18+
function doFoo(
19+
string $string,
20+
object $object,
21+
int $int,
22+
float $float,
23+
bool $bool,
24+
int|string $intOrString,
25+
string $classString,
26+
string $classStringOfFoo,
27+
Foo $foo
28+
): void
29+
{
30+
$class = get_class_name();
31+
new $class;
32+
33+
new $string;
34+
new $object;
35+
new $int;
36+
new $float;
37+
new $bool;
38+
new $intOrString;
39+
new $classString;
40+
new $classStringOfFoo;
41+
new $foo;
42+
43+
$array = ['a'];
44+
new $array;
45+
}

0 commit comments

Comments
 (0)