-
Notifications
You must be signed in to change notification settings - Fork 668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Calling Closure/callable leads to ImpureFunctionCall #8116
Comments
I found these snippets: https://psalm.dev/r/392de9f519<?php
/**
* @template A
* @implements Collection<A>
*/
final class ArrayList implements Collection
{
/**
* @param list<A> $items
*/
public function __construct(private array $items)
{
}
/**
* @template B
*
* @param callable(A): B $ab
* @return ArrayList<B>
*
* @psalm-mutation-free
*/
public function map(callable $ab): ArrayList
{
$agg = [];
foreach ($this->items as $item) {
$agg[] = $ab($item);
}
return new ArrayList($agg);
}
}
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<B>
*
* @psalm-mutation-free
*/
public function map(callable $ab): Collection;
}
|
Psalm can't check that the callable won't make you method mutate, so it has to be sure the callable itself is pure. I'm not sure I understood what you meant |
I don't understand how to make, for example Imagine /**
* @template T
*/
final class KVStore
{
/**
* @return T
*/
public function get(string $id): mixed
{
throw new RuntimeException('???');
}
} Next imagine the client side. final class FetchObjectByIds
{
/**
* @param KVStore<array{id: string, value: string}> $cache
*/
public function __construct(private KVStore $cache) {}
/**
* @param ArrayList<string> $ids
* @return ArrayList<array{id: string, value: string}>
*/
public function getByIds(ArrayList $ids): ArrayList
{
return $ids->map(fn($id) => $this->cache->get($id));
}
} So Where is the usefulness from |
I found these snippets: https://psalm.dev/r/282a8ab4b0<?php
final class FetchObjectByIds
{
/**
* @param KVStore<array{id: string, value: string}> $cache
*/
public function __construct(private KVStore $cache) {}
/**
* @param ArrayList<string> $ids
* @return ArrayList<array{id: string, value: string}>
*/
public function getByIds(ArrayList $ids): ArrayList
{
return $ids->map(fn($id) => $this->cache->get($id));
}
}
/**
* @template T
*/
final class KVStore
{
/**
* @return T
*/
public function get(string $id): mixed
{
throw new RuntimeException('???');
}
}
/**
* @template A
* @implements Collection<A>
*/
final class ArrayList implements Collection
{
/**
* @param list<A> $items
*/
public function __construct(private array $items)
{
}
/**
* @template B
*
* @param pure-callable(A): B $ab
* @return ArrayList<B>
*
* @psalm-mutation-free
*/
public function map(callable $ab): ArrayList
{
$agg = [];
foreach ($this->items as $item) {
$agg[] = $ab($item);
}
return new ArrayList($agg);
}
}
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param pure-callable(A): B $ab
* @return Collection<B>
*
* @psalm-mutation-free
*/
public function map(callable $ab): Collection;
}
|
|
Then let's say it differently. How I can do useful covariant collection? Psalm prevent me to put covariant template to contravariant position (https://psalm.dev/r/76c25e34c3). But How I can do covariant collection? I should suppress any Closure/callable inside |
I found these snippets: https://psalm.dev/r/76c25e34c3<?php
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<B>
*/
public function map(callable $ab): Collection;
}
https://psalm.dev/r/244dea724a<?php
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<B>
*
* @psalm-mutation-free
*/
public function map(callable $ab): Collection;
}
https://psalm.dev/r/b47232b299<?php
/**
* @template-covariant A
* @psalm-immutable
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<B>
*/
public function map(callable $ab): Collection;
}
https://psalm.dev/r/627b50d716<?php
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<A>
*
* @psalm-mutation-free
*/
public function forEach(callable $ab): Collection;
}
/** @var Collection<int> $collection */;
$collection->forEach(function($i) {
print_r($i);
});
|
Why this code don't break any guaranties? https://3v4l.org/9qcZ1 |
Ah, that makes sense now. I think this is an XY problem and the real issue here is with the covariance/contravariance rules.
Because we know that I'll think about it a bit more and add this as a testcase, hopefully it's not too difficult to fix. For now I think you just have to |
I found these snippets: https://psalm.dev/r/b04e379623<?php
/**
* @template-covariant A
* @implements Collection<A>
* @implements IteratorAggregate<int, A>
*/
final class ArrayList implements Collection, IteratorAggregate
{
/**
* @param list<A> $items
*/
public function __construct(private array $items)
{
}
/**
* @param A $a
*/
public function push($a): void
{
$this->items[] = $a;
}
public function getIterator(): Traversable
{
return new ArrayObject($this->items);
}
}
/**
* @template-covariant A
*/
interface Collection
{
/**
* @param A $a
*/
public function push($a): void;
}
class Animal {}
class Dog extends Animal
{
public function bark(): void
{
echo "Woof";
}
}
class Cat extends Animal
{
public function meow(): void
{
echo "Meow";
}
}
$dogs = new ArrayList([new Dog()]);
/** @param ArrayList<Animal> $animals */
function pushCat(ArrayList $animals): void
{
$animals->push(new Cat());
}
pushCat($dogs);
foreach ($dogs as $dog) {
$dog->bark();
}
https://psalm.dev/r/76c25e34c3<?php
/**
* @template-covariant A
*/
interface Collection
{
/**
* @template B
*
* @param callable(A): B $ab
* @return Collection<B>
*/
public function map(callable $ab): Collection;
}
|
https://psalm.dev/r/392de9f519
Why I can't call callable/Closure in
psalm-mutation-free
/psalm-immutable
context?These annotations should garantie that current class instance doesn't mutate. Isn't?
(Go to use pure-Closure/pure-callable is not good idea)
The text was updated successfully, but these errors were encountered: