Skip to content

Commit

Permalink
RegexArrayShapeMatcher: Support resolving of constants in patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Jul 25, 2024
1 parent 4d055b0 commit 2f10677
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 28 deletions.
62 changes: 34 additions & 28 deletions src/Type/Php/RegexArrayShapeMatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Php\PhpVersion;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
Expand Down Expand Up @@ -55,6 +56,7 @@ final class RegexArrayShapeMatcher

public function __construct(
private PhpVersion $phpVersion,
private InitializerExprTypeResolver $initializerExprTypeResolver,
)
{
}
Expand Down Expand Up @@ -725,38 +727,42 @@ private function getPatternType(Expr $patternExpr, Scope $scope): Type
*/
private function resolvePatternConcat(Expr\BinaryOp\Concat $concat, Scope $scope): Type
{
if (
$concat->left instanceof Expr\FuncCall
&& $concat->left->name instanceof Name
&& $concat->left->name->toLowerString() === 'preg_quote'
) {
$left = new ConstantStringType('');
} elseif ($concat->left instanceof Expr\BinaryOp\Concat) {
$left = $this->resolvePatternConcat($concat->left, $scope);
} else {
$left = $scope->getType($concat->left);
}
$resolver = new class($scope) {

if (
$concat->right instanceof Expr\FuncCall
&& $concat->right->name instanceof Name
&& $concat->right->name->toLowerString() === 'preg_quote'
) {
$right = new ConstantStringType('');
} elseif ($concat->right instanceof Expr\BinaryOp\Concat) {
$right = $this->resolvePatternConcat($concat->right, $scope);
} else {
$right = $scope->getType($concat->right);
}
public function __construct(private Scope $scope)
{
}

public function resolve(Expr $expr): Type
{
if (
$expr instanceof Expr\FuncCall
&& $expr->name instanceof Name
&& $expr->name->toLowerString() === 'preg_quote'
) {
return new ConstantStringType('');
}

if ($expr instanceof Expr\BinaryOp\Concat) {
$left = $this->resolve($expr->left);
$right = $this->resolve($expr->right);

$strings = [];
foreach ($left->toString()->getConstantStrings() as $leftString) {
foreach ($right->toString()->getConstantStrings() as $rightString) {
$strings[] = new ConstantStringType($leftString->getValue() . $rightString->getValue());
}
}

$strings = [];
foreach ($left->getConstantStrings() as $leftString) {
foreach ($right->getConstantStrings() as $rightString) {
$strings[] = new ConstantStringType($leftString->getValue() . $rightString->getValue());
return TypeCombinator::union(...$strings);
}

return $this->scope->getType($expr);
}
}

return TypeCombinator::union(...$strings);
};

return $this->initializerExprTypeResolver->getConcatType($concat->left, $concat->right, static fn (Expr $expr): Type => $resolver->resolve($expr));
}

}
20 changes: 20 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug11384.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);

namespace Bug11384;

use function PHPStan\Testing\assertType;

class Bar
{
public const VAL = 3;
}

class HelloWorld
{
public function sayHello(string $s): void
{
if (preg_match('{(' . Bar::VAL . ')}', $s, $m)) {
assertType('array{string, numeric-string}', $m);
}
}
}

0 comments on commit 2f10677

Please sign in to comment.