Skip to content

Value of a variable is forgotten in case where it ought to be known with certainty #11705

Open

Description

@mind-bending-forks

Bug report

I have encountered a scenario where the value of a variable appears to be 'forgotten' by phpstan in a case where it ought to be known with certainty, and as a result, phpstan fails to narrow a type and so reports an incorrect error.

Code snippet that reproduces the problem

https://phpstan.org/r/f1aad144-7261-4e0b-ac2c-bba065431bcb

Expected output

No error reported by phpstan.

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

Phpstan doesn't eliminate the need for testing to check that the code does what it is supposed to do, of course, but the code becomes more precisely specified and so dependable, which makes it easier to maintain. Developing or working with a code base that has been raised to a high phpstan strictness level is a pleasure, since phpstan will quickly warn you if you attempt to make a change of dubious quality. Very happy about this!

Activity

  1. changed the title [-]Value and type of variable are forgotten in case where they ought to be known with certainty[/-] [+]Value of a variable is forgotten in case where it ought to be known with certainty[/+] on Sep 17, 2024
  2. ondrejmirtes commented on Sep 18, 2024

    @ondrejmirtes
    Member

    Problem simplified to: https://phpstan.org/r/d62a02ab-388d-4fb8-81c6-a246ff6d831b

    The problem lies in array_key_exists at the beginning, removing it fixes it: https://phpstan.org/r/1e120370-b233-467a-9eda-531745a0b920

  3. added this to the Easy fixes milestone on Sep 18, 2024
  4. added a commit that references this issue on Mar 10, 2026
    2716e22
  5. staabm commented on Mar 12, 2026

    @staabm
    Contributor

    the problem is that we have 2 different information for $theInput[$tag].

    in Scope we remembered $theInput[$tag] is string|array<int,string>
    while for $theInput-type we know array{name: string, owners: array<int, string>}

    see https://phpstan.org/r/4ebcaacf-37a1-4c65-ae95-a49c8fbc1cec

    I don't think we can fix this in ArrayKeyExistsFunctionTypeSpecifyingExtension because at that point we only know $tag will be 'name'|'owners'. via switch we later on narrow $tag to 'name'.

    I think the fix would be to foget the remembered expression for $theInput[$tag], as soon as $tag gets narrowed.
    that way scope-type-resolving would pickup the type from $theInput's constant array type.

    wdyt @ondrejmirtes ?

  6. added a commit that references this issue on Mar 12, 2026
    55773e6
  7. added 2 commits that reference this issue on Mar 12, 2026
    e3e9895
    84f9914
  8. added a commit that references this issue on Mar 12, 2026
    75cffd0
  9. phpstan-bot commented on Apr 24, 2026

    @phpstan-bot
    Contributor

    @staabm After the latest push in 2.2.x, PHPStan now reports different result with your code snippet:

    @@ @@
     $theTags (Yes): non-empty-array<int, string>
     __phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>
     $tag (Yes): 'name'|'owners'
    +__phpstanOriginalForeachValue(tag) (Yes): string
     $theInput[$tag] (Yes): array<int, string>|string
     native $theInput (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theInput) (Yes): array
    @@ @@
     native $theTags (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theTags) (Yes): array
     native $tag (Yes): 'name'|'owners'
    +native __phpstanOriginalForeachValue(tag) (Yes): mixed
     native $theInput[$tag] (Yes): array<int, string>|string
     18: $theInput (Yes): array{name: string, owners: array<int, string>}
     __phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}
    @@ @@
     $theTags (Yes): non-empty-array<int, string>
     __phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>
     $tag (Yes): 'name'
    +__phpstanOriginalForeachValue(tag) (Yes): string
     $theInput[$tag] (Yes): array<int, string>|string
     native $theInput (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theInput) (Yes): array
    @@ @@
     native $theTags (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theTags) (Yes): array
     native $tag (Yes): 'name'
    +native __phpstanOriginalForeachValue(tag) (Yes): mixed
     native $theInput[$tag] (Yes): array<int, string>|string
     19: Dumped type: array{name: string, owners: array<int, string>}
     20: Parameter #1 (array<int, string>|string) of echo cannot be converted to string.
    Full report
    Line Error
    15 `$theInput (Yes): array{name: string, owners: array<int, string>}__phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}$theTags (Yes): non-empty-array<int, string>__phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>$tag (Yes): 'name'
    18 `$theInput (Yes): array{name: string, owners: array<int, string>}__phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}$theTags (Yes): non-empty-array<int, string>__phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>$tag (Yes): 'name'__phpstanOriginalForeachValue(tag) (Yes): string$theInput[$tag] (Yes): array<int, string>
    19 Dumped type: array{name: string, owners: array<int, string>}
    20 `Parameter #1 (array<int, string>
  10. phpstan-bot commented on Sep 20, 2026

    @phpstan-bot
    Contributor

    @staabm After the latest push in 2.3.x, PHPStan now reports different result with your code snippet:

    @@ @@
     $theTags (Yes): non-empty-array<int, string>
     __phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>
     $tag (Yes): 'name'|'owners'
    +__phpstanOriginalForeachValue(tag) (Yes): string
     $theInput[$tag] (Yes): array<int, string>|string
    +!\array_key_exists($tag, $theInput) (Yes): false
     native $theInput (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theInput) (Yes): array
     native $theTags (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theTags) (Yes): array
     native $tag (Yes): 'name'|'owners'
    +native __phpstanOriginalForeachValue(tag) (Yes): mixed
     native $theInput[$tag] (Yes): array<int, string>|string
    +native !\array_key_exists($tag, $theInput) (Yes): false
     18: $theInput (Yes): array{name: string, owners: array<int, string>}
     __phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}
     $theTags (Yes): non-empty-array<int, string>
     __phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>
     $tag (Yes): 'name'
    +__phpstanOriginalForeachValue(tag) (Yes): string
     $theInput[$tag] (Yes): array<int, string>|string
    +!\array_key_exists($tag, $theInput) (Yes): false
     native $theInput (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theInput) (Yes): array
     native $theTags (Yes): non-empty-array
     native __phpstanParameterVariableOriginalValue(theTags) (Yes): array
     native $tag (Yes): 'name'
    +native __phpstanOriginalForeachValue(tag) (Yes): mixed
     native $theInput[$tag] (Yes): array<int, string>|string
    +native !\array_key_exists($tag, $theInput) (Yes): false
     19: Dumped type: array{name: string, owners: array<int, string>}
     20: Parameter #1 (array<int, string>|string) of echo cannot be converted to string.
    Full report
    Line Error
    15 `$theInput (Yes): array{name: string, owners: array<int, string>}__phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}$theTags (Yes): non-empty-array<int, string>__phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>$tag (Yes): 'name'
    18 `$theInput (Yes): array{name: string, owners: array<int, string>}__phpstanParameterVariableOriginalValue(theInput) (Yes): array{name: string, owners: array<int, string>}$theTags (Yes): non-empty-array<int, string>__phpstanParameterVariableOriginalValue(theTags) (Yes): array<int, string>$tag (Yes): 'name'__phpstanOriginalForeachValue(tag) (Yes): string$theInput[$tag] (Yes): array<int, string>
    19 Dumped type: array{name: string, owners: array<int, string>}
    20 `Parameter #1 (array<int, string>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions