Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 26 additions & 29 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 @@ -724,38 +726,33 @@ 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);
}

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);
}
$resolveConcat = static function (Expr $expr) use (&$resolveConcat, $scope) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are usually memory leaks. It'd be nicer to have this as a private method.

Copy link
Contributor Author

@staabm staabm Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with a inline class, hopefully that works for you.
didn't have a better idea on how to get the lexical $scope variable into the callable

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

if ($expr instanceof Expr\BinaryOp\Concat) {
$left = $resolveConcat($expr->left);
$right = $resolveConcat($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 TypeCombinator::union(...$strings);
return $scope->getType($expr);
};

return $this->initializerExprTypeResolver->getConcatType($concat->left, $concat->right, $resolveConcat);
}

}
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);
}
}
}