vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.56k stars 660 forks source link

@template-covariant should be unnecessary when immutable or when used with pure or mutation-free functions #8936

Open still-dreaming-1 opened 1 year ago

still-dreaming-1 commented 1 year ago

I don't know if this actually makes sense or not, I might be oversimplifying things or missing something, but if it does, I think it would be extremely helpful to alter Psalm to work this way. It could get rid of a lot of the fuss about using @template vs @template-covariant, which can get pretty annoying. I understand there is a point to the distinction and it makes sense for Psalm to care about it, the documentation does a good job of explaining this.

It seems to me there are 2 cases where the distinction is unnecessary and Psalm could not emit any issues where it currently does.

One case can be illustrated by a collection class using templates that is immutable, being used by a function that expects a specific type that is compatible, but not the same. Here is an example: https://psalm.dev/r/a3891f5a67 In that example since ImmutableCollection is @psalm-immutable, it cannot be modified, so it is safe to pass it to a function that works with AbstractAnimal without worrying the types will get altered. At least that is my hope/idea.

The second case is if the function you are passing it to is either pure or mutation free: https://psalm.dev/r/056d6bdcf3

An example of the benefits is these collection classes could then implement interfaces such as ArrayAccess or IteratorAggregate, which don't work with @template-covariant keys and values.

psalm-github-bot[bot] commented 1 year ago

I found these snippets:

https://psalm.dev/r/a3891f5a67 ```php $list */ public function __construct(public array $list) { } } function makeSound(AbstractAnimal $animal): void { echo $animal->getSound(); } /** * @param ImmutableCollection $animalCollection */ function makeTheSounds(ImmutableCollection $animalCollection): void { foreach ($animalCollection->list as $animal) { makeSound($animal); } } $cats = new ImmutableCollection([new Cat(), new Cat()]); makeTheSounds($cats); ``` ``` Psalm output (using commit 96cb44b): ERROR: InvalidArgument - 51:15 - Argument 1 of makeTheSounds expects ImmutableCollection, but ImmutableCollection provided ```
https://psalm.dev/r/056d6bdcf3 ```php $list */ public function __construct(public array $list) { } } /** * @param Collection $animalCollection * @psalm-mutation-free * @return list */ function makeAnimalSong(Collection $animalCollection): array { $sounds = []; foreach ($animalCollection->list as $animal) { $sounds[] = $animal->getSound(); } return $sounds; } $cats = new Collection([new Cat(), new Cat()]); makeAnimalSong($cats); ``` ``` Psalm output (using commit 96cb44b): ERROR: InvalidArgument - 54:16 - Argument 1 of makeAnimalSong expects Collection, but Collection provided ```