Skip to content

Commit 4d312be

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents a0b6b3b + c55aa05 commit 4d312be

File tree

8 files changed

+356
-1
lines changed

8 files changed

+356
-1
lines changed

src/Analyser/MutatingScope.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4865,6 +4865,68 @@ public function processAlwaysIterableForeachScopeWithoutPollute(self $finalScope
48654865
);
48664866
}
48674867

4868+
public function processAlwaysIterableForScopeWithoutPollute(self $finalScope, self $initScope): self
4869+
{
4870+
$expressionTypes = $this->expressionTypes;
4871+
$initScopeExpressionTypes = $initScope->expressionTypes;
4872+
foreach ($finalScope->expressionTypes as $variableExprString => $variableTypeHolder) {
4873+
if (!isset($expressionTypes[$variableExprString])) {
4874+
if (isset($initScopeExpressionTypes[$variableExprString])) {
4875+
$expressionTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4876+
continue;
4877+
}
4878+
4879+
$expressionTypes[$variableExprString] = $variableTypeHolder;
4880+
continue;
4881+
}
4882+
4883+
$expressionTypes[$variableExprString] = new ExpressionTypeHolder(
4884+
$variableTypeHolder->getExpr(),
4885+
$variableTypeHolder->getType(),
4886+
$variableTypeHolder->getCertainty()->and($expressionTypes[$variableExprString]->getCertainty()),
4887+
);
4888+
}
4889+
4890+
$nativeTypes = $this->nativeExpressionTypes;
4891+
$initScopeNativeExpressionTypes = $initScope->nativeExpressionTypes;
4892+
foreach ($finalScope->nativeExpressionTypes as $variableExprString => $variableTypeHolder) {
4893+
if (!isset($nativeTypes[$variableExprString])) {
4894+
if (isset($initScopeNativeExpressionTypes[$variableExprString])) {
4895+
$nativeTypes[$variableExprString] = ExpressionTypeHolder::createMaybe($variableTypeHolder->getExpr(), $variableTypeHolder->getType());
4896+
continue;
4897+
}
4898+
4899+
$nativeTypes[$variableExprString] = $variableTypeHolder;
4900+
continue;
4901+
}
4902+
4903+
$nativeTypes[$variableExprString] = new ExpressionTypeHolder(
4904+
$variableTypeHolder->getExpr(),
4905+
$variableTypeHolder->getType(),
4906+
$variableTypeHolder->getCertainty()->and($nativeTypes[$variableExprString]->getCertainty()),
4907+
);
4908+
}
4909+
4910+
return $this->scopeFactory->create(
4911+
$this->context,
4912+
$this->isDeclareStrictTypes(),
4913+
$this->getFunction(),
4914+
$this->getNamespace(),
4915+
$expressionTypes,
4916+
$nativeTypes,
4917+
$this->conditionalExpressions,
4918+
$this->inClosureBindScopeClasses,
4919+
$this->anonymousFunctionReflection,
4920+
$this->inFirstLevelStatement,
4921+
[],
4922+
[],
4923+
[],
4924+
$this->afterExtractCall,
4925+
$this->parentScope,
4926+
$this->nativeTypesPromoted,
4927+
);
4928+
}
4929+
48684930
public function generalizeWith(self $otherScope): self
48694931
{
48704932
$variableTypeHolders = $this->generalizeVariableTypeHolders(

src/Analyser/NodeScopeResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1402,7 +1402,7 @@ private function processStmtNode(
14021402
}
14031403
} else {
14041404
if (!$this->polluteScopeWithLoopInitialAssignments) {
1405-
$finalScope = $finalScope->mergeWith($scope);
1405+
$finalScope = $scope->processAlwaysIterableForScopeWithoutPollute($finalScope, $initScope);
14061406
}
14071407
}
14081408

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class ForLoopNoScopePollutionTest extends TypeInferenceTestCase
8+
{
9+
10+
/** @return iterable<array<string, mixed[]>> */
11+
public function dataFileAsserts(): iterable
12+
{
13+
yield from $this->gatherAssertTypes(__DIR__ . '/data/for-loop-no-scope-pollution.php');
14+
}
15+
16+
/**
17+
* @dataProvider dataFileAsserts
18+
* @param mixed ...$args
19+
*/
20+
public function testFileAsserts(
21+
string $assertType,
22+
string $file,
23+
...$args,
24+
): void
25+
{
26+
$this->assertFileAsserts($assertType, $file, ...$args);
27+
}
28+
29+
public static function getAdditionalConfigFiles(): array
30+
{
31+
return [
32+
__DIR__ . '/forLoopNoScopePollution.neon',
33+
];
34+
}
35+
36+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ForLoopNoScopePollution;
4+
5+
6+
use PHPStan\TrinaryLogic;
7+
use function PHPStan\Testing\assertNativeType;
8+
use function PHPStan\Testing\assertType;
9+
use function PHPStan\Testing\assertVariableCertainty;
10+
11+
class ForLoop
12+
{
13+
14+
/** @param int $b */
15+
public function loopThatIteratesAtLeastOnce(int $a, $b): void
16+
{
17+
$j = 0;
18+
for ($i = 0, $j = 10; $i < 10; $i++, $j--) {
19+
$a = rand(0, 1);
20+
$b = rand(0, 1);
21+
$c = rand(0, 1);
22+
}
23+
24+
assertType('int<10, max>', $i);
25+
assertNativeType('int<10, max>', $i);
26+
assertVariableCertainty(TrinaryLogic::createMaybe(), $i);
27+
28+
assertType('int<min, 10>', $j);
29+
assertNativeType('int<min, 10>', $j);
30+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
31+
32+
assertType('int<0, 1>', $a);
33+
assertNativeType('int', $a);
34+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
35+
36+
assertType('int<0, 1>', $b);
37+
assertNativeType('int', $b);
38+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
39+
40+
assertType('int<0, 1>', $c);
41+
assertNativeType('int', $c);
42+
assertVariableCertainty(TrinaryLogic::createYes(), $c);
43+
}
44+
45+
/** @param int $b */
46+
public function loopThatMightIterateAtLeastOnce(int $a, $b): void
47+
{
48+
$j = 0;
49+
for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) {
50+
$a = rand(0, 1);
51+
$b = rand(0, 1);
52+
$c = rand(0, 1);
53+
}
54+
55+
assertType('int<0, max>', $i);
56+
assertNativeType('int<0, max>', $i);
57+
assertVariableCertainty(TrinaryLogic::createMaybe(), $i);
58+
59+
assertType('int<min, 10>', $j);
60+
assertNativeType('int<min, 10>', $j);
61+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
62+
63+
assertType('int', $a);
64+
assertNativeType('int', $a);
65+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
66+
67+
assertType('int', $b);
68+
assertNativeType('mixed', $b);
69+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
70+
71+
assertType('int<0, 1>', $c);
72+
assertNativeType('int', $c);
73+
assertVariableCertainty(TrinaryLogic::createMaybe(), $c);
74+
}
75+
76+
/** @param int $b */
77+
public function loopThatNeverIterates(int $a, $b): void
78+
{
79+
$j = 0;
80+
for ($i = 0, $j = 10; $i > 10; $i++, $j--) {
81+
$a = rand(0, 1);
82+
$b = rand(0, 1);
83+
$c = rand(0, 1);
84+
}
85+
86+
assertType('*ERROR*', $i);
87+
assertNativeType('*ERROR*', $i);
88+
assertVariableCertainty(TrinaryLogic::createNo(), $i);
89+
90+
assertType('0', $j);
91+
assertNativeType('0', $j);
92+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
93+
94+
assertType('int', $a);
95+
assertNativeType('int', $a);
96+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
97+
98+
assertType('int', $b);
99+
assertNativeType('mixed', $b);
100+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
101+
102+
assertType('*ERROR*', $c);
103+
assertNativeType('*ERROR*', $c);
104+
assertVariableCertainty(TrinaryLogic::createNo(), $c);
105+
}
106+
107+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
parameters:
2+
polluteScopeWithLoopInitialAssignments: false
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace ForLoop;
4+
5+
6+
use PHPStan\TrinaryLogic;
7+
use function PHPStan\Testing\assertNativeType;
8+
use function PHPStan\Testing\assertType;
9+
use function PHPStan\Testing\assertVariableCertainty;
10+
11+
class ForLoop
12+
{
13+
14+
/** @param int $b */
15+
public function loopThatIteratesAtLeastOnce(int $a, $b): void
16+
{
17+
$j = 0;
18+
for ($i = 0, $j = 10; $i < 10; $i++, $j--) {
19+
$a = rand(0, 1);
20+
$b = rand(0, 1);
21+
$c = rand(0, 1);
22+
}
23+
24+
assertType('int<10, max>', $i);
25+
assertNativeType('int<10, max>', $i);
26+
assertVariableCertainty(TrinaryLogic::createYes(), $i);
27+
28+
assertType('int<min, 10>', $j);
29+
assertNativeType('int<min, 10>', $j);
30+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
31+
32+
assertType('int<0, 1>', $a);
33+
assertNativeType('int', $a);
34+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
35+
36+
assertType('int<0, 1>', $b);
37+
assertNativeType('int', $b);
38+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
39+
40+
assertType('int<0, 1>', $c);
41+
assertNativeType('int', $c);
42+
assertVariableCertainty(TrinaryLogic::createYes(), $c);
43+
}
44+
45+
/** @param int $b */
46+
public function loopThatMightIterateAtLeastOnce(int $a, $b): void
47+
{
48+
$j = 0;
49+
for ($i = 0, $j = 10; $i < rand(0, 1); $i++, $j--) {
50+
$a = rand(0, 1);
51+
$b = rand(0, 1);
52+
$c = rand(0, 1);
53+
}
54+
55+
assertType('int<0, max>', $i);
56+
assertNativeType('int<0, max>', $i);
57+
assertVariableCertainty(TrinaryLogic::createYes(), $i);
58+
59+
assertType('int<min, 10>', $j);
60+
assertNativeType('int<min, 10>', $j);
61+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
62+
63+
assertType('int', $a);
64+
assertNativeType('int', $a);
65+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
66+
67+
assertType('int', $b);
68+
assertNativeType('mixed', $b);
69+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
70+
71+
assertType('int<0, 1>', $c);
72+
assertNativeType('int', $c);
73+
assertVariableCertainty(TrinaryLogic::createMaybe(), $c);
74+
}
75+
76+
/** @param int $b */
77+
public function loopThatNeverIterates(int $a, $b): void
78+
{
79+
$j = 0;
80+
for ($i = 0, $j = 10; $i > 10; $i++, $j--) {
81+
$a = rand(0, 1);
82+
$b = rand(0, 1);
83+
$c = rand(0, 1);
84+
}
85+
86+
assertType('0', $i);
87+
assertNativeType('0', $i);
88+
assertVariableCertainty(TrinaryLogic::createYes(), $i);
89+
90+
assertType('10', $j);
91+
assertNativeType('10', $j);
92+
assertVariableCertainty(TrinaryLogic::createYes(), $j);
93+
94+
assertType('int', $a);
95+
assertNativeType('int', $a);
96+
assertVariableCertainty(TrinaryLogic::createYes(), $a);
97+
98+
assertType('int', $b);
99+
assertNativeType('mixed', $b);
100+
assertVariableCertainty(TrinaryLogic::createYes(), $b);
101+
102+
assertType('*ERROR*', $c);
103+
assertNativeType('*ERROR*', $c);
104+
assertVariableCertainty(TrinaryLogic::createNo(), $c);
105+
}
106+
107+
}

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,4 +1068,13 @@ public function testBug10228(): void
10681068
$this->analyse([__DIR__ . '/data/bug-10228.php'], []);
10691069
}
10701070

1071+
public function testBug9550(): void
1072+
{
1073+
$this->cliArgumentsVariablesRegistered = true;
1074+
$this->polluteScopeWithLoopInitialAssignments = false;
1075+
$this->checkMaybeUndefinedVariables = true;
1076+
$this->polluteScopeWithAlwaysIterableForeach = true;
1077+
$this->analyse([__DIR__ . '/data/bug-9550.php'], []);
1078+
}
1079+
10711080
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug9550;
4+
5+
class Foo
6+
{
7+
protected static function makeShortTextFromScalar(string $value, int $maxUtf8Length = 50): string
8+
{
9+
$maxUtf8Length = max(20, min($maxUtf8Length, 100));
10+
11+
$vStrLonger = mb_substr($value, 0, $maxUtf8Length + 1000);
12+
13+
$withThreeDots = false;
14+
\PHPStan\dumpType($maxUtf8Length);
15+
for ($l = $maxUtf8Length; $l > 0; --$l) {
16+
$vStr = mb_substr($vStrLonger, 0, $l);
17+
if ($vStr !== $vStrLonger) {
18+
$vStrLonger = $vStr;
19+
$vStr = mb_substr($vStr, 0, $l - 3);
20+
$withThreeDots = true;
21+
} else {
22+
$vStrLonger = $vStr;
23+
}
24+
$vStr = str_replace(["\0", "\t", "\n", "\r"], ['\0', '\t', '\n', '\r'], $vStr);
25+
if (mb_strlen($vStr) <= $maxUtf8Length - ($withThreeDots ? 3 : 0)) {
26+
break;
27+
}
28+
}
29+
30+
return '\'' . $vStr . '\'' . ($withThreeDots ? '...' : '');
31+
}
32+
}

0 commit comments

Comments
 (0)