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
//! The names Nix resolves as **bare identifiers** — one list, shared by every
//! engine.
//!
//! # Why this is shared rather than mirrored
//!
//! Nix exposes a small subset of `builtins` at the top level, so `map`,
//! `throw`, `import` and friends work unqualified. Every engine needs that set,
//! and until now each carried its own hand-written copy:
//!
//! | engine | site | count | had `break`? |
//! |---|---|---|---|
//! | tree-walker | `sui-eval/src/builtins/mod.rs` `DEFAULT_SCOPE` | 21 | **no** |
//! | `sui-ir` | `sui-ir/src/builtins.rs` — comment: *"mirrored from sui-eval"* | 21 | **no** |
//! | bytecode VM | `sui-bytecode/src/compiler.rs` `is_global_builtin` | 19 | **yes** |
//!
//! They had already drifted, and the drift was a real wrong answer:
//! `with { break = "LIB"; }; break` evaluated to `"LIB"` on the tree-walker
//! while nix and the VM both say `false`. A genuine global cannot be shadowed
//! by a `with`, so the walker letting one through changes the meaning of a
//! program.
//!
//! Measured against nix 2.31.5 — `break` resolves to a `lambda`, so the VM was
//! right and the other two were missing it:
//!
//! ```text
//! $ nix eval --impure --raw --expr 'builtins.typeOf break' → lambda
//! ```
//!
//! This is the third instance of one shape. `IMPERSONATED_NIX_VERSION`
//! (`crate::versions`) was the first — the VM sat two minor versions behind the
//! walker and silently forked the derivation graph — and the `builtins` attrset
//! name set is the second. Each was N hand-maintained copies of one fact, free
//! to disagree, that did.
//!
//! # The two groups, and why engines treat them differently
//!
//! [`STRUCTURAL_GLOBALS`] are resolved by *construction* rather than by name
//! lookup: `true`/`false`/`null` are compiled as literals and `builtins` is the
//! attrset itself. An engine handles them before it ever consults a name list,
//! which is why the VM's list is 19 where the walker's is 21 — the counts
//! differ for a correct reason, and folding them into one list would force
//! every consumer to re-filter.
//!
//! [`CALLABLE_GLOBALS`] are the rest: names looked up in the `builtins` attrset
//! and bound into the initial scope.
/// Globals an engine resolves structurally, not by looking up a name.
///
/// `true`/`false`/`null` compile to literals; `builtins` is the attrset being
/// built. Listed here so the full global set is derivable in one place, not so
/// that consumers iterate it — most handle these earlier and by other means.
pub const STRUCTURAL_GLOBALS: & = &;
/// Globals resolved by looking the name up in the `builtins` attrset.
///
/// Sorted, so a diff against `builtins.attrNames builtins` reads directly.
///
/// Two entries carry history worth keeping:
/// - **`break`** — the one that had drifted. A real nix global; the walker and
/// `sui-ir` were both missing it, so a `with` could shadow it.
/// - **`placeholder`** — nixpkgs uses `(placeholder "out")` unqualified (e.g.
/// cpython's `--enable-framework=${placeholder "out"}`); without it the bare
/// reference resolves to null inside the module fixpoint and eval breaks.
///
/// The fetchers and `fromTOML` are here for the same reason: nixpkgs uses bare
/// `fetchGit` / `fetchTree` / `fromTOML` unqualified.
pub const CALLABLE_GLOBALS: & = &;
/// Every name nix resolves as a bare identifier.
/// Total size of the global scope — the number to compare against nix.