Skip to content

A list and a possibly-empty non-list array share the empty array - #6800

Open
HawkForge wants to merge 1 commit into
phpstan:2.3.xfrom
HawkForge:fix/15450-empty-list-overlap
Open

HawkForge wants to merge 1 commit into
phpstan:2.3.xfrom
HawkForge:fix/15450-empty-list-overlap

Conversation

@HawkForge

Copy link
Copy Markdown
Contributor

Closes phpstan/phpstan#15450

Thanks @lucianofedericopereira for the report.

array_is_list() is stubbed as ($value is list ? true : false). For array<string, mixed>, list<mixed> (array<int<0, max>, mixed> & AccessoryArrayListType) answered no from IntersectionType::isSuperTypeOf(), because the key types are disjoint and ArrayType::isList() is no. That ignores [], which is both a list and a valid array<string, mixed>. So the call was reported as always false, and narrowing the true branch through TypeCombinator::intersect() returned *NEVER* because the two ArrayType members are disjoint.

Changes:

  • IntersectionType::isSuperTypeOf(): when a list type would answer no for an array that is not a list (isList()->no()), the answer becomes maybe if both sides admit []. A non-empty-array<string, mixed> does not admit [], so it stays no (as requested in the issue).
  • TypeCombinator::intersect(): when two array members are disjoint, one of them can never be a list, the intersection contains AccessoryArrayListType, and both admit [], the result is array{} intersected with the remaining members instead of never.
  • The same changes are mirrored in turbo-ext/src/IntersectionType.cpp and turbo-ext/src/TypeCombinator.cpp.

The fix is limited to list-ness on purpose. Generic arrays with disjoint key or value types (array<int, string> & array<string, string>, list<A> vs list<B>) behave as before. The existing TypeCombinatorTest cases and the MethodSignatureRule messages still pass unchanged.

Tests:

  • New tests/PHPStan/Analyser/nsrt/bug-15450.php (the issue's reproducer) asserts array{} / non-empty-array<string, mixed> for array<string, mixed>, and *NEVER* for non-empty-array<string, mixed>.
  • ImpossibleCheckTypeFunctionCallRuleTest::testArrayIsList no longer expects the error for array<string, int>. A new non-empty-array<string, int> case keeps the error.
  • nsrt/bug-14177.php: list{0: string, 1?: string, 2?: string, 3?: string} after unset($b[$int]) is array{0?: string, ...}, so array_is_list() is bool, not false. At runtime $b = ['x']; unset($b[0]); leaves [], and array_is_list() returns true.

Without the source change, the new nsrt test, the updated bug-14177 assertion and testArrayIsList all fail.

`[]` is a list, so `array<string, mixed>` is not disjoint from `list`:
array_is_list() on it is not always false and its true branch narrows
to `array{}`. A non-empty array whose keys cannot form a list is still
reported as never being a list.

Closes phpstan/phpstan#15450
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

array_is_list() on array<string, mixed> narrows to *NEVER*, but the empty array is both

1 participant