Skip to content

Commit bf4bd86

Browse files
VasekPurchartondrejmirtes
authored andcommitted
Handle side effects of constructors in new
1 parent 8d7c3ff commit bf4bd86

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,6 +2447,12 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
24472447
$expr->getArgs(),
24482448
$constructorReflection->getVariants(),
24492449
);
2450+
$hasSideEffects = $constructorReflection->hasSideEffects();
2451+
if ($hasSideEffects->yes()) {
2452+
foreach ($expr->getArgs() as $arg) {
2453+
$scope = $scope->invalidateExpression($arg->value, true);
2454+
}
2455+
}
24502456
$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $classReflection, $expr, $expr->class, $expr->getArgs(), $scope);
24512457
if ($constructorThrowPoint !== null) {
24522458
$throwPoints[] = $constructorThrowPoint;

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ public function dataFileAsserts(): iterable
265265

266266
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4343.php');
267267
yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-method.php');
268+
yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-constructor.php');
268269
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4351.php');
269270
yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-use.php');
270271
yield from $this->gatherAssertTypes(__DIR__ . '/data/var-above-declare.php');
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace ImpureConstuctor;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/** @var bool */
11+
private $active;
12+
13+
public function __construct()
14+
{
15+
$this->active = false;
16+
}
17+
18+
public function getActive(): bool
19+
{
20+
return $this->active;
21+
}
22+
23+
public function activate(): void
24+
{
25+
$this->active = true;
26+
}
27+
28+
}
29+
30+
31+
class ClassWithImpureConstructorNotMarked
32+
{
33+
34+
public function __construct(Foo $foo)
35+
{
36+
$foo->activate();
37+
}
38+
39+
}
40+
41+
class ClassWithImpureConstructorMarked
42+
{
43+
44+
/**
45+
* @phpstan-impure
46+
*/
47+
public function __construct(Foo $foo)
48+
{
49+
$foo->activate();
50+
}
51+
52+
}
53+
54+
class ClassWithPureConstructorMarked
55+
{
56+
57+
/**
58+
* @phpstan-pure
59+
*/
60+
public function __construct(Foo $foo)
61+
{
62+
}
63+
64+
}
65+
66+
class ClassWithImpureConstructorNotMarkedWithoutParameters
67+
{
68+
69+
/** @var string */
70+
private $lorem;
71+
72+
public function __construct()
73+
{
74+
$this->lorem = 'lorem';
75+
}
76+
77+
}
78+
79+
class Test
80+
{
81+
82+
public function testClassWithImpureConstructorNotMarked()
83+
{
84+
$foo = new Foo();
85+
assertType('bool', $foo->getActive());
86+
87+
assert(!$foo->getActive());
88+
assertType('false', $foo->getActive());
89+
90+
new ClassWithImpureConstructorNotMarked($foo);
91+
assertType('false', $foo->getActive());
92+
}
93+
94+
public function testClassWithImpureConstructorMarked()
95+
{
96+
$foo = new Foo();
97+
assertType('bool', $foo->getActive());
98+
99+
assert(!$foo->getActive());
100+
assertType('false', $foo->getActive());
101+
102+
new ClassWithImpureConstructorMarked($foo);
103+
assertType('bool', $foo->getActive());
104+
}
105+
106+
public function testClassWithPureConstructorMarked()
107+
{
108+
$foo = new Foo();
109+
assertType('bool', $foo->getActive());
110+
111+
assert(!$foo->getActive());
112+
assertType('false', $foo->getActive());
113+
114+
new ClassWithPureConstructorMarked($foo);
115+
assertType('false', $foo->getActive());
116+
}
117+
118+
public function testClassWithImpureConstructorNotMarkedWithoutParameters()
119+
{
120+
$foo = new Foo();
121+
assertType('bool', $foo->getActive());
122+
123+
assert(!$foo->getActive());
124+
assertType('false', $foo->getActive());
125+
126+
new ClassWithImpureConstructorNotMarkedWithoutParameters();
127+
assertType('false', $foo->getActive());
128+
}
129+
130+
}

0 commit comments

Comments
 (0)