@@ -69,6 +69,8 @@ zend_string *pt_feh_identical_narrowing_helper_class = nullptr;
6969zend_string *pt_feh_empty_or_object_narrowing_helper_class = nullptr ;
7070zend_string *pt_feh_traversable_class = nullptr ;
7171zend_string *pt_feh_iterator_aggregate_class = nullptr ;
72+ zend_string *pt_feh_iterator_class = nullptr ;
73+ zend_string *pt_feh_valid = nullptr ;
7274zend_string *pt_feh_get_iterator = nullptr ;
7375
7476/* $node->getAttributes() / ->setAttributes($attributes) */
@@ -374,6 +376,8 @@ class ForeachHandler
374376 if (UNEXPECTED (foreachIterateeType.isUndef ())) return zv::Val ();
375377 zv::Val foreachNativeIterateeType = pt_expression_result_get_native_type (condResult.raw ());
376378 if (UNEXPECTED (foreachNativeIterateeType.isUndef ())) return zv::Val ();
379+ zv::Val iteratorValidExpr = newIteratorValidExpr (iteratee, foreachIterateeType.raw ());
380+ if (UNEXPECTED (iteratorValidExpr.isUndef ())) return zv::Val ();
377381
378382 if (parts.keyVarIsVariable ) {
379383 zv::Val keyTypeExpr = newIterableTypeExpr (originalScope.raw (), foreachIterateeType.raw (), foreachNativeIterateeType.raw (), true );
@@ -458,6 +462,10 @@ class ForeachHandler
458462 if (UNEXPECTED (storage.isUndef ())) return zv::Val ();
459463
460464 originalScope = zv::Val::copyOf (nonEmptyIterateeScope.ref ());
465+ if (!iteratorValidExpr.isNull ()) {
466+ originalScope = pt_mutating_scope_filter_by_value (Z_OBJ_P (originalScope.raw ()), Z_OBJ_P (iteratorValidExpr.raw ()), true );
467+ if (UNEXPECTED (originalScope.isUndef ())) return zv::Val ();
468+ }
461469 // $originalScope may narrow the iteratee to a non-empty array - a genuinely
462470 // different scope than its own. The narrowing is tracked by the scope
463471 // (getTypeOnScope's authoritative read), so the iteratee only needs
@@ -506,6 +514,10 @@ class ForeachHandler
506514 zv::Val prevScope = zv::Val::copyOf (bodyScope.ref ());
507515 bodyScope = pt_mutating_scope_merge_with (Z_OBJ_P (bodyScope.raw ()), nonEmptyIterateeScope.raw ());
508516 if (UNEXPECTED (bodyScope.isUndef ())) return zv::Val ();
517+ if (!iteratorValidExpr.isNull ()) {
518+ bodyScope = pt_mutating_scope_filter_by_value (Z_OBJ_P (bodyScope.raw ()), Z_OBJ_P (iteratorValidExpr.raw ()), true );
519+ if (UNEXPECTED (bodyScope.isUndef ())) return zv::Val ();
520+ }
509521 if (!prevEntryScope.isNull ()) {
510522 bool equal;
511523 if (UNEXPECTED (!ptlh::scopesEqual (bodyScope.raw (), prevEntryScope.raw (), equal))) return zv::Val ();
@@ -579,6 +591,10 @@ class ForeachHandler
579591
580592 bodyScope = pt_mutating_scope_merge_with (Z_OBJ_P (bodyScope.raw ()), nonEmptyIterateeScope.raw ());
581593 if (UNEXPECTED (bodyScope.isUndef ())) return zv::Val ();
594+ if (!iteratorValidExpr.isNull ()) {
595+ bodyScope = pt_mutating_scope_filter_by_value (Z_OBJ_P (bodyScope.raw ()), Z_OBJ_P (iteratorValidExpr.raw ()), true );
596+ if (UNEXPECTED (bodyScope.isUndef ())) return zv::Val ();
597+ }
582598 zv::Val finalEntryScope = zv::Val::copyOf (bodyScope.ref ());
583599 zval *lastPass = lastRecordedPass (previousPass);
584600 zval *storage = originalStorage;
@@ -806,6 +822,12 @@ class ForeachHandler
806822 finalScope = std::move (assigned);
807823 }
808824
825+ if (!iteratorValidExpr.isNull ()) {
826+ zv::Val filtered = pt_mutating_scope_filter_by_value (Z_OBJ_P (finalScope.raw ()), Z_OBJ_P (iteratorValidExpr.raw ()), false );
827+ if (UNEXPECTED (filtered.isUndef ())) return zv::Val ();
828+ finalScope = std::move (filtered);
829+ }
830+
809831 zv::Val bindingFlow;
810832 {
811833 zval flows[5 ];
@@ -1777,6 +1799,35 @@ class ForeachHandler
17771799 return pt_mutating_scope_assign_variable (Z_OBJ_P (scope), name, type, nativeType, pt_trinary_singleton (PT_TRI_YES ));
17781800 }
17791801
1802+ /* processStmt()'s `$iteratorValidExpr`: `$iteratee->valid() === true` when
1803+ * the iterated type is always an Iterator, null otherwise, UNDEF = pending
1804+ * exception */
1805+ zv::Val newIteratorValidExpr (zval *iteratee, zval *iterateeType) const
1806+ {
1807+ zv::Val iteratorType;
1808+ {
1809+ zval out;
1810+ if (UNEXPECTED (!pt_object_type_new (&out, pt_feh_iterator_class))) return zv::Val ();
1811+ iteratorType = zv::Val::adopt (out);
1812+ }
1813+ zend_long superType = isSuperTypeOf (iteratorType.raw (), iterateeType);
1814+ if (UNEXPECTED (superType < 0 )) return zv::Val ();
1815+ if (superType != PT_TRI_YES ) return zv::Val::null ();
1816+
1817+ zv::Args methodCallArgv{iteratee, pt_feh_valid};
1818+ zv::Val methodCall = pt_type_new (PT_CLASS_METHOD_CALL , 2 , methodCallArgv);
1819+ if (UNEXPECTED (methodCall.isUndef ())) return zv::Val ();
1820+ zval name;
1821+ ZVAL_STRINGL (&name, " true" , 4 );
1822+ zv::Val nameValue = zv::Val::adopt (name);
1823+ zv::Val fullyQualified = pt_name_node_new (PT_CLASS_FULLY_QUALIFIED , nameValue.raw ());
1824+ if (UNEXPECTED (fullyQualified.isUndef ())) return zv::Val ();
1825+ zv::Val constFetch = pt_type_new (PT_CLASS_CONST_FETCH , 1 , fullyQualified.raw ());
1826+ if (UNEXPECTED (constFetch.isUndef ())) return zv::Val ();
1827+ zv::Args identicalArgv{methodCall.raw (), constFetch.raw ()};
1828+ return pt_type_new (PT_CLASS_IDENTICAL_EXPR , 2 , identicalArgv);
1829+ }
1830+
17801831 /* Mirrors the private getTraversableForeachThrowPoint(); the throw point
17811832 * or null, UNDEF = pending exception */
17821833 zv::Val getTraversableForeachThrowPoint (zval *scope, zval *iteratee, zval *exprType) const
@@ -1994,6 +2045,8 @@ PT_MINIT_REGISTRATION(pt_register_foreach_handler)
19942045 pt_feh_empty_or_object_narrowing_helper_class = zend_string_init_interned (PT_LC (" PHPStan\\ Analyser\\ ExprHandler\\ Helper\\ ForeachEmptyOrObjectNarrowingHelper" ), 1 );
19952046 pt_feh_traversable_class = zend_string_init_interned (PT_LC (" Traversable" ), 1 );
19962047 pt_feh_iterator_aggregate_class = zend_string_init_interned (PT_LC (" IteratorAggregate" ), 1 );
2048+ pt_feh_iterator_class = zend_string_init_interned (PT_LC (" Iterator" ), 1 );
2049+ pt_feh_valid = zend_string_init_interned (PT_LC (" valid" ), 1 );
19972050 pt_feh_get_iterator = zend_string_init_interned (PT_LC (" getIterator" ), 1 );
19982051
19992052 reg::Class cls (" PHPStan\\ Analyser\\ StmtHandler\\ ForeachHandler" );
0 commit comments