1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Forbid iterating over generators with non-nullable send()

Fixes vimeo/psalm#6985
This commit is contained in:
Bruce Weirdan 2024-02-12 05:14:17 +01:00
parent e9ea999f50
commit 8a0bc19fa6
2 changed files with 73 additions and 2 deletions

View File

@ -657,6 +657,7 @@ final class ForeachAnalyzer
$key_type,
$value_type,
$has_valid_iterator,
$invalid_iterator_types,
);
} else {
$raw_object_types[] = $iterator_atomic_type->value;
@ -725,6 +726,7 @@ final class ForeachAnalyzer
return null;
}
/** @param list<string> $invalid_iterator_types */
public static function handleIterable(
StatementsAnalyzer $statements_analyzer,
TNamedObject $iterator_atomic_type,
@ -733,7 +735,8 @@ final class ForeachAnalyzer
Context $context,
?Union &$key_type,
?Union &$value_type,
bool &$has_valid_iterator
bool &$has_valid_iterator,
array &$invalid_iterator_types = []
): void {
if ($iterator_atomic_type->extra_types) {
$iterator_atomic_types = array_merge(
@ -753,7 +756,6 @@ final class ForeachAnalyzer
}
$has_valid_iterator = true;
if ($iterator_atomic_type instanceof TIterable
|| (strtolower($iterator_atomic_type->value) === 'traversable'
@ -781,6 +783,8 @@ final class ForeachAnalyzer
)
)
) {
$has_valid_iterator = true;
$old_data_provider = $statements_analyzer->node_data;
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
@ -867,6 +871,7 @@ final class ForeachAnalyzer
$key_type,
$value_type,
$has_valid_iterator,
$invalid_iterator_types,
);
continue;
@ -899,6 +904,37 @@ final class ForeachAnalyzer
$value_type = Type::combineUnionTypes($value_type, $value_type_part);
}
}
} elseif ($iterator_atomic_type instanceof TGenericObject
&& strtolower($iterator_atomic_type->value) === 'generator'
) {
$type_params = $iterator_atomic_type->type_params;
if (isset($type_params[2]) && !$type_params[2]->isNullable() && !$type_params[2]->isMixed()) {
$invalid_iterator_types[] = $iterator_atomic_type->getKey();
} else {
$has_valid_iterator = true;
}
$iterator_value_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,
$context,
'current',
);
$iterator_key_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,
$context,
'key',
);
if ($iterator_value_type && !$iterator_value_type->isMixed()) {
$value_type = Type::combineUnionTypes($value_type, $iterator_value_type);
}
if ($iterator_key_type && !$iterator_key_type->isMixed()) {
$key_type = Type::combineUnionTypes($key_type, $iterator_key_type);
}
} elseif ($codebase->classImplements(
$iterator_atomic_type->value,
'Iterator',
@ -911,6 +947,7 @@ final class ForeachAnalyzer
)
)
) {
$has_valid_iterator = true;
$iterator_value_type = self::getFakeMethodCallType(
$statements_analyzer,
$foreach_expr,

View File

@ -1169,6 +1169,28 @@ class ForeachTest extends TestCase
}
PHP,
],
'generatorWithUnspecifiedSend' => [
'code' => <<<'PHP'
<?php
/** @return Generator<int,int> */
function gen() : Generator {
return yield 1;
}
$gen = gen();
foreach ($gen as $i) {}
PHP,
],
'generatorWithMixedSend' => [
'code' => <<<'PHP'
<?php
/** @return Generator<int,int, mixed, mixed> */
function gen() : Generator {
return yield 1;
}
$gen = gen();
foreach ($gen as $i) {}
PHP,
],
];
}
@ -1395,6 +1417,18 @@ class ForeachTest extends TestCase
PHP,
'error_message' => 'LessSpecificReturnStatement',
],
'generatorWithNonNullableSend' => [
'code' => <<<'PHP'
<?php
/** @return Generator<int,int,string,string> */
function gen() : Generator {
return yield 1;
}
$gen = gen();
foreach ($gen as $i) {}
PHP,
'error_message' => 'InvalidIterator',
],
];
}
}