Skip to content

Commit e6a3b1f

Browse files
committed
Merge remote-tracking branch 'origin/1.12.x' into 2.1.x
2 parents 9ab24a9 + ce0aaf2 commit e6a3b1f

File tree

8 files changed

+204
-3
lines changed

8 files changed

+204
-3
lines changed

src/Analyser/NodeScopeResolver.php

+6-2
Original file line numberDiff line numberDiff line change
@@ -1515,9 +1515,11 @@ private function processStmtNode(
15151515
$exitPointsForOuterLoop = [];
15161516
$throwPoints = $condResult->getThrowPoints();
15171517
$impurePoints = $condResult->getImpurePoints();
1518+
$fullCondExpr = null;
15181519
foreach ($stmt->cases as $caseNode) {
15191520
if ($caseNode->cond !== null) {
15201521
$condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond);
1522+
$fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr);
15211523
$caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep());
15221524
$scopeForBranches = $caseResult->getScope();
15231525
$hasYield = $hasYield || $caseResult->hasYield();
@@ -1526,6 +1528,7 @@ private function processStmtNode(
15261528
$branchScope = $caseResult->getTruthyScope()->filterByTruthyValue($condExpr);
15271529
} else {
15281530
$hasDefaultCase = true;
1531+
$fullCondExpr = null;
15291532
$branchScope = $scopeForBranches;
15301533
}
15311534

@@ -1547,8 +1550,9 @@ private function processStmtNode(
15471550
if ($branchScopeResult->isAlwaysTerminating()) {
15481551
$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating();
15491552
$prevScope = null;
1550-
if (isset($condExpr)) {
1551-
$scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr);
1553+
if (isset($fullCondExpr)) {
1554+
$scopeForBranches = $scopeForBranches->filterByFalseyValue($fullCondExpr);
1555+
$fullCondExpr = null;
15521556
}
15531557
if (!$branchFinalScopeResult->isAlwaysTerminating()) {
15541558
$finalScope = $branchScope->mergeWith($finalScope);
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug11064;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
interface IClass {}
8+
class ClassA implements IClass {}
9+
class ClassB implements IClass {}
10+
11+
/**
12+
* @param null|'nil'|IClass $val
13+
*/
14+
function test($val): string {
15+
switch (true) {
16+
case $val === null:
17+
case $val === 'nil':
18+
assertType("'nil'|null", $val);
19+
return 'null';
20+
21+
case $val instanceof ClassA:
22+
assertType('Bug11064\\ClassA', $val);
23+
return 'class a';
24+
25+
default:
26+
assertType('Bug11064\\IClass~Bug11064\\ClassA', $val);
27+
throw new RuntimeException('unsupported class: ' . get_class($val));
28+
}
29+
}
30+

tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php

+21
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,25 @@ public function testPropertyHooks(): void
370370
]);
371371
}
372372

