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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
use fs;
use io;
use ;
use ;
use Serialize;
use crate;
// ---------------------------------------------------------------------------
// Path resolution (private helpers for the graph directory and cache paths).
// ---------------------------------------------------------------------------
// The def/ref graph lives in the git COMMON dir (shared by every linked
// worktree, alongside the manifest), one level down; a non-git root falls
// back to `<root>/.scout/graph/`.
pub
/// Path to the graph artifact (`graph.json`) for `root`.
/// Path to the project-model staleness sidecar for `root`.
///
/// Holds exactly the bytes `graph.json`'s `units` array would carry -- the
/// serialized `Vec<GraphUnit>` and nothing else. `map` compares this file
/// against a freshly discovered model to notice a `.csproj` edit, which no
/// mtime in the fragments index can see: `.csproj` is not a `SOURCE_EXT`, so
/// editing one moves no graph file and `index_is_stale` stays false. Written
/// by `rebuild_graph` only when a model exists and DELETED when one does not,
/// so a repo that never had a `.csproj` never grows the file and one whose
/// last `.csproj` was removed still sees a difference on the next run.
/// Path to the Rust crate-model sidecar for `root`.
///
/// Holds the serialized Cargo package model (`rsgraph::crates::RustCrates`)
/// the last rebuild resolved Rust paths against. `map` compares it with a
/// freshly discovered model to notice a `Cargo.toml` edit, which moves no
/// walked mtime. Written only when a package is in scope and deleted
/// otherwise, the same rule as `project_units_path`.
// The version in both cache filenames is the fragment SCHEMA version, bumped
// whenever the extractor starts recording something old cached fragments
// lack -- v10 added def `type_params`/`base_generic_args` and the new
// `ctor-param` ref kind (with its `args` field), v11 added markup graph
// facts (a `.xaml` file's fragment now carries the `x:Class` def and its
// element/`x:Bind` refs, where every cached markup fragment before it carried
// names only), v12 added the TS/JS reference fragment, v13 added def
// `propertyTypes` and ref `receiverPropertyOwner` plus the ref
// `receiverCallOwner`/`receiverCallMember` pair, plus the foreach
// element-type fact -- no new field (it settles into the existing
// `receiver_type`), but a cached fragment from before it can still disagree
// with a fresh one for the same unchanged file, so it rides the same bump
// rather than skipping it. v16 added the ref `receiverBase` flag, set only
// for a `base.` qualifier -- a cached v15 fragment carries none, so every
// `base.` receiver would silently resolve (or fail to resolve) as if it
// were a plain `this.` receiver. v17 added no field: the extractor now
// blanks the inactive arms of `#if`/`#else` groups before parsing, so a
// cached v16 fragment of an unchanged file can carry twin defs and refs
// from both arms that a fresh one no longer records -- the same
// "disagrees for an unchanged file" case that moved v13, so it rides a
// bump too. v18 added def `methodParams` and ref `receiverLambda`, the
// untyped-lambda-parameter callee slot -- a cached v17 fragment carries
// neither, so every per-overload parameter shape and every untyped-lambda
// callee-slot lookup they back would silently see no candidates. v19
// added def `overrideMethods` (the `override`-marked method names a type
// declares) and the fragment-level `registrations` list (each two-type-argument
// DI service registration a file's invocations record) -- a cached v18
// fragment carries neither, so every override-chain lookup and every
// registration-driven `implements` edge would silently see no facts. v20
// added ref `receiverNullable` and `lambdaArgArity` -- a cached v19
// fragment carries neither, so a `T?` receiver's own `Nullable<T>.Value`/
// `HasValue`/`GetValueOrDefault` unwrap would silently keep resolving
// against `T`'s own same-named member, and a delegate-shaped argument whose
// own parameter count disagrees with a candidate's delegate parameter
// shape would silently keep binding it. v21 fills ref `typeArgCount` on
// every `uses-member` ref -- a cached v20 fragment carries `null` there, so
// a member qualifier written with a type-argument list would silently keep
// binding a same-named sibling of the WRONG arity, and a bare qualifier
// would silently keep binding whichever same-named sibling the def index
// happened to order first. v22 added def `baseTypeArgs` (a base's NESTED
// generic-argument descriptors, alongside the already-flat
// `baseGenericArgs`), the fragment-level `publishes` list (each
// message-bus publish-site fact a file's invocations record), def
// `propertyMessageArgs` (the single type arguments a type's own properties
// wrap), publish `enclosingMethod` (set when a publish hands on its own
// caller's message, which makes the enclosing method a forwarding
// wrapper), the fragment-level `handlerRegistrations` list (each
// one-type-argument handler registration a file's invocations record), and
// publish sites whose message is named by the call itself under a verb
// this engine does not ship -- a cached v21 fragment carries none of them,
// so a nested base's inner type argument (`IConsumer<Batch<T>>`'s `T`),
// every publish-site fact and every message vocabulary a repository
// declares about itself would silently be missing from a reused cache.
// v23 added def `arrayMessageBases` (the array-marked subset of a def's own
// `bases`, for a base whose message-position argument is an array type) --
// a cached v22 fragment carries none, so a single message and an array of
// it would read as the same base-list argument. v22 never shipped in an
// accepted or released build; every run that wrote one, including this
// lane's own measurement runs, is superseded by the rename same as any
// other generation.
// v24 drops explicit interface implementations (`IFace.Member` declarations)
// from a def's `methods`, `methodReturns`, `methodReturnArgs`,
// `nonPublicMethods`, `properties` and `propertyTypes`, and from the
// class-scope receiver facts a bare property name reads -- a cached v23
// fragment of an unchanged file still lists them, so `this.M()`, a property
// hop and a bare name inside the implementing type would keep binding a
// member C# lookup never finds there.
// v25 adds `namespace` to a plain `usings` entry: the namespace the directive
// is declared in, which bare type-name resolution reads to consult a using
// only at its own namespace level, after that level's types. A cached v24
// fragment of an unchanged file carries no such key and reads every using as
// declared at compilation-unit level, so a using written inside a namespace
// block would lose to a type in a shallower namespace and one written in a
// sibling block would still apply. The same generation keeps the written
// qualifier on a publish `message` the call names itself (`new Ns.M()` is
// `Ns.M`, no longer `M`), so the message resolves through that qualifier; a
// cached v24 fragment of an unchanged file still carries the bare name, which
// the call site's namespace can bind to a different same-named type.
// v26 adds ref `methodTypeArgCount` (the length of a type-argument list
// written on the invoked member itself), def `methodTypeArities` (each
// overload's own type-parameter count, aligned with `methodArities`) and
// extension-entry `typeParamCount`, and keeps two extension overloads that
// differ only in their type-parameter count as separate entries. A cached v25
// fragment of an unchanged file carries none of them, so `x.M<A, B>()` would
// keep binding an `M<T>` overload C# refuses, where a fresh fragment of the
// same file binds only an overload with two type parameters.
// v27 adds ref `lambdaArgShape` (whether each positional lambda literal's body
// can only produce a value, and whether its parameters are implicitly typed),
// def `methodTypeParamSites` (where each overload's own type parameters occur
// in its parameter types, aligned with `methodParams`) and def
// `delegateSignature` (a delegate's return and where its type parameters
// occur). A cached v26 fragment of an unchanged file carries none of them, so
// `list.Any(m => true)` would keep binding an `Any(Action<F>)` or an
// uninferable `Any<T>(FilterDelegate<IX<T>>)` overload C# refuses, where a
// fresh fragment of the same file skips both.
// v28 adds the Rust fragment arm (`rs: 1`). No fragment an earlier build
// wrote changes shape; the bump exists so a build without that arm never
// meets a Rust fragment, because one cache entry no arm accepts makes the
// whole cache fail to parse and every file reparse on every run.
// v29 adds Rust resolution facts (`modFiles`, `ctors`, `tests`, `impls`,
// `uses`, `blocks`, `sites`) and module scopes extended by item macros.
// A cached v28 Rust fragment lacks these facts, so edges of an unchanged
// file would be missing and globs reaching an item-macro module could be
// resolved incorrectly.
// v30 appends Rust `modPaths` (the file a `#[path]` attribute names for a
// `mod x;` declaration) and lists in `tests` the functions a `cfg_attr`
// marks as tests. A cached v29 Rust fragment of an unchanged file carries
// neither, so its `#[path]` modules would keep reaching the default file
// rustc does not compile, and its `cfg_attr` tests would stay unmarked.
// v31 adds `bound_defs` to a TS `import` edge: the definitions its named,
// default and type-only bindings resolve to. The fragments themselves are
// unchanged, but an older cache pair of an unchanged repository would let `map`
// skip the rebuild and keep a `graph.json` whose import edges bind nothing,
// so every TS import row would stay missing until some file changed.
// v32 adds ref `receiverTypeQualified` and fact `qualifiedType` -- the
// written path of a receiver's own declared type, alongside the bare last
// segment `receiverType`/`type` already carry. A cached v31 fragment of an
// unchanged file carries neither, so a receiver declared `Outer.Type` would
// keep resolving through its tail, which the ladder's using and
// graph-wide-uniqueness steps answer with whatever same-named type they
// reach, where a fresh fragment of the same file resolves the written path.
// v33 adds ref `argTypes`, an ordered static-type fact for each written call
// argument, and records conversion, variance and selected result descriptors
// in the existing `methodParams` table. A cached v32 fragment has none of
// these facts, so a same-count overload that rejects the arguments could
// still bind precisely ahead of an applicable base or extension method.
// The rename IS the invalidation mechanism:
// pre-bump caches stop being found, every file reparses
// once, no reader carries version-compat logic. Writers delete every
// superseded generation (see `remove_superseded_caches`).
pub
pub
// Every generation below the current one, not just the immediately previous:
// a repo mapped last under v1 and never since would otherwise keep its v1
// pair forever.
pub const SUPERSEDED_CACHE_FILES: & = &;
pub
// ---------------------------------------------------------------------------
// Atomic writes -- tmp file in the SAME directory as the target (so
// the final `rename` is same-filesystem and therefore atomic), unique name
// so concurrent writers (this process's own sequential graph/fragments/index
// writes, or a genuinely concurrent process) never collide. Writing to a
// tmp file and renaming is a deliberate upgrade over an in-place write --
// output BYTES are unaffected, only the write's crash-safety.
// ---------------------------------------------------------------------------
static TMP_COUNTER: AtomicU64 = new;
pub
pub