← All results

Closure parameter types from array_map / array_filter

Group: Callables · Category: language · File: callables_closure_param_from_array_callback.php

An untyped closure parameter takes its type from the array the callback is applied to: array_map(fn ($x) => …, $listOfUsers) sees each $x as a User, so a method User lacks is an error inside the callback. The stubs for array_map and array_filter carry the callable signature (callable(T): U, callable(T): bool); a tool that only reads the native callable declaration leaves $x as mixed and stays silent. References: - PHPStan / Psalm array_map and array_filter stubs

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 Pass
callables_closure_param_from_array_callback.php:34: Call to undeclared method \Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing [PhanUndeclaredMethod]
callables_closure_param_from_array_callback.php:41: Call to undeclared method \Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing [PhanUndeclaredMethod]
callables_closure_param_from_array_callback.php:48: Call to undeclared method \Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing [PhanUndeclaredMethod]
phpstan 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Pass reported Lv.2+
callables_closure_param_from_array_callback.php:34: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing(). [reported-from-level=2]
callables_closure_param_from_array_callback.php:41: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing(). [reported-from-level=2]
callables_closure_param_from_array_callback.php:48: Anonymous function should return string but returns mixed. [reported-from-level=10]
callables_closure_param_from_array_callback.php:48: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing(). [reported-from-level=2]
With strict-rules
callables_closure_param_from_array_callback.php:34: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
callables_closure_param_from_array_callback.php:41: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
callables_closure_param_from_array_callback.php:48: Anonymous function should return string but returns mixed.
callables_closure_param_from_array_callback.php:48: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
psalm 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c
next: 7.0.0-rc2
Pass
callables_closure_param_from_array_callback.php:34: Closure does not have a return type, expecting mixed [MissingClosureReturnType]
callables_closure_param_from_array_callback.php:34: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]
callables_closure_param_from_array_callback.php:41: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]
callables_closure_param_from_array_callback.php:48: Could not infer a return type [MixedReturnStatement]
callables_closure_param_from_array_callback.php:48: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]
mago 1.57.0mago 1.57.0 Pass
callables_closure_param_from_array_callback.php:34: Method `missing` does not exist on type `Conformance\Tests\CallablesClosureParamFromArrayCallback\User`. [non-existent-method]
callables_closure_param_from_array_callback.php:41: Method `missing` does not exist on type `Conformance\Tests\CallablesClosureParamFromArrayCallback\User`. [non-existent-method]
callables_closure_param_from_array_callback.php:48: Method `missing` does not exist on type `Conformance\Tests\CallablesClosureParamFromArrayCallback\User`. [non-existent-method]
callables_closure_param_from_array_callback.php:48: Could not infer a precise return type for function `{closure:tests/callables_closure_param_from_array_callback.php:47:26}`. Saw type `mixed`. [mixed-return-statement]
mir 0.87.0mir 0.87.0 Pass
callables_closure_param_from_array_callback.php:34: UndefinedMethod: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing() does not exist [MIR0004]
callables_closure_param_from_array_callback.php:41: UndefinedMethod: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing() does not exist [MIR0004]
callables_closure_param_from_array_callback.php:48: UndefinedMethod: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing() does not exist [MIR0004]
callables_closure_param_from_array_callback.php:48: MixedReturnStatement: Cannot return a mixed type from function with declared return type 'string' [MIR1212]
phpantom 0.11.1phpantom_lsp 0.11.1 Pass
callables_closure_param_from_array_callback.php:34: Method 'missing' not found on class 'Conformance\Tests\CallablesClosureParamFromArrayCallback\User' [unknown_member]
callables_closure_param_from_array_callback.php:41: Method 'missing' not found on class 'Conformance\Tests\CallablesClosureParamFromArrayCallback\User' [unknown_member]
callables_closure_param_from_array_callback.php:48: Method 'missing' not found on class 'Conformance\Tests\CallablesClosureParamFromArrayCallback\User' [unknown_member]
intelephense 1.18.5intelephense 1.18.5 Pass
callables_closure_param_from_array_callback.php:34: Undefined method 'missing'. [P1013]
callables_closure_param_from_array_callback.php:41: Undefined method 'missing'. [P1013]
callables_closure_param_from_array_callback.php:48: Expected type 'string'. Found 'mixed'. [P1006]
callables_closure_param_from_array_callback.php:48: Undefined method 'missing'. [P1013]
phpactor 2026.10.07.0Phpactor 2026.10.07.0 Fail

No diagnostics reported.

Expectation diff
Line 34: Expected 1 error(s)
Line 41: Expected 1 error(s)
Line 48: Expected 1 error(s)
phpy 1.0.19409phpy 1.0.19409 Fail

No diagnostics reported.

Expectation diff
Line 34: Expected 1 error(s)
Line 41: Expected 1 error(s)
Line 48: Expected 1 error(s)
qodana 262.10968.76PhpStorm 262.10968.76 Fail

No diagnostics reported.

Expectation diff
Line 34: Expected 1 error(s)
Line 41: Expected 1 error(s)
Line 48: 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
callables_closure_param_from_array_callback.php:31: param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]
callables_closure_param_from_array_callback.php:38: param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]
callables_closure_param_from_array_callback.php:45: param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]
callables_closure_param_from_array_callback.php:48: Call to undefined method {\Conformance\Tests\CallablesClosureParamFromArrayCallback\User|mixed}->missing() [undefinedMethod]
Expectation diff
Line 34: Expected 1 error(s)
Line 41: Expected 1 error(s)
Line 31: Unexpected errors ["param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]"]
Line 38: Unexpected errors ["param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]"]
Line 45: Unexpected errors ["param $users miss matched with phpdoc type <<list<User>>> [funcParamTypeMissMatch]"]
steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Fail

No diagnostics reported.

Expectation diff
Line 34: Expected 1 error(s)
Line 41: Expected 1 error(s)
Line 48: Expected 1 error(s)
phpstan-strict 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Pass
callables_closure_param_from_array_callback.php:34: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
callables_closure_param_from_array_callback.php:41: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
callables_closure_param_from_array_callback.php:48: Anonymous function should return string but returns mixed.
callables_closure_param_from_array_callback.php:48: Call to an undefined method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing().
psalm-next 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 Pass
callables_closure_param_from_array_callback.php:34: Closure does not have a return type, expecting mixed [MissingClosureReturnType]
callables_closure_param_from_array_callback.php:34: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]
callables_closure_param_from_array_callback.php:41: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]
callables_closure_param_from_array_callback.php:48: Could not infer a return type [MixedReturnStatement]
callables_closure_param_from_array_callback.php:48: Method Conformance\Tests\CallablesClosureParamFromArrayCallback\User::missing does not exist [UndefinedMethod]

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\CallablesClosureParamFromArrayCallback;

/**
 * Closure parameter types from array_map / array_filter.
 *
 * An untyped closure parameter takes its type from the array the callback is
 * applied to: `array_map(fn ($x) => …, $listOfUsers)` sees each `$x` as a
 * User, so a method User lacks is an error inside the callback.
 *
 * The stubs for `array_map` and `array_filter` carry the callable signature
 * (`callable(T): U`, `callable(T): bool`); a tool that only reads the native
 * `callable` declaration leaves `$x` as mixed and stays silent.
 *
 * References:
 * - PHPStan / Psalm array_map and array_filter stubs
 */

final class User
{
    public function name(): string
    {
        return 'user';
    }
}

/** @param list<User> $users */
function mapsUsers(array $users): void
{
    echo implode(',', array_map(fn ($user) => $user->name(), $users)); // V
    echo count(array_map(fn ($user) => $user->missing(), $users)); // E: User has no missing()
}

/** @param list<User> $users */
function filtersUsers(array $users): void
{
    echo count(array_filter($users, fn ($user) => $user->name() !== '')); // V
    echo count(array_filter($users, fn ($user) => $user->missing() !== '')); // E: User has no missing()
}

/** @param list<User> $users */
function mapsUsersWithClosure(array $users): void
{
    echo count(array_map(function ($user): string {
        return $user->missing(); // E: User has no missing()
    }, $users));
}