https://getpsalm.org/r/6512bf307d
<?php
namespace NS;
/**
* @template TKey
* @template TValue
*/
interface ICollection extends \IteratorAggregate {
/** @return \Traversable<TKey,TValue> */
public function getIterator();
}
class Collection implements ICollection {
/** @var array */
private $data;
public function __construct(array $data) {
$this->data = $data;
}
/** @psalm-suppress LessSpecificImplementedReturnType */
public function getIterator(): \Traversable {
return new \ArrayIterator($this->data);
}
}
/** @var ICollection<string, int> */
$c = new Collection(["a" => 1]);
foreach ($c as $k => $v) { atan($k); strlen($v); } // $k and $v are reported as TKey, TValue, (and mixed if any methods are called on them)
foreach ($c->getIterator() as $k => $v) { atan($k); strlen($v); } // $k and $v resolved to string/int property
Expected: both loops result in $k/$v types inferred as string/int.
Actual: only loop with explicit getIterator() resolve $k/$v as expected. Implicit call in first loop reports variables as TKey/TValue, and mixed if any method calls are performed on them.
Note: when I dropped the ICollection interface and moved template definitions to the Collection itself Psalm was able to infer both cases properly. So it might have something to do with inheritance.
Did this work before https://github.com/vimeo/psalm/commit/1d3e66c79818d1c022670bc8f299f48ffd85d998?
It didn't, the output is the same with 80cd77832b0020f5acb0ede2653ff773c438dd79
Thanks for this!