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
is_callable is not taken into account #5082
Comments
I found these snippets: https://psalm.dev/r/583d409fde<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():(X|null)|X|null
*/
/**
* @param LazyX $x
* @return X|null
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
|
|
I found these snippets: https://psalm.dev/r/26400469cc<?php
/**
* @psalm-type X = array{string|object,string}
* @psalm-type LazyX = callable():(X|null)|X|null
*/
/**
* @param LazyX $x
* @return X|null
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
|
Hello Weirddan, Thank you for the swift response! I understand why that is not callable but isn't that what Kind regards, Ghlen |
technically, |
I still don't fully understand. If I follow this logic correctly, the key within the array |
nope sorry, possible callable would be |
Thank you. To me, it just looks like a false positive. The example works in simpler cases such as in the second function in this snippet: https://psalm.dev/r/5eece5d7da. |
I found these snippets: https://psalm.dev/r/5eece5d7da<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():(X|null)|X|null
*/
/**
* @param LazyX $x
* @return X|null
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
/**
* @param callable():string|string $x
*/
function read_no_false_positive_example($x): string
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
|
Can you explain why the return type of the callable is Psalm is doing weird things around that: @weirdan I believe we can reopen this, there's something fishy here |
I found these snippets: https://psalm.dev/r/0ae0ab2322<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():X
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
https://psalm.dev/r/54ec32ea26<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():X|null
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
https://psalm.dev/r/efea950ba4<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():X|null|(X|null)
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
https://psalm.dev/r/fefad7a0c4<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():X|null|(string)
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
|
Hey Orklah, Thank you for helping. I am using this callable structure for lazy evaluation. On the other hand, the value is also optional, hence the callable():(X|null)|X|null. My library is customisable through injections, but these injections can be quite heavy, so I am using the lazy evaluation pattern. Sometimes it seems a bit like overkill, but for some injections, it is not. So for the sake of consistency, all injections can be lazily injected. I could have also written it like this: https://psalm.dev/r/df8b0887f4, but as you can see, the result is the same. |
I found these snippets: https://psalm.dev/r/df8b0887f4<?php
/**
* @psalm-type X = array{test: string}|null
* @psalm-type LazyX = callable():X|X
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
if (is_callable($x)) {
return call_user_func($x);
}
return $x;
}
|
What I meant precisely is that I don't see the difference between And when you remove this duplication, it seems to work, because your is_callable check is, in fact, really redundant |
But the type isn't |
ooooooooh I get it now I'll run some tests |
Ok! When entering is_callable, it will remove null (not callable) and it will transform your In practice, you now have a dual type where one side is a legit callable and the other side will fail when trying to interpret it in call_user_func. |
I found these snippets: https://psalm.dev/r/25d05302f0<?php
/**
* @psalm-type X = array{test: string}
* @psalm-type LazyX = callable():(X|null)|X|null
*/
/**
* @param LazyX $x
* @return mixed
*/
function read_lazy_x($x)
{
/** @psalm-trace $x */;
if (is_callable($x)) {
/** @psalm-trace $x */;
return call_user_func($x);
}
return $x;
}
|
Thank you for the detailed explanation! I learned a lot and never knew about psalm-trace, very interesting! From the way I understand it, the callable-array concept seems a bit over-generalized and deeply integrated into the type system. I hope this will get addressed some time, but I understand it is not high on the priority list. In any case, there doesn't seem to be a lot I can do in this case except using |
Found an easy solution. Your array has only 1 element so it's easy to exclude it from being transformed into an array-callable. If the PR is accepted, it should solve your issue |
This was incredibly fast! Thanks orklah :) |
Hello,
Psalm is giving me a hard time in a case resembling this snippet: https://psalm.dev/r/583d409fde.
I am trying to accept a lazy-loaded variable which is possibly wrapped around a closure. If I test it with is_callable, it still complains the type might not be a callable. I don't know what causes this.
Kind regards,
Ghlen
The text was updated successfully, but these errors were encountered: