1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use Arc;
use Issue;
use *;
// `analyze_file` tracked query: the single value-returning analysis driver.
//
// Issues and reference locations are *returned* from the query rather than
// pushed through salsa accumulators. Accumulators were measured to be the
// wrong vehicle here:
//
// 1. `accumulated_by` performs an untracked read, so any tracked query
// consuming accumulator output is permanently invalidated and re-runs
// every revision — derived aggregates can never be built on top.
// 2. Every `accumulated()` call performs a fresh DFS over the query's
// entire dependency subtree (hundreds of edges per file), which is
// O(total_edges) per read at workspace scale.
//
// Returning an `Arc<AnalyzeOutput>` makes the memo a plain value: cheap to
// fetch, shareable with downstream aggregates, and validated by salsa's
// regular red-green algorithm.
/// Reference-index entry as produced during analysis. Mirrors the tuple
/// shape that `Codebase::record_ref` accepts:
///
/// - `symbol_key`: interner-bound string (`"fn:foo"`, `"cls:Foo"`,
/// `"prop:Foo::$bar"`, `"cnst:Foo::BAR"`, `"meth:Foo::bar"` — same keys
/// `Codebase::mark_*_referenced_at` use).
/// - `file`: the file in which the reference appears.
/// - `(line, col_start, col_end)`: span within the file.
/// Singleton salsa input carrying the analysis parameters that aren't
/// already captured by `SourceFile` itself. Lazily created once per
/// database (see `MirDatabase::analyze_config`) so tracked queries that
/// read it get a stable memo key; `MirDbStorage::set_php_version` writes
/// through to it, invalidating dependents on version change.
/// Everything `analyze_file` produces for one file: diagnostics plus the
/// reference-index entries recorded while analyzing its bodies.
///
/// `ref_locs` is sorted + deduplicated so the memo value is deterministic
/// regardless of analysis traversal order — required for salsa backdating
/// (unchanged output ⇒ dependents stay green).
///
/// Per-expression resolved symbols are intentionally NOT part of the memo:
/// a typical file resolves thousands of expressions and retaining them in
/// the salsa cache balloons memory (~50 KiB/file measured on Laravel).
/// Consumers that need symbols call `BodyAnalyzer` directly via
/// `FileAnalyzer`.
unsafe
/// Analyze one file: parse-error issues plus full body analysis, assembled
/// from per-scope memos (see [`super::scopes::infer_scope`]).
///
/// Reads the PHP version from the [`AnalyzeFileInput`] singleton (a tracked
/// field read), so the memo key is just `file` and version changes
/// invalidate all memos at once.
///
/// The per-scope merge means this whole-file memo is cheap to recompute:
/// when a dependency change invalidates it, only the scopes actually
/// reached by the change re-execute — the rest return their memoized
/// results during the merge.