Skip to content

Commit f4d060b

Browse files
committed
Understand that methods called from constructor throwing exception cannot leave object in an uninitializad state
1 parent 58a4ae9 commit f4d060b

File tree

2 files changed

+82
-0
lines changed

2 files changed

+82
-0
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,13 +2185,31 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
21852185
$calledMethodEndScope = null;
21862186
foreach ($executionEnds as $executionEnd) {
21872187
$statementResult = $executionEnd->getStatementResult();
2188+
$endNode = $executionEnd->getNode();
2189+
if ($endNode instanceof Node\Stmt\Throw_) {
2190+
continue;
2191+
}
2192+
if ($endNode instanceof Node\Stmt\Expression) {
2193+
$exprType = $statementResult->getScope()->getType($endNode->expr);
2194+
if ($exprType instanceof NeverType && $exprType->isExplicit()) {
2195+
continue;
2196+
}
2197+
}
21882198
if ($calledMethodEndScope === null) {
21892199
$calledMethodEndScope = $statementResult->getScope();
21902200
continue;
21912201
}
21922202

21932203
$calledMethodEndScope = $calledMethodEndScope->mergeWith($statementResult->getScope());
21942204
}
2205+
foreach ($methodReturnStatementsNode->getReturnStatements() as $returnStatement) {
2206+
if ($calledMethodEndScope === null) {
2207+
$calledMethodEndScope = $returnStatement->getScope();
2208+
continue;
2209+
}
2210+
2211+
$calledMethodEndScope = $calledMethodEndScope->mergeWith($returnStatement->getScope());
2212+
}
21952213

21962214
if ($calledMethodEndScope !== null) {
21972215
$scope = $scope->mergeInitializedProperties($calledMethodEndScope);

tests/PHPStan/Rules/Properties/data/uninitialized-property.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,3 +406,67 @@ private function returnNever()
406406
}
407407

408408
}
409+
410+
class InitializedInPrivateSetterWithThrow
411+
{
412+
413+
private int $foo;
414+
415+
public function __construct()
416+
{
417+
$this->setFoo();
418+
$this->doSomething();
419+
}
420+
421+
private function setFoo()
422+
{
423+
if (rand(0, 1)) {
424+
$this->foo = 1;
425+
return;
426+
}
427+
428+
throw new \Exception();
429+
}
430+
431+
public function doSomething()
432+
{
433+
echo $this->foo;
434+
}
435+
436+
}
437+
438+
class InitializedInPrivateSetterWithReturnNever
439+
{
440+
441+
private int $foo;
442+
443+
public function __construct()
444+
{
445+
$this->setFoo();
446+
$this->doSomething();
447+
}
448+
449+
private function setFoo()
450+
{
451+
if (rand(0, 1)) {
452+
$this->foo = 1;
453+
return;
454+
}
455+
456+
$this->returnNever();
457+
}
458+
459+
public function doSomething()
460+
{
461+
echo $this->foo;
462+
}
463+
464+
/**
465+
* @return never
466+
*/
467+
private function returnNever()
468+
{
469+
throw new \Exception();
470+
}
471+
472+
}

0 commit comments

Comments
 (0)