|
13 | 13 | use PhpParser\Node\Stmt\Class_; |
14 | 14 | use PhpParser\Node\Stmt\ClassMethod; |
15 | 15 | use PhpParser\Node\Stmt\Property; |
| 16 | +use PhpParser\Node\Stmt\Return_; |
16 | 17 | use PhpParser\NodeVisitor; |
17 | 18 | use PHPStan\Analyser\Scope; |
18 | 19 | use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; |
@@ -176,6 +177,10 @@ private function refactorProperty(Class_ $class, Property $property, Scope $scop |
176 | 177 | if ($this->propertyFetchAssignManipulator->isAssignedMultipleTimesInConstructor($class, $property)) { |
177 | 178 | return null; |
178 | 179 | } |
| 180 | + // returned by reference can be mutated outside the class, so it cannot be readonly |
| 181 | + if ($this->isPropertyReturnedByRef($class, (string) $this->getName($property))) { |
| 182 | + return null; |
| 183 | + } |
179 | 184 | $this->visibilityManipulator->makeReadonly($property); |
180 | 185 | $this->removeReadOnlyDoc($property); |
181 | 186 | return $property; |
@@ -226,10 +231,33 @@ private function refactorParam(Class_ $class, ClassMethod $classMethod, Param $p |
226 | 231 | if ($this->isPromotedPropertyAssigned($class, $param)) { |
227 | 232 | return null; |
228 | 233 | } |
| 234 | + // returned by reference can be mutated outside the class, so it cannot be readonly |
| 235 | + if ($this->isPropertyReturnedByRef($class, (string) $this->getName($param))) { |
| 236 | + return null; |
| 237 | + } |
229 | 238 | $this->visibilityManipulator->makeReadonly($param); |
230 | 239 | $this->removeReadOnlyDoc($param); |
231 | 240 | return $param; |
232 | 241 | } |
| 242 | + private function isPropertyReturnedByRef(Class_ $class, string $propertyName): bool |
| 243 | + { |
| 244 | + foreach ($class->getMethods() as $classMethod) { |
| 245 | + if (!$classMethod->byRef) { |
| 246 | + continue; |
| 247 | + } |
| 248 | + $returns = $this->betterNodeFinder->findInstanceOf($classMethod, Return_::class); |
| 249 | + foreach ($returns as $return) { |
| 250 | + if (!$return->expr instanceof Expr) { |
| 251 | + continue; |
| 252 | + } |
| 253 | + $propertyFetch = $this->betterNodeFinder->findFirst($return->expr, fn(Node $subNode): bool => $subNode instanceof PropertyFetch && $this->isName($subNode->var, 'this') && $this->isName($subNode, $propertyName)); |
| 254 | + if ($propertyFetch instanceof PropertyFetch) { |
| 255 | + return \true; |
| 256 | + } |
| 257 | + } |
| 258 | + } |
| 259 | + return \false; |
| 260 | + } |
233 | 261 | private function isPromotedPropertyAssigned(Class_ $class, Param $param): bool |
234 | 262 | { |
235 | 263 | $constructClassMethod = $class->getMethod(MethodName::CONSTRUCT); |
|
0 commit comments