Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit ce0aaf2

Browse files
Fix condition of fall-through case not used for exhaustive checks
1 parent 21923d3 commit ce0aaf2

File tree

8 files changed

+203
-3
lines changed

8 files changed

+203
-3
lines changed

‎src/Analyser/NodeScopeResolver.php‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,9 +1449,11 @@ private function processStmtNode(
14491449
$exitPointsForOuterLoop = [];
14501450
$throwPoints = $condResult->getThrowPoints();
14511451
$impurePoints = $condResult->getImpurePoints();
1452+
$fullCondExpr = null;
14521453
foreach ($stmt->cases as $caseNode) {
14531454
if ($caseNode->cond !== null) {
14541455
$condExpr = new BinaryOp\Equal($stmt->cond, $caseNode->cond);
1456+
$fullCondExpr = $fullCondExpr === null ? $condExpr : new BooleanOr($fullCondExpr, $condExpr);
14551457
$caseResult = $this->processExprNode($stmt, $caseNode->cond, $scopeForBranches, $nodeCallback, ExpressionContext::createDeep());
14561458
$scopeForBranches = $caseResult->getScope();
14571459
$hasYield = $hasYield || $caseResult->hasYield();
@@ -1460,6 +1462,7 @@ private function processStmtNode(
14601462
$branchScope = $caseResult->getTruthyScope()->filterByTruthyValue($condExpr);
14611463
} else {
14621464
$hasDefaultCase = true;
1465+
$fullCondExpr = null;
14631466
$branchScope = $scopeForBranches;
14641467
}
14651468

@@ -1481,8 +1484,9 @@ private function processStmtNode(
14811484
if ($branchScopeResult->isAlwaysTerminating()) {
14821485
$alwaysTerminating = $alwaysTerminating && $branchFinalScopeResult->isAlwaysTerminating();
14831486
$prevScope = null;
1484-
if (isset($condExpr)) {
1485-
$scopeForBranches = $scopeForBranches->filterByFalseyValue($condExpr);
1487+
if (isset($fullCondExpr)) {
1488+
$scopeForBranches = $scopeForBranches->filterByFalseyValue($fullCondExpr);
1489+
$fullCondExpr = null;
14861490
}
14871491
if (!$branchFinalScopeResult->isAlwaysTerminating()) {
14881492
$finalScope = $branchScope->mergeWith($finalScope);
Lines changed: 30 additions & 0 deletions
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‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,25 @@ public function testBug9374(): void
343343
$this->analyse([__DIR__ . '/data/bug-9374.php'], []);
344344
}
345345

346+
public function testBug3488Two(): void
347+
{
348+
$this->checkExplicitMixedMissingReturn = true;
349+
$this->analyse([__DIR__ . '/data/bug-3488-2.php'], [
350+
[
351+
'Method Bug3488\C::invalidCase() should return int but return statement is missing.',
352+
30,
353+
],
354+
]);
355+
}
356+
357+
public function testBug12722(): void
358+
{
359+
if (PHP_VERSION_ID < 80100) {
360+
$this->markTestSkipped('Test requires PHP 8.1.');
361+
}
362+
363+
$this->checkExplicitMixedMissingReturn = true;
364+
$this->analyse([__DIR__ . '/data/bug-12722.php'], []);
365+
}
366+
346367
}
Lines changed: 32 additions & 0 deletions
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+
}
Lines changed: 52 additions & 0 deletions
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‎

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 testBug8719(): void
1072+
{
1073+
$this->cliArgumentsVariablesRegistered = true;
1074+
$this->polluteScopeWithLoopInitialAssignments = true;
1075+
$this->checkMaybeUndefinedVariables = true;
1076+
$this->polluteScopeWithAlwaysIterableForeach = true;
1077+
$this->analyse([__DIR__ . '/data/bug-8719.php'], []);
1078+
}
1079+
10711080
}

‎tests/PHPStan/Rules/Variables/IssetRuleTest.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,12 @@ public function testVariableCertaintyInIsset(): void
277277
112,
278278
],
279279
[
280-
'Variable $variableInFirstCase in isset() always exists and is not nullable.',
280+
// could be Variable $variableInFirstCase in isset() always exists and is not nullable.
281+
'Variable $variableInFirstCase in isset() is never defined.',
281282
116,
282283
],
283284
[
285+
// could be Variable $variableInSecondCase in isset() always exists and is not nullable.
284286
'Variable $variableInSecondCase in isset() is never defined.',
285287
117,
286288
],
Lines changed: 50 additions & 0 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /