Skip to content

Cannot combine generic key types when @template is limited to array-key #10871

Closed

Description

@shira-374

Bug report

When I define a generic key type as @template TKey of array-key, PHPStan doesn't seem to want to combine it with other types via @return:

/**
 * @template TOtherKey of array-key
 * @template TOtherValue
 *
 * @param iterable<TOtherKey, TOtherValue> ...$iterables
 * @return self<TKey|TOtherKey, TValue|TOtherValue>
 */
function merge(iterable ...$iterables): self;

Calling this method does not combine TKey with TOtherKey - it just keeps it as TKey:
https://phpstan.org/r/31d3d483-7e0c-4239-8c88-67cf00c807ae

This seems to work fine in Psalm: https://psalm.dev/r/8671dd080b

Using string|int instead of array-key seems to "fix" this:
https://phpstan.org/r/edf51290-fe27-4024-b07d-3e7c327e5825

And so does removing the type entirely:
https://phpstan.org/r/ff4f41fa-691c-4fe4-8837-ef0ec2179c04

Code snippet that reproduces the problem

https://phpstan.org/r/31d3d483-7e0c-4239-8c88-67cf00c807ae

Expected output

-Dumped type: Map<string, bool|int>
+Dumped type: Map<int|string, bool|int>
-Dumped type: Map<string, bool|int|stdClass>
+Dumped type: Map<int|string, bool|int|stdClass>

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

No response

Activity

  1. added this to the Generics milestone on May 30, 2024
  2. SerethiX commented on Jul 23, 2024

    @SerethiX

    The type checking also seems to stop, once array-key is used.

    <?php declare(strict_types = 1);
    
    /** 
    -  * @template T of array-key
    +  * @template T of int|string
      * @param non-empty-array<T, mixed> $bar
      * @return T
      */
    function foo(array $bar): int|string {
        \PHPStan\dumpType($bar);
    	
    	int_only(array_key_first($bar));
    
    	return array_key_first($bar);
    }
    
    function int_only(int $param): void {
    }
    
    if (is_string(foo(['foo' => 'bar']))) echo 'is_string';
    if (is_int(foo(['bar']))) echo 'is_int';

    This example failes to detect the possible type error, while int|string correctly detects it: https://phpstan.org/r/8213d7d7-f36c-4e34-8fbf-64976a1dcde0

  3. phpstan-bot commented on Feb 21, 2025

    @phpstan-bot
    Contributor

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

    @@ @@
     19: Call to function is_string() with 'foo' will always evaluate to true.
     20: Call to function is_int() with 0 will always evaluate to true.
     
    -PHP 7.3 – 7.4 (4 errors)
    +PHP 7.2 – 7.4 (4 errors)
     ==========
     
      8: Function foo() uses native union types but they're supported only on PHP 8.0 and later.
      9: Dumped type: non-empty-array<T of (int|string) (function foo(), argument), mixed>
    -19: Call to function is_string() with 'foo' will always evaluate to true.
    -20: Call to function is_int() with 0 will always evaluate to true.
    -
    -PHP 7.2 (6 errors)
    -==========
    -
    - 8: Function foo() uses native union types but they're supported only on PHP 8.0 and later.
    - 9: Dumped type: non-empty-array<T of (int|string) (function foo(), argument), mixed>
    -11: Function array_key_first not found.
    -13: Function array_key_first not found.
     19: Call to function is_string() with 'foo' will always evaluate to true.
     20: Call to function is_int() with 0 will always evaluate to true.
    Full report

    PHP 8.0 – 8.3 (3 errors)

    Line Error
    9 `Dumped type: non-empty-array<T of (int
    19 Call to function is_string() with 'foo' will always evaluate to true.
    20 Call to function is_int() with 0 will always evaluate to true.

    PHP 7.2 – 7.4 (4 errors)

    Line Error
    8 Function foo() uses native union types but they're supported only on PHP 8.0 and later.
    9 `Dumped type: non-empty-array<T of (int
    19 Call to function is_string() with 'foo' will always evaluate to true.
    20 Call to function is_int() with 0 will always evaluate to true.
  4. phpstan-bot commented on Feb 21, 2025

    @phpstan-bot
    Contributor

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

    @@ @@
     19: Call to function is_string() with 'foo' will always evaluate to true.
     20: Call to function is_int() with 0 will always evaluate to true.
     
    -PHP 7.3 – 7.4 (5 errors)
    +PHP 7.2 – 7.4 (5 errors)
     ==========
     
      8: Function foo() uses native union types but they're supported only on PHP 8.0 and later.
      9: Dumped type: non-empty-array<T of int|string (function foo(), argument), mixed>
     11: Parameter #1 $param of function int_only expects int, T of int|string given.
    -19: Call to function is_string() with 'foo' will always evaluate to true.
    -20: Call to function is_int() with 0 will always evaluate to true.
    -
    -PHP 7.2 (6 errors)
    -==========
    -
    - 8: Function foo() uses native union types but they're supported only on PHP 8.0 and later.
    - 9: Dumped type: non-empty-array<T of int|string (function foo(), argument), mixed>
    -11: Function array_key_first not found.
    -13: Function array_key_first not found.
     19: Call to function is_string() with 'foo' will always evaluate to true.
     20: Call to function is_int() with 0 will always evaluate to true.
    Full report

    PHP 8.0 – 8.3 (4 errors)

    Line Error
    9 `Dumped type: non-empty-array<T of int
    11 `Parameter #1 $param of function int_only expects int, T of int
    19 Call to function is_string() with 'foo' will always evaluate to true.
    20 Call to function is_int() with 0 will always evaluate to true.

    PHP 7.2 – 7.4 (5 errors)

    Line Error
    8 Function foo() uses native union types but they're supported only on PHP 8.0 and later.
    9 `Dumped type: non-empty-array<T of int
    11 `Parameter #1 $param of function int_only expects int, T of int
    19 Call to function is_string() with 'foo' will always evaluate to true.
    20 Call to function is_int() with 0 will always evaluate to true.
  5. phpstan-bot commented on Sep 9, 2026

    @phpstan-bot
    Contributor

    Closed via merging phpstan/phpstan-src#6376

  6. github-actions commented on Oct 11, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

  7. locked as resolved and limited conversation to collaborators on Oct 11, 2026
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