🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Incomplete fix for #14807: conditional narrowing still leaks when an intermediate if narrows another value, and 2.2.3 added a new in_array/function.alreadyNarrowedType symptom #14908

Description

@momala454

Bug report

This is a follow-up to #14807 ("Object property narrowing leaks out of a previous if when a 2-case enum is narrowed with !=="), which was closed by phpstan/phpstan-src#5848 and released in 2.2.3.

The minimal repro from #14807 is indeed fixed on 2.2.3/2.2.4. However, the fix only covers the narrow reduced shape. The same underlying leak in MutatingScope::createConditionalExpressions() still fires on a slightly richer shape, and 2.2.3 additionally introduced a new symptom on in_array() set-narrowing.

The extra ingredient that defeats the fix: an intermediate if between the two enum ifs that narrows another value with === false inside a disjunction (below: $extra === false). With it present:

  1. The original property-narrowing false positive (identical.alwaysFalse on $flags->flagA === false) comes back — it was never fixed for this shape (present in 2.2.2, 2.2.3 and 2.2.4).
  2. A new false positive appears: in_array($enum, [...], true) in the second if is reported as function.alreadyNarrowedType ("will always evaluate to true"), because the enum-set narrowing from the first if also leaks. This one is a regression introduced in 2.2.3 (absent in 2.2.2).

Version matrix (same repro below)

Version L31 $flags->flagA === false L34 in_array($kind, ..., true)
2.2.2 ❌ false positive ✅ no error
2.2.3 ❌ false positive new false positive
2.2.4 ❌ false positive ❌ false positive

Expected output

No errors. The first if can be skipped for reasons unrelated to $flags->flagA or to $kind (e.g. the $grade condition, $cond, or $flags->flagA itself being false), so neither $flags->flagA === false nor the in_array() set-membership check in the later block is statically decidable.

Actual output (2.2.4)

L31  Strict comparison using === between true and false will always evaluate to false.  (identical.alwaysFalse)
L34  Call to function in_array() with arguments Kind::K1|Kind::K2|Kind::K3, array{Kind::K1, Kind::K2} and true will always evaluate to true.  (function.alreadyNarrowedType)

Reproduces on 2.2.3 and 2.2.4 without bleeding edge. See #14807 for the original (now partially-fixed) report.

Code snippet that reproduces the problem

https://phpstan.org/r/ae549d6e-b042-42b9-9138-c81e8adcbee6

Did PHPStan help you today? Did it make you happy in any way?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions