You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Global constant "not found" when an earlier function body in its file uses ::class #15482
A top-level const in a file of an analysed directory is reported as Constant … not found when an earlier function body in the same file uses Foo::class. Functions declared after that point are still found. We hit it in a Pest tests/Pest.php, which declares helper functions and constants side by side.
The cause seems to be in PhpFileCleaner::clean(), as SymbolFinderInFiles::findSymbols() calls it:
findSymbols() passes the number of keyword matches as $maxMatches.
When that number isn't 1, the cleaner sets $inType = true on any class, interface, trait or enum. It skips the (?<![\$:>]) lookbehind that $type['pattern'] applies in the single-match path, so the class in stdClass::class counts.
$typeLevel isn't tracked outside a type, so the function's closing } takes it to -1, and it doesn't get back to 0.
From then on, every top-level const NAME is rewritten to class_const NAME. The symbol regex never matches that, so the constant is missing from OptimizedDirectorySourceLocator's map.
Reproduced with 2.2.15. PhpFileCleaner on the 2.3.x branch has the same code.
Code snippet that reproduces the problem
The playground analyses one file, so it can't show this: the constant must come from another file in an analysed directory.
------ ------------------------------------------------------------
Line a_usage.php
------ ------------------------------------------------------------
3 Constant BAR not found.
🪪 constant.notFound
3 Parameter #1 (mixed) of echo cannot be converted to string.
🪪 echo.nonString
------ ------------------------------------------------------------
Variations:
With return 'stdClass'; in foo(), there are no errors.
With a function named className(), there are none either. The cleaner then enters the "type" at the name and leaves it at the function's closing brace.
Expected output
No errors: BAR is a global constant declared in an analysed file.
A possible fix is to apply the lookbehind before setting $inType in the multi-match path as well. Only the lookbehind, not the whole pattern, so that new class { still counts. Keeping $typeLevel from going below 0 might also work.
Activity
mergeable commentedon Oct 15, 2026
This bug report is missing a link to reproduction at phpstan.org/try.
It will most likely be closed after manual review.