Skip to content

Commit

Permalink
Apply conditional expressions for non-empty arrays inside foreach
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jan 8, 2021
1 parent e9b783a commit 35db779
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 27 deletions.
16 changes: 8 additions & 8 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -761,12 +761,16 @@ private function processStmtNode(
} elseif ($stmt instanceof Foreach_) {
$condResult = $this->processExprNode($stmt->expr, $scope, $nodeCallback, ExpressionContext::createDeep());
$scope = $condResult->getScope();
$bodyScope = $this->enterForeach($scope, $stmt);
$arrayComparisonExpr = new BinaryOp\NotIdentical(
$stmt->expr,
new Array_([])
);
$bodyScope = $this->enterForeach($scope->filterByTruthyValue($arrayComparisonExpr), $stmt);
$hasYield = false;
$count = 0;
do {
$prevScope = $bodyScope;
$bodyScope = $bodyScope->mergeWith($scope);
$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr));
$bodyScope = $this->enterForeach($bodyScope, $stmt);
$bodyScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, static function (): void {
})->filterOutLoopExitPoints();
Expand All @@ -785,7 +789,7 @@ private function processStmtNode(
$count++;
} while (!$alwaysTerminating && $count < self::LOOP_SCOPE_ITERATIONS);

$bodyScope = $bodyScope->mergeWith($scope);
$bodyScope = $bodyScope->mergeWith($scope->filterByTruthyValue($arrayComparisonExpr));
$bodyScope = $this->enterForeach($bodyScope, $stmt);
$finalScopeResult = $this->processStmtNodes($stmt, $stmt->stmts, $bodyScope, $nodeCallback)->filterOutLoopExitPoints();
$finalScope = $finalScopeResult->getScope();
Expand All @@ -801,11 +805,7 @@ private function processStmtNode(
$finalScope = $scope;
} elseif ($isIterableAtLeastOnce->maybe()) {
if ($this->polluteScopeWithAlwaysIterableForeach) {
$arrayComparisonExpr = new BinaryOp\NotIdentical(
$stmt->expr,
new Array_([])
);
$finalScope = $finalScope->filterByTruthyValue($arrayComparisonExpr)->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr));
$finalScope = $finalScope->mergeWith($scope->filterByFalseyValue($arrayComparisonExpr));
} else {
$finalScope = $finalScope->mergeWith($scope);
}
Expand Down
10 changes: 8 additions & 2 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4774,7 +4774,7 @@ public function dataForeachArrayType(): array
],
[
__DIR__ . '/data/foreach/foreach-with-specified-key-type.php',
'array<string, float|int|string>',
'array<string, float|int|string>&nonEmpty',
'$list',
],
[
Expand Down Expand Up @@ -6537,7 +6537,7 @@ public function dataIterable(): array
'$unionBar',
],
[
'array',
'array&nonEmpty',
'$mixedUnionIterableType',
],
[
Expand Down Expand Up @@ -10603,6 +10603,11 @@ public function dataBug4339(): array
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4339.php');
}

public function dataBug4343(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-4343.php');
}

/**
* @param string $file
* @return array<string, mixed[]>
Expand Down Expand Up @@ -10807,6 +10812,7 @@ private function gatherAssertTypes(string $file): array
* @dataProvider dataBug3986
* @dataProvider dataBug4188
* @dataProvider dataBug4339
* @dataProvider dataBug4343
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/data/bug-4343.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Bug4343;

use PHPStan\TrinaryLogic;
use function PHPStan\Analyser\assertVariableCertainty;

function (array $a) {
if (count($a) > 0) {
$test = new \stdClass();
}

foreach ($a as $my) {
assertVariableCertainty(TrinaryLogic::createYes(), $test);
}
};
8 changes: 4 additions & 4 deletions tests/PHPStan/Analyser/data/native-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ public function doForeach(array $array): void
assertNativeType('array', $array);

foreach ($array as $key => $value) {
assertType('array<string, int>', $array);
assertNativeType('array', $array);
assertType('array<string, int>&nonEmpty', $array);
assertNativeType('array&nonEmpty', $array);

assertType('string', $key);
assertNativeType('(int|string)', $key);
Expand Down Expand Up @@ -124,8 +124,8 @@ public function doForeachArrayDestructuring(array $array)
assertType('array<string, array(int, string)>', $array);
assertNativeType('array', $array);
foreach ($array as $key => [$i, $s]) {
assertType('array<string, array(int, string)>', $array);
assertNativeType('array', $array);
assertType('array<string, array(int, string)>&nonEmpty', $array);
assertNativeType('array&nonEmpty', $array);

assertType('string', $key);
assertNativeType('(int|string)', $key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,7 @@ public function testTypesAssignedToPropertiesExpressionNames(): void
97,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$float_test (float) does not accept float|int|string.',
110,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$foo (string) does not accept float|int|string.',
110,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept float|int|string.',
110,
],
[
'Property PropertiesFromArrayIntoObject\Foo::$test (int|null) does not accept float|int|string.',
'Property PropertiesFromArrayIntoObject\Foo::$lall (int) does not accept string.',
110,
],
[
Expand Down

0 comments on commit 35db779

Please sign in to comment.