Skip to content

fix: respawn worker on stale dependency (cross-file false negatives)#4

Merged
fdaviddpt merged 1 commit into
mainfrom
fix/phpstan-cross-file-staleness
Jun 21, 2026
Merged

fix: respawn worker on stale dependency (cross-file false negatives)#4
fdaviddpt merged 1 commit into
mainfrom
fix/phpstan-cross-file-staleness

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Problem

The warm worker memoises a class's reflection for its whole life. Re-analysing a file only re-reads that file's AST — never its dependencies'. So after a dependency was edited (e.g. a method removed), re-analysing a dependent that calls it returned 0 errors, while a cold phpstan run reports the now-undefined call. A silent cross-file false negative — the quiet sibling of the loud rector ClassReflection failure (claude-supertool#273). The same-file edit case was already fine (phpstan re-reads the analysed file).

Proven against a real DVSI checkout: edit Dep (remove value()), validate Dep (clean — it's fine on its own), then validate User which calls Dep::value() → warm returns 0 errors; cold returns Call to an undefined method Dep::value().

Fix

PHPStan's worker exposes no per-class invalidation, so the only cure is a fresh worker. PhpstanRunner now respawns the worker when a non-target file it has analysed changed since the worker booted (workerBootedAt + an analysed-file set, checked before ensureWorker()).

Scoped so speed survives:

  • The analysis target is excluded (phpstan re-reads it anyway), so iterating on a single file never respawns and stays fully warm.
  • The ~20s cold boot is paid only when you switch to a different file after editing a dependency.
  • The staleness check stats only the analysed working set, not the whole --paths tree.

Measured on real DVSI: cold 20.3s vs warm 2.8s — which is why respawn-on-every-edit was rejected in favour of target-excluded scoping.

Tests

  • testStaleDependencyIsCaughtWhenDependentReanalysed — edit a dependency, re-analyse a dependent through the same warm worker, assert the undefined call is reported. Proven red without the respawn, green with it.
  • Full suite 14/14.

CHANGELOG → 0.6.0. After merge: tag 0.6.0 so DVSI can require it.

Honest residual cost

Ping-ponging between two files you've both edited can trigger a 20s respawn per switch; same-file iteration stays warm; CI full phpstan is the backstop. No cheaper correct option exists — the worker has no per-class invalidation.

…ives

The warm worker memoises a class's reflection for its whole life and
re-analysing a file only re-reads that file's own AST — never its
dependencies'. So after a dependency was edited (a method removed), re-analysing
a dependent that calls it returned 0 errors while a cold phpstan run reported the
now-undefined call. Silent cross-file false negative — the quiet sibling of the
loud rector ClassReflection failure (claude-supertool#273). The same-file edit
case was already fine (phpstan re-reads the analysed file).

PHPStan's worker exposes no per-class invalidation, so PhpstanRunner now respawns
the worker when a non-target file it has analysed changed since the worker booted
(workerBootedAt + analysed-file set, checked before ensureWorker). Scoped: the
analysis target is excluded, so iterating on a single file never respawns and
stays fully warm — the ~20s cold boot is paid only when switching to a different
file after editing a dependency. The check stats only the analysed working set,
not the whole --paths tree.

Adds testStaleDependencyIsCaughtWhenDependentReanalysed (proven red without the
respawn, green with it). Full suite 14/14. CHANGELOG -> 0.6.0.

Co-Authored-By: Max <noreply>
@fdaviddpt fdaviddpt merged commit d6cad68 into main Jun 21, 2026
3 checks passed
@fdaviddpt fdaviddpt deleted the fix/phpstan-cross-file-staleness branch June 21, 2026 19:47
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