Open ollieread opened 1 year ago
Please try to reproduce on psalm.dev
It's hard to think about why it happens without knowing for example, the hierarchy between Func and BiFunc
Please try to reproduce on psalm.dev
It's hard to think about why it happens without knowing for example, the hierarchy between Func and BiFunc
I tried, but I can't recreate it. Func
and BiFunc
have no hierarchy. They are Func<mixed, mixed>
and BiFunc<mixed, mixed, mixed>
.
If I change the whole if
statement to the following:
if ($mapper instanceof BiFunc) {
$value = $mapper->apply($key, $value);
} else if ($mapper instanceof Func) {
$value = $mapper->apply($value);
}
It still complains. In fact, the only way it doesn't, is if I make sure the second call ISN'T a BiFunc
, which is pretty ridiculous,
if ($mapper instanceof BiFunc) {
$value = $mapper->apply($key, $value);
} else if ($mapper instanceof Func && ! ($mapper instanceof BiFunc)) {
$value = $mapper->apply($value);
}
The two interfaces Func
and BiFunc
can be seen here: https://gist.github.com/ollieread/97f46aa77d85c5006f51244845a4d100
Somewhat of a reproducer: https://psalm.dev/r/07b8322f27
What (I think what) happens here: Psalm thinks $mapper
might implement Func
and BiFunc
at the same time. That is why psalm can not pin down the type of $mapper->apply
just with the one instanceof
-check, bcs psalm thinks it still might implement both function contracts.
On the other hand it should be possible to know that in the else case $mapper
can not be of instance BiFunc
. Possibly some bug with type reconciliation and template params I'd guess...
I found these snippets:
Somewhat of a reproducer: https://psalm.dev/r/07b8322f27
What (I think what) happens here: Psalm thinks
$mapper
might implementFunc
andBiFunc
at the same time. That is why psalm can not pin down the type of$mapper->apply
just with the oneinstanceof
-check, bcs psalm thinks it still might implement both function contracts.On the other hand it should be possible to know that in the else case
$mapper
can not be of instanceBiFunc
. Possibly some bug with type reconciliation and template params I'd guess...
I think you might be right, in terms of what is happening, because getting errors that it is simultaneously both implementations is...weird.
I found these snippets:
It is perfectly ok for a class to implement both interfaces. Actually tested that just moments ago. So it is the right thing that psalm takes that into account and reports to us if we forgot that...
But having a line like if( ! $mapper instanceof BiFunc)
does nothing (type-wise) while if( ! $mapper instanceof Func)
seem to work correctly...
If we try to display $mapper type: https://psalm.dev/r/882d7306e7
It outputs this (I cleaned up the output for easier reading):
$mapper: BiFunc|(BiFunc&Func<V, R>)|(BiFunc<K, V, R>&Func<V, R>)|(Func<V, R>&Func<V, R>)
That seems a little odd to me, BiFunc seems to have lost its templates in some cases and we have an intersection with Func in both sides.
So I guess we have an issue in here. But once that is fixed, we should have something like
BiFunc<K, V, R>|Func<V, R>|(BiFunc<K, V, R>&Func<V, R>)
Checking if we have an instance of BiFunc does not allow to exclude that the variable is also a Func and I don't think Psalm allows to document that an interface is exclusive
I found these snippets:
Thanks @orklah and @ygottschalk, this definitely seems to be a weird issue, though I have resolved my particular problem. Turns out that when I wrote the original Stream
interface, I had intended to call $mapper->apply($value, $key)
to make use of the shared method name, but somehow forgot that when I came to implement it.
I have encountered a bunch of other weird issues that I have suppressed for now, so I'll go through and add any more issues if I encounter them.
I've got Psalm 5.9.0 running on a project with error level 1, but I'm getting errors about things that aren't issues.
I have the following method:
I'm getting the following errors for the following lines.
This produces the following error, complaining that the type of
$mapper
isFunc
, despite it being wrapped in anif
statement that makes sure it isn't.Amusingly, I'm also getting:
For the following line that exists in the
else
branch, and will literally never be touched if$mapper
is in fact an instance ofBiFunc
: