Skip to content

Commit b05cb5e

Browse files
takaramondrejmirtes
authored andcommitted
Handle impure/throw point of inherited constructors of anonymous classes
1 parent 1029bba commit b05cb5e

File tree

3 files changed

+114
-25
lines changed

3 files changed

+114
-25
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3653,31 +3653,6 @@ static function (): void {
36533653

36543654
} else {
36553655
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope); // populates $expr->class->name
3656-
$constructorResult = null;
3657-
$this->processStmtNode($expr->class, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void {
3658-
$nodeCallback($node, $scope);
3659-
if (!$node instanceof MethodReturnStatementsNode) {
3660-
return;
3661-
}
3662-
if ($constructorResult !== null) {
3663-
return;
3664-
}
3665-
$currentClassReflection = $node->getClassReflection();
3666-
if ($currentClassReflection->getName() !== $classReflection->getName()) {
3667-
return;
3668-
}
3669-
if (!$currentClassReflection->hasConstructor()) {
3670-
return;
3671-
}
3672-
if ($currentClassReflection->getConstructor()->getName() !== $node->getMethodReflection()->getName()) {
3673-
return;
3674-
}
3675-
$constructorResult = $node;
3676-
}, StatementContext::createTopLevel());
3677-
if ($constructorResult !== null) {
3678-
$throwPoints = array_merge($throwPoints, $constructorResult->getStatementResult()->getThrowPoints());
3679-
$impurePoints = array_merge($impurePoints, $constructorResult->getImpurePoints());
3680-
}
36813656
if ($classReflection->hasConstructor()) {
36823657
$constructorReflection = $classReflection->getConstructor();
36833658
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
@@ -3686,6 +3661,54 @@ static function (): void {
36863661
$constructorReflection->getVariants(),
36873662
$constructorReflection->getNamedArgumentsVariants(),
36883663
);
3664+
3665+
if ($constructorReflection->getDeclaringClass()->getName() === $classReflection->getName()) {
3666+
$constructorResult = null;
3667+
$this->processStmtNode($expr->class, $scope, static function (Node $node, Scope $scope) use ($nodeCallback, $classReflection, &$constructorResult): void {
3668+
$nodeCallback($node, $scope);
3669+
if (!$node instanceof MethodReturnStatementsNode) {
3670+
return;
3671+
}
3672+
if ($constructorResult !== null) {
3673+
return;
3674+
}
3675+
$currentClassReflection = $node->getClassReflection();
3676+
if ($currentClassReflection->getName() !== $classReflection->getName()) {
3677+
return;
3678+
}
3679+
if (!$currentClassReflection->hasConstructor()) {
3680+
return;
3681+
}
3682+
if ($currentClassReflection->getConstructor()->getName() !== $node->getMethodReflection()->getName()) {
3683+
return;
3684+
}
3685+
$constructorResult = $node;
3686+
}, StatementContext::createTopLevel());
3687+
if ($constructorResult !== null) {
3688+
$throwPoints = $constructorResult->getStatementResult()->getThrowPoints();
3689+
$impurePoints = $constructorResult->getImpurePoints();
3690+
}
3691+
} else {
3692+
$this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel());
3693+
$declaringClass = $constructorReflection->getDeclaringClass();
3694+
$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $classReflection, $expr, new Name\FullyQualified($declaringClass->getName()), $expr->getArgs(), $scope);
3695+
if ($constructorThrowPoint !== null) {
3696+
$throwPoints[] = $constructorThrowPoint;
3697+
}
3698+
3699+
if (!$constructorReflection->hasSideEffects()->no()) {
3700+
$certain = $constructorReflection->isPure()->no();
3701+
$impurePoints[] = new ImpurePoint(
3702+
$scope,
3703+
$expr,
3704+
'new',
3705+
sprintf('instantiation of class %s', $declaringClass->getDisplayName()),
3706+
$certain,
3707+
);
3708+
}
3709+
}
3710+
} else {
3711+
$this->processStmtNode($expr->class, $scope, $nodeCallback, StatementContext::createTopLevel());
36893712
}
36903713
}
36913714

tests/PHPStan/Rules/DeadCode/NoopRuleTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,18 @@ public function testBug13067(): void
158158
$this->analyse([__DIR__ . '/data/bug-13067.php'], []);
159159
}
160160

161+
public function testBug13698(): void
162+
{
163+
$this->analyse([__DIR__ . '/data/bug-13698.php'], [
164+
[
165+
'Expression "new class extends \Bug13698\NoConstructorClass…" on a separate line does not do anything.',
166+
47,
167+
],
168+
[
169+
'Expression "new class…" on a separate line does not do anything.',
170+
50,
171+
],
172+
]);
173+
}
174+
161175
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13698;
4+
5+
class ImpureConstructorClass
6+
{
7+
public function __construct()
8+
{
9+
echo 'foo';
10+
}
11+
}
12+
13+
class ThrowingConstructorClass
14+
{
15+
public function __construct()
16+
{
17+
if (rand(0, 1) === 0) {
18+
throw new \RuntimeException('Error in constructor');
19+
}
20+
}
21+
}
22+
23+
class NoConstructorClass {}
24+
25+
function test(): void
26+
{
27+
new class() extends ImpureConstructorClass {
28+
};
29+
30+
new class() extends ImpureConstructorClass {
31+
public function __construct()
32+
{
33+
parent::__construct();
34+
}
35+
};
36+
37+
new class() extends ThrowingConstructorClass {
38+
};
39+
40+
new class() extends ThrowingConstructorClass {
41+
public function __construct()
42+
{
43+
parent::__construct();
44+
}
45+
};
46+
47+
new class() extends NoConstructorClass {
48+
};
49+
50+
new class() {
51+
};
52+
}

0 commit comments

Comments
 (0)