New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
closure parameter are analysed without the function context ( generics ) when used with userland functions. #5820
Comments
I found these snippets: https://psalm.dev/r/3e6b2451c3<?php
/**
* @template T
* @template Ts
* @param list<T> $a
* @param callable(T): Ts $c
* @return list<Ts>
*/
function map(array $a, callable $c): array {
$res = [];
foreach($a as $v) { $res[] = $c($v); }
return $res;
}
/** @var list<array{a: string, b: int}> $input */
array_map(function(array $in): string {
return $in['a'];
}, $input);
map($input, function(array $in): string {
return $in['a'];
});
https://psalm.dev/r/15e391e947<?php
/**
* @template Tk
* @template Tv
* @template Ts
* @param array<Tk, Tv> $a
* @param callable(Tv): Ts $c
* @return array<Tk, Ts>
*/
function map(array $a, callable $c): array {
$res = [];
foreach($a as $k => $v) { $res[$k] = $c($v); }
return $res;
}
/** @var list<array{a: string, b: int}> $input */
array_map(function(array $in): string {
return $in['a'];
}, $input);
map($input,
/**
* @param array{a: string, b: int} $in
*/
function(array $in): string {
return $in['a'];
}
);
|
I found these snippets: https://psalm.dev/r/3ec04d7b36<?php
/**
* @template Tk
* @template Tv
* @template Ts
* @param array<Tk, Tv> $a
* @param callable(Tv): Ts $c
* @return array<Tk, Ts>
*/
function map(array $a, callable $c): array {
$res = [];
foreach($a as $k => $v) { $res[$k] = $c($v); }
return $res;
}
/** @var list<array{a: string, b: int}> $input */
array_map(function(array $in): string {
return $in['a'];
}, $input);
map($input, function(array $in): string {
return $in['a'];
});
|
Psalm has some special behaviour for Imagine that the params are flipped: https://psalm.dev/r/5db81b6045 Now Psalm must know to scan the second argument first to get the param type so it can then use the correct types on the first. This is infeasible (and Hack does not do this either AFAIK). |
I found these snippets: https://psalm.dev/r/5db81b6045<?php
/**
* @template T
* @template Ts
* @param list<T> $a
* @param callable(T): Ts $c
* @return list<Ts>
*/
function map(callable $c, array $a): array {
$res = [];
foreach($a as $v) { $res[] = $c($v); }
return $res;
}
/** @var list<array{a: string, b: int}> $input */
array_map(function(array $in): string {
return $in['a'];
}, $input);
map(function(array $in): string {
return $in['a'];
}, $input);
|
can this special behavior be replicated for other functions using a plugin? i would like to do so in |
I stand very corrected! |
No, but once I figure out how Hack is doing it this will work in Psalm. Given this code: function maptwice<T1, T2, T3>((function(T2):T3) $c2, (function(T1):T2) $c1, vec<T1> $a): vec<T3> {
$res = vec[];
foreach($a as $v) { $res[] = $c2($c1($v)); }
return $res;
}
function foo(vec<shape('a' => string, 'b' => int)> $input): vec<int> {
return maptwice(
($in) ==> $in + 3,
($in) ==> $in['b'],
$input
);
} The process could go like this:
|
Equivalent PHP: https://psalm.dev/r/2767b16320 |
I found these snippets: https://psalm.dev/r/2767b16320<?php
/**
* @template T1
* @template T2
* @template T3
* @param Closure(T2):T3 $c2
* @param Closure(T1):T2 $c1
* @param list<T1> $a
* @return list<T3>
*/
function maptwice(Closure $c2, Closure $c1, array $a): array {
$res = [];
foreach($a as $v) { $res[] = $c2($c1($v)); }
return $res;
}
/**
* @param list<array{a: string, b: int}> $input
* @return array<int>
*/
function foo(array $input): array {
return maptwice(
fn($in) => $in + 3,
fn($in) => $in['b'],
$input
);
}
|
No, the example provided by @muglug is still failing ( https://psalm.dev/r/90597ecafa ), so i think this problem still persists |
I found these snippets: https://psalm.dev/r/90597ecafa<?php
/**
* @template T1
* @template T2
* @template T3
* @param Closure(T2):T3 $c2
* @param Closure(T1):T2 $c1
* @param list<T1> $a
* @return list<T3>
*/
function maptwice(Closure $c2, Closure $c1, array $a): array {
$res = [];
foreach($a as $v) { $res[] = $c2($c1($v)); }
return $res;
}
/**
* @param list<array{a: string, b: int}> $input
* @return array<int>
*/
function foo(array $input): array {
return maptwice(
static fn($in) => $in + 3,
static fn($in) => $in['b'],
$input
);
}
|
Great! Thanks for the diagnosis! |
I disagree, this problem currently results in the need of writing useless docblocks from the end user perspective to explain things to psalm. As shown above, this is already supported by other type checkers such as hh ( hack ). |
Your first example has no issues now.
Currently I work at new plugin hook here #7471.
With new hook we will able to write plugin for function with weird params ordering. We'll can implement behavior like Look at test: |
I found these snippets: https://psalm.dev/r/3e6b2451c3<?php
/**
* @template T
* @template Ts
* @param list<T> $a
* @param callable(T): Ts $c
* @return list<Ts>
*/
function map(array $a, callable $c): array {
$res = [];
foreach($a as $v) { $res[] = $c($v); }
return $res;
}
/** @var list<array{a: string, b: int}> $input */
array_map(function(array $in): string {
return $in['a'];
}, $input);
map($input, function(array $in): string {
return $in['a'];
});
|
The following example shows the issue: https://psalm.dev/r/3e6b2451c3
both map and array_map act the same way, however, psalm complains about missing
@param
type declaration for the closure with user land implementations, but not builtin array_map.This results in useless type declaration being added ( see https://psalm.dev/r/15e391e947 ) to satisfy psalm.
The text was updated successfully, but these errors were encountered: