Given the following function:
public function getRenderersForClass(string $nodeClass): iterable
{
// If renderers are defined for this specific class, return them immediately
if (isset($this->renderersByClass[$nodeClass])) {
return $this->renderersByClass[$nodeClass];
}
// Check if a renderer is registered for any parent class
while ($parent = \get_parent_class($parent ?? $nodeClass)) {
if (! isset($this->renderersByClass[$parent])) {
continue;
}
// "Cache" this result to avoid future loops
return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent];
}
return [];
}
Psalm incorrectly raises the following TypeDoesNotContainType error:

Immediately before the first iteration of that loop, $parent is null, but Psalm thinks it's set to a class-string which it isn't until after that expression is evaluated.
Hey @colinodell, can you reproduce the issue on https://psalm.dev ?
Yes, I can reproduce it: https://psalm.dev/r/c10550b943
I found these snippets:
https://psalm.dev/r/c10550b943
<?php
final class Foo
{
/** @var array<string, iterable> */
private $renderersByClass = [];
public function getRenderersForClass(string $nodeClass): iterable
{
// If renderers are defined for this specific class, return them immediately
if (isset($this->renderersByClass[$nodeClass])) {
return $this->renderersByClass[$nodeClass];
}
// Check if a renderer is registered for any parent class
while ($parent = \get_parent_class($parent ?? $nodeClass)) {
if (! isset($this->renderersByClass[$parent])) {
continue;
}
// "Cache" this result to avoid future loops
return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent];
}
return [];
}
}
Psalm output (using commit 3e16aec):
ERROR: TypeDoesNotContainType - 16:44 - class-string is always defined and non-null
Workaround: https://psalm.dev/r/5b885422e8
I found these snippets:
https://psalm.dev/r/5b885422e8
<?php
final class Foo
{
/** @var array<string, iterable> */
private $renderersByClass = [];
public function getRenderersForClass(string $nodeClass): iterable
{
// If renderers are defined for this specific class, return them immediately
if (isset($this->renderersByClass[$nodeClass])) {
return $this->renderersByClass[$nodeClass];
}
$parent = $nodeClass;
// Check if a renderer is registered for any parent class
while ($parent = \get_parent_class($parent)) {
if (! isset($this->renderersByClass[$parent])) {
continue;
}
// "Cache" this result to avoid future loops
return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent];
}
return [];
}
}
Psalm output (using commit 3e16aec):
No issues!
Reduced to
function foo(string $a): void {
while ($b = getString($b ?? $a)) {
$c = "hello";
}
}
function getString(string $s) : ?string {
return rand(0, 1) ? $s : null;
}