-
Notifications
You must be signed in to change notification settings - Fork 550
Infer non-empty-array after array_key_first/last() #4536
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
0ea52a0
d1aece6
37ea055
2505ba4
1427f89
7d4c4dc
c8f2b3b
3532071
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 |
|---|---|---|
|
|
@@ -2383,6 +2383,22 @@ private function resolveNormalizedIdentical(Expr\BinaryOp\Identical $expr, Scope | |
| } | ||
| } | ||
|
|
||
| // array_key_first($a) !== null | ||
| // array_key_last($a) !== null | ||
| if ( | ||
| $unwrappedLeftExpr instanceof FuncCall | ||
| && $unwrappedLeftExpr->name instanceof Name | ||
| && in_array($unwrappedLeftExpr->name->toLowerString(), ['array_key_first', 'array_key_last'], true) | ||
| && isset($unwrappedLeftExpr->getArgs()[0]) | ||
| && $rightType->isNull()->yes() | ||
|
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. You're getting escaped mutant here, but if I understand correctly, it should be supported by
Contributor
Author
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. agree. I tested it locally and the test starts failling when I manually mutate the code. |
||
| ) { | ||
| $args = $unwrappedLeftExpr->getArgs(); | ||
| $argType = $scope->getType($args[0]->value); | ||
| if ($argType->isArray()->yes()) { | ||
| return $this->create($args[0]->value, new NonEmptyArrayType(), $context->negate(), $scope)->setRootExpr($expr); | ||
| } | ||
| } | ||
|
|
||
| // preg_match($a) === $b | ||
| if ( | ||
| $context->true() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| <?php | ||
| namespace Bug13546; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** @param array<int> $array */ | ||
| function first(array $array): void | ||
| { | ||
| if (array_key_first($array) !== null) { | ||
| assertType('non-empty-array<int>', $array); | ||
| } else { | ||
| assertType('array{}', $array); | ||
| } | ||
| assertType('array<int>', $array); | ||
| } | ||
|
|
||
| /** @param array<int> $array */ | ||
| function firstReversed(array $array): void | ||
| { | ||
| if (null !== array_key_first($array)) { | ||
| assertType('non-empty-array<int>', $array); | ||
| } else { | ||
| assertType('array{}', $array); | ||
| } | ||
| assertType('array<int>', $array); | ||
| } | ||
|
|
||
| /** @param array<int> $array */ | ||
| function last(array $array): void | ||
| { | ||
| if (array_key_last($array) !== null) { | ||
| assertType('non-empty-array<int>', $array); | ||
| } else { | ||
| assertType('array{}', $array); | ||
| } | ||
| assertType('array<int>', $array); | ||
| } | ||
|
|
||
| function maybeArray(array $array, $mixed): void | ||
| { | ||
| $arrayOrMixed = rand(0, 1) ? $array : $mixed; | ||
|
|
||
| if (array_key_last($arrayOrMixed) !== null) { | ||
| assertType('mixed', $arrayOrMixed); | ||
| } else { | ||
| assertType('mixed', $arrayOrMixed); | ||
| } | ||
| assertType('mixed', $arrayOrMixed); | ||
| } | ||
|
|
||
| function mixedLast($mixed): void | ||
| { | ||
| if (is_array($mixed)) { | ||
| return; | ||
| } | ||
|
|
||
| if (array_key_last($mixed) !== null) { | ||
| assertType('mixed~array<mixed, mixed>', $mixed); | ||
| } else { | ||
| assertType('mixed~array<mixed, mixed>', $mixed); | ||
| } | ||
| assertType('mixed~array<mixed, mixed>', $mixed); | ||
| } | ||
|
|
||
| /** @param list<string> $array */ | ||
| function firstInCondition(array $array) | ||
| { | ||
| if (($key = array_key_first($array)) !== null) { | ||
| assertType('list<string>', $array); // could be 'non-empty-list<string>' | ||
| return $array[$key]; | ||
| } | ||
| assertType('list<string>', $array); | ||
| return null; | ||
| } | ||
|
|
||
| /** @param list<string> $array */ | ||
| function maybeNull(array $array, ?int $nullOrInt, ?string $nullOrString): void | ||
| { | ||
| if (array_key_first($array) !== $nullOrInt) { | ||
| assertType('list<string>', $array); | ||
| } | ||
| if (array_key_first($array) !== $nullOrString) { | ||
| assertType('list<string>', $array); | ||
| } | ||
| assertType('list<string>', $array); | ||
|
|
||
| if (array_key_last($array) !== $nullOrInt) { | ||
| assertType('list<string>', $array); | ||
| } | ||
| if (array_key_last($array) !== $nullOrString) { | ||
| assertType('list<string>', $array); | ||
| } | ||
| assertType('list<string>', $array); | ||
| } |
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.
I thought you forgot
null !== array_key_first($a)but seems like it works looking at the test.I'm not familiar with this code to know why at first sight.
Uh oh!
There was an error while loading. Please reload this page.
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.
we are in
resolveNormalizedIdentical, which is invoked in both directions here:phpstan-src/src/Analyser/TypeSpecifier.php
Lines 2258 to 2269 in eb4e247