← All results

Nested ($key is Model ? … : ($key is Arrayable ? …)) must narrow $key in the inner condition to the else of the outer one

Group: Cross-analyzer regressions · Category: regression · File: regressions_nested_param_conditional.php

find(?User): the outer is Model takes the User arm (User|null). The inner is Arrayable only sees what remains (null), which is not Arrayable, so static (the collection) is not in the result. PHPStan 2.2.15 resolved the inner condition against the whole ?User, User matched Arrayable, and the return became Collection<User>|User|null. Source: phpstan/phpstan#15296, phpstan/phpstan-src#6586 (2.2.16). References: - phpstan#15296 - phpstan-src#6586

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.10Phan 6.0.10 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 Enforced
regressions_nested_param_conditional.php:75: Argument 1 ($user) is $collection->find([1]) of type \Conformance\Tests\RegressionsNestedParamConditional\Collection|\Conformance\Tests\RegressionsNestedParamConditional\Collection<\Conformance\Tests\RegressionsNestedParamConditional\User> but \Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull() takes ?\Conformance\Tests\RegressionsNestedParamConditional\User defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/regressions_nested_param_conditional.php:50 [PhanTypeMismatchArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
phpstan 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Unrecognized
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns Conformance\Tests\RegressionsNestedParamConditional\Collection<TModel of Conformance\Tests\RegressionsNestedParamConditional\Model> so it can be removed from the return type. [reported-from-level=4]
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns TModel of Conformance\Tests\RegressionsNestedParamConditional\Model so it can be removed from the return type. [reported-from-level=4]
regressions_nested_param_conditional.php:75: Parameter #1 $user of function Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> given. [reported-from-level=5]
With strict-rules
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns Conformance\Tests\RegressionsNestedParamConditional\Collection<TModel of Conformance\Tests\RegressionsNestedParamConditional\Model> so it can be removed from the return type.
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns TModel of Conformance\Tests\RegressionsNestedParamConditional\Model so it can be removed from the return type.
regressions_nested_param_conditional.php:75: Parameter #1 $user of function Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> given.
  • Recognition: spelling not resolved — reported on declaration line(s) 44
  • Enforcement: 3/3 of the expected violations reported — incidental, since the spelling was not resolved
psalm 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c
next: 7.0.0-rc2
Enforced (pzoom≠)
regressions_nested_param_conditional.php:75: Argument 1 of Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, but Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> provided [InvalidArgument]
pzoom (Psalm port)
regressions_nested_param_conditional.php:74: Argument 1 of takesCollection expects Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>, but parent type Conformance\Tests\RegressionsNestedParamConditional\Collection provided [ArgumentTypeCoercion]
regressions_nested_param_conditional.php:75: Argument 1 of takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, but Conformance\Tests\RegressionsNestedParamConditional\Collection provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
mago 1.57.0mago 1.57.0 Partly enforced (2/3)
regressions_nested_param_conditional.php:66: Possible argument type mismatch for argument #1 of `Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull`: expected `Conformance\Tests\RegressionsNestedParamConditional\User|null`, but possibly received `Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>|Conformance\Tests\RegressionsNestedParamConditional\User|null`. [possibly-invalid-argument]
regressions_nested_param_conditional.php:75: Invalid argument type for argument #1 of `Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull`: expected `Conformance\Tests\RegressionsNestedParamConditional\User|null`, but found `Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>`. [invalid-argument]
  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
Expectation diff
Line 66: Expected silence (quiet probe), got ["Possible argument type mismatch for argument #1 of `Conformance\\Tests\\RegressionsNestedParamConditional\\takesUserOrNull`: expected `Conformance\\Tests\\RegressionsNestedParamConditional\\User|null`, but possibly received `Conformance\\Tests\\RegressionsNestedParamConditional\\Collection<Conformance\\Tests\\RegressionsNestedParamConditional\\User>|Conformance\\Tests\\RegressionsNestedParamConditional\\User|null`. [possibly-invalid-argument]"]
mir 0.87.0mir 0.87.0 Partly enforced (1/3)
regressions_nested_param_conditional.php:66: PossiblyInvalidArgument: Argument $user of takesUserOrNull() expects 'Conformance\Tests\RegressionsNestedParamConditional\User|null', possibly different type 'Conformance\Tests\RegressionsNestedParamConditional\User|null|static()' provided [MIR0105]
regressions_nested_param_conditional.php:74: PossiblyNullArgument: Argument $collection of takesCollection() might be null [MIR0104]
regressions_nested_param_conditional.php:74: InvalidArgument: Argument $collection of takesCollection() expects 'Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>', got 'Conformance\Tests\RegressionsNestedParamConditional\User|null|static()' [MIR0201]
regressions_nested_param_conditional.php:75: PossiblyInvalidArgument: Argument $user of takesUserOrNull() expects 'Conformance\Tests\RegressionsNestedParamConditional\User|null', possibly different type 'Conformance\Tests\RegressionsNestedParamConditional\User|null|static()' provided [MIR0105]
  • Recognition: spelling resolved
  • Enforcement: 1/3 of the expected violations reported
Expectation diff
Line 66: Expected silence (quiet probe), got ["PossiblyInvalidArgument: Argument $user of takesUserOrNull() expects 'Conformance\\Tests\\RegressionsNestedParamConditional\\User|null', possibly different type 'Conformance\\Tests\\RegressionsNestedParamConditional\\User|null|static()' provided [MIR0105]"]
phpantom 0.11.1phpantom_lsp 0.11.1 Partly enforced (2/3)
regressions_nested_param_conditional.php:66: Argument 1 ($user) expects ?Conformance\Tests\RegressionsNestedParamConditional\User, got Conformance\Tests\RegressionsNestedParamConditional\User|Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>|null (Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> does not satisfy ?Conformance\Tests\RegressionsNestedParamConditional\User) [type_mismatch_argument]
regressions_nested_param_conditional.php:75: Argument 1 ($user) expects ?Conformance\Tests\RegressionsNestedParamConditional\User, got Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> [type_mismatch_argument]
  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
Expectation diff
Line 66: Expected silence (quiet probe), got ["Argument 1 ($user) expects ?Conformance\\Tests\\RegressionsNestedParamConditional\\User, got Conformance\\Tests\\RegressionsNestedParamConditional\\User|Conformance\\Tests\\RegressionsNestedParamConditional\\Collection<Conformance\\Tests\\RegressionsNestedParamConditional\\User>|null (Conformance\\Tests\\RegressionsNestedParamConditional\\Collection<Conformance\\Tests\\RegressionsNestedParamConditional\\User> does not satisfy ?Conformance\\Tests\\RegressionsNestedParamConditional\\User) [type_mismatch_argument]"]
intelephense 1.18.5intelephense 1.18.5 Partly enforced (2/3)
regressions_nested_param_conditional.php:66: Expected type 'null|Conformance\Tests\RegressionsNestedParamConditional\User'. Found 'Conformance\Tests\RegressionsNestedParamConditional\User|null|Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>'. [P1006]
regressions_nested_param_conditional.php:75: Expected type 'null|Conformance\Tests\RegressionsNestedParamConditional\User'. Found 'Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User>'. [P1006]
  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
Expectation diff
Line 66: Expected silence (quiet probe), got ["Expected type 'null|Conformance\\Tests\\RegressionsNestedParamConditional\\User'. Found 'Conformance\\Tests\\RegressionsNestedParamConditional\\User|null|Conformance\\Tests\\RegressionsNestedParamConditional\\Collection<Conformance\\Tests\\RegressionsNestedParamConditional\\User>'. [P1006]"]
phpactor 2026.10.07.0Phpactor 2026.10.07.0 Partly enforced (2/3)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
phpy 1.0.19409phpy 1.0.19409 Enforced
regressions_nested_param_conditional.php:75: Argument '1' passed to Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull() is expected to be of type Conformance\Tests\RegressionsNestedParamConditional\User|null, Conformance\Tests\RegressionsNestedParamConditional\Collection given
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported
qodana 262.10968.76PhpStorm 262.10968.76 Partly enforced (2/3)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Unrecognized
regressions_nested_param_conditional.php:42: Class or interface named \Conformance\Tests\RegressionsNestedParamConditional\$key does not exist [undefinedClass]
regressions_nested_param_conditional.php:57: param $collection miss matched with phpdoc type <<Collection<User>>> [funcParamTypeMissMatch]
regressions_nested_param_conditional.php:64: param $collection miss matched with phpdoc type <<Collection<User>>> [funcParamTypeMissMatch]
regressions_nested_param_conditional.php:72: param $collection miss matched with phpdoc type <<Collection<User>>> [funcParamTypeMissMatch]
  • Recognition: spelling not resolved — reported on declaration line(s) 42
  • Enforcement: 2/3 of the expected violations reported — incidental, since the spelling was not resolved
steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Partly enforced (2/3)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/3 of the expected violations reported
phpstan-strict 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Unrecognized
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns Conformance\Tests\RegressionsNestedParamConditional\Collection<TModel of Conformance\Tests\RegressionsNestedParamConditional\Model> so it can be removed from the return type.
regressions_nested_param_conditional.php:44: Method Conformance\Tests\RegressionsNestedParamConditional\Collection::find() never returns TModel of Conformance\Tests\RegressionsNestedParamConditional\Model so it can be removed from the return type.
regressions_nested_param_conditional.php:75: Parameter #1 $user of function Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> given.
  • Recognition: spelling not resolved — reported on declaration line(s) 44
  • Enforcement: 3/3 of the expected violations reported — incidental, since the spelling was not resolved
psalm-next 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 Enforced
regressions_nested_param_conditional.php:75: Argument 1 of Conformance\Tests\RegressionsNestedParamConditional\takesUserOrNull expects Conformance\Tests\RegressionsNestedParamConditional\User|null, but Conformance\Tests\RegressionsNestedParamConditional\Collection<Conformance\Tests\RegressionsNestedParamConditional\User> provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 3/3 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\RegressionsNestedParamConditional;

/**
 * Nested `($key is Model ? … : ($key is Arrayable ? …))` must narrow `$key`
 * in the inner condition to the else of the outer one.
 *
 * `find(?User)`: the outer `is Model` takes the `User` arm (`User|null`). The
 * inner `is Arrayable` only sees what remains (`null`), which is not
 * `Arrayable`, so `static` (the collection) is not in the result. PHPStan
 * 2.2.15 resolved the inner condition against the whole `?User`, `User`
 * matched `Arrayable`, and the return became `Collection<User>|User|null`.
 *
 * Source: phpstan/phpstan#15296, phpstan/phpstan-src#6586 (2.2.16).
 *
 * References:
 * - https://github.com/phpstan/phpstan/issues/15296
 * - https://github.com/phpstan/phpstan-src/pull/6586
 */

interface Arrayable
{
}

class Model implements Arrayable
{
}

final class User extends Model
{
}

/**
 * @template TModel of Model
 */
final class Collection
{
    /**
     * @return ($key is Model ? TModel|null : ($key is (Arrayable|array<mixed>) ? static : TModel|null))
     */
    public function find(mixed $key): mixed // T: ($key is Model ? TModel|null : ($key is (Arrayable|array<mixed>) ? static : TModel|null)) // E?[noise]: the body is not under test
    {
        return null;
    }
}

function takesUserOrNull(?User $user): void
{
}

/**
 * @param Collection<User> $collection
 */
function takesCollection(Collection $collection): void // E?[noise]: some tools flag the phpdoc/native generic mismatch
{
}

/**
 * @param Collection<User> $collection
 */
function fromNullableUser(Collection $collection, ?User $user): void // E?[noise]: some tools flag the phpdoc/native generic mismatch
{
    takesUserOrNull($collection->find($user)); // Q: User|null — a Collection arm here is the 2.2.15 leak
}

/**
 * @param Collection<User> $collection
 */
function fromArrayKey(Collection $collection): void // E?[noise]: some tools flag the phpdoc/native generic mismatch
{
    takesCollection($collection->find([1])); // Q?: array key takes the inner Arrayable|array branch
    takesUserOrNull($collection->find([1])); // E?: that branch is Collection, not ?User
}