Split bootstrap autoloaders at the project's class loader - #6281
Split bootstrap autoloaders at the project's class loader#6281SanderMuller wants to merge 1 commit into
Conversation
|
phpstan/phpstan, where the compiled PHAR exists, also has e2e/ tests. Would be nice to reproduce it there with actual project, make sure it's red, and then make sure here it's green. |
|
If the project is an existing actually-used open-source project and not just a reproducing repository, it could be added to integration-tests.yml (showing errors on the latest 2.2.x) instead of e2e in other-tests.yml. |
collectNewAutoloadFunctions() decided whether a bootstrap-registered autoloader runs before or after the static source locators by looking for the first Composer ClassLoader instance in the spl_autoload queue. That instance is PHPStan's own: bin/phpstan requires its own autoloader long before any project code runs, so every collected autoloader looked like it came after Composer and was consulted only after the static locators. The boundary is the analysed project's loader, so skip the first ClassLoader in the before-snapshot and split at the last one instead. When that loader is gone from the queue after the bootstrap files ran, a bootstrap has taken Composer's place - typo3/class-alias-loader unregisters [$composerLoader, 'loadClass'] and registers a wrapper - and its loader carries Composer's runtime priority, so the collected autoloaders are consulted first, as they were until 2.2.9. e2e/bug-15102 covers the replacement shape: an IDE-only stub in the analysed paths declares a legacy name that only exists as a class_alias at runtime. The static locator returns the stub without the fix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2d20290 to
af675e6
Compare
|
Done, and following your ask found that my first version was wrong - so this PR now carries a different fix. I built a phar from the branch to check it properly. The Each run below has its own
The fix now splits at the project's loader (skip the first ClassLoader in the before-snapshot, take the last), and the replaced-Composer case falls out of it naturally. On the e2e request: it turns out this half does reproduce in a source run, so the project lives here as For #6265 I have the same red/green matrix against the release phars (2.2.8 clean, 2.2.9 On the real-project alternative: TYPO3 core is the actual consumer here, and |
We could have both, it's always nice to add another integration project into the pipeline. Please rebase, there's now a conflict after merging your other PR. |
The ordering half of phpstan/phpstan#15102 - @sbuerk's original report. #6265 fixes @hoetaek's half.
The mechanism is not the one the issue proposes. I built a phar from the first version of this PR (which implemented the suggested
$composerIndex === nullfallback) and it does not fix the reported case. Measured on thee2e/bug-15102project added here, each run with its owntmpDirso no result cache is shared:$composerIndex === nullfallback)What actually happens
collectNewAutoloadFunctions()looks for the firstComposer\Autoload\ClassLoaderinstance in the queue to decide what came "before Composer". That instance is PHPStan's own -bin/phpstanrequires its own autoloader long before any project code runs. Dumping the queue during analysis:So every bootstrap-registered autoloader is classified as "after Composer" and consulted only after the static source locators.
$composerIndexis nevernull- which is why the fallback the issue proposes changes nothing on its own.The fix skips the first
ClassLoaderin the before-snapshot and splits at the last one, i.e. the analysed project's loader. When that loader is gone after the bootstrap files ran, a bootstrap has replaced Composer (typo3/class-alias-loaderunregisters[$composerLoader, 'loadClass']and registers a wrapper) and there is no boundary left, so the collected loaders are consulted first - the order they had until 2.2.9.Verification
e2e/bug-15102(new, registered ine2e-tests.yml): a bootstrap replaces the project's loader and resolvesLegacy\Validateviaclass_alias, while an IDE-only stub in the analysed paths declares the same name as a plain class - the TYPO3 shape. Without the fix the stub wins and PHPStan reportsCall to an undefined method Legacy\Validate::doFoo(); with it, clean. It reproduces in a source run, so no phar is needed to keep it honest.CollectNewAutoloadFunctionsTest: 6 green, 4 fail without the source change. The cases now model the real snapshot, which always starts with PHPStan's own loader, andtestPhpstansOwnLoaderIsNotTheBoundarypins the regression directly.e2e/bug-12972b,e2e/bug-12972c(Consult bootstrap-registered custom autoloaders only after the static source locators #6069's own ordering coverage) ande2e/bug-14988exit 0.Closes the ordering half of phpstan/phpstan#15102