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
//! Source positions for `builtins.unsafeGetAttrPos` (and `__curPos`).
//!
//! CppNix records, for every attribute-set binding, the source position of
//! its KEY — file + line + column. `builtins.unsafeGetAttrPos name set`
//! returns `{ file; line; column; }` for the key `name` in `set` (or `null`
//! when the key/position is unknown). nixpkgs `lib/types.nix`'s `attrTag`
//! computes each tag's `declarations` from `[ pos.file ]`, so a stub that
//! returns `null` makes every `attrTag` sub-option's `declarations` empty —
//! the `options.json` dock-declarations byte-parity divergence (the six
//! `system.defaults.dock.persistent-{apps,others}.*` fields).
//!
//! This module gives each attrset built from a LITERAL (`eval_attrset`) an
//! optional [`AttrPositions`] table — its static keys' byte offsets keyed by
//! interned `Symbol`, plus the `source_id` of the parse tree it came from —
//! and a per-`source_id` [`SourceInfo`] registry (file path + full text) so
//! an offset resolves to a 1-based line/column.
//!
//! The reported `.file` is passed through [`crate::path::dematerialize`] so a
//! fetched flake input's cache-dir path is lifted to its
//! `/nix/store/<h>-source` store path — the reverse of the read-time
//! `materialize` redirect. That is what makes nix-darwin's `doc/manual`
//! `hasPrefix <nix-darwin>.outPath decl` rewrite fire (`decl` must carry the
//! store prefix), producing the `<nix-darwin/…>` declaration entries.
//!
//! Everything here only produces a REPORTED value on an explicit
//! `unsafeGetAttrPos` call; no value the evaluator observes elsewhere is
//! mutated — the byte-parity invariant.
use RefCell;
use ;
use Rc;
use FxHashMap;
use Symbol;
/// The static keys of one attrset literal, keyed by interned `Symbol`, with
/// each key's byte offset into the source text of `file`.
///
/// Attached (as `Option<Rc<AttrPositions>>`) only to attrsets built by
/// `eval_attrset` from a literal with static (`Ident`/`Str`) keys — `None`
/// for the vast majority of attrsets (merges, overlays, builtin-built,
/// dynamic-key). Shared behind `Rc` so cloning an attrset is a refcount
/// bump, never a map copy.
///
/// `file` is the file the literal was built in — captured at
/// `eval_attrset`-force time from the evaluator's eval-file stack (which a
/// thunk restores to its captured file when it forces), NOT the current
/// parse tree. A lazily-forced attrset literal from `dock.nix` therefore
/// records `dock.nix`, not whatever file happened to be top-of-stack.
thread_local!
/// Register a file's source text so a key offset in that file resolves to a
/// line/column. Called by `eval_with_file`. A `None` file (a `<string>`
/// eval) registers nothing (it has no reportable position).
/// Clear the source-text registry. Called between independent top-level
/// evaluations (alongside the ident-cache clear) so a stale path→text entry
/// from a prior pass doesn't persist.
/// Fetch a registered file's source text (an `Rc<str>` clone), if any.
/// A resolved source position: the file (store-source-lifted) + 1-based
/// line and column — the shape `unsafeGetAttrPos` returns.
/// Resolve `(file, offset)` to a [`ResolvedPos`] — the file is lifted from a
/// fetcher-cache path to its `/nix/store/<h>-source` store path via
/// [`crate::path::dematerialize`]. Returns `None` when `file` is `None` (a
/// `<string>` eval, no position) or the file was never parsed (no source
/// text registered — the attrset can't have originated in a real file).
///
/// LINE/COLUMN match CppNix's *observed* `unsafeGetAttrPos` behavior exactly
/// (byte-parity is sacred, even for a quirk): for a position obtained this
/// way CppNix reports `line = 1` and `column = byte_offset + 1` — the raw
/// 1-based byte offset into the file, NOT a newline-resolved visual position.
/// Verified against `nix eval` on multi-line files (aaaaa@off4→col5,
/// bbbbb@off17→col18, ccccc@off30→col31, all line 1) via both `import` and
/// `--file`. `options.json` itself drops `declarationPositions` (so only
/// `.file` feeds the dock-declarations root), but matching line/column keeps
/// every `unsafeGetAttrPos`-consuming surface byte-identical.
/// CppNix's observed `unsafeGetAttrPos` line/column: `line = 1`,
/// `column = byte_offset + 1` (the raw 1-based byte offset into the file, not
/// a newline-resolved position). See [`resolve`] for the verification.