Skip to content

Commit 9665e16

Browse files
committed
Fixed expression invalidation when ++ is involved
1 parent ca37ebd commit 9665e16

File tree

6 files changed

+100
-11
lines changed

6 files changed

+100
-11
lines changed

src/Analyser/MutatingScope.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,26 +2574,31 @@ public function assignExpression(Expr $expr, Type $type): self
25742574
return $scope->specifyExpressionType($expr, $type);
25752575
}
25762576

2577-
public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self
2577+
public function invalidateExpression(Expr $expressionToInvalidate): self
25782578
{
25792579
$exprStringToInvalidate = $this->printer->prettyPrintExpr($expressionToInvalidate);
25802580
$moreSpecificTypeHolders = $this->moreSpecificTypes;
25812581
foreach (array_keys($moreSpecificTypeHolders) as $exprString) {
25822582
$exprString = (string) $exprString;
2583-
if ($requireMoreCharacters && $exprString === $exprStringToInvalidate) {
2584-
continue;
2583+
if (Strings::startsWith($exprString, $exprStringToInvalidate)) {
2584+
if ($exprString === $exprStringToInvalidate) {
2585+
unset($moreSpecificTypeHolders[$exprString]);
2586+
continue;
2587+
}
2588+
$nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1);
2589+
if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') === null) {
2590+
unset($moreSpecificTypeHolders[$exprString]);
2591+
continue;
2592+
}
25852593
}
2586-
if (!Strings::startsWith($exprString, $exprStringToInvalidate)) {
2594+
$matches = \Nette\Utils\Strings::matchAll($exprString, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#');
2595+
if ($matches === []) {
25872596
continue;
25882597
}
25892598

2590-
if ($exprString === $exprStringToInvalidate) {
2591-
unset($moreSpecificTypeHolders[$exprString]);
2592-
continue;
2593-
}
2599+
$matches = array_column($matches, 0);
25942600

2595-
$nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1);
2596-
if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') !== null) {
2601+
if (!in_array($exprStringToInvalidate, $matches, true)) {
25972602
continue;
25982603
}
25992604

src/Analyser/NodeScopeResolver.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
14921492
$result = $this->processArgs($methodReflection, $parametersAcceptor, $expr->args, $scope, $nodeCallback, $context);
14931493
$scope = $result->getScope();
14941494
if ($methodReflection !== null && $methodReflection->hasSideEffects()->yes()) {
1495-
$scope = $scope->invalidateExpression($expr->var, true);
1495+
$scope = $scope->invalidateExpression($expr->var);
14961496
}
14971497
$hasYield = $hasYield || $result->hasYield();
14981498
} elseif ($expr instanceof StaticCall) {
@@ -1829,6 +1829,8 @@ static function (MutatingScope $scope): ExpressionResult {
18291829
},
18301830
false
18311831
)->getScope();
1832+
} else {
1833+
$scope = $scope->invalidateExpression($expr->var);
18321834
}
18331835
}
18341836
} elseif ($expr instanceof Ternary) {

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9752,6 +9752,11 @@ public function dataListType(): array
97529752
return $this->gatherAssertTypes(__DIR__ . '/data/list-type.php');
97539753
}
97549754

9755+
public function dataBug2835(): array
9756+
{
9757+
return $this->gatherAssertTypes(__DIR__ . '/data/bug-2835.php');
9758+
}
9759+
97559760
/**
97569761
* @dataProvider dataBug2574
97579762
* @dataProvider dataBug2577
@@ -9775,6 +9780,7 @@ public function dataListType(): array
97759780
* @dataProvider dataPhpDocInheritanceConstructors
97769781
* @dataProvider dataListType
97779782
* @dataProvider dataBug2822
9783+
* @dataProvider dataBug2835
97789784
* @param ConstantStringType $expectedType
97799785
* @param Type $actualType
97809786
*/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Bug2835AssertTypes;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param array<int, array> $tokens
12+
* @return bool
13+
*/
14+
public function doFoo(array $tokens): bool {
15+
$i = 0;
16+
while (isset($tokens[$i])) {
17+
assertType('int', $i);
18+
if ($tokens[$i]['code'] !== 1) {
19+
assertType('mixed~1', $tokens[$i]['code']);
20+
$i++;
21+
assertType('int', $i);
22+
assertType('mixed', $tokens[$i]['code']);
23+
continue;
24+
}
25+
assertType('1', $tokens[$i]['code']);
26+
$i++;
27+
assertType('int', $i);
28+
assertType('mixed', $tokens[$i]['code']);
29+
if ($tokens[$i]['code'] !== 2) {
30+
assertType('mixed~2', $tokens[$i]['code']);
31+
$i++;
32+
assertType('int', $i);
33+
continue;
34+
}
35+
assertType('2', $tokens[$i]['code']);
36+
return true;
37+
}
38+
return false;
39+
}
40+
41+
}

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,4 +413,10 @@ public function testStrictComparisonPropertyNativeTypesPhp74(): void
413413
]);
414414
}
415415

416+
public function testBug2835(): void
417+
{
418+
$this->checkAlwaysTrueStrictComparison = true;
419+
$this->analyse([__DIR__ . '/data/bug-2835.php'], []);
420+
}
421+
416422
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Bug2835;
4+
5+
class Foo
6+
{
7+
8+
/**
9+
* @param array<int, array> $tokens
10+
* @return bool
11+
*/
12+
public function doFoo(array $tokens): bool {
13+
$i = 0;
14+
while (isset($tokens[$i])) {
15+
if ($tokens[$i]['code'] !== 1) {
16+
$i++;
17+
continue;
18+
}
19+
$i++;
20+
if ($tokens[$i]['code'] !== 2) {
21+
$i++;
22+
continue;
23+
}
24+
return true;
25+
}
26+
return false;
27+
}
28+
29+
}

0 commit comments

Comments
 (0)