From 86a3fbd78bd4cf339fd169e15363e3e7894e1fd2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 23 Dec 2025 09:05:30 +0100 Subject: [PATCH 1/2] Cache ast-parsing in RegexGroupParser --- src/Type/Regex/RegexGroupParser.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index 3162a1c783..c7af3af1da 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -44,6 +44,9 @@ final class RegexGroupParser private static ?Parser $parser = null; + /** @var array */ + private static array $parsedAst = []; + public function __construct( private PhpVersion $phpVersion, private RegexExpressionHelper $regexExpressionHelper, @@ -77,7 +80,7 @@ public function parseGroups(string $regex): ?RegexAstWalkResult $rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex); try { - $ast = self::$parser->parse($rawRegex); + $ast = self::$parsedAst[$rawRegex] ??= self::$parser->parse($rawRegex); } catch (Exception) { return null; } From 087eb8b29316cafbe15472b155216a7dacc5c198 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 23 Dec 2025 09:59:38 +0100 Subject: [PATCH 2/2] cache earlier --- src/Type/Regex/RegexGroupParser.php | 52 +++++++++++++++++------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/src/Type/Regex/RegexGroupParser.php b/src/Type/Regex/RegexGroupParser.php index c7af3af1da..74bdadf540 100644 --- a/src/Type/Regex/RegexGroupParser.php +++ b/src/Type/Regex/RegexGroupParser.php @@ -21,6 +21,7 @@ use PHPStan\Type\StringType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use function array_key_exists; use function array_values; use function count; use function in_array; @@ -44,7 +45,7 @@ final class RegexGroupParser private static ?Parser $parser = null; - /** @var array */ + /** @var array */ private static array $parsedAst = []; public function __construct( @@ -59,30 +60,39 @@ public function parseGroups(string $regex): ?RegexAstWalkResult /** @throws void */ self::$parser ??= Llk::load(new Read(__DIR__ . '/../../../resources/RegexGrammar.pp')); - try { - Strings::match('', $regex); - } catch (RegexpException) { - // pattern is invalid, so let the RegularExpressionPatternRule report it - return null; - } - - $modifiers = $this->regexExpressionHelper->getPatternModifiers($regex) ?? ''; - foreach (self::NOT_SUPPORTED_MODIFIERS as $notSupportedModifier) { - if (str_contains($modifiers, $notSupportedModifier)) { + if (array_key_exists($regex, self::$parsedAst)) { + $ast = self::$parsedAst[$regex]; + if ($ast === null) { return null; } - } - if (str_contains($modifiers, 'x')) { - // in freespacing mode the # character starts a comment and runs until the end of the line - $regex = preg_replace('/(?regexExpressionHelper->getPatternModifiers($regex) ?? ''; + } else { + try { + Strings::match('', $regex); + } catch (RegexpException) { + // pattern is invalid, so let the RegularExpressionPatternRule report it + return self::$parsedAst[$regex] = null; + } - $rawRegex = $this->regexExpressionHelper->removeDelimitersAndModifiers($regex); - try { - $ast = self::$parsedAst[$rawRegex] ??= self::$parser->parse($rawRegex); - } catch (Exception) { - return null; + $modifiers = $this->regexExpressionHelper->getPatternModifiers($regex) ?? ''; + foreach (self::NOT_SUPPORTED_MODIFIERS as $notSupportedModifier) { + if (str_contains($modifiers, $notSupportedModifier)) { + return self::$parsedAst[$regex] = null; + } + } + + if (str_contains($modifiers, 'x')) { + // in freespacing mode the # character starts a comment and runs until the end of the line + $regex = preg_replace('/(?regexExpressionHelper->removeDelimitersAndModifiers($regex); + try { + $ast = self::$parsedAst[$regex] = self::$parser->parse($rawRegex); + } catch (Exception) { + return self::$parsedAst[$regex] = null; + } } $this->updateAlternationAstRemoveVerticalBarsAndAddEmptyToken($ast);