This makes a global constant name written with asterisks parse, the way Foo::BAR_* already does for class constants:
/** @param PHP_ROUND_* $mode */
/** @param E_*_ERROR $level */
/** @return \Foo\BAR_*|null */
A name followed by an asterisk with no space in between is read as a ConstFetchNode with an empty class name and the pattern as its name: new ConstFetchNode('', 'PHP_ROUND_*'). This is the parser half of phpstan/phpstan#7272; the PHPStan half, which resolves the pattern to the matching constants, is phpstan/phpstan-src#6995.
Merge after phpstan/phpstan-src#6995. The "Test PHPStan" job runs phpstan-src 2.3.x with this parser, and its existing tests already write @param FILTER_VALIDATE_* $intFilter, which now parses and makes TypeNodeResolver throw ShouldNotHappenException until that PR is in. "Test Slevomat Coding Standard" fails on 2.4.x itself as well (NameNode), independently of this change.
Nothing that parsed before parses differently. Such a PHPDoc was an InvalidTagValueNode until now. The asterisk has to touch the name, which is what keeps @return string *Note:* text a type followed by a description (there is a test for that). Keywords stay invalid as before: null*, true* and array* are still errors.
What changes:
ConstExprParser: after a name, a glued * starts a pattern. The loop that reads Foo::BAR_* moved into parseConstantNamePattern(), which both forms use.
TypeParser: isConstantFetchAfterName() decides whether a name starts a constant expression (::, or now a glued *). Both the main path and the callable-return-type path use it, so callable(): FOO_* works like callable(): Foo::*.
- Grammars: an asterisk written right after a name is classified as
T_NAME_WILDCARD / T_NAME_WILDCARD_WS, and ConstantFetch, the type-name lookaheads and the free-text tokens accept it. The fuzzer knows how to write it. GrammarSyncTest rejects the new test inputs without these grammar changes, and the whole suite passes with them.
🤖 Generated with Claude Code