1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Strip null used to signify completed iterations in foreach context

Even though `Generator::current()` can return `null` once generator is
exhausted, `foreach()` never iterates after iterator ends, so we can
safely remove `null` (unless, of course, generator can yield `null`).
This commit is contained in:
Bruce Weirdan 2024-02-12 07:47:58 +01:00
parent 8a0bc19fa6
commit 27461c98f6
2 changed files with 49 additions and 0 deletions

View File

@ -929,10 +929,24 @@ final class ForeachAnalyzer
); );
if ($iterator_value_type && !$iterator_value_type->isMixed()) { if ($iterator_value_type && !$iterator_value_type->isMixed()) {
// remove null coming from current() to signify invalid iterations
// we're in a foreach context, so we know we're not going iterate past the end
if (isset($type_params[1]) && !$type_params[1]->isNullable()) {
$iterator_value_type = $iterator_value_type->getBuilder();
$iterator_value_type->removeType('null');
$iterator_value_type = $iterator_value_type->freeze();
}
$value_type = Type::combineUnionTypes($value_type, $iterator_value_type); $value_type = Type::combineUnionTypes($value_type, $iterator_value_type);
} }
if ($iterator_key_type && !$iterator_key_type->isMixed()) { if ($iterator_key_type && !$iterator_key_type->isMixed()) {
// remove null coming from key() to signify invalid iterations
// we're in a foreach context, so we know we're not going iterate past the end
if (isset($type_params[0]) && !$type_params[0]->isNullable()) {
$iterator_key_type = $iterator_key_type->getBuilder();
$iterator_key_type->removeType('null');
$iterator_key_type = $iterator_key_type->freeze();
}
$key_type = Type::combineUnionTypes($key_type, $iterator_key_type); $key_type = Type::combineUnionTypes($key_type, $iterator_key_type);
} }
} elseif ($codebase->classImplements( } elseif ($codebase->classImplements(

View File

@ -1191,6 +1191,41 @@ class ForeachTest extends TestCase
foreach ($gen as $i) {} foreach ($gen as $i) {}
PHP, PHP,
], ],
'nullableGenerator' => [
'code' => <<<'PHP'
<?php
/** @return Generator<int,int|null> */
function gen() : Generator {
yield null;
yield 1;
}
$gen = gen();
$a = "";
foreach ($gen as $i) {
$a = $i;
}
PHP,
'assertions' => [
'$a===' => "''|int|null",
],
],
'nonNullableGenerator' => [
'code' => <<<'PHP'
<?php
/** @return Generator<int,int> */
function gen() : Generator {
yield 1;
}
$gen = gen();
$a = "";
foreach ($gen as $i) {
$a = $i;
}
PHP,
'assertions' => [
'$a===' => "''|int",
],
],
]; ];
} }