Skip to content

Drop false from array_search(..., true) when every finite needle value is guaranteed in the haystack#5947

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-q1cy8o1
Open

Drop false from array_search(..., true) when every finite needle value is guaranteed in the haystack#5947
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-q1cy8o1

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

array_search($needle, $haystack, true) returns the matching key or false. When the needle is known to take a finite set of values that is a subset of the haystack's guaranteed values, the search can never return false, so a check like array_search($needle, $a, true) !== false is always true. PHPStan kept false in the return type whenever the needle was a union of finite values (or an enum case), so it failed to report the always-true comparison. This mirrors the recently fixed in_array(..., true) case (#14873).

This change drops false from the array_search() result whenever every possible finite needle value is guaranteed to be present, which lets the strict-comparison rule report the always-true !== false.

Changes

  • src/Type/Constant/ConstantArrayType.phpsearchArray() now detects "guaranteed found" for finite union and enum-case needles via getFiniteTypes() (new private needleIsGuaranteedToBeFound() helper), not just for a single ConstantScalarType. A needle is guaranteed found when every one of its finite values is a subtype of some non-optional value that is itself a single finite type.
  • src/Type/IntersectionType.phpsearchArray() removes false from the intersected result for a non-empty general array whose iterable value type is a single finite type that is a supertype of every finite needle value (e.g. non-empty-array<int, 1> searched for 1). Mirrors the non-empty-array handling that in_array already had.
  • src/Type/Accessory/HasOffsetValueType.phpsearchArray() now uses getFiniteTypes()/isSuperTypeOf() rather than instanceof ConstantScalarType + ===, so a known offset value also recognizes enum-case (and other finite, non-scalar) needles as guaranteed found. The loose-equality scalar branch is kept for non-strict comparisons.

Probed and found already correct (no change needed): ArrayType::searchArray (plain, possibly-empty arrays cannot guarantee a value), UnionType::searchArray (delegates per-member, so false removal already composes), and the AccessoryArrayListType/OversizedArrayType/NonEmptyArrayType accessory members (their results are combined through IntersectionType::searchArray).

Root cause

The "needle is guaranteed present, so the result has no false" logic was expressed only in terms of a single ConstantScalarType needle. The general pattern is finite types: a needle that can only be one of finitely many values is guaranteed found when each of those values is guaranteed under a non-optional key. Three searchArray() implementations encoded the narrow scalar-only form of this check:

  • ConstantArrayType only set hasIdenticalValue for a single matching constant scalar.
  • The non-empty general-array case had no handling at all (always kept false).
  • HasOffsetValueType checked instanceof ConstantScalarType + getValue() === getValue(), missing enum cases.

The fix replaces these with the finite-types formulation (getFiniteTypes() + isSuperTypeOf()), in line with the codebase rule of querying type properties via Type methods rather than instanceof.

Test

  • tests/PHPStan/Analyser/nsrt/bug-14877.phpassertType() coverage for array_search(..., true) over constant arrays (full/subset/partial finite-union string, integer and enum needles), non-strict search, general arrays (maybe-empty, non-empty multi-value, non-empty single-value), plain objects (no finite values, false retained), and known-offset (HasOffsetValueType) enum/scalar needles.
  • tests/PHPStan/Rules/Comparison/data/bug-14877.php + StrictComparisonOfDifferentTypesRuleTest::testBug14877 — asserts array_search(..., true) !== false is reported as always-true for the guaranteed cases and not reported for partial/maybe-empty/multi-value cases.

Both tests fail before the fix and pass after it.

Fixes phpstan/phpstan#14877

…value is guaranteed in the haystack

- ConstantArrayType::searchArray: generalize the "guaranteed found" detection to finite union and enum-case needles via getFiniteTypes() in strict mode, instead of only recognizing a single ConstantScalarType needle. When every possible needle value is guaranteed under a non-optional key, false is dropped from the result, so `array_search($needle, $a, true) !== false` is reported as always true.
- IntersectionType::searchArray: for a non-empty general (non-constant) array whose values all share a single finite type that is a supertype of every finite needle value, remove false from the searchArray result (mirrors the non-empty-array handling for in_array).
- HasOffsetValueType::searchArray: use getFiniteTypes()/isSuperTypeOf() instead of a `ConstantScalarType` + `===` check so a known-offset value also recognizes enum-case (and other finite, non-scalar) needles as guaranteed found; the loose-equality scalar branch is preserved.
- Add type-inference regression test (nsrt/bug-14877) and a StrictComparisonOfDifferentTypesRule test (data/bug-14877) covering constant arrays, enums, integers, known offsets and general arrays.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants