@@ -1294,41 +1294,19 @@ public function processStmtNode(
12941294
12951295 $ originalKeyVarExpr = null ;
12961296 $ continueExitPointHasUnoriginalKeyType = false ;
1297- $ byRefWithoutKey = $ stmt ->byRef
1298- && $ stmt ->keyVar === null
1299- && $ stmt ->valueVar instanceof Variable && is_string ($ stmt ->valueVar ->name );
1300-
13011297 if ($ stmt ->keyVar instanceof Variable && is_string ($ stmt ->keyVar ->name )) {
13021298 $ originalKeyVarExpr = new OriginalForeachKeyExpr ($ stmt ->keyVar ->name );
13031299 if ($ finalScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
13041300 $ scopesWithIterableValueType [] = $ finalScope ;
13051301 } else {
13061302 $ continueExitPointHasUnoriginalKeyType = true ;
13071303 }
1308- } elseif ($ byRefWithoutKey ) {
1309- $ originalValueExpr = new OriginalForeachValueExpr ($ stmt ->valueVar ->name );
1310- if (!$ finalScope ->hasExpressionType ($ originalValueExpr )->yes ()) {
1311- $ scopesWithIterableValueType [] = $ finalScope ;
1312- } else {
1313- $ continueExitPointHasUnoriginalKeyType = true ;
1314- }
13151304 }
13161305
13171306 foreach ($ finalScopeResult ->getExitPointsByType (Continue_::class) as $ continueExitPoint ) {
13181307 $ continueScope = $ continueExitPoint ->getScope ();
13191308 $ finalScope = $ continueScope ->mergeWith ($ finalScope );
1320- if ($ originalKeyVarExpr !== null ) {
1321- if (!$ continueScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
1322- $ continueExitPointHasUnoriginalKeyType = true ;
1323- continue ;
1324- }
1325- } elseif ($ byRefWithoutKey ) {
1326- $ originalValueExpr = new OriginalForeachValueExpr ($ stmt ->valueVar ->name );
1327- if ($ continueScope ->hasExpressionType ($ originalValueExpr )->yes ()) {
1328- $ continueExitPointHasUnoriginalKeyType = true ;
1329- continue ;
1330- }
1331- } else {
1309+ if ($ originalKeyVarExpr === null || !$ continueScope ->hasExpressionType ($ originalKeyVarExpr )->yes ()) {
13321310 $ continueExitPointHasUnoriginalKeyType = true ;
13331311 continue ;
13341312 }
@@ -1349,82 +1327,58 @@ public function processStmtNode(
13491327 count ($ breakExitPoints ) === 0
13501328 && count ($ scopesWithIterableValueType ) > 0
13511329 && !$ continueExitPointHasUnoriginalKeyType
1352- && ( $ stmt ->keyVar !== null || $ byRefWithoutKey )
1330+ && $ stmt ->keyVar !== null
13531331 && (!$ hasExpr ->no () || !$ stmt ->expr instanceof Variable)
13541332 && $ exprType ->isArray ()->yes ()
13551333 && $ exprType ->isConstantArray ()->no ()
13561334 ) {
1357- $ nativeExprType = $ scope ->getNativeType ($ stmt ->expr );
1358- $ arrayDimFetchLoopType = $ exprType ->getIterableValueType ();
1359- $ arrayDimFetchLoopNativeType = $ nativeExprType ->getIterableValueType ();
1360- $ keyLoopType = $ exprType ->getIterableKeyType ();
1361- $ keyLoopNativeType = $ nativeExprType ->getIterableKeyType ();
1362- $ valueTypeChanged = false ;
1363- $ keyTypeChanged = false ;
1364-
1365- if ($ stmt ->keyVar !== null ) {
1366- $ arrayExprDimFetch = new ArrayDimFetch ($ stmt ->expr , $ stmt ->keyVar );
1367- $ originalValueExpr = null ;
1368- if ($ stmt ->valueVar instanceof Variable && is_string ($ stmt ->valueVar ->name )) {
1369- $ originalValueExpr = new OriginalForeachValueExpr ($ stmt ->valueVar ->name );
1370- }
1371- $ arrayDimFetchLoopTypes = [];
1372- $ keyLoopTypes = [];
1373- foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1374- $ dimFetchType = $ scopeWithIterableValueType ->getType ($ arrayExprDimFetch );
1375- // Condition-based narrowings like `is_string($type)` apply to the value
1376- // variable but not automatically to the array dim fetch, even though the
1377- // two describe the same element for a given iteration. If the value var
1378- // hasn't been reassigned (OriginalForeachValueExpr still tracked) we use
1379- // the narrowed value-var type in place of the broader dim fetch type so
1380- // the loop's final array rewrite below picks up the sharper element type.
1381- if ($ originalValueExpr !== null && $ scopeWithIterableValueType ->hasExpressionType ($ originalValueExpr )->yes ()) {
1382- $ valueVarType = $ scopeWithIterableValueType ->getType ($ stmt ->valueVar );
1383- if ($ dimFetchType ->isSuperTypeOf ($ valueVarType )->yes ()) {
1384- $ dimFetchType = $ valueVarType ;
1385- }
1386- }
1387- $ arrayDimFetchLoopTypes [] = $ dimFetchType ;
1388- $ keyLoopTypes [] = $ scopeWithIterableValueType ->getType ($ stmt ->keyVar );
1389- }
1390-
1391- $ arrayDimFetchLoopType = TypeCombinator::union (...$ arrayDimFetchLoopTypes );
1392- $ keyLoopType = TypeCombinator::union (...$ keyLoopTypes );
1393-
1394- $ arrayDimFetchLoopNativeTypes = [];
1395- $ keyLoopNativeTypes = [];
1396- foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1397- $ dimFetchNativeType = $ scopeWithIterableValueType ->getNativeType ($ arrayExprDimFetch );
1398- if ($ originalValueExpr !== null && $ scopeWithIterableValueType ->hasExpressionType ($ originalValueExpr )->yes ()) {
1399- $ valueVarNativeType = $ scopeWithIterableValueType ->getNativeType ($ stmt ->valueVar );
1400- if ($ dimFetchNativeType ->isSuperTypeOf ($ valueVarNativeType )->yes ()) {
1401- $ dimFetchNativeType = $ valueVarNativeType ;
1402- }
1335+ $ arrayExprDimFetch = new ArrayDimFetch ($ stmt ->expr , $ stmt ->keyVar );
1336+ $ originalValueExpr = null ;
1337+ if ($ stmt ->valueVar instanceof Variable && is_string ($ stmt ->valueVar ->name )) {
1338+ $ originalValueExpr = new OriginalForeachValueExpr ($ stmt ->valueVar ->name );
1339+ }
1340+ $ arrayDimFetchLoopTypes = [];
1341+ $ keyLoopTypes = [];
1342+ foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1343+ $ dimFetchType = $ scopeWithIterableValueType ->getType ($ arrayExprDimFetch );
1344+ // Condition-based narrowings like `is_string($type)` apply to the value
1345+ // variable but not automatically to the array dim fetch, even though the
1346+ // two describe the same element for a given iteration. If the value var
1347+ // hasn't been reassigned (OriginalForeachValueExpr still tracked) we use
1348+ // the narrowed value-var type in place of the broader dim fetch type so
1349+ // the loop's final array rewrite below picks up the sharper element type.
1350+ if ($ originalValueExpr !== null && $ scopeWithIterableValueType ->hasExpressionType ($ originalValueExpr )->yes ()) {
1351+ $ valueVarType = $ scopeWithIterableValueType ->getType ($ stmt ->valueVar );
1352+ if ($ dimFetchType ->isSuperTypeOf ($ valueVarType )->yes ()) {
1353+ $ dimFetchType = $ valueVarType ;
14031354 }
1404- $ arrayDimFetchLoopNativeTypes [] = $ dimFetchNativeType ;
1405- $ keyLoopNativeTypes [] = $ scopeWithIterableValueType ->getType ($ stmt ->keyVar );
14061355 }
1356+ $ arrayDimFetchLoopTypes [] = $ dimFetchType ;
1357+ $ keyLoopTypes [] = $ scopeWithIterableValueType ->getType ($ stmt ->keyVar );
1358+ }
14071359
1408- $ arrayDimFetchLoopNativeType = TypeCombinator::union (...$ arrayDimFetchLoopNativeTypes );
1409- $ keyLoopNativeType = TypeCombinator::union (...$ keyLoopNativeTypes );
1360+ $ arrayDimFetchLoopType = TypeCombinator::union (...$ arrayDimFetchLoopTypes );
1361+ $ keyLoopType = TypeCombinator::union (...$ keyLoopTypes );
14101362
1411- $ valueTypeChanged = !$ arrayDimFetchLoopType ->equals ($ exprType ->getIterableValueType ());
1412- $ keyTypeChanged = !$ keyLoopType ->equals ($ exprType ->getIterableKeyType ());
1413- } elseif ($ byRefWithoutKey ) {
1414- $ arrayDimFetchLoopTypes = [];
1415- foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1416- $ arrayDimFetchLoopTypes [] = $ scopeWithIterableValueType ->getType ($ stmt ->valueVar );
1363+ $ arrayDimFetchLoopNativeTypes = [];
1364+ $ keyLoopNativeTypes = [];
1365+ foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1366+ $ dimFetchNativeType = $ scopeWithIterableValueType ->getNativeType ($ arrayExprDimFetch );
1367+ if ($ originalValueExpr !== null && $ scopeWithIterableValueType ->hasExpressionType ($ originalValueExpr )->yes ()) {
1368+ $ valueVarNativeType = $ scopeWithIterableValueType ->getNativeType ($ stmt ->valueVar );
1369+ if ($ dimFetchNativeType ->isSuperTypeOf ($ valueVarNativeType )->yes ()) {
1370+ $ dimFetchNativeType = $ valueVarNativeType ;
1371+ }
14171372 }
1418- $ arrayDimFetchLoopType = TypeCombinator::union (...$ arrayDimFetchLoopTypes );
1373+ $ arrayDimFetchLoopNativeTypes [] = $ dimFetchNativeType ;
1374+ $ keyLoopNativeTypes [] = $ scopeWithIterableValueType ->getType ($ stmt ->keyVar );
1375+ }
14191376
1420- $ arrayDimFetchLoopNativeTypes = [];
1421- foreach ($ scopesWithIterableValueType as $ scopeWithIterableValueType ) {
1422- $ arrayDimFetchLoopNativeTypes [] = $ scopeWithIterableValueType ->getNativeType ($ stmt ->valueVar );
1423- }
1424- $ arrayDimFetchLoopNativeType = TypeCombinator::union (...$ arrayDimFetchLoopNativeTypes );
1377+ $ arrayDimFetchLoopNativeType = TypeCombinator::union (...$ arrayDimFetchLoopNativeTypes );
1378+ $ keyLoopNativeType = TypeCombinator::union (...$ keyLoopNativeTypes );
14251379
1426- $ valueTypeChanged = !$ arrayDimFetchLoopType ->equals ($ exprType ->getIterableValueType ());
1427- }
1380+ $ valueTypeChanged = !$ arrayDimFetchLoopType ->equals ($ exprType ->getIterableValueType ());
1381+ $ keyTypeChanged = ! $ keyLoopType -> equals ( $ exprType -> getIterableKeyType ());
14281382
14291383 if ($ valueTypeChanged || $ keyTypeChanged ) {
14301384 $ newExprType = TypeTraverser::map ($ exprType , static function (Type $ type , callable $ traverse ) use ($ arrayDimFetchLoopType , $ keyLoopType , $ valueTypeChanged , $ keyTypeChanged ): Type {
@@ -1441,6 +1395,7 @@ public function processStmtNode(
14411395 $ valueTypeChanged ? $ arrayDimFetchLoopType : $ type ->getIterableValueType (),
14421396 );
14431397 });
1398+ $ nativeExprType = $ scope ->getNativeType ($ stmt ->expr );
14441399 $ newExprNativeType = TypeTraverser::map ($ nativeExprType , static function (Type $ type , callable $ traverse ) use ($ arrayDimFetchLoopNativeType , $ keyLoopNativeType , $ valueTypeChanged , $ keyTypeChanged ): Type {
14451400 if ($ type instanceof UnionType || $ type instanceof IntersectionType) {
14461401 return $ traverse ($ type );
0 commit comments