Docblocks allow you to tell Psalm some simple information about how your code works. For example `@return int` in a function return type tells Psalm that a function should return an `int` and `@return MyContainer` tells Psalm that a function should return an instance of a user-defined class `MyContainer`. In either case, Psalm can check that the function actually returns those types _and_ that anything calling that function uses its returned value properly.
Templated annotations provide us with a workaround - we can define a generic/templated param `T` that is a placeholder for the value inside `MyContainer`:
Now we can substitute values for that templated param when we reference `MyContainer` in docblocks e.g. `@return MyContainer<int>`. This tells Psalm to substitute `T` for `int` when evaluating that return type, effectively treating it as a class that looks like
This pattern can be used in large number of different situations like mocking, collections, iterators and loading arbitrary objects. Psalm has a large number of annotations to make it easy to use templated types in your codebase.
That last call `addAnimal($dog_collection)` breaks the type of the collection – suddenly a collection of dogs becomes a collection of dogs _or_ cats. That is bad.
To prevent this, Psalm emits an error when calling `addAnimal($dog_collection)` saying "addAnimal expects a `Collection<Animal>`, but `Collection<Dog>` was passed". If you haven't encountered this rule before it's probably confusing to you – any function that accepted an `Animal` would be happy to accept a subtype thereof. But as we see in the example above, doing so can lead to problems.
But there are also times where it's perfectly safe to pass template param subtypes:
public function getNoise() : string { return "woof"; }
}
class Cat extends Animal {
public function getNoise() : string { return "miaow"; }
}
/**
*@template T
*/
class Collection {
/** @var array<int,T> */
public array $list = [];
}
/**
*@param Collection<Animal> $collection
*/
function getNoises(Collection $collection) : void {
foreach ($collection->list as $animal) {
echo $animal->getNoise();
}
}
/**
*@param Collection<Dog> $dog_collection
*/
function takesDogList(Collection $dog_collection) : void {
getNoises($dog_collection);
}
```
Here we're not doing anything bad – we're just iterating over an array of objects. But Psalm still gives that same basic error – "getNoises expects a `Collection<Animal>`, but `Collection<Dog>` was passed".
We can tell Psalm that it's safe to pass subtypes for the templated param `T` by using the annotation `@template-covariant T` (or `@psalm-template-covariant T`):
But `@template-covariant` doesn't get rid of _all_ errors – if you add it to the first example, you get a new error – [https://psalm.dev/r/0fcd699231](https://psalm.dev/r/0fcd699231) – complaining that you're attempting to use a covariant template parameter for function input. That’s no good, as it means you're likely altering the collection somehow (which is, again, a violation).
Psalm has [comprehensive support for declaring functional immutability](https://psalm.dev/articles/immutability-and-beyond).
If we make sure that the class is immutable, we can declare a class with an `add` method that still takes a covariant param as input, but which does not modify the collection at all, instead returning a new one: