Skip to content

Commit

Permalink
Invalidating expressions without regexes
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 14, 2021
1 parent 3dc41f3 commit 4220e43
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 24 deletions.
36 changes: 15 additions & 21 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use PhpParser\Node\Scalar\EncapsedStringPart;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\NodeFinder;
use PHPStan\Node\ExecutionEndNode;
use PHPStan\Parser\Parser;
use PHPStan\Reflection\ClassMemberReflection;
Expand Down Expand Up @@ -3479,17 +3480,13 @@ public function unsetExpression(Expr $expr): self
);
}

$args = [new Node\Arg($expr->var)];

$arrays = TypeUtils::getArrays($varType);
$scope = $this;
if (count($arrays) > 0) {
$scope = $scope->specifyExpressionType($expr->var, TypeCombinator::union(...$arrays));
}

return $scope->invalidateExpression($expr->var)
->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), $args))
->invalidateExpression(new FuncCall(new Name('count'), $args));
return $scope->invalidateExpression($expr->var);
}

return $this;
Expand Down Expand Up @@ -3609,28 +3606,25 @@ public function invalidateExpression(Expr $expressionToInvalidate, bool $require
$moreSpecificTypeHolders = $this->moreSpecificTypes;
$nativeExpressionTypes = $this->nativeExpressionTypes;
$invalidated = false;
$nodeFinder = new NodeFinder();
foreach (array_keys($moreSpecificTypeHolders) as $exprString) {
$exprString = (string) $exprString;
if (Strings::startsWith($exprString, $exprStringToInvalidate)) {
if ($exprString === $exprStringToInvalidate && $requireMoreCharacters) {
continue;
}
$nextLetter = substr($exprString, strlen($exprStringToInvalidate), 1);
if (Strings::match($nextLetter, '#[a-zA-Z_0-9\x7f-\xff]#') === null) {
unset($moreSpecificTypeHolders[$exprString]);
unset($nativeExpressionTypes[$exprString]);
$invalidated = true;
continue;
}
$expr = $this->parser->parseString('<?php ' . $exprString . ';')[0];
if (!$expr instanceof Node\Stmt\Expression) {
throw new \PHPStan\ShouldNotHappenException();
}
$matches = \Nette\Utils\Strings::matchAll($exprString, '#\$[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*#');
if ($matches === []) {
$found = $nodeFinder->findFirst([$expr->expr], function (Node $node) use ($exprStringToInvalidate): bool {
if (!$node instanceof Expr) {
return false;
}

return $this->getNodeKey($node) === $exprStringToInvalidate;
});
if ($found === null) {
continue;
}

$matches = array_column($matches, 0);

if (!in_array($exprStringToInvalidate, $matches, true)) {
if ($requireMoreCharacters && $exprString === $exprStringToInvalidate) {
continue;
}

Expand Down
4 changes: 1 addition & 3 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -1858,9 +1858,7 @@ function (MutatingScope $scope) use ($expr, $nodeCallback, $context): Expression
) {
$arrayArg = $expr->args[0]->value;
$constantArrays = TypeUtils::getConstantArrays($scope->getType($arrayArg));
$scope = $scope->invalidateExpression($arrayArg)
->invalidateExpression(new FuncCall(new Name\FullyQualified('count'), [$expr->args[0]]))
->invalidateExpression(new FuncCall(new Name('count'), [$expr->args[0]]));
$scope = $scope->invalidateExpression($arrayArg);
if (count($constantArrays) > 0) {
$resultArrayTypes = [];

Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-4415.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5259.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5293.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5129.php');
}

/**
Expand Down
63 changes: 63 additions & 0 deletions tests/PHPStan/Analyser/data/bug-5129.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Bug5129;

use function PHPStan\Testing\assertType;

class HelloWorld
{

private $foo;

public function sayHello(string $s): void
{
$this->foo = '';
assertType('0', strlen($this->foo));
if (strlen($this->foo) > 0) {
return;
}

assertType('0', strlen($this->foo));

$this->foo = 'x';
assertType('int<1, max>', strlen($this->foo));
if (strlen($this->foo) > 0) {
return;
}

assertType('0', strlen($this->foo));

$this->foo = $s;
assertType('int<0, max>', strlen($this->foo));
}

public function sayHello2(string $s): void
{
$this->foo = '';
if (!$this->isFoo($this->foo)) {
return;
}

assertType('true', $this->isFoo($this->foo));

$this->foo = 'x';
assertType('bool', $this->isFoo($this->foo));
if (!$this->isFoo($this->foo)) {
return;
}
assertType('true', $this->isFoo($this->foo));

$this->foo = $s;
assertType('bool', $this->isFoo($this->foo));
if (!$this->isFoo($this->foo)) {
return;
}
assertType('true', $this->isFoo($this->foo));
}

public function isFoo(string $s): bool
{
return strlen($s) % 3;
}

}

0 comments on commit 4220e43

Please sign in to comment.