Skip to content

Commit c8174d5

Browse files
committed
parent::__construct() resets $this state
1 parent 2de6a24 commit c8174d5

4 files changed

Lines changed: 99 additions & 1 deletion

File tree

src/Analyser/NodeScopeResolver.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
use PHPStan\PhpDoc\ResolvedPhpDocBlock;
7979
use PHPStan\Reflection\ClassReflection;
8080
use PHPStan\Reflection\FunctionReflection;
81+
use PHPStan\Reflection\MethodReflection;
8182
use PHPStan\Reflection\Native\NativeMethodReflection;
8283
use PHPStan\Reflection\ParametersAcceptor;
8384
use PHPStan\Reflection\ParametersAcceptorSelector;
@@ -1799,6 +1800,21 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
17991800
}
18001801
$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context, $closureBindScope ?? null);
18011802
$scope = $result->getScope();
1803+
$scopeFunction = $scope->getFunction();
1804+
if (
1805+
$methodReflection !== null
1806+
&& !$methodReflection->isStatic()
1807+
&& $methodReflection->hasSideEffects()->yes()
1808+
&& $scopeFunction instanceof MethodReflection
1809+
&& !$scopeFunction->isStatic()
1810+
&& $scope->isInClass()
1811+
&& (
1812+
$scope->getClassReflection()->getName() === $methodReflection->getDeclaringClass()->getName()
1813+
|| $scope->getClassReflection()->isSubclassOf($methodReflection->getDeclaringClass()->getName())
1814+
)
1815+
) {
1816+
$scope = $scope->invalidateExpression(new Variable('this'), true);
1817+
}
18021818
$hasYield = $hasYield || $result->hasYield();
18031819
} elseif ($expr instanceof PropertyFetch) {
18041820
$result = $this->processExprNode($expr->var, $scope, $nodeCallback, $context->enterDeep());

src/Reflection/Php/PhpMethodReflection.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,17 +452,22 @@ public function getThrowType(): ?Type
452452
public function hasSideEffects(): TrinaryLogic
453453
{
454454
$name = strtolower($this->getName());
455+
$isVoid = $this->getReturnType() instanceof VoidType;
455456

456457
if (
457458
$name !== '__construct'
458-
&& $this->getReturnType() instanceof VoidType
459+
&& $isVoid
459460
) {
460461
return TrinaryLogic::createYes();
461462
}
462463
if ($this->isPure !== null) {
463464
return TrinaryLogic::createFromBoolean(!$this->isPure);
464465
}
465466

467+
if ($isVoid) {
468+
return TrinaryLogic::createYes();
469+
}
470+
466471
return TrinaryLogic::createMaybe();
467472
}
468473

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10705,6 +10705,11 @@ public function dataImpureMethod(): array
1070510705
return $this->gatherAssertTypes(__DIR__ . '/data/impure-method.php');
1070610706
}
1070710707

10708+
public function dataBug4351(): array
10709+
{
10710+
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4351.php');
10711+
}
10712+
1070810713
/**
1070910714
* @param string $file
1071010715
* @return array<string, mixed[]>
@@ -10911,6 +10916,7 @@ private function gatherAssertTypes(string $file): array
1091110916
* @dataProvider dataBug4339
1091210917
* @dataProvider dataBug4343
1091310918
* @dataProvider dataImpureMethod
10919+
* @dataProvider dataBug4351
1091410920
* @param string $assertType
1091510921
* @param string $file
1091610922
* @param mixed ...$args
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
namespace Bug4351;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Thing
8+
{
9+
public function doSomething(): void
10+
{
11+
}
12+
}
13+
14+
class ParentC
15+
{
16+
/** @var Thing|null */
17+
protected $thing;
18+
19+
protected function __construct()
20+
{
21+
$this->thing = null;
22+
}
23+
}
24+
25+
class HelloWorld extends ParentC
26+
{
27+
public function __construct(Thing $thing)
28+
{
29+
assertType('Bug4351\Thing|null', $this->thing);
30+
$this->thing = $thing;
31+
assertType('Bug4351\Thing', $this->thing);
32+
33+
parent::__construct();
34+
assertType('Bug4351\Thing|null', $this->thing);
35+
}
36+
37+
public function doFoo(Thing $thing)
38+
{
39+
assertType('Bug4351\Thing|null', $this->thing);
40+
$this->thing = $thing;
41+
assertType('Bug4351\Thing', $this->thing);
42+
43+
UnrelatedClass::doFoo();
44+
assertType('Bug4351\Thing', $this->thing);
45+
}
46+
47+
public function doBar(Thing $thing)
48+
{
49+
assertType('Bug4351\Thing|null', $this->thing);
50+
$this->thing = $thing;
51+
assertType('Bug4351\Thing', $this->thing);
52+
53+
UnrelatedClass::doStaticFoo();
54+
assertType('Bug4351\Thing', $this->thing);
55+
}
56+
}
57+
58+
class UnrelatedClass
59+
{
60+
61+
public function doFoo(): void
62+
{
63+
64+
}
65+
66+
public static function doStaticFoo(): void
67+
{
68+
69+
}
70+
71+
}

0 commit comments

Comments
 (0)