Is something wrong with templates in this example: https://psalm.dev/r/113297eeaf?
Why Psalm doesn't agree that Pet<Cat|Dog>
and Cat|Dog
are the same types here?
Can this be solved somehow (besides baseline or suppression)?
<?php
/**
* @template T
*/
abstract class Animal
{
}
/**
* @template T of Cat|Dog
* @extends Animal<T>
*/
abstract class Pet extends Animal
{
abstract public function say(): string;
}
/**
* @extends Pet<Cat>
*/
class Cat extends Pet
{
public function say(): string
{
return 'meow';
}
}
/**
* @extends Pet<Dog>
*/
class Dog extends Pet
{
public function say(): string
{
return 'woof';
}
}
function someFunction(Pet $pet): void
{
echo $pet->say();
}
$pet = rand(0,1) === 0
? new Dog()
: new Cat()
;
someFunction($pet);
ERROR: InvalidArgument - 52:14 - Argument 1 of someFunction expects Pet<Cat|Dog>, but Cat|Dog provided
Using generic typing is not useful in your example. extends
is sufficient.
A Cat
is a Pet
. A Dog
is a Pet
.
Generic typing is useful to ensure a given function, method, or class returns (and/or accepts) the wanted type.
For example a PetHouse
can host either a Cat
or a Dog
, but a PetHouse<Cat>
can only host (and return) a Cat
.
<?php
/**
* An animal
*/
abstract class Animal
{
}
/**
* @extends Animal
*/
abstract class Pet extends Animal
{
abstract public function say(): string;
}
/**
* @extends Pet
*/
class Cat extends Pet
{
public function say(): string
{
return 'nya';
}
public function meow(): string
{
return $this->say();
}
}
/**
* @extends Pet
*/
class Dog extends Pet
{
public function say(): string
{
return 'woof';
}
public function bark(): string
{
return $this->say();
}
}
/**
* A Pet house
*
* @template T of Pet
*/
class PetHouse {
/**
* @param T $pet the pet
*/
public function __construct(
protected Pet $pet
) {
}
/**
* @return T
*/
public function getPet(): Pet {
return $this->pet
}
}
function someFunction(Pet $pet): void
{
echo $pet->say();
}
/**
* @param PetHouse<Cat>
*/
function someOtherFunction(PetHouse $house): void
{
echo $house->getPet()->meow();
}
$pet = rand(0,1) === 0
? new Dog()
: new Cat()
;
someFunction($pet);
/** @var PetHouse<Cat> $catHouse */
$catHouse = new PetHouse(new Cat());
someOtherFunction($catHouse);