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
Template generics inside callables #4485
Comments
I found these snippets: https://psalm.dev/r/eafedd80ab<?php
/**
* @template T
*
* @psalm-param callable(T): mixed $func
* @psalm-return Closure(T):T
*/
function tee(callable $func): Closure {
/**
* @template A
* @psalm-param A $a
* @psalm-return A
*/
return function ($a) use ($func) {
$func($a);
return $a;
};
}
/** @psalm-trace $f */
$f = tee(fn (int $a): string => $a . 'foo');
/** @psalm-trace $result */
$result = $f(5);
|
Sorry, this is the correct snippet: |
I found these snippets: https://psalm.dev/r/6c5b012d93<?php
/**
* @template T
* @psalm-param callable(T): mixed $func
* @psalm-return Closure(T): T
*/
function tee(callable $func): Closure {
return function ($a) use ($func) {
$func($a);
return $a;
};
}
/** @psalm-trace $f */
$f = tee(fn (int $a): string => $a . 'foo');
/** @psalm-trace $result */
$result = $f(5);
|
It's not really a bug. You're defining a lower-bound for the param, but not an upper bound. A slightly different version of the code helps Psalm understand what's going on by providing the upper bound for In a language like Hack you could explicitly pass the template params in the call like |
I found these snippets: https://psalm.dev/r/ee47d3ca0f<?php
/**
* @template T
* @psalm-param callable(T):mixed $func
* @psalm-param T $a
* @psalm-return pure-Closure(): T
*/
function tee(callable $func, $a): Closure {
return function () use ($func, $a) {
$func($a);
return $a;
};
}
function foo(int $i) : int {
/** @psalm-trace $f */
$f = tee(fn (int $a): string => $a . 'foo', $i);
return $f();
}
|
@muglug I was playing with templates, and I'm wondering why the same function with a wrapped callable works, and a simple callable not. https://psalm.dev/r/02085aa6f8 I don't really know the internals, and I don't know if it will be possible, maybe it could be an improvement, but why a templated object works and a callable not? If it works with an object, does it means that psalm can infer types correctly even for closures? |
I found these snippets: https://psalm.dev/r/02085aa6f8<?php
/**
* @template A
* @template B
*/
class ClosureContainer {
/** @var callable(A): B */
private $f;
/**
* @param callable(A): B $f
*/
public function __construct(callable $f)
{
$this->f = $f;
}
/**
* @param A $x
* @return B
*/
public function __invoke($x)
{
return ($this->f)($x);
}
}
/**
* @template T
* @psalm-param callable(T): mixed $func
* @psalm-return callable(T): T
*/
function tee(callable $func): callable {
return
/**
* @psalm-param T $value
* @psalm-return T
*/
function ($value) use ($func) {
$func($value);
return $value;
};
}
/**
* Wrapped closure version
*
* @template T
* @param ClosureContainer<T, mixed> $func
* @return ClosureContainer<T, T>
*/
function tee2(ClosureContainer $func): ClosureContainer {
$f =
/**
* @psalm-param T $value
* @psalm-return T
*/
function ($value) use ($func) {
$func($value);
return $value;
};
return new ClosureContainer($f);
}
$f = fn (string $a): int => strlen($a);
/** @psalm-trace $value */
$value = tee($f)('foo');
// Wrapped closure version
/** @psalm-trace $value */
$value = tee2(new ClosureContainer($f))('foo');
|
You're right, it should, and now it does! |
Hi, I'm trying to implements templates, but psalm infer return type as
callable(empty): empty
and I don't understand why, is it a bug?https://psalm.dev/r/eafedd80ab
The text was updated successfully, but these errors were encountered: