Skip to content

Commit 61a968e

Browse files
VincentLangletphpstan-bot
authored andcommitted
Skip non-discriminating guards in createConditionalExpressions even when target is not tracked in the other scope
- In `MutatingScope::createConditionalExpressions`, the filter that skips non-discriminating guards previously required the target expression to exist in the other scope's expression types. When the target was absent (e.g. `$options['multiple']` not explicitly narrowed in the truthy branch), the filter did not fire, allowing guards like `$instructions` (an unrelated mutable variable) to create spurious conditional expression holders. - Add an additional check: if the guard variable's type in our scope is definitively a supertype of its type in the other scope (`.yes()`), the guard is non-discriminating regardless of the target's presence, so skip it. - The original check (requiring both target and guard in the other scope, using `!.no()`) is preserved for cases where the target IS tracked, to avoid regressions with `maybe` supertype results (e.g. `Event` vs `OrderInterface`). - Also fixes the same bug with strict comparison (`!==` instead of `!=`).
1 parent 93d00a6 commit 61a968e

2 files changed

Lines changed: 54 additions & 4 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3749,11 +3749,16 @@ private function createConditionalExpressions(
37493749

37503750
foreach ($variableTypeGuards as $guardExprString => $guardHolder) {
37513751
if (
3752-
array_key_exists($exprString, $theirExpressionTypes)
3753-
&& $theirExpressionTypes[$exprString]->getCertainty()->yes()
3754-
&& array_key_exists($guardExprString, $theirExpressionTypes)
3752+
array_key_exists($guardExprString, $theirExpressionTypes)
37553753
&& $theirExpressionTypes[$guardExprString]->getCertainty()->yes()
3756-
&& !$guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->no()
3754+
&& (
3755+
(
3756+
array_key_exists($exprString, $theirExpressionTypes)
3757+
&& $theirExpressionTypes[$exprString]->getCertainty()->yes()
3758+
&& !$guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->no()
3759+
)
3760+
|| $guardHolder->getType()->isSuperTypeOf($theirExpressionTypes[$guardExprString]->getType())->yes()
3761+
)
37573762
) {
37583763
continue;
37593764
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
namespace Bug14595;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param array{
9+
* multiple: 0|1|2
10+
* , total: bool
11+
* } $options
12+
*/
13+
function arrayAppendGuard(array $options): void {
14+
$instructions = [ ];
15+
$instructions[] = "foo";
16+
if ($options['multiple'] != 1 || $options['total'])
17+
$instructions[] = "bar";
18+
assertType('0|1|2', $options['multiple']);
19+
if (!$options['total'])
20+
$instructions[] = "baz";
21+
assertType('0|1|2', $options['multiple']);
22+
if (!$options['total'])
23+
$instructions[] = "qux";
24+
assertType('0|1|2', $options['multiple']);
25+
}
26+
27+
/**
28+
* @param array{
29+
* multiple: 0|1|2
30+
* , total: bool
31+
* } $options
32+
*/
33+
function strictComparisonGuard(array $options): void {
34+
$instructions = [ ];
35+
$instructions[] = "foo";
36+
if ($options['multiple'] !== 1 || $options['total'])
37+
$instructions[] = "bar";
38+
assertType('0|1|2', $options['multiple']);
39+
if (!$options['total'])
40+
$instructions[] = "baz";
41+
assertType('0|1|2', $options['multiple']);
42+
if (!$options['total'])
43+
$instructions[] = "qux";
44+
assertType('0|1|2', $options['multiple']);
45+
}

0 commit comments

Comments
 (0)