As you can see, psalm is complaining about a mixed|uninferrable return type un Unboxer::unbox, even if the return type is correctly inferred afterwards, once the template type Unboxer::T and the inferred template type Unboxer::TFinal are both populated.
The same "T as mixed" template type is returned in Box::unbox, and no issue is present there.
Actually, hovering over the left and right ternary branches in Unboxer::unbox, I see that psalm resolves a "mixed" (not "TFinal as mixed") type on "$this->boxOrVal->unbox()", and an "empty-mixed" type on the right branch, instead of "TFinal as mixed".
https://psalm.dev/r/5df148c0b9
```php
val = $val;
}
/**
* @return T
*/
public function unbox()
{
return $this->val;
}
}
/**
* @template TFinal
* @template T as Box|TFinal
*/
class Unboxer
{
/**
* @var T
*/
public $boxOrVal;
/**
* @param T $boxOrVal
*/
public function __construct($boxOrVal) {
$this->boxOrVal = $boxOrVal;
}
/**
* @psalm-return TFinal
*/
public function unbox() {
return $this->boxOrVal instanceof Box ? $this->boxOrVal->unbox() : $this->boxOrVal;
}
}
$a = new Unboxer(new Box(0));
/** @psalm-trace $res */
$res = $a->unbox();
$a = new Unboxer(0);
/** @psalm-trace $res */
$res = $a->unbox();
```
```
Psalm output (using commit 9e05254):
INFO: Trace - 50:1 - $res: int(0)
INFO: Trace - 54:1 - $res: int(0)
INFO: UnusedVariable - 50:1 - $res is never referenced or the value is not used
INFO: UnusedVariable - 54:1 - $res is never referenced or the value is not used
ERROR: DocblockTypeContradiction - 44:76 - Docblock-defined type T:Unboxer as (Box)|(TFinal:Unboxer as mixed) for $this->boxOrVal is always Box
INFO: MixedReturnStatement - 44:16 - Could not infer a return type
INFO: MixedInferredReturnType - 41:22 - Could not verify return type 'TFinal' for Unboxer::unbox
```
https://psalm.dev/r/5df148c0b9
As you can see, psalm is complaining about a mixed|uninferrable return type un Unboxer::unbox, even if the return type is correctly inferred afterwards, once the template type Unboxer::T and the inferred template type Unboxer::TFinal are both populated. The same "T as mixed" template type is returned in Box::unbox, and no issue is present there.
Actually, hovering over the left and right ternary branches in Unboxer::unbox, I see that psalm resolves a "mixed" (not "TFinal as mixed") type on "$this->boxOrVal->unbox()", and an "empty-mixed" type on the right branch, instead of "TFinal as mixed".