Skip to content
Merged
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: 2 additions & 0 deletions .typos.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
[files]
extend-exclude = [
".git/",
# generated by `make regex-parser`, contains lexer regexes
"src/Type/Regex/Parser/RegexParser.php",
]
ignore-hidden = false

Expand Down
13 changes: 12 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,17 @@ Collects structural information about a class during AST traversal: properties,
- **`PHPStan\Cache`** - Caching infrastructure
- **`PHPStan\Testing`** - `RuleTestCase`, `TypeInferenceTestCase`, and other test utilities

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

- **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/`.

## Extension points

PHPStan is highly extensible. Key extension interfaces:
Expand Down Expand Up @@ -339,4 +350,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
- `hoa/compiler` - Used for regex type parsing
- `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`)
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ 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
7 changes: 0 additions & 7 deletions bin/phpstan
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ use Symfony\Component\Console\Helper\ProgressBar;
}
$devOrPharLoader->unregister();

// fix missing key for vendor/hoa/protocol/Wrapper.php
$existingComposerAutoloadFiles = $GLOBALS['__composer_autoload_files'] ?? [];
$GLOBALS['__composer_autoload_files'] = array_merge(
$existingComposerAutoloadFiles,
array_fill_keys(['3e76f7f02b41af8cea96018933f6b7e3'], true),
);

Comment on lines -55 to -61

@acrobat acrobat Oct 26, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ondrejmirtes can this actually be removed? As this was added because of autoload error in projects using hoa/protocol phpstan/phpstan#13369

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Entire Hoa library was removed from PHPStan, so yes.

$autoloaderInWorkingDirectory = $vendorDirectory . '/autoload.php';
$composerAutoloaderProjectPaths = [];

Expand Down
1 change: 1 addition & 0 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ parameters:
- ../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: 1 addition & 0 deletions build/regex-parser/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
vendor
Loading
Loading