373+
public function testBug3488Two(): void
374+
{
375+
$this->checkExplicitMixedMissingReturn = true;
376+
$this->analyse([__DIR__ . '/data/bug-3488-2.php'], [
377+
[
378+
'Method Bug3488\C::invalidCase() should return int but return statement is missing.',
379+
30,
380+
],
381+
]);
382+
}
383+
384+
public function testBug12722(): void
385+
{
386+
if (PHP_VERSION_ID < 80100) {
387+
$this->markTestSkipped('Test requires PHP 8.1.');
388+
}
389+
390+
$this->checkExplicitMixedMissingReturn = true;
391+
$this->analyse([__DIR__ . '/data/bug-12722.php'], []);
392+
}
393+
373394
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1); // lint >= 8.1
2+
3+
namespace Bug12722;
4+
5+
enum states {
6+
case state1;
7+
case statealmost1;
8+
case state3;
9+
}
10+
11+
class HelloWorld
12+
{
13+
public function intentional_fallthrough(states $state): int
14+
{
15+
switch($state) {
16+
17+
case states::state1: //intentional fall-trough this case...
18+
case states::statealmost1: return 1;
19+
case states::state3: return 3;
20+
}
21+
}
22+
23+
public function no_fallthrough(states $state): int
24+
{
25+
switch($state) {
26+
27+
case states::state1: return 1;
28+
case states::statealmost1: return 1;
29+
case states::state3: return 3;
30+
}
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug3488;
4+
5+
interface I {
6+
const THING_A = 'a';
7+
const THING_B = 'b';
8+
const THING_C = 'c';
9+
}
10+
11+
class C
12+
{
13+
/**
14+
* @param I::THING_* $thing
15+
*/
16+
public function allCovered(string $thing): int
17+
{
18+
switch ($thing) { // should not error
19+
case I::THING_A:
20+
case I::THING_B:
21+
case I::THING_C:
22+
return 0;
23+
}
24+
}
25+
/**
26+
* @param I::THING_* $thing
27+
*/
28+
public function invalidCase(string $thing): int
29+
{
30+
switch ($thing) {
31+
case I::THING_A:
32+
case I::THING_B:
33+
return 0;
34+
case 'd':
35+
throw new Exception('The error marker should be on the line above');
36+
}
37+
}
38+
/**
39+
* @param I::THING_* $thing
40+
*/
41+
public function defaultUnnecessary(string $thing): int
42+
{
43+
switch ($thing) {
44+
case I::THING_A:
45+
case I::THING_B:
46+
case I::THING_C:
47+
return 0;
48+
default:
49+
throw new Exception('This should be detected as unreachable');
50+
}
51+
}
52+
}

tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -1176,4 +1176,14 @@ public function testDynamicAccess(): void
11761176
]);
11771177
}
11781178

1179+
public function testBug8719(): void
1180+
{
1181+
$this->cliArgumentsVariablesRegistered = true;
1182+
$this->polluteScopeWithLoopInitialAssignments = true;
1183+
$this->checkMaybeUndefinedVariables = true;
1184+
$this->polluteScopeWithAlwaysIterableForeach = true;
1185+
1186+
$this->analyse([__DIR__ . '/data/bug-8719.php'], []);
1187+
}
1188+
11791189
}

tests/PHPStan/Rules/Variables/IssetRuleTest.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,12 @@ public function testVariableCertaintyInIsset(): void
268268
112,
269269
],
270270
[
271-
'Variable $variableInFirstCase in isset() always exists and is not nullable.',
271+
// could be Variable $variableInFirstCase in isset() always exists and is not nullable.
272+
'Variable $variableInFirstCase in isset() is never defined.',
272273
116,
273274
],
274275
[
276+
// could be Variable $variableInSecondCase in isset() always exists and is not nullable.
275277
'Variable $variableInSecondCase in isset() is never defined.',
276278
117,
277279
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug8719;
4+
5+
class HelloWorld
6+
{
7+
private const CASE_1 = 1;
8+
private const CASE_2 = 2;
9+
private const CASE_3 = 3;
10+
11+
/**
12+
* @return self::CASE_*
13+
*/
14+
private function getCase(): int
15+
{
16+
return random_int(1,3);
17+
}
18+
19+
public function ok(): string
20+
{
21+
switch($this->getCase()) {
22+
case self::CASE_1:
23+
$foo = 'bar';
24+
break;
25+
case self::CASE_2:
26+
$foo = 'baz';
27+
break;
28+
case self::CASE_3:
29+
$foo = 'barbaz';
30+
break;
31+
}
32+
33+
return $foo;
34+
}
35+
36+
public function not_ok(): string
37+
{
38+
switch($this->getCase()) {
39+
case self::CASE_1:
40+
$foo = 'bar';
41+
break;
42+
case self::CASE_2:
43+
case self::CASE_3:
44+
$foo = 'barbaz';
45+
break;
46+
}
47+
48+
return $foo;
49+
}
50+
}

0 commit comments

Comments
 (0)