77use PHPStan \DependencyInjection \AutowiredParameter ;
88use PHPStan \DependencyInjection \RegisteredRule ;
99use PHPStan \Node \ClassPropertiesNode ;
10+ use PHPStan \Node \ClassPropertyNode ;
1011use PHPStan \Node \Property \PropertyRead ;
12+ use PHPStan \Reflection \MethodReflection ;
1113use PHPStan \Reflection \Php \PhpMethodFromParserNodeReflection ;
1214use PHPStan \Rules \Properties \ReadWritePropertiesExtensionProvider ;
1315use PHPStan \Rules \Rule ;
@@ -120,6 +122,7 @@ public function processNode(Node $node, Scope $scope): array
120122 'node ' => $ property ,
121123 'onlyReadable ' => $ property ->isReadable () && !$ property ->isWritable (),
122124 'onlyWritable ' => $ property ->isWritable () && !$ property ->isReadable (),
125+ 'hasTrueRead ' => $ alwaysRead ,
123126 ];
124127 }
125128
@@ -194,6 +197,7 @@ public function processNode(Node $node, Scope $scope): array
194197 if (!$ classType ->isSuperTypeOf ($ fetchedOnType )->no ()) {
195198 if ($ usage instanceof PropertyRead) {
196199 $ properties [$ propertyName ]['read ' ] = true ;
200+ $ properties [$ propertyName ]['hasTrueRead ' ] = true ;
197201 } else {
198202 $ properties [$ propertyName ]['written ' ] = true ;
199203 }
@@ -204,6 +208,7 @@ public function processNode(Node $node, Scope $scope): array
204208 if (!$ classType ->isSuperTypeOf ($ fetchedOnType )->no ()) {
205209 if ($ usage instanceof PropertyRead) {
206210 $ properties [$ propertyName ]['read ' ] = true ;
211+ $ properties [$ propertyName ]['hasTrueRead ' ] = true ;
207212 } else {
208213 $ properties [$ propertyName ]['written ' ] = true ;
209214 }
@@ -213,12 +218,25 @@ public function processNode(Node $node, Scope $scope): array
213218
214219 if ($ usage instanceof PropertyRead) {
215220 $ properties [$ propertyName ]['read ' ] = true ;
221+ if (!$ this ->isPropertySelfWrite ($ usageScope , $ propertyName , $ propertyNode , $ classReflection ->getName ())) {
222+ $ properties [$ propertyName ]['hasTrueRead ' ] = true ;
223+ }
216224 } else {
217225 $ properties [$ propertyName ]['written ' ] = true ;
218226 }
219227 }
220228 }
221229
230+ foreach ($ properties as $ propertyName => $ data ) {
231+ if (!$ data ['read ' ] || $ data ['hasTrueRead ' ]) {
232+ continue ;
233+ }
234+ if (!$ data ['node ' ]->isPromoted ()) {
235+ continue ;
236+ }
237+ $ properties [$ propertyName ]['read ' ] = false ;
238+ }
239+
222240 [$ uninitializedProperties ] = $ node ->getUninitializedProperties ($ scope , []);
223241
224242 $ errors = [];
@@ -270,4 +288,42 @@ public function processNode(Node $node, Scope $scope): array
270288 return $ errors ;
271289 }
272290
291+ private function isPropertySelfWrite (
292+ Scope $ usageScope ,
293+ string $ propertyName ,
294+ ClassPropertyNode $ propertyNode ,
295+ string $ className ,
296+ ): bool
297+ {
298+ if (!$ propertyNode ->isPromoted ()) {
299+ return false ;
300+ }
301+
302+ $ callStack = $ usageScope ->getFunctionCallStackWithParameters ();
303+ if ($ callStack === []) {
304+ return false ;
305+ }
306+
307+ $ lastCall = $ callStack [count ($ callStack ) - 1 ];
308+ [$ calleeReflection , $ parameterReflection ] = $ lastCall ;
309+
310+ if (!$ calleeReflection instanceof MethodReflection) {
311+ return false ;
312+ }
313+
314+ if ($ calleeReflection ->getName () !== '__construct ' ) {
315+ return false ;
316+ }
317+
318+ if ($ calleeReflection ->getDeclaringClass ()->getName () !== $ className ) {
319+ return false ;
320+ }
321+
322+ if ($ parameterReflection === null ) {
323+ return false ;
324+ }
325+
326+ return $ parameterReflection ->getName () === $ propertyName ;
327+ }
328+
273329}
0 commit comments