Repository navigation
Wrap NameResolver errors into ParserErrorsException so they are attributed to the file they come from - #6455
Merged
ondrejmirtes merged 1 commit intoSep 17, 2026
Conversation
…ttributed to the file they come from
* `RichParser`, `SimpleParser` and `StubParser` now catch `PhpParser\Error` thrown
from the name-resolving `NodeTraverser` run and rethrow it as
`ParserErrorsException`, honouring the `@throws` contract of `PHPStan\Parser\Parser`.
* `parseFile()` then attaches the parsed file name, so `FileNodesFetcher` and
`ExportedNodeFetcher` degrade gracefully (symbol treated as not found) instead of
letting the exception escape into the file that merely looks the symbol up.
* Covers both errors `PhpParser\NameContext` can raise: the duplicate `use` alias
("Cannot use X as Y because the name is already in use", for `use`, `use function`,
`use const` and group uses) and "'\self' is an invalid class name".
* Also covers the analogous reach-another-file paths: class instantiation through the
source locators, and trait resolution (`Class X uses unknown trait Y.`).
* Parse errors of this kind reported for the analysed file itself now include the
" on line N" suffix, matching how syntax errors collected by php-parser are reported.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a file that is only scanned (or analysed separately) contained a
usealias clashing with an earlier one, php-parser'sNameResolverthrew a rawPhpParser\Errorwhile PHPStan was resolving a symbol declared in that file. The exception escaped the source locators and was finally caught byFileAnalyser::analyseFile(), which assumed it came from parsing the current file — so the error was reported with the wrong file, a line number from the other file, no pointer to the real culprit, and the innocent file's analysis was aborted.Now such a file is treated exactly like a file with a syntax error: the symbol is simply not found, and the file that declares it reports the error on its own line.
Changes
src/Parser/RichParser.php,src/Parser/SimpleParser.php,src/Parser/StubParser.php: the name-resolvingNodeTraverser::traverse()call is wrapped in atry/catch (PhpParser\Error)that rethrows asParserErrorsException. All three parsers declare@throws ParserErrorsExceptionthrough thePHPStan\Parser\Parserinterface, so this restores the documented contract.FileNodesFetcher,ExportedNodeFetcherorFileAnalyser— they already handleParserErrorsException, andFileAnalyseralready attributes it toParserErrorsException::getParsedFile().Analogous cases probed and covered by the same fix:
NameContexterror:'\self' is an invalid class name(raised bygetResolvedName()for qualifiedself/parent/static) had exactly the same misattribution; it is fixed and tested.usekinds:use function,use constand group uses all go throughNameContext::addAlias(); covered by the new parser test cases.OptimizedDirectorySourceLocator/OptimizedSingleFileSourceLocator/AutoloadSourceLocator→FileNodesFetcher), trait resolution parses the trait's file too (MutatingScope::parseFile(),TraitUseHandler,FileAnalyserCallback). Both are tested.SimpleParserandStubParsershare theNameResolvertraversal withRichParserand got the same fix, even though onlySimpleParseris on the reported code path (the reflection parser is routed throughCleaningParser→SimpleParser).ExportedNodeFetcher(result cache / dependency export) has the samecatch (ParserErrorsException)shape asFileNodesFetcherand would previously have let the raw error escape; it is fixed by the same change.Root cause
PHPStan\Parser\Parser::parseFile()/parseString()promise to signal parse failures withParserErrorsException, which carries the file the failure came from. Syntax errors honoured that promise becauseParserRunner::parse()is given aCollectingerror handler. Name-resolution errors did not:NameResolveris constructed without an error handler, soNameContextusesErrorHandler\Throwingand a rawPhpParser\Error— with no file attached — propagated out ofparseString().Every caller that parses another file (
FileNodesFetcher,ExportedNodeFetcher,FileTypeMapper,PhpClassReflectionExtension,StubPhpDocProvider,MutatingScope::parseFile(),TraitUseHandler,FileAnalyserCallback) only guards againstParserErrorsException, so the raw error travelled all the way toFileAnalyser'scatch (\PhpParser\Error $e)handler, which reports it against the file currently being analysed. Converting the error at the single place it originates fixes all of those call sites at once.One user-visible consequence: when the offending file is itself analysed, the message now reads
Cannot use Ns\Foo2 as Foo because the name is already in use on line 6, i.e. it gains theon line Nsuffix thatPhpParser\Error::getMessage()adds and that syntax errors reported throughParserErrorsExceptionalready had. These errors are non-ignorable, so they cannot appear in baselines.Test
AnalyserIntegrationTest::testBug15252()— analysesdata/bug-15252.php, which instantiatesBug15252\Helper(declared indata/bug-15252-helper.phpwith a clashingusealias) andBug15252\InvalidName(declared indata/bug-15252-invalid-name.phpwithnew \self()). Both now produceInstantiated class ... not found./Call to method name() on an unknown class ...instead of a foreign parse error.AnalyserIntegrationTest::testBug15252Trait()—data/bug-15252-trait-user.phpuses a trait declared in a file with a clashing alias and now getsClass Bug15252\TraitUser uses unknown trait Bug15252\HelperTrait.AnalyserIntegrationTest::testBug15252AnalysedFileWithDuplicateAlias()andtestBug15252AnalysedFileWithInvalidClassName()— the offending files, when analysed themselves, still report their own error on their own line.RichParserTest::testNameResolutionErrors()— assertsRichParser,SimpleParserandStubParserall throwParserErrorsException(with the right raw message and start line) for duplicate class/function/const/group-use aliases and for'\self'.AnalyserIntegrationTest::testBug10358()— updated for theon line 6message suffix.All of the above fail without the source change.
Fixes phpstan/phpstan#15252