Skip to content

Commit d458a5b

Browse files
committed
Read regex shapes with PHPRegex instead of the generated parser
The shapes of preg_match(), preg_match_all() and preg_replace_callback() are read from the AST and the capture shape analysis of PHPRegex, a PCRE2 pattern parser that runs on PHP 7.4. The generated parser, its grammar, its build script and the phplrt runtime are removed, and ignoreErrors patterns are validated with the same parser. Patterns are read for the analysed PHP version and the PCRE2 version it bundles; a pcreVersion parameter, or config.platform.lib-pcre in composer.json, can set that version. With bleeding edge, RegularExpressionPatternRule also reports a pattern the running PHP compiles but the analysed PHP does not. A new test matches every constant pattern of the preg_* fixtures with the engine, on generated subjects and with every flag combination, and checks that the inferred type accepts what the engine writes.
1 parent 091c5a7 commit d458a5b

103 files changed

Lines changed: 5892 additions & 5517 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.typos.toml‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
[files]
22
extend-exclude = [
33
".git/",
4-
# generated by `make regex-parser`, contains lexer regexes
5-
"src/Type/Regex/Parser/RegexParser.php",
64
]
75
ignore-hidden = false
86

‎CLAUDE.md‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,13 @@ Collects structural information about a class during AST traversal: properties,
187187

188188
### Regex parser (`src/Type/Regex/`)
189189

190-
`RegexGroupParser` parses regex patterns to infer `preg_match()` array shapes (via `RegexArrayShapeMatcher`) and `IgnoredRegexValidator` uses it to validate `ignoreErrors` patterns. The parser itself is generated by [phplrt](https://github.com/phplrt/phplrt):
190+
`RegexGroupParser` reads regex patterns to infer `preg_match()` array shapes (via `RegexArrayShapeMatcher`) and `IgnoredRegexValidator` uses the same reader to validate `ignoreErrors` patterns. Patterns are parsed by [PHPRegex](https://github.com/php-regex/php-regex), from the `php-regex/regex-parser-php74` package (its PHP 7.4 compatible build, so it works after the PHAR is downgraded):
191191

192-
- **Grammar**: `build/regex-parser/RegexGrammar.pp3` (phplrt `.pp3` format, derived from Hoa's regex grammar). The reducers in the grammar build a tree of `PHPStan\Type\Regex\Parser\RegexNode` objects (`#capturing`, `#alternation`, `#quantification`, `token`, ...) that `RegexGroupParser` walks.
193-
- **Generated parser**: `src/Type/Regex/Parser/RegexParser.php`. Never edit it by hand. Change the grammar and regenerate, then commit both files together.
194-
- **Regenerating**: `make regex-parser`. It installs the phplrt compiler into `build/regex-parser/vendor` (separate `build/regex-parser/composer.json`, needs PHP 8.4+) and runs `build/regex-parser/generate.php`. The script targets PHP 8.1 syntax and post-processes the output so that the PHAR's PHP 7.4 downgrader can handle it (first-class callables become `\Closure::fromCallable()`) and adds `@throws` to `parse()`.
195-
- **Runtime**: the generated parser only needs the phplrt runtime, which comes from the `ondrejmirtes/phplrt-runtime` fork (a PHP 7.4 compatible downgrade of the `phplrt/*` runtime packages that replaces them). The compiler is a build-time dependency only and is never shipped.
196-
- Parse failures throw `Phplrt\Contracts\Parser\Exception\ParserExceptionInterface` or `RegexSyntaxException`; callers treat both as "pattern not supported" and infer no shape.
197-
- After changing the grammar or `RegexGroupParser`, run the regex tests: `tests/PHPStan/Analyser/nsrt/preg_*.php`, `tests/PHPStan/Type/Php/`, `tests/PHPStan/Type/Regex/` and `tests/PHPStan/Rules/Regexp/`.
192+
- **Reader**: `PHPRegexReader` parses a pattern for the analysed PHP version and the PCRE2 version it bundles (`pcreVersion` parameter, or `config.platform.lib-pcre` in composer.json, see `PcreVersion`). A pattern the analysed engine refuses, or that the library cannot read, gets no shape.
193+
- **Shapes**: the type of each group comes from the library's capture shape analysis (`CaptureShapeAnalyzer`: participation, values, length, digit and truthiness facts, per-branch arms). `RegexGroupParser` only walks the tree to place each group: its parent group and the alternation branch it belongs to.
194+
- Library exceptions implement `PHPRegex\Parser\Exception\ExceptionInterface`; callers treat them as "pattern not supported" and infer no shape.
195+
- `RegexArrayShapeMatcherEngineTest` matches every constant pattern of the `preg_*` fixtures with the engine and checks that the inferred type accepts what the engine writes.
196+
- After changing `RegexGroupParser` or `RegexArrayShapeMatcher`, run the regex tests: `tests/PHPStan/Analyser/nsrt/preg_*.php`, `tests/PHPStan/Type/Php/`, `tests/PHPStan/Type/Regex/` and `tests/PHPStan/Rules/Regexp/`.
198197

199198
## Extension points
200199

@@ -350,4 +349,4 @@ When adding or editing PHPDoc comments in this codebase, follow these guidelines
350349
- `nette/neon` - Configuration file format
351350
- `react/child-process`, `react/async` - Parallel analysis
352351
- `symfony/console` - CLI interface
353-
- `ondrejmirtes/phplrt-runtime` - PHP 7.4 compatible fork of the phplrt runtime, runs the regex parser generated from `build/regex-parser/RegexGrammar.pp3` (`make regex-parser`)
352+
- `php-regex/regex-parser-php74` - PHP 7.4 compatible build of the PHPRegex parser, reads regex patterns for `preg_*()` shapes and `ignoreErrors`

‎Makefile‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,14 +178,6 @@ phpstan-generate-baseline-php:
178178
phpstan-pro:
179179
php -d memory_limit=450M bin/phpstan --pro
180180

181-
# Regenerates src/Type/Regex/Parser/RegexParser.php from
182-
# build/regex-parser/RegexGrammar.pp3 with the phplrt compiler. The compiler
183-
# needs PHP 8.4+, the generated parser only needs the phplrt runtime.
184-
.PHONY: regex-parser
185-
regex-parser:
186-
composer install --working-dir build/regex-parser
187-
php build/regex-parser/generate.php
188-
189181
name-collision:
190182
php vendor/bin/detect-collisions --configuration build/collision-detector.json
191183

‎build/phpstan.neon‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ parameters:
3939
- ../tests/PHPStan/Reflection/MixedTypeTest.php
4040
- ../tests/e2e/magic-setter/*
4141
- ../tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php
42-
- ../tests/PHPStan/Command/IgnoredRegexValidatorTest.php
43-
- ../src/Command/IgnoredRegexValidator.php
44-
- ../src/Type/Regex/Parser/RegexParser.php
4542
exceptions:
4643
uncheckedExceptionClasses:
4744
- 'PHPStan\ShouldNotHappenException'

‎build/regex-parser/.gitignore‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)