diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index 6c3c260b7..e70b9aa64 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -215,6 +215,7 @@ class ForeachAnalyzer if (self::checkIteratorType( $statements_analyzer, $stmt, + $stmt->expr, $iterator_type, $codebase, $context, @@ -335,11 +336,13 @@ class ForeachAnalyzer } /** + * @param PhpParser\Node\Stmt\Foreach_|PhpParser\Node\Expr\YieldFrom $stmt * @return false|null */ public static function checkIteratorType( StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Stmt\Foreach_ $stmt, + PhpParser\NodeAbstract $stmt, + PhpParser\Node\Expr $expr, Type\Union $iterator_type, Codebase $codebase, Context $context, @@ -351,7 +354,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new NullIterator( 'Cannot iterate over null', - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -361,7 +364,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new PossiblyNullIterator( 'Cannot iterate over nullable var ' . $iterator_type, - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -371,7 +374,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new PossiblyFalseIterator( 'Cannot iterate over falsable var ' . $iterator_type, - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -415,7 +418,7 @@ class ForeachAnalyzer $iterator_atomic_type = $iterator_atomic_type->getGenericArrayType(); } elseif ($iterator_atomic_type instanceof Type\Atomic\TList) { $list_var_id = ExpressionIdentifier::getArrayVarId( - $stmt->expr, + $expr, $statements_analyzer->getFQCLN(), $statements_analyzer ); @@ -444,7 +447,7 @@ class ForeachAnalyzer ArrayFetchAnalyzer::taintArrayFetch( $statements_analyzer, - $stmt->expr, + $expr, null, $value_type, Type::getMixed() @@ -479,7 +482,7 @@ class ForeachAnalyzer ArrayFetchAnalyzer::taintArrayFetch( $statements_analyzer, - $stmt->expr, + $expr, null, $value_type, Type::getMixed() @@ -592,7 +595,7 @@ class ForeachAnalyzer if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName( $statements_analyzer, $iterator_atomic_type->value, - new CodeLocation($statements_analyzer->getSource(), $stmt->expr), + new CodeLocation($statements_analyzer->getSource(), $expr), $context->self, $context->calling_method_id, $statements_analyzer->getSuppressedIssues() @@ -609,7 +612,7 @@ class ForeachAnalyzer self::handleIterable( $statements_analyzer, $iterator_atomic_type, - $stmt->expr, + $expr, $codebase, $context, $key_type, @@ -647,7 +650,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new PossibleRawObjectIteration( 'Possibly undesired iteration over regular object ' . \reset($raw_object_types), - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -657,7 +660,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new RawObjectIteration( 'Possibly undesired iteration over regular object ' . \reset($raw_object_types), - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -671,7 +674,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new PossiblyInvalidIterator( 'Cannot iterate over ' . $invalid_iterator_types[0], - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { @@ -681,7 +684,7 @@ class ForeachAnalyzer if (IssueBuffer::accepts( new InvalidIterator( 'Cannot iterate over ' . $invalid_iterator_types[0], - new CodeLocation($statements_analyzer->getSource(), $stmt->expr) + new CodeLocation($statements_analyzer->getSource(), $expr) ), $statements_analyzer->getSuppressedIssues() )) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php index f88403855..f52909061 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php @@ -5,6 +5,7 @@ use PhpParser; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Context; +use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer; use Psalm\Type; use function strtolower; @@ -26,6 +27,24 @@ class YieldFromAnalyzer } if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) { + $key_type = null; + $value_type = null; + $always_non_empty_array = true; + if (ForeachAnalyzer::checkIteratorType( + $statements_analyzer, + $stmt, + $stmt->expr, + $stmt_expr_type, + $statements_analyzer->getCodebase(), + $context, + $key_type, + $value_type, + $always_non_empty_array + ) === false + ) { + return false; + } + $yield_from_type = null; foreach ($stmt_expr_type->getAtomicTypes() as $atomic_type) { diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php index 0f7909b3a..277add9ef 100644 --- a/tests/GeneratorTest.php +++ b/tests/GeneratorTest.php @@ -314,6 +314,63 @@ class GeneratorTest extends TestCase }', 'error_message' => 'NullableReturnStatement', ], + 'invalidIterator' => [ + ' 'InvalidIterator', + ], + 'rawObjectIteration' => [ + ' 'RawObjectIteration', + ], + 'possibleRawObjectIteration' => [ + ' 5) { + $arr[] = new A; + } else { + $arr = new B; + } + + yield from $arr; + }', + 'error_message' => 'PossibleRawObjectIteration', + ], + 'possibleRawObjectIterationFromIsset' => [ + ' 'PossibleRawObjectIteration', + ], ]; } } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index b7fda5236..2226c3413 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -2024,7 +2024,8 @@ class ClassTemplateTest extends TestCase ], 'yieldFromGenericObjectNotExtendingIterator' => [ ' */ + class Foo extends \ArrayObject {} class A { /**