🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

Reanalyse trait-consuming files after a body-only trait change even when the trait shares its file with another symbol - #6033

Merged
ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-8i5ocht
Jul 10, 2026
Merged

Reanalyse trait-consuming files after a body-only trait change even when the trait shares its file with another symbol#6033
ondrejmirtes merged 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-8i5ocht

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

When a trait's method body changed (but its signature stayed the same) and the reused result cache predated that change, PHPStan failed to reanalyse the classes consuming the trait — as long as the trait's file also declared another symbol (a class, enum, etc.) next to it. As a result, real errors introduced by the change were silently missed on cached runs (and --generate-baseline regenerated an empty baseline / ignore.unmatched fired on CI), while a fresh cache reported them correctly.

The fix makes the trait body-change cache invalidation trigger whenever the changed file contains a trait, instead of only when the file contains nothing but traits.

Changes

  • src/Analyser/ResultCache/ResultCacheManager.php: in the branch handling a file whose hash changed but whose exported nodes are identical (a body-only change), stop bailing out via continue 2 on the first non-trait exported node. Instead, detect whether the file contains at least one ExportedTraitNode; if it does, re-queue that file's usedTraitDependentFiles (the classes using the trait). Files with no trait node keep the previous behaviour (no dependent reanalysis).
  • e2e/result-cache-trait-alongside-class/: new e2e reproduction — a trait plus a marker class in one file, a class using the trait, a warm cache, then a patch that changes only the trait method body to return the wrong type. The consuming class must report the return.type error on the cached run.
  • .github/workflows/e2e-tests.yml: run the new e2e scenario.

Root cause

Trait method bodies are analysed in the context of every consuming class, so a body-only change to a trait must reanalyse those consumers — regular dependency tracking only covers signature-level changes. ResultCacheManager handled this, but guarded it with an "all exported nodes in this file are traits" loop that did continue 2 (skipping the trait-consumer re-queueing entirely) the moment it encountered any non-trait node. A trait declared next to a class in the same file therefore never had its consumers reanalysed on a body-only change. The guard is now "the file contains a trait", which is the condition that actually determines whether trait consumers need reanalysing; a body-only change to the co-located non-trait symbols still (correctly) does not force their dependents to be reanalysed.

Analogous cases probed

  • Signature changes to a trait sharing its file (exportedNodesChanged returns true/false): verified these already reanalyse consumers through the regular dependentFiles dependency graph, so no parallel fix was needed there.
  • Files containing multiple traits / traits + multiple classes: the new "contains at least one trait" check re-queues the file's trait consumers correctly for all such combinations.

Test

e2e/result-cache-trait-alongside-class reproduces the exact reported conditions (trait alongside a class in one file, warm cache predating the change, unchanged consuming classes) and asserts the consuming class reports the introduced error on the cached run. I confirmed it fails before the fix (exit 0, no error) and passes after (exit 1, return.type error reported).

Fixes phpstan/phpstan#14943

…hen the trait shares its file with another symbol

- In ResultCacheManager, the body-only-change branch (exported nodes unchanged) only re-queued the trait-consuming files when *every* exported node in the file was a trait; it did `continue 2` and skipped them as soon as it saw a non-trait node (e.g. a class declared next to the trait).
- Replace the "all exported nodes are traits" check with "the file contains at least one trait": when a trait's method body changes but its signature stays the same, the classes using that trait are now always reanalysed, regardless of any non-trait symbol living in the same file.
- A body-only change of the non-trait symbols in that file still correctly does not force their dependents to be reanalysed.
- Add e2e/result-cache-trait-alongside-class covering the reported scenario: a trait alongside a class in one file, a warm cache, then a trait-body change that introduces an error in a consuming class.
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.

Trait errors behave differently when cached vs non-cached.

2 participants