Context: I'm currently trying to implement a library that allows type-safe decoration of arbitrary data structures via interfaces. The idea is to do something like:
function decorate(
object $input,
string $addedInterface,
callable $methodDefinition
): object {}
To achieve type-safety on the above, I would need to add $addedInterface and $input to the types of the return value, but psalm doesn't seem to consider input type definitions when using complex types.
Given following snippet (https://psalm.dev/r/29133cb42f):
<?php
interface Input {}
interface HasFoo { function foo() : int; }
interface HasBar { function bar() : string; }
/**
* @psalm-template InputType of Input
* @psalm-param InputType $input
* @psalm-return InputType&HasFoo
*/
function decorateWithFoo(Input $input): Input
{
throw new \BadMethodCallException('not implemented for ' . get_class($input));
}
/**
* @psalm-template InputType of Input
* @psalm-param InputType $input
* @psalm-return InputType&HasBar
*/
function decorateWithBar(Input $input): Input
{
throw new \BadMethodCallException('not implemented for ' . get_class($input));
}
/** @param HasFoo&HasBar $input */
function useFooAndBar(object $input): string
{
return 'foo: ' . $input->foo()
. ' bar: ' . $input->bar();
}
function consume(Input $input): void
{
echo useFooAndBar(decorateWithFoo(decorateWithBar($input)));
}
Psalm reports:
Psalm output (using commit 95bc960):
ERROR: InvalidArgument - 36:23 - Argument 1 of useFooAndBar expects HasFoo&HasBar, Input&HasFoo provided
The expectation in this case is that the call to useFooAndBar() is correctly inferred with HasFoo&HasBar as input value.
I found these snippets:
https://psalm.dev/r/29133cb42f
<?php
interface Input {}
interface HasFoo { function foo() : int; }
interface HasBar { function bar() : string; }
/**
* @psalm-template InputType of Input
* @psalm-param InputType $input
* @psalm-return InputType&HasFoo
*/
function decorateWithFoo(Input $input): Input
{
throw new \BadMethodCallException('not implemented for ' . get_class($input));
}
/**
* @psalm-template InputType of Input
* @psalm-param InputType $input
* @psalm-return InputType&HasBar
*/
function decorateWithBar(Input $input): Input
{
throw new \BadMethodCallException('not implemented for ' . get_class($input));
}
/** @param HasFoo&HasBar $input */
function useFooAndBar(object $input): string
{
return 'foo: ' . $input->foo()
. ' bar: ' . $input->bar();
}
function consume(Input $input): void
{
echo useFooAndBar(decorateWithFoo(decorateWithBar($input)));
}
Psalm output (using commit 95bc960):
ERROR: InvalidArgument - 36:23 - Argument 1 of useFooAndBar expects HasFoo&HasBar, Input&HasFoo provided
Use-cases for this would be:
Any kind of checked upcast could become an &HasFoo, solving potentially a gazillion of issues :-)
well that was fast :O
Most helpful comment
well that was fast :O