Track file dependencies for global constant fetches#6023
Conversation
DependencyResolver recorded dependencies for class constants, functions, methods and properties, but not for global/namespaced constant fetches (Node\Expr\ConstFetch). A file that used only a global constant declared elsewhere therefore recorded no dependency on it, so changing the constant did not re-analyse the consumer from the result cache, leaving a stale result (also surfaced by package-granular cache invalidation when the declaring package is bumped). Resolve the constant and record its declaring file, guarding the ubiquitous true/false/null literals so the common path stays cheap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| } | ||
| } | ||
| } | ||
| } elseif ($node instanceof Node\Expr\ConstFetch) { |
There was a problem hiding this comment.
changes in DependencyResolver are usually covered with a e2e tests, see e.g. e5db864
There was a problem hiding this comment.
Adding the e2e test actually surfaced a gap, working on it!
There was a problem hiding this comment.
Thanks, good catch. I added e2e/result-cache-constants modelled on result-cache-traits: it analyses, renames a global constant via a patch, and asserts the file reading it re-analyses and reports the constant as undefined.
Writing the e2e surfaced that the dependency edge alone wasn't enough: global constant declarations weren't exported nodes either, so a constant change never invalidated the reading files (the result cache re-analyses a changed file's dependents only when its exported nodes change). I pushed a second commit exporting global constants (name + value), like class constants already are. With both, the e2e fails before the change and passes after.
The dependency edge added in the previous commit records that a file reads a global constant, but the result cache only re-analyses a changed file's dependents when that file's exported nodes change, and global constant declarations were not exported nodes. So editing a global constant still left the reading files with stale cached results. Export global constant declarations (name + value) like class constants already are, so renaming or changing a constant re-analyses the files that use it. Covered by a result-cache e2e test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This pull request has been marked as ready for review. |
DependencyResolverrecorded file dependencies for class constants, functions, methods and properties, but not for global/namespaced constant fetches (Node\Expr\ConstFetch). On top of that, global constant declarations were not exported nodes. The result cache re-analyses a changed file's dependents only when the changed file's exported nodes change, so a file that reads a global constant was never re-analysed when that constant changed, leaving a stale result.This fixes both halves:
ConstFetchinDependencyResolver), guarding the ubiquitoustrue/false/nullliterals so the common path stays a couple of string comparisons.RootExportedNodes, like class constants already are, so renaming or changing a constant re-analyses the files that use it.Two ways to hit the stale result, both fixed and covered by the
result-cache-constantse2e test:Performance
The
ConstFetchbranch runs for every constant fetch, so it is guarded. Measured over a cold, single-process analysis:ConstFetchnodestrue/false/null)getConstant()callsAdded time is under 0.02% of analysis CPU, and that figure is an upper bound (it includes the measurement probes). The exported-node part runs only for global
constdeclarations, which are rare.Reproducing the staleness
Before this change the second run keeps
b.phpcached and reports nothing; a cold run reports thatf()returnsstring. With the change the second run re-analysesb.phpand matches the cold run.