Skip to content

Commit a8acf5a

Browse files
committed
Remember types of readonly properties
1 parent 446cebc commit a8acf5a

File tree

2 files changed

+64
-27
lines changed

2 files changed

+64
-27
lines changed

src/Analyser/MutatingScope.php

+42-27
Original file line numberDiff line numberDiff line change
@@ -294,41 +294,57 @@ public function enterDeclareStrictTypes(): self
294294
);
295295
}
296296

297-
public function rememberConstructorScope(): self
297+
/**
298+
* @param array<string, ExpressionTypeHolder> $expressions
299+
* @return array<string, ExpressionTypeHolder>
300+
*/
301+
private function rememberConstructorExpressions(array $currentExpressionTypes): array
298302
{
299303
$expressionTypes = [];
300-
foreach ($this->expressionTypes as $exprString => $expressionTypeHolder) {
304+
foreach ($currentExpressionTypes as $exprString => $expressionTypeHolder) {
301305
$expr = $expressionTypeHolder->getExpr();
302-
if (!$expr instanceof ConstFetch) {
303-
continue;
304-
}
305-
$expressionTypes[$exprString] = $expressionTypeHolder;
306-
}
306+
if ($expr instanceof PropertyFetch) {
307+
if (
308+
!$expr->name instanceof Node\Identifier
309+
|| !$expr->var instanceof Variable
310+
|| $expr->var->name !== 'this'
311+
|| !$this->phpVersion->supportsReadOnlyProperties()
312+
) {
313+
continue;
314+
}
307315

308-
$nativeExpressionTypes = [];
309-
foreach ($this->nativeExpressionTypes as $exprString => $expressionTypeHolder) {
310-
$expr = $expressionTypeHolder->getExpr();
311-
if (!$expr instanceof ConstFetch) {
316+
$propertyReflection = $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $this);
317+
if ($propertyReflection === null) {
318+
continue;
319+
}
320+
321+
$nativePropertyReflection = $propertyReflection->getNativeReflection();
322+
if ($nativePropertyReflection === null || !$nativePropertyReflection->isReadOnly()) {
323+
continue;
324+
}
325+
} elseif (!$expr instanceof ConstFetch) {
312326
continue;
313327
}
314328

315-
$nativeExpressionTypes[$exprString] = $expressionTypeHolder;
329+
$expressionTypes[$exprString] = $expressionTypeHolder;
316330
}
317331

318-
if (array_key_exists('$this', $this->expressionTypes)) {
319-
$expressionTypes['$this'] = $this->expressionTypes['$this'];
320-
}
321-
if (array_key_exists('$this', $this->nativeExpressionTypes)) {
322-
$nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this'];
332+
if (array_key_exists('$this', $currentExpressionTypes)) {
333+
$expressionTypes['$this'] = $currentExpressionTypes['$this'];
323334
}
324335

336+
return $expressionTypes;
337+
}
338+
339+
public function rememberConstructorScope(): self
340+
{
325341
return $this->scopeFactory->create(
326342
$this->context,
327343
$this->isDeclareStrictTypes(),
328344
$this->getFunction(),
329345
$this->getNamespace(),
330-
$expressionTypes,
331-
$nativeExpressionTypes,
346+
$this->rememberConstructorExpressions($this->expressionTypes),
347+
$this->rememberConstructorExpressions($this->nativeExpressionTypes),
332348
$this->conditionalExpressions,
333349
$this->inClosureBindScopeClasses,
334350
$this->anonymousFunctionReflection,
@@ -3334,7 +3350,7 @@ public function enterFunction(
33343350

33353351
private function enterFunctionLike(
33363352
PhpFunctionFromParserNodeReflection $functionReflection,
3337-
bool $preserveThis,
3353+
bool $preserveConstructorScope,
33383354
): self
33393355
{
33403356
$parametersByName = [];
@@ -3346,6 +3362,12 @@ private function enterFunctionLike(
33463362
$expressionTypes = [];
33473363
$nativeExpressionTypes = [];
33483364
$conditionalTypes = [];
3365+
3366+
if ($preserveConstructorScope) {
3367+
$expressionTypes = $this->rememberConstructorExpressions($this->expressionTypes);
3368+
$nativeExpressionTypes = $this->rememberConstructorExpressions($this->nativeExpressionTypes);
3369+
}
3370+
33493371
foreach ($functionReflection->getParameters() as $parameter) {
33503372
$parameterType = $parameter->getType();
33513373

@@ -3396,13 +3418,6 @@ private function enterFunctionLike(
33963418
$nativeExpressionTypes[$parameterOriginalValueExprString] = ExpressionTypeHolder::createYes($parameterOriginalValueExpr, $nativeParameterType);
33973419
}
33983420

3399-
if ($preserveThis && array_key_exists('$this', $this->expressionTypes)) {
3400-
$expressionTypes['$this'] = $this->expressionTypes['$this'];
3401-
}
3402-
if ($preserveThis && array_key_exists('$this', $this->nativeExpressionTypes)) {
3403-
$nativeExpressionTypes['$this'] = $this->nativeExpressionTypes['$this'];
3404-
}
3405-
34063421
return $this->scopeFactory->create(
34073422
$this->context,
34083423
$this->isDeclareStrictTypes(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace RememberReadOnlyConstructor;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld {
8+
private readonly int $i;
9+
10+
public function __construct()
11+
{
12+
if (rand(0,1)) {
13+
$this->i = 4;
14+
} else {
15+
$this->i = 10;
16+
}
17+
}
18+
19+
public function doFoo() {
20+
assertType('4|10', $this->i);
21+
}
22+
}

0 commit comments

Comments
 (0)