Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
[files]
extend-exclude = [
".git/",
# generated by `make regex-parser`, contains lexer regexes
"src/Type/Regex/Parser/RegexParser.php",
]
ignore-hidden = false

Expand Down
15 changes: 7 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,13 @@ Collects structural information about a class during AST traversal: properties,

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

`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):
`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):

- **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.
- **Generated parser**: `src/Type/Regex/Parser/RegexParser.php`. Never edit it by hand. Change the grammar and regenerate, then commit both files together.
- **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()`.
- **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.
- Parse failures throw `Phplrt\Contracts\Parser\Exception\ParserExceptionInterface` or `RegexSyntaxException`; callers treat both as "pattern not supported" and infer no shape.
- 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/`.
- **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.
- **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.
- Library exceptions implement `PHPRegex\Parser\Exception\ExceptionInterface`; callers treat them as "pattern not supported" and infer no shape.
- `RegexArrayShapeMatcherEngineTest` matches every constant pattern of the `preg_*` fixtures with the engine and checks that the inferred type accepts what the engine writes.
- 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/`.

## Extension points

Expand Down Expand Up @@ -350,4 +349,4 @@ When adding or editing PHPDoc comments in this codebase, follow these guidelines
- `nette/neon` - Configuration file format
- `react/child-process`, `react/async` - Parallel analysis
- `symfony/console` - CLI interface
- `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`)
- `php-regex/regex-parser-php74` - PHP 7.4 compatible build of the PHPRegex parser, reads regex patterns for `preg_*()` shapes and `ignoreErrors`
8 changes: 0 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,6 @@ phpstan-generate-baseline-php:
phpstan-pro:
php -d memory_limit=450M bin/phpstan --pro

# Regenerates src/Type/Regex/Parser/RegexParser.php from
# build/regex-parser/RegexGrammar.pp3 with the phplrt compiler. The compiler
# needs PHP 8.4+, the generated parser only needs the phplrt runtime.
.PHONY: regex-parser
regex-parser:
composer install --working-dir build/regex-parser
php build/regex-parser/generate.php

name-collision:
php vendor/bin/detect-collisions --configuration build/collision-detector.json

Expand Down
3 changes: 0 additions & 3 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ parameters:
- ../tests/PHPStan/Reflection/MixedTypeTest.php
- ../tests/e2e/magic-setter/*
- ../tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php
- ../tests/PHPStan/Command/IgnoredRegexValidatorTest.php
- ../src/Command/IgnoredRegexValidator.php
- ../src/Type/Regex/Parser/RegexParser.php
exceptions:
uncheckedExceptionClasses:
- 'PHPStan\ShouldNotHappenException'
Expand Down
1 change: 0 additions & 1 deletion build/regex-parser/.gitignore

This file was deleted.

Loading