Skip to content

Commit 218aad0

Browse files
committed
Fix access to uninitialized property via extension in additional constructor
1 parent 8d5deb9 commit 218aad0

File tree

3 files changed

+68
-19
lines changed

3 files changed

+68
-19
lines changed

src/Node/ClassPropertiesNode.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ public function getUninitializedProperties(
109109
$originalProperties = [];
110110
$initialInitializedProperties = [];
111111
$initializedProperties = [];
112+
if ($extensions === null) {
113+
$extensions = $this->readWritePropertiesExtensionProvider->getExtensions();
114+
}
112115
foreach ($this->getProperties() as $property) {
113116
if ($property->isStatic()) {
114117
continue;
@@ -121,34 +124,29 @@ public function getUninitializedProperties(
121124
}
122125
$originalProperties[$property->getName()] = $property;
123126
$is = TrinaryLogic::createFromBoolean($property->isPromoted() && !$property->isPromotedFromTrait());
127+
if (!$is->yes()) {
128+
foreach ($extensions as $extension) {
129+
if (!$classReflection->hasNativeProperty($property->getName())) {
130+
continue;
131+
}
132+
$propertyReflection = $classReflection->getNativeProperty($property->getName());
133+
if (!$extension->isInitialized($propertyReflection, $property->getName())) {
134+
continue;
135+
}
136+
$is = TrinaryLogic::createYes();
137+
break;
138+
}
139+
}
124140
$initialInitializedProperties[$property->getName()] = $is;
125141
foreach ($constructors as $constructor) {
126142
$initializedProperties[$constructor][$property->getName()] = $is;
127143
}
128-
if ($property->isPromoted() && !$property->isPromotedFromTrait()) {
144+
if ($is->yes()) {
129145
continue;
130146
}
131147
$uninitializedProperties[$property->getName()] = $property;
132148
}
133149

134-
if ($extensions === null) {
135-
$extensions = $this->readWritePropertiesExtensionProvider->getExtensions();
136-
}
137-
138-
foreach (array_keys($uninitializedProperties) as $name) {
139-
foreach ($extensions as $extension) {
140-
if (!$classReflection->hasNativeProperty($name)) {
141-
continue;
142-
}
143-
$propertyReflection = $classReflection->getNativeProperty($name);
144-
if (!$extension->isInitialized($propertyReflection, $name)) {
145-
continue;
146-
}
147-
unset($uninitializedProperties[$name]);
148-
break;
149-
}
150-
}
151-
152150
if ($constructors === []) {
153151
return [$uninitializedProperties, [], []];
154152
}

tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PHPStan\Reflection\PropertyReflection;
77
use PHPStan\Rules\Rule;
88
use PHPStan\Testing\RuleTestCase;
9+
use function strpos;
910

1011
/**
1112
* @extends RuleTestCase<UninitializedPropertyRule>
@@ -20,6 +21,7 @@ protected function getRule(): Rule
2021
self::getContainer(),
2122
[
2223
'UninitializedProperty\\TestCase::setUp',
24+
'Bug9619\\AdminPresenter::startup',
2325
],
2426
),
2527
);
@@ -46,6 +48,27 @@ public function isInitialized(PropertyReflection $property, string $propertyName
4648
}
4749

4850
},
51+
52+
// bug-9619
53+
new class() implements ReadWritePropertiesExtension {
54+
55+
public function isAlwaysRead(PropertyReflection $property, string $propertyName): bool
56+
{
57+
return false;
58+
}
59+
60+
public function isAlwaysWritten(PropertyReflection $property, string $propertyName): bool
61+
{
62+
return $this->isInitialized($property, $propertyName);
63+
}
64+
65+
public function isInitialized(PropertyReflection $property, string $propertyName): bool
66+
{
67+
return $property->isPublic() &&
68+
strpos($property->getDocComment() ?? '', '@inject') !== false;
69+
}
70+
71+
},
4972
];
5073
}
5174

@@ -156,4 +179,9 @@ public function testEfabricaLatteBug(): void
156179
$this->analyse([__DIR__ . '/data/efabrica-latte-bug.php'], []);
157180
}
158181

182+
public function testBug9619(): void
183+
{
184+
$this->analyse([__DIR__ . '/data/bug-9619.php'], []);
185+
}
186+
159187
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php // lint >= 7.4
2+
3+
namespace Bug9619;
4+
5+
interface User
6+
{
7+
8+
public function isLoggedIn(): bool;
9+
10+
}
11+
12+
class AdminPresenter
13+
{
14+
/** @inject */
15+
public User $user;
16+
17+
public function startup()
18+
{
19+
if (!$this->user->isLoggedIn()) {
20+
// do something
21+
}
22+
}
23+
}

0 commit comments

Comments
 (0)