-
Notifications
You must be signed in to change notification settings - Fork 570
Fix phpstan/phpstan#4090: Array detected as empty while it cannot #5387
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
base: 2.1.x
Are you sure you want to change the base?
Changes from 3 commits
967ed70
5c37e8a
13f95d5
3f56074
0e31ce7
96c0249
992f476
5241dfc
d5a6d29
f7f30a7
665d455
11d3571
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 |
|---|---|---|
|
|
@@ -2144,6 +2144,36 @@ | |
| ); | ||
| } | ||
|
|
||
| private function conditionalExpressionHolderMatches(ExpressionTypeHolder $specified, ExpressionTypeHolder $condition): bool | ||
| { | ||
| if ($specified->equals($condition)) { | ||
| return true; | ||
| } | ||
|
|
||
| if (!$specified->getCertainty()->equals($condition->getCertainty())) { | ||
| return false; | ||
| } | ||
|
|
||
| $conditionType = $condition->getType(); | ||
|
|
||
| // Only use isSuperTypeOf for non-constant integer types (i.e. IntegerRangeType). | ||
| // This is needed for count()-in-variable patterns: when $count = count($a) creates | ||
| // a condition on int<1, max>, and $count is later narrowed to int<2, max> or 1, | ||
| // the subtype relationship correctly preserves the array narrowing. | ||
| // | ||
| // We cannot safely extend this to all types because filterBySpecifiedTypes() | ||
| // intersects results from ALL matching conditional expressions (line ~3251). | ||
| // For types like bool, isSuperTypeOf would match both a 'false' condition AND | ||
| // a 'bool' condition simultaneously, and intersecting their (potentially conflicting) | ||
| // results produces *NEVER*. Integer ranges don't have this problem because | ||
| // count() creates non-overlapping conditions (e.g. int<1, max> vs 0). | ||
| if (!$conditionType->isInteger()->yes() || $conditionType->isConstantScalarValue()->yes()) { | ||
|
Check warning on line 2170 in src/Analyser/MutatingScope.php
|
||
|
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. Can't this be extended to more Types ? You ddin't explain why it would work for some and not some others. Stop focusing on IntegerRange
Collaborator
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. All background tasks have finished. The work is complete — commit pushed, all tests and static analysis passing. |
||
| return false; | ||
| } | ||
|
|
||
| return $conditionType->isSuperTypeOf($specified->getType())->yes(); | ||
| } | ||
|
|
||
| private function expressionTypeIsUnchangeable(ExpressionTypeHolder $typeHolder): bool | ||
| { | ||
| $expr = $typeHolder->getExpr(); | ||
|
|
@@ -3218,7 +3248,7 @@ | |
| } | ||
| foreach ($conditionalExpressions as $conditionalExpression) { | ||
| foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { | ||
| if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) { | ||
| if (!array_key_exists($holderExprString, $specifiedExpressions) || !$this->conditionalExpressionHolderMatches($specifiedExpressions[$holderExprString], $conditionalTypeHolder)) { | ||
| continue 2; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace Bug4090; | ||
|
|
||
| use function PHPStan\Testing\assertType; | ||
|
|
||
| /** @param string[] $a */ | ||
| function foo(array $a): void | ||
| { | ||
| if (count($a) > 1) { | ||
| assertType('non-empty-array<string>', $a); | ||
| echo implode(',', $a); | ||
| } elseif (count($a) === 1) { | ||
| assertType('non-empty-array<string>', $a); | ||
| echo trim(current($a)); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /** @param string[] $a */ | ||
| function bar(array $a): void | ||
| { | ||
| $count = count($a); | ||
| if ($count > 1) { | ||
| assertType('non-empty-array<string>', $a); | ||
| echo implode(',', $a); | ||
| } elseif ($count === 1) { | ||
| assertType('non-empty-array<string>', $a); | ||
| echo trim(current($a)); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| /** @param string[] $a */ | ||
| function qux(array $a): void | ||
| { | ||
| switch (count($a)) { | ||
| case 0: | ||
| assertType('array{}', $a); | ||
| break; | ||
| case 1: | ||
| assertType('non-empty-array<string>', $a); | ||
| echo trim(current($a)); | ||
| break; | ||
| default: | ||
| assertType('non-empty-array<string>', $a); | ||
| echo implode(',', $a); | ||
| break; | ||
| } | ||
| } |
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.
Remove the comment, and look for another strategy cause it should also works for UnionType and certainly some others Type.
Maybe only setting $conditions OR $specifiedExpressions in specific case ; but feel free to explore other ideas
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.
All done. Every background task has completed successfully — tests and static analysis all green. The commit is pushed.