-
Notifications
You must be signed in to change notification settings - Fork 568
Fix phpstan/phpstan#14407: !in_array with enum values incorrectly narrows to *NEVER* #5365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3e9f5ca
2692dab
36073fb
b36ddd3
7ceb9fd
7697ba5
97bb9cf
fdf627c
95757bf
c51adc8
83bcddf
82aa655
4fc7fd6
3664373
2b059ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php // lint >= 8.1 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug13421; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| enum Bar | ||
| { | ||
| case Bar1; | ||
| case Bar2; | ||
| case Bar3; | ||
| } | ||
|
|
||
| /** | ||
| * @param non-empty-array<Bar> $nonEmptyFilterArray | ||
| */ | ||
| function test(array $nonEmptyFilterArray): void | ||
| { | ||
| $bars = [Bar::Bar1, Bar::Bar2, Bar::Bar3]; | ||
|
|
||
| $filteredBars = array_filter($bars, fn (Bar $bar) => in_array($bar, $nonEmptyFilterArray)); | ||
|
|
||
| assertType("array{0?: Bug13421\Bar::Bar1, 1?: Bug13421\Bar::Bar2, 2?: Bug13421\Bar::Bar3}", $filteredBars); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| <?php // lint >= 8.1 | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug14407; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the origin example contains a error like
we should have a rule-test, this no longer happens. use the |
||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| enum SomeEnum { | ||
| case A; | ||
| case B; | ||
| } | ||
|
|
||
| function () { | ||
| $arr = []; | ||
|
|
||
| if (rand(0, 1) === 0) { | ||
| $arr[] = SomeEnum::A; | ||
| } else { | ||
| $arr[] = SomeEnum::B; | ||
| } | ||
|
|
||
| if (rand(0, 1) === 0) { | ||
| $x = SomeEnum::A; | ||
| } else { | ||
| $x = SomeEnum::B; | ||
| } | ||
|
|
||
| if (!in_array($x, $arr)) { | ||
| // either $x=A, $arr=[B] or $x=B, $arr=[A] | ||
| assertType('Bug14407\SomeEnum::A|Bug14407\SomeEnum::B', $x); | ||
| } | ||
|
|
||
| if (!in_array($x, $arr) && $x === SomeEnum::A) { | ||
| // $x=A, $arr=[B] | ||
| assertType('Bug14407\SomeEnum::A', $x); | ||
| } | ||
| }; | ||
|
|
||
| function () { | ||
| $arr = [SomeEnum::A, SomeEnum::B]; | ||
|
|
||
| if (rand(0, 1) === 0) { | ||
| $x = SomeEnum::A; | ||
| } else { | ||
| $x = SomeEnum::B; | ||
| } | ||
|
|
||
| if (!in_array($x, $arr)) { | ||
| // array always contains both A and B, so this is correctly *NEVER* | ||
| assertType('*NEVER*', $x); | ||
| } | ||
| }; | ||
|
|
||
| function () { | ||
| $arr = []; | ||
|
|
||
| $r = rand(0, 2); | ||
|
|
||
| if ($r === 0) { | ||
| $arr[] = SomeEnum::A; | ||
| } elseif ($r === 1) { | ||
| $arr[] = SomeEnum::B; | ||
| } | ||
|
|
||
| if (rand(0, 1) === 0) { | ||
| $x = SomeEnum::A; | ||
| } else { | ||
| $x = SomeEnum::B; | ||
| } | ||
|
|
||
| // arr might be empty, so no narrowing possible | ||
| if (!in_array($x, $arr) && $x === SomeEnum::A) { | ||
| assertType('Bug14407\SomeEnum::A', $x); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * @param 'a'|'b'|'c' $x | ||
| * @param array{a: 'a', c: 'c'}|array{a?:'a', b: 'b'} $a | ||
| */ | ||
| function testUnionWithOptionalKeys($x, $a): void | ||
| { | ||
| assertType("array{a: 'a', c: 'c'}|array{a?: 'a', b: 'b'}", $a); | ||
| if (!\in_array($x, $a, true)) { | ||
| assertType("'a'|'b'|'c'", $x); | ||
| } | ||
| }; | ||
|
|
||
| /** | ||
| * @param 'a'|'b'|'c' $x | ||
| * @param non-empty-array<'a'|'b'> $a | ||
| */ | ||
| function testNonConstantArray($x, $a): void | ||
| { | ||
| assertType("non-empty-array<'a'|'b'>", $a); | ||
| if (!\in_array($x, $a, true)) { | ||
| assertType("'a'|'b'|'c'", $x); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types = 1); | ||
|
|
||
| namespace Bug8864; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** | ||
| * @param array{0: 1, 1?: 2} $a | ||
| */ | ||
| function test(array $a): void | ||
| { | ||
| if (in_array(2, $a, true)) { | ||
| assertType('array{0: 1, 1?: 2}', $a); | ||
| } | ||
|
|
||
| if (!in_array(2, $a, true)) { | ||
| assertType('array{0: 1, 1?: *NEVER*}', $a); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param 1|2 $x | ||
| * @param array{0: 1, 1?: 2} $a | ||
| */ | ||
| function testNeedle($x, array $a): void | ||
| { | ||
| if (in_array($x, $a, true)) { | ||
| assertType('1|2', $x); | ||
| } | ||
|
|
||
| if (!in_array($x, $a, true)) { | ||
| // 1 is guaranteed in the array, so if not in_array, x must be 2 | ||
| assertType('2', $x); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the origin example contains a error like
we should have a rule-test, this no longer happens.
use the
tests/PHPStan/Analyser/nsrt/bug-13421.phpfile in the rule-test - don't create another copy of this file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full test suite also passed. All 11710 tests green, static analysis clean. The review feedback from staabm has been addressed and pushed.