Skip to content

Commit

Permalink
Improve conditional type resolving performance
Browse files Browse the repository at this point in the history
  • Loading branch information
rajyan committed Dec 2, 2022
1 parent 94ca7b2 commit 6c0f6eb
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 15 deletions.
27 changes: 12 additions & 15 deletions src/Analyser/MutatingScope.php
Expand Up @@ -119,6 +119,7 @@
use function array_map;
use function array_merge;
use function array_pop;
use function array_reverse;
use function array_slice;
use function count;
use function explode;
Expand Down Expand Up @@ -3720,7 +3721,6 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
$specifiedExpressions = [];
foreach ($typeSpecifications as $typeSpecification) {
$expr = $typeSpecification['expr'];
$specifiedExpressions[$this->getNodeKey($expr)] = true;
$type = $typeSpecification['type'];
if ($typeSpecification['sure']) {
if ($specifiedTypes->shouldOverwrite()) {
Expand All @@ -3731,27 +3731,24 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
} else {
$scope = $scope->removeTypeFromExpression($expr, $type);
}
$specifiedExpressions[$this->getNodeKey($expr)] = $scope->getType($expr);
}

$newConditionalExpressions = $specifiedTypes->getNewConditionalExpressionHolders();
foreach ($scope->conditionalExpressions as $variableExprString => $conditionalExpressions) {
if (array_key_exists($variableExprString, $specifiedExpressions)) {
continue;
}
$newConditionalExpressions[$variableExprString] = $conditionalExpressions;
foreach ($conditionalExpressions as $conditionalExpression) {
$targetTypeHolder = $conditionalExpression->getTypeHolder();
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $conditionalTypeHolder) {
if (!$scope->invalidateExpression($targetTypeHolder->getExpr())->getType($conditionalTypeHolder->getExpr())->equals($conditionalTypeHolder->getType())) {
foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) {
foreach (array_reverse($conditionalExpressions) as $conditionalExpression) {
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder->getType())) {
continue 2;
}
}

if ($targetTypeHolder->getCertainty()->no()) {
unset($scope->expressionTypes[$variableExprString]);
if ($conditionalExpression->getTypeHolder()->getCertainty()->no()) {
unset($scope->expressionTypes[$conditionalExprString]);
} else {
$scope->expressionTypes[$variableExprString] = $targetTypeHolder;
$scope->expressionTypes[$conditionalExprString] = $conditionalExpression->getTypeHolder();
$specifiedExpressions[$conditionalExprString] = $conditionalExpression->getTypeHolder()->getType();
}
continue 2;
}
}

Expand All @@ -3762,7 +3759,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
$scope->getNamespace(),
$scope->expressionTypes,
$scope->nativeExpressionTypes,
$newConditionalExpressions,
array_merge($specifiedTypes->getNewConditionalExpressionHolders(), $scope->conditionalExpressions),
$scope->inClosureBindScopeClass,
$scope->anonymousFunctionReflection,
$scope->inFirstLevelStatement,
Expand Down
43 changes: 43 additions & 0 deletions tests/PHPStan/Rules/SlowdownRuleTest.php
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<Rule>
*/
class SlowdownRuleTest extends RuleTestCase
{

/**
* @return Rule<Node>
*/
protected function getRule(): Rule
{
return new class implements Rule {

public function getNodeType(): string
{
return Node::class;
}

/**
* @return string[]
*/
public function processNode(Node $node, Scope $scope): array
{
return [];
}

};
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/1.9.x-slowdown.php'], []);
}

}
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php
Expand Up @@ -918,4 +918,13 @@ public function testBug4173(): void
]);
}

public function testBug5803(): void
{
$this->cliArgumentsVariablesRegistered = true;
$this->polluteScopeWithLoopInitialAssignments = false;
$this->checkMaybeUndefinedVariables = true;
$this->polluteScopeWithAlwaysIterableForeach = true;
$this->analyse([__DIR__ . '/data/bug-5803.php'], []);
}

}
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Variables/data/bug-5803.php
@@ -0,0 +1,15 @@
<?php declare(strict_types = 1);

namespace Bug5803;

function () {
if (!($_GET['foo'])) { // if 'foo' is falsy, SET $var
$var = "set";
}

if ($_GET['foo']) {

} else {
echo $var;
}
};
39 changes: 39 additions & 0 deletions tests/PHPStan/Rules/data/1.9.x-slowdown.php
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace Foo;

use function trim;

class Foo
{

public const FIELD_TITLE = 'title';
public const FIELD_SOURCE = 'source';
public const FIELD_BODY = 'body';
public const EMPTY_NOTE_BODY = '-';

public const FIELD_NOTES = 'notes';
public const SUBFIELD_NOTE = 'note';

/**
* @param array<mixed> $data
* @return array<mixed>
*/
private function someMethod(array $data): array
{
foreach ($data[self::FIELD_NOTES][self::SUBFIELD_NOTE] ?? [] as $index => $noteData) {
$noteTitle = $noteData[self::FIELD_TITLE] ?? null;
$noteSource = $noteData[self::FIELD_SOURCE] ?? null;
$noteBody = $noteData[self::FIELD_BODY] ?? null;

if ($noteBody === null || trim($noteBody) === '') {
$data[self::FIELD_NOTES] = self::EMPTY_NOTE_BODY;
}
}

if (isset($data[self::FIELD_NOTES][self::SUBFIELD_NOTE])) {}

return $data;
}

}

0 comments on commit 6c0f6eb

Please sign in to comment.