← All results

array_key_exists($key, $map) narrows $key to the keys $map is known to have, not only the offset access after the check

Group: Assertions and narrowing · Category: ecosystem · File: assertions_array_key_exists_key_narrowing.php

A int|string key that is known to exist in array<string, int> is a string. Passing it to a string sink is silent after the guard and rejected without it. The same intersection applies to a literal map ['a' => 1, 'b' => 2]: a string key becomes 'a'|'b'. Source: carthage-software/mago#2199 References: - PHP array_key_exists() - mago#2199

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.7Phan 6.0.7 php-ast version 1.1.3 PHP version used to run Phan: 8.5.9 Fail

No diagnostics reported.

Expectation diff
Line 43: Expected 1 error(s)
phpstan 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 Pass reported Lv.7+
assertions_array_key_exists_key_narrowing.php:43: Parameter #1 $value of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, int|string given. [reported-from-level=7]
assertions_array_key_exists_key_narrowing.php:64: Parameter #1 $key of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB expects 'a'|'b', string given. [reported-from-level=7]
With strict-rules
assertions_array_key_exists_key_narrowing.php:43: Parameter #1 $value of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, int|string given.
assertions_array_key_exists_key_narrowing.php:64: Parameter #1 $key of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB expects 'a'|'b', string given.
psalm 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac
next: 7.0.0-beta19
Fail
assertions_array_key_exists_key_narrowing.php:40: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, but int|string provided [InvalidScalarArgument]
assertions_array_key_exists_key_narrowing.php:43: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, but int|string provided [InvalidScalarArgument]
assertions_array_key_exists_key_narrowing.php:64: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB expects 'a'|'b', but parent type string provided [ArgumentTypeCoercion]
Expectation diff
Line 40: Expected silence (quiet probe), got ["Argument 1 of Conformance\\Tests\\AssertionsArrayKeyExistsKeyNarrowing\\takesString expects string, but int|string provided [InvalidScalarArgument]"]
mago 1.47.4mago 1.47.4 Pass
assertions_array_key_exists_key_narrowing.php:43: Possible argument type mismatch for argument #1 of `Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString`: expected `string`, but possibly received `int|string`. [possibly-invalid-argument]
assertions_array_key_exists_key_narrowing.php:64: Possible argument type mismatch for argument #1 of `Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB`: expected `string('a')|string('b')`, but possibly received `string`. [possibly-invalid-argument]
mir 0.72.1mir 0.72.1 Fail
assertions_array_key_exists_key_narrowing.php:40: PossiblyInvalidArgument: Argument $value of takesString() expects 'string', possibly different type 'int|string' provided [MIR0105]
assertions_array_key_exists_key_narrowing.php:43: PossiblyInvalidArgument: Argument $value of takesString() expects 'string', possibly different type 'int|string' provided [MIR0105]
Expectation diff
Line 40: Expected silence (quiet probe), got ["PossiblyInvalidArgument: Argument $value of takesString() expects 'string', possibly different type 'int|string' provided [MIR0105]"]
phpantom 0.10.0phpantom_lsp 0.10.0 Fail
assertions_array_key_exists_key_narrowing.php:40: Argument 1 ($value) expects string, got int|string (int does not satisfy string) [type_mismatch_argument]
assertions_array_key_exists_key_narrowing.php:43: Argument 1 ($value) expects string, got int|string (int does not satisfy string) [type_mismatch_argument]
assertions_array_key_exists_key_narrowing.php:61: Argument 1 ($key) expects 'a'|'b', got string [type_mismatch_argument]
assertions_array_key_exists_key_narrowing.php:64: Argument 1 ($key) expects 'a'|'b', got string [type_mismatch_argument]
Expectation diff
Line 40: Expected silence (quiet probe), got ["Argument 1 ($value) expects string, got int|string (int does not satisfy string) [type_mismatch_argument]"]
intelephense 1.18.5intelephense 1.18.5 Fail
assertions_array_key_exists_key_narrowing.php:40: Expected type 'string'. Found 'int|string'. [P1006]
assertions_array_key_exists_key_narrowing.php:43: Expected type 'string'. Found 'int|string'. [P1006]
Expectation diff
Line 40: Expected silence (quiet probe), got ["Expected type 'string'. Found 'int|string'. [P1006]"]
phpactor 2026.07.22.0Phpactor 2026.07.22.0 Fail

No diagnostics reported.

Expectation diff
Line 43: Expected 1 error(s)
phpy 0.2.0phpy 0.2.0 Fail

No diagnostics reported.

Expectation diff
Line 43: Expected 1 error(s)
qodana 262.9437.196Qodana 262.9437.196 Fail

No diagnostics reported.

Expectation diff
Line 43: Expected 1 error(s)
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Fail
assertions_array_key_exists_key_narrowing.php:30: param $key miss matched with phpdoc type <<'a'|'b'>> [funcParamTypeMissMatch]
assertions_array_key_exists_key_narrowing.php:37: param $values miss matched with phpdoc type <<array<string, int>>> [funcParamTypeMissMatch]
assertions_array_key_exists_key_narrowing.php:40: potentially not safe call in function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString signature of param value [notSafeCall]
assertions_array_key_exists_key_narrowing.php:43: potentially not safe call in function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString signature of param value [notSafeCall]
assertions_array_key_exists_key_narrowing.php:49: param $values miss matched with phpdoc type <<array<string, int>>> [funcParamTypeMissMatch]
Expectation diff
Line 40: Expected silence (quiet probe), got ["potentially not safe call in function Conformance\\Tests\\AssertionsArrayKeyExistsKeyNarrowing\\takesString signature of param value [notSafeCall]"]
steins 0.1.6steins 0.1.6 (2026-08-25 revision c13b4c7) Fail

No diagnostics reported.

Expectation diff
Line 43: Expected 1 error(s)
phpstan-strict 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 Pass
assertions_array_key_exists_key_narrowing.php:43: Parameter #1 $value of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, int|string given.
assertions_array_key_exists_key_narrowing.php:64: Parameter #1 $key of function Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB expects 'a'|'b', string given.
psalm-next 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 Fail
assertions_array_key_exists_key_narrowing.php:40: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, but int|string provided [InvalidScalarArgument]
assertions_array_key_exists_key_narrowing.php:43: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesString expects string, but int|string provided [InvalidScalarArgument]
assertions_array_key_exists_key_narrowing.php:64: Argument 1 of Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing\takesAB expects 'a'|'b', but parent type string provided [ArgumentTypeCoercion]
Expectation diff
Line 40: Expected silence (quiet probe), got ["Argument 1 of Conformance\\Tests\\AssertionsArrayKeyExistsKeyNarrowing\\takesString expects string, but int|string provided [InvalidScalarArgument]"]

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\AssertionsArrayKeyExistsKeyNarrowing;

/**
 * `array_key_exists($key, $map)` narrows `$key` to the keys `$map` is known
 * to have, not only the offset access after the check.
 *
 * A `int|string` key that is known to exist in `array<string, int>` is a
 * `string`. Passing it to a `string` sink is silent after the guard and
 * rejected without it. The same intersection applies to a literal map
 * `['a' => 1, 'b' => 2]`: a `string` key becomes `'a'|'b'`.
 *
 * Source: carthage-software/mago#2199
 *
 * References:
 * - PHP `array_key_exists()`
 * - https://github.com/carthage-software/mago/issues/2199
 */

function takesString(string $value): void
{
}

/**
 * @param 'a'|'b' $key
 */
function takesAB(string $key): void // E?[noise]: some tools do not parse 'a'|'b'
{
}

/**
 * @param array<string, int> $values
 */
function genericStringKeys(int|string $key, array $values): void // E?[noise]: some tools do not parse array<string, int>
{
    if (array_key_exists($key, $values)) {
        takesString($key); // Q: $key is string after the guard
    }

    takesString($key); // E: int|string is not string
}

/**
 * @param array<string, int> $values
 */
function alreadyString(string $key, array $values): void // E?[noise]: some tools do not parse array<string, int>
{
    if (array_key_exists($key, $values)) {
        takesString($key); // V: $key was already string
    }
}

function knownStringKeys(string $key): void
{
    $values = ['a' => 1, 'b' => 2];

    if (array_key_exists($key, $values)) {
        takesAB($key); // Q?: $key is 'a'|'b' after the guard
    }

    takesAB($key); // E?: string is not 'a'|'b'
}

takesAB('a'); // V
takesAB('b'); // V