Skip to content

Commit ce47279

Browse files
staabmondrejmirtes
authored andcommitted
Remember narrowed types from the constructor from deep PropertyFetches
1 parent d6a3456 commit ce47279

File tree

2 files changed

+58
-15
lines changed

2 files changed

+58
-15
lines changed

src/Analyser/MutatingScope.php

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -356,23 +356,30 @@ public function rememberConstructorScope(): self
356356

357357
private function isReadonlyPropertyFetchOnThis(PropertyFetch $expr): bool
358358
{
359-
if (
360-
!$expr->name instanceof Node\Identifier
361-
|| !$expr->var instanceof Variable
362-
|| $expr->var->name !== 'this'
363-
|| !$this->phpVersion->supportsReadOnlyProperties()
364-
) {
365-
return false;
366-
}
359+
while ($expr instanceof PropertyFetch) {
360+
if ($expr->var instanceof Variable) {
361+
if (
362+
! $expr->name instanceof Node\Identifier
363+
|| !is_string($expr->var->name)
364+
|| $expr->var->name !== 'this'
365+
) {
366+
return false;
367+
}
368+
} elseif (!$expr->var instanceof PropertyFetch) {
369+
return false;
370+
}
367371

368-
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
369-
if ($propertyReflection === null) {
370-
return false;
371-
}
372+
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
373+
if ($propertyReflection === null) {
374+
return false;
375+
}
372376

373-
$nativePropertyReflection = $propertyReflection->getNativeReflection();
374-
if ($nativePropertyReflection === null || !$nativePropertyReflection->isReadOnly()) {
375-
return false;
377+
$nativePropertyReflection = $propertyReflection->getNativeReflection();
378+
if ($nativePropertyReflection === null || !$nativePropertyReflection->isReadOnly()) {
379+
return false;
380+
}
381+
382+
$expr = $expr->var;
376383
}
377384

378385
return true;

tests/PHPStan/Analyser/nsrt/remember-readonly-constructor-narrowed.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace RememberReadOnlyConstructor;
44

5+
use LogicException;
56
use function PHPStan\Testing\assertType;
67

78
class HelloWorldReadonlyProperty {
@@ -107,3 +108,38 @@ public function doFoo() {
107108
assertType('4|10', $this->i);
108109
}
109110
}
111+
112+
class Foo {
113+
public readonly int $readonly;
114+
public int $writable;
115+
116+
public function __construct()
117+
{
118+
$this->readonly = 5;
119+
$this->writable = rand(0,1) ? 5 : 10;
120+
}
121+
}
122+
123+
class DeepPropertyFetching {
124+
public readonly ?Foo $prop;
125+
126+
public function __construct() {
127+
$this->prop = new Foo();
128+
if($this->prop->readonly != 5) {
129+
throw new LogicException();
130+
}
131+
if ($this->prop->writable != 5) {
132+
throw new LogicException();
133+
}
134+
135+
assertType(Foo::class, $this->prop);
136+
assertType('5', $this->prop->readonly);
137+
assertType('5', $this->prop->writable);
138+
}
139+
140+
public function doFoo() {
141+
assertType(Foo::class, $this->prop);
142+
assertType('5', $this->prop->readonly);
143+
assertType('int', $this->prop->writable);
144+
}
145+
}

0 commit comments

Comments
 (0)