Skip to content

Read a global constant name written with asterisks, like PHP_ROUND_* - #342

Open
gnutix wants to merge 1 commit into
phpstan:2.4.xfrom
gnutix:global-constant-wildcard
Open

gnutix wants to merge 1 commit into
phpstan:2.4.xfrom
gnutix:global-constant-wildcard

Conversation

@gnutix

@gnutix gnutix commented Nov 7, 2026 •

Copy link
Copy Markdown

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

A name followed by an asterisk with no space in between is now read as a
ConstFetchNode with an empty class name and the pattern as its name, the
way Foo::BAR_* already is for class constants: PHP_ROUND_*, E_*_ERROR,
Foo\BAR_*. Such a PHPDoc was invalid until now, so nothing that parsed
before parses differently. The asterisk has to touch the name, which is
what keeps "@return string *Note:* text" a type followed by a
description.

The grammars tell an asterisk written right after a name apart as
T_NAME_WILDCARD, so that they read the same language.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant