@llui/compiler 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accessor-resolver.d.ts +58 -0
  3. package/dist/accessor-resolver.d.ts.map +1 -0
  4. package/dist/accessor-resolver.js +119 -0
  5. package/dist/accessor-resolver.js.map +1 -0
  6. package/dist/binding-descriptors.d.ts +105 -0
  7. package/dist/binding-descriptors.d.ts.map +1 -0
  8. package/dist/binding-descriptors.js +340 -0
  9. package/dist/binding-descriptors.js.map +1 -0
  10. package/dist/collect-deps.d.ts +49 -0
  11. package/dist/collect-deps.d.ts.map +1 -0
  12. package/dist/collect-deps.js +444 -0
  13. package/dist/collect-deps.js.map +1 -0
  14. package/dist/compiler-cache.d.ts +20 -0
  15. package/dist/compiler-cache.d.ts.map +1 -0
  16. package/dist/compiler-cache.js +20 -0
  17. package/dist/compiler-cache.js.map +1 -0
  18. package/dist/cross-file-resolver.d.ts +109 -0
  19. package/dist/cross-file-resolver.d.ts.map +1 -0
  20. package/dist/cross-file-resolver.js +530 -0
  21. package/dist/cross-file-resolver.js.map +1 -0
  22. package/dist/cross-file-walker.d.ts +63 -0
  23. package/dist/cross-file-walker.d.ts.map +1 -0
  24. package/dist/cross-file-walker.js +516 -0
  25. package/dist/cross-file-walker.js.map +1 -0
  26. package/dist/diagnostic.d.ts +76 -0
  27. package/dist/diagnostic.d.ts.map +1 -0
  28. package/dist/diagnostic.js +59 -0
  29. package/dist/diagnostic.js.map +1 -0
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +37 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/introspection-factory.d.ts +54 -0
  35. package/dist/introspection-factory.d.ts.map +1 -0
  36. package/dist/introspection-factory.js +46 -0
  37. package/dist/introspection-factory.js.map +1 -0
  38. package/dist/manifest.d.ts +144 -0
  39. package/dist/manifest.d.ts.map +1 -0
  40. package/dist/manifest.js +209 -0
  41. package/dist/manifest.js.map +1 -0
  42. package/dist/module.d.ts +222 -0
  43. package/dist/module.d.ts.map +1 -0
  44. package/dist/module.js +256 -0
  45. package/dist/module.js.map +1 -0
  46. package/dist/modules/_element-helpers.d.ts +4 -0
  47. package/dist/modules/_element-helpers.d.ts.map +1 -0
  48. package/dist/modules/_element-helpers.js +138 -0
  49. package/dist/modules/_element-helpers.js.map +1 -0
  50. package/dist/modules/_msg-variants.d.ts +10 -0
  51. package/dist/modules/_msg-variants.d.ts.map +1 -0
  52. package/dist/modules/_msg-variants.js +97 -0
  53. package/dist/modules/_msg-variants.js.map +1 -0
  54. package/dist/modules/_shared.d.ts +16 -0
  55. package/dist/modules/_shared.d.ts.map +1 -0
  56. package/dist/modules/_shared.js +30 -0
  57. package/dist/modules/_shared.js.map +1 -0
  58. package/dist/modules/accessibility.d.ts +3 -0
  59. package/dist/modules/accessibility.d.ts.map +1 -0
  60. package/dist/modules/accessibility.js +82 -0
  61. package/dist/modules/accessibility.js.map +1 -0
  62. package/dist/modules/accessor-side-effect.d.ts +3 -0
  63. package/dist/modules/accessor-side-effect.d.ts.map +1 -0
  64. package/dist/modules/accessor-side-effect.js +113 -0
  65. package/dist/modules/accessor-side-effect.js.map +1 -0
  66. package/dist/modules/agent-emits-drift.d.ts +3 -0
  67. package/dist/modules/agent-emits-drift.d.ts.map +1 -0
  68. package/dist/modules/agent-emits-drift.js +158 -0
  69. package/dist/modules/agent-emits-drift.js.map +1 -0
  70. package/dist/modules/agent-example-on-payload.d.ts +3 -0
  71. package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
  72. package/dist/modules/agent-example-on-payload.js +53 -0
  73. package/dist/modules/agent-example-on-payload.js.map +1 -0
  74. package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
  75. package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
  76. package/dist/modules/agent-exclusive-annotations.js +68 -0
  77. package/dist/modules/agent-exclusive-annotations.js.map +1 -0
  78. package/dist/modules/agent-missing-intent.d.ts +3 -0
  79. package/dist/modules/agent-missing-intent.d.ts.map +1 -0
  80. package/dist/modules/agent-missing-intent.js +47 -0
  81. package/dist/modules/agent-missing-intent.js.map +1 -0
  82. package/dist/modules/agent-msg-resolvable.d.ts +3 -0
  83. package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
  84. package/dist/modules/agent-msg-resolvable.js +161 -0
  85. package/dist/modules/agent-msg-resolvable.js.map +1 -0
  86. package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
  87. package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
  88. package/dist/modules/agent-nonextractable-handler.js +127 -0
  89. package/dist/modules/agent-nonextractable-handler.js.map +1 -0
  90. package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
  91. package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
  92. package/dist/modules/agent-optional-field-undocumented.js +67 -0
  93. package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
  94. package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
  95. package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
  96. package/dist/modules/agent-tagsend-translator-missing.js +58 -0
  97. package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
  98. package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
  99. package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
  100. package/dist/modules/agent-warning-on-confirm.js +46 -0
  101. package/dist/modules/agent-warning-on-confirm.js.map +1 -0
  102. package/dist/modules/async-update.d.ts +3 -0
  103. package/dist/modules/async-update.d.ts.map +1 -0
  104. package/dist/modules/async-update.js +86 -0
  105. package/dist/modules/async-update.js.map +1 -0
  106. package/dist/modules/binding-descriptors.d.ts +4 -0
  107. package/dist/modules/binding-descriptors.d.ts.map +1 -0
  108. package/dist/modules/binding-descriptors.js +48 -0
  109. package/dist/modules/binding-descriptors.js.map +1 -0
  110. package/dist/modules/bitmask-overflow.d.ts +3 -0
  111. package/dist/modules/bitmask-overflow.d.ts.map +1 -0
  112. package/dist/modules/bitmask-overflow.js +152 -0
  113. package/dist/modules/bitmask-overflow.js.map +1 -0
  114. package/dist/modules/compiler-stamp.d.ts +3 -0
  115. package/dist/modules/compiler-stamp.d.ts.map +1 -0
  116. package/dist/modules/compiler-stamp.js +44 -0
  117. package/dist/modules/compiler-stamp.js.map +1 -0
  118. package/dist/modules/component-meta.d.ts +3 -0
  119. package/dist/modules/component-meta.d.ts.map +1 -0
  120. package/dist/modules/component-meta.js +44 -0
  121. package/dist/modules/component-meta.js.map +1 -0
  122. package/dist/modules/controlled-input.d.ts +3 -0
  123. package/dist/modules/controlled-input.d.ts.map +1 -0
  124. package/dist/modules/controlled-input.js +68 -0
  125. package/dist/modules/controlled-input.js.map +1 -0
  126. package/dist/modules/core-synthesis.d.ts +18 -0
  127. package/dist/modules/core-synthesis.d.ts.map +1 -0
  128. package/dist/modules/core-synthesis.js +748 -0
  129. package/dist/modules/core-synthesis.js.map +1 -0
  130. package/dist/modules/direct-state-in-view.d.ts +3 -0
  131. package/dist/modules/direct-state-in-view.d.ts.map +1 -0
  132. package/dist/modules/direct-state-in-view.js +103 -0
  133. package/dist/modules/direct-state-in-view.js.map +1 -0
  134. package/dist/modules/each-closure-violation.d.ts +3 -0
  135. package/dist/modules/each-closure-violation.d.ts.map +1 -0
  136. package/dist/modules/each-closure-violation.js +255 -0
  137. package/dist/modules/each-closure-violation.js.map +1 -0
  138. package/dist/modules/each-memo.d.ts +15 -0
  139. package/dist/modules/each-memo.d.ts.map +1 -0
  140. package/dist/modules/each-memo.js +115 -0
  141. package/dist/modules/each-memo.js.map +1 -0
  142. package/dist/modules/effect-without-handler.d.ts +3 -0
  143. package/dist/modules/effect-without-handler.d.ts.map +1 -0
  144. package/dist/modules/effect-without-handler.js +92 -0
  145. package/dist/modules/effect-without-handler.js.map +1 -0
  146. package/dist/modules/element-rewrite.d.ts +22 -0
  147. package/dist/modules/element-rewrite.d.ts.map +1 -0
  148. package/dist/modules/element-rewrite.js +1017 -0
  149. package/dist/modules/element-rewrite.js.map +1 -0
  150. package/dist/modules/empty-props.d.ts +3 -0
  151. package/dist/modules/empty-props.d.ts.map +1 -0
  152. package/dist/modules/empty-props.js +50 -0
  153. package/dist/modules/empty-props.js.map +1 -0
  154. package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
  155. package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
  156. package/dist/modules/exhaustive-effect-handling.js +61 -0
  157. package/dist/modules/exhaustive-effect-handling.js.map +1 -0
  158. package/dist/modules/exhaustive-update.d.ts +3 -0
  159. package/dist/modules/exhaustive-update.d.ts.map +1 -0
  160. package/dist/modules/exhaustive-update.js +146 -0
  161. package/dist/modules/exhaustive-update.js.map +1 -0
  162. package/dist/modules/forgotten-spread.d.ts +3 -0
  163. package/dist/modules/forgotten-spread.d.ts.map +1 -0
  164. package/dist/modules/forgotten-spread.js +51 -0
  165. package/dist/modules/forgotten-spread.js.map +1 -0
  166. package/dist/modules/form-boilerplate.d.ts +3 -0
  167. package/dist/modules/form-boilerplate.d.ts.map +1 -0
  168. package/dist/modules/form-boilerplate.js +101 -0
  169. package/dist/modules/form-boilerplate.js.map +1 -0
  170. package/dist/modules/imperative-dom-in-view.d.ts +3 -0
  171. package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
  172. package/dist/modules/imperative-dom-in-view.js +123 -0
  173. package/dist/modules/imperative-dom-in-view.js.map +1 -0
  174. package/dist/modules/item-dedup.d.ts +7 -0
  175. package/dist/modules/item-dedup.d.ts.map +1 -0
  176. package/dist/modules/item-dedup.js +204 -0
  177. package/dist/modules/item-dedup.js.map +1 -0
  178. package/dist/modules/map-on-state-array.d.ts +3 -0
  179. package/dist/modules/map-on-state-array.d.ts.map +1 -0
  180. package/dist/modules/map-on-state-array.js +84 -0
  181. package/dist/modules/map-on-state-array.js.map +1 -0
  182. package/dist/modules/mask-legend.d.ts +10 -0
  183. package/dist/modules/mask-legend.d.ts.map +1 -0
  184. package/dist/modules/mask-legend.js +50 -0
  185. package/dist/modules/mask-legend.js.map +1 -0
  186. package/dist/modules/missing-memo.d.ts +3 -0
  187. package/dist/modules/missing-memo.d.ts.map +1 -0
  188. package/dist/modules/missing-memo.js +114 -0
  189. package/dist/modules/missing-memo.js.map +1 -0
  190. package/dist/modules/msg-annotations.d.ts +9 -0
  191. package/dist/modules/msg-annotations.d.ts.map +1 -0
  192. package/dist/modules/msg-annotations.js +54 -0
  193. package/dist/modules/msg-annotations.js.map +1 -0
  194. package/dist/modules/msg-schema.d.ts +10 -0
  195. package/dist/modules/msg-schema.d.ts.map +1 -0
  196. package/dist/modules/msg-schema.js +70 -0
  197. package/dist/modules/msg-schema.js.map +1 -0
  198. package/dist/modules/namespace-import.d.ts +3 -0
  199. package/dist/modules/namespace-import.d.ts.map +1 -0
  200. package/dist/modules/namespace-import.js +80 -0
  201. package/dist/modules/namespace-import.js.map +1 -0
  202. package/dist/modules/nested-send-in-update.d.ts +3 -0
  203. package/dist/modules/nested-send-in-update.d.ts.map +1 -0
  204. package/dist/modules/nested-send-in-update.js +77 -0
  205. package/dist/modules/nested-send-in-update.js.map +1 -0
  206. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
  207. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
  208. package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
  209. package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
  210. package/dist/modules/no-eager-item-accessor.d.ts +3 -0
  211. package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
  212. package/dist/modules/no-eager-item-accessor.js +74 -0
  213. package/dist/modules/no-eager-item-accessor.js.map +1 -0
  214. package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
  215. package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
  216. package/dist/modules/no-let-reactive-accessor.js +227 -0
  217. package/dist/modules/no-let-reactive-accessor.js.map +1 -0
  218. package/dist/modules/no-list-render-in-sample.d.ts +3 -0
  219. package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
  220. package/dist/modules/no-list-render-in-sample.js +89 -0
  221. package/dist/modules/no-list-render-in-sample.js.map +1 -0
  222. package/dist/modules/no-sample-in-accessor.d.ts +3 -0
  223. package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
  224. package/dist/modules/no-sample-in-accessor.js +141 -0
  225. package/dist/modules/no-sample-in-accessor.js.map +1 -0
  226. package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
  227. package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
  228. package/dist/modules/no-sample-in-reactive-position.js +72 -0
  229. package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
  230. package/dist/modules/pure-update-function.d.ts +3 -0
  231. package/dist/modules/pure-update-function.d.ts.map +1 -0
  232. package/dist/modules/pure-update-function.js +127 -0
  233. package/dist/modules/pure-update-function.js.map +1 -0
  234. package/dist/modules/reactive-paths.d.ts +3 -0
  235. package/dist/modules/reactive-paths.d.ts.map +1 -0
  236. package/dist/modules/reactive-paths.js +77 -0
  237. package/dist/modules/reactive-paths.js.map +1 -0
  238. package/dist/modules/row-factory.d.ts +12 -0
  239. package/dist/modules/row-factory.d.ts.map +1 -0
  240. package/dist/modules/row-factory.js +385 -0
  241. package/dist/modules/row-factory.js.map +1 -0
  242. package/dist/modules/schema-hash.d.ts +15 -0
  243. package/dist/modules/schema-hash.d.ts.map +1 -0
  244. package/dist/modules/schema-hash.js +70 -0
  245. package/dist/modules/schema-hash.js.map +1 -0
  246. package/dist/modules/spread-in-children.d.ts +3 -0
  247. package/dist/modules/spread-in-children.d.ts.map +1 -0
  248. package/dist/modules/spread-in-children.js +144 -0
  249. package/dist/modules/spread-in-children.js.map +1 -0
  250. package/dist/modules/state-mutation.d.ts +3 -0
  251. package/dist/modules/state-mutation.d.ts.map +1 -0
  252. package/dist/modules/state-mutation.js +138 -0
  253. package/dist/modules/state-mutation.js.map +1 -0
  254. package/dist/modules/state-schema.d.ts +8 -0
  255. package/dist/modules/state-schema.d.ts.map +1 -0
  256. package/dist/modules/state-schema.js +55 -0
  257. package/dist/modules/state-schema.js.map +1 -0
  258. package/dist/modules/static-items.d.ts +3 -0
  259. package/dist/modules/static-items.d.ts.map +1 -0
  260. package/dist/modules/static-items.js +125 -0
  261. package/dist/modules/static-items.js.map +1 -0
  262. package/dist/modules/static-on.d.ts +3 -0
  263. package/dist/modules/static-on.d.ts.map +1 -0
  264. package/dist/modules/static-on.js +100 -0
  265. package/dist/modules/static-on.js.map +1 -0
  266. package/dist/modules/string-effect-callback.d.ts +3 -0
  267. package/dist/modules/string-effect-callback.d.ts.map +1 -0
  268. package/dist/modules/string-effect-callback.js +50 -0
  269. package/dist/modules/string-effect-callback.js.map +1 -0
  270. package/dist/modules/structural-mask.d.ts +8 -0
  271. package/dist/modules/structural-mask.d.ts.map +1 -0
  272. package/dist/modules/structural-mask.js +76 -0
  273. package/dist/modules/structural-mask.js.map +1 -0
  274. package/dist/modules/subapp-requires-reason.d.ts +3 -0
  275. package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
  276. package/dist/modules/subapp-requires-reason.js +129 -0
  277. package/dist/modules/subapp-requires-reason.js.map +1 -0
  278. package/dist/modules/text-mask.d.ts +12 -0
  279. package/dist/modules/text-mask.d.ts.map +1 -0
  280. package/dist/modules/text-mask.js +63 -0
  281. package/dist/modules/text-mask.js.map +1 -0
  282. package/dist/modules/view-bag-import.d.ts +3 -0
  283. package/dist/modules/view-bag-import.d.ts.map +1 -0
  284. package/dist/modules/view-bag-import.js +80 -0
  285. package/dist/modules/view-bag-import.js.map +1 -0
  286. package/dist/msg-annotations.d.ts +104 -0
  287. package/dist/msg-annotations.d.ts.map +1 -0
  288. package/dist/msg-annotations.js +242 -0
  289. package/dist/msg-annotations.js.map +1 -0
  290. package/dist/msg-schema.d.ts +130 -0
  291. package/dist/msg-schema.d.ts.map +1 -0
  292. package/dist/msg-schema.js +770 -0
  293. package/dist/msg-schema.js.map +1 -0
  294. package/dist/schema-hash.d.ts +16 -0
  295. package/dist/schema-hash.d.ts.map +1 -0
  296. package/dist/schema-hash.js +31 -0
  297. package/dist/schema-hash.js.map +1 -0
  298. package/dist/state-schema.d.ts +41 -0
  299. package/dist/state-schema.d.ts.map +1 -0
  300. package/dist/state-schema.js +156 -0
  301. package/dist/state-schema.js.map +1 -0
  302. package/dist/transform.d.ts +109 -0
  303. package/dist/transform.d.ts.map +1 -0
  304. package/dist/transform.js +1390 -0
  305. package/dist/transform.js.map +1 -0
  306. package/dist/version.d.ts +11 -0
  307. package/dist/version.d.ts.map +1 -0
  308. package/dist/version.js +11 -0
  309. package/dist/version.js.map +1 -0
  310. package/package.json +47 -0
@@ -0,0 +1,748 @@
1
+ // `core-synthesis` — owns the co-emitted core trio:
2
+ // - `__update` — Phase 1/2 dispatcher; reads `structuralMask`
3
+ // - `__handlers` — per-message-type specialized handlers
4
+ // - `__prefixes` — array of path-keyed reference-stable closures
5
+ //
6
+ // The three are NOT decomposable into separate modules (per v2c §7.9.2
7
+ // design decision (a) vs (b)): they share `topLevelBits` /
8
+ // `structuralMask` / `fieldBits` intermediates, and `__prefixes`
9
+ // ordering is bit-position-keyed (the array index *is* the bit position
10
+ // used by every binding's mask). Producing them in three independent
11
+ // emit passes would either duplicate the analysis or require a shared
12
+ // scratchpad slot — both lose vs the function's existing single pass.
13
+ //
14
+ // So this module owns the entire synthesis: `tryInjectDirty` plus its
15
+ // ~600 lines of supporting helpers (`tryBuildHandlers`,
16
+ // `buildCaseHandler`, `buildUpdateBody`, `buildPrefixesProp`,
17
+ // `computeStructuralMask`, `buildAccess`, plus the case-analysis
18
+ // helpers `detectArrayOp`, `findReturnArray`, `detectStrideLoop`,
19
+ // `hasSliceAssignment`, `analyzeModifiedFields`). Moved verbatim from
20
+ // transform.ts in v2c/decomp-21.
21
+ //
22
+ // Side-effect: the inline call sets `usesApplyBinding = true` when
23
+ // the rewrite fires (drives `__runPhase2` + `__handleMsg` imports in
24
+ // `cleanupImports`). The module surfaces this via `CORE_SYNTHESIS_SLOT`
25
+ // for the umbrella to read after `registry.run`.
26
+ import ts from 'typescript';
27
+ import { computeAccessorMask, createMaskLiteral, isComponentCall } from '../transform.js';
28
+ export const CORE_SYNTHESIS_SLOT = 'core-synthesis:state';
29
+ export function coreSynthesisModule(opts) {
30
+ const { fieldBits, fieldBitsHi, lluiImport } = opts;
31
+ return {
32
+ name: 'core-synthesis',
33
+ compilerVersion: '^0.3.0',
34
+ diagnostics: [],
35
+ visitors: {},
36
+ transformCallEnter(ctx, node) {
37
+ if (!isComponentCall(node, lluiImport))
38
+ return null;
39
+ const rewritten = tryInjectDirty(node, fieldBits, ctx.factory, fieldBitsHi);
40
+ if (!rewritten)
41
+ return null;
42
+ const slot = ctx.analysis.perModule.get(CORE_SYNTHESIS_SLOT);
43
+ if (slot)
44
+ slot.usesApplyBinding = true;
45
+ else
46
+ ctx.analysis.perModule.set(CORE_SYNTHESIS_SLOT, {
47
+ usesApplyBinding: true,
48
+ });
49
+ return rewritten;
50
+ },
51
+ };
52
+ }
53
+ // ─── Synthesis implementation (moved verbatim from transform.ts) ────
54
+ function tryInjectDirty(node, fieldBits, f, fieldBitsHi = new Map()) {
55
+ if (fieldBits.size === 0 && fieldBitsHi.size === 0)
56
+ return null;
57
+ const configArg = node.arguments[0];
58
+ if (!configArg || !ts.isObjectLiteralExpression(configArg))
59
+ return null;
60
+ // Check if __dirty already exists
61
+ for (const prop of configArg.properties) {
62
+ if (ts.isPropertyAssignment(prop) &&
63
+ ts.isIdentifier(prop.name) &&
64
+ prop.name.text === '__dirty') {
65
+ return null;
66
+ }
67
+ }
68
+ // Top-level field → aggregated bit mask. Sub-paths under one field
69
+ // (`route.page`, `route.data`) collapse into a single entry so
70
+ // `tryBuildHandlers` can reason per-field. Positions 0..30 live here;
71
+ // 31..61 in the parallel high-word map below. `__maskLegend` itself
72
+ // is now owned by `maskLegendModule` (v2c/decomp-9).
73
+ const topLevelBits = new Map();
74
+ for (const [path, bit] of fieldBits) {
75
+ const topField = path.split('.')[0];
76
+ topLevelBits.set(topField, (topLevelBits.get(topField) ?? 0) | bit);
77
+ }
78
+ const topLevelBitsHi = new Map();
79
+ for (const [path, bit] of fieldBitsHi) {
80
+ const topField = path.split('.')[0];
81
+ topLevelBitsHi.set(topField, (topLevelBitsHi.get(topField) ?? 0) | bit);
82
+ }
83
+ // Structural mask — used by both __update and __handlers
84
+ const structuralMask = computeStructuralMask(configArg, fieldBits);
85
+ const updateBody = buildUpdateBody(f, structuralMask);
86
+ // `dHi` is the high-word dirty mask, appended as the trailing
87
+ // positional arg so stale 5-param compiled bundles continue to gate
88
+ // correctly: the runtime calls `__update(s, d, b, bl, p, dHi)`,
89
+ // old bundles' 5-param arrow ignores the extra arg (for ≤31-prefix
90
+ // components dHi is always 0 anyway). New bundles use it for
91
+ // precise two-word Phase 1 gating.
92
+ const updateFn = f.createArrowFunction(undefined, undefined, [
93
+ f.createParameterDeclaration(undefined, undefined, 's'),
94
+ f.createParameterDeclaration(undefined, undefined, 'd'),
95
+ f.createParameterDeclaration(undefined, undefined, 'b'),
96
+ f.createParameterDeclaration(undefined, undefined, 'bl'),
97
+ f.createParameterDeclaration(undefined, undefined, 'p'),
98
+ f.createParameterDeclaration(undefined, undefined, 'dHi', undefined, undefined, f.createNumericLiteral(0)),
99
+ ], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), updateBody);
100
+ const updateProp = f.createPropertyAssignment('__update', updateFn);
101
+ // __handlers: per-message-type specialized update functions.
102
+ // Analyzes the update() switch/case and generates direct handlers
103
+ // that bypass the generic Phase 1/2 pipeline for single-message updates.
104
+ const handlersProp = tryBuildHandlers(configArg, topLevelBits, topLevelBitsHi, structuralMask, f);
105
+ // Both `__update` and `__handlers` carry two-word gates: `__update`'s
106
+ // Phase 1 block loop uses `(mask & d) | (maskHi & dHi)`, and
107
+ // `__handlers` passes `caseDirtyHi` to `_handleMsg` which gates blocks
108
+ // against both words. `dHi` defaults to 0 so any stale 5-arg call site
109
+ // still works. `__dirty` is no longer emitted — `__prefixes` (below)
110
+ // is strictly more precise, and the runtime throws on hand-authored
111
+ // `__dirty`. `__maskLegend` survives because the agent layer uses it
112
+ // to decode runtime dirty masks back to top-level field names.
113
+ // `__maskLegend` is emitted by `maskLegendModule` via the registry
114
+ // bridge (v2c/decomp-9); the umbrella's `applyRegistryEmissions` step
115
+ // splices it into the same config-arg literal we return here.
116
+ const extraProps = [updateProp];
117
+ if (handlersProp)
118
+ extraProps.push(handlersProp);
119
+ // __prefixes: opt-in path-keyed reactivity (see
120
+ // docs/proposals/unified-composition-model.md). One closure per
121
+ // distinct path that an accessor reads, hoisted into a stable array;
122
+ // the array position IS the bit position used by the path's bindings.
123
+ // The runtime prefers __prefixes when present and computes the
124
+ // combinedDirty mask by reference-comparing `prefix(prev)` vs
125
+ // `prefix(next)` for each entry — strictly more precise than the
126
+ // top-level-conflated __dirty (which always co-fires bindings sharing
127
+ // a top-level field even when only one sub-path actually mutated).
128
+ //
129
+ // Emit `__prefixes` whenever any reactive paths are present. For
130
+ // components with ≤31 paths, the runtime's
131
+ // `computeDirtyFromPrefixes` returns a single `number`; for
132
+ // 32..61-path components it returns a `[lo, hi]` tuple that the
133
+ // runtime fans out into `combinedDirty` + `combinedDirtyHi`. The
134
+ // binding-level mask gating is still single-word at the compiler
135
+ // emit layer today, so high-position bindings still re-evaluate
136
+ // every cycle — but the dirty computation itself is now precise,
137
+ // which lets memo()'d aggregates short-circuit correctly.
138
+ const prefixesProp = buildPrefixesProp(fieldBits, fieldBitsHi, f);
139
+ if (prefixesProp)
140
+ extraProps.push(prefixesProp);
141
+ const newConfig = f.createObjectLiteralExpression([...configArg.properties, ...extraProps], true);
142
+ // `updateCallExpression` (not `createCallExpression`) so the new
143
+ // node inherits `node.pos` / `node.end` from the original. Phase 2b
144
+ // downstream consumers (componentMetaModule, etc.) read pos via
145
+ // `getStart(sf)` for line info; a synthetic node (pos=-1) would
146
+ // collapse every `component()` call's `__componentMeta.line` to 0.
147
+ return f.updateCallExpression(node, node.expression, node.typeArguments, [
148
+ newConfig,
149
+ ...node.arguments.slice(1),
150
+ ]);
151
+ }
152
+ /**
153
+ * Analyze update() switch/case and generate per-message-type handlers.
154
+ *
155
+ * Each handler receives (inst, msg) and returns [newState, effects].
156
+ * The handler calls update() to get the new state, then directly invokes
157
+ * the appropriate runtime primitives (reconcileItems, __directUpdate, etc.)
158
+ * instead of going through the generic Phase 1/2 pipeline.
159
+ *
160
+ * Conservative: only generates handlers for cases where the field
161
+ * modifications are statically determinable. Complex cases are skipped.
162
+ */
163
+ function tryBuildHandlers(configArg, topLevelBits, topLevelBitsHi, structuralMask, f) {
164
+ if (topLevelBits.size === 0 && topLevelBitsHi.size === 0)
165
+ return null;
166
+ // Find the update function in the component config
167
+ let updateFn = null;
168
+ for (const prop of configArg.properties) {
169
+ if (ts.isPropertyAssignment(prop) &&
170
+ ts.isIdentifier(prop.name) &&
171
+ prop.name.text === 'update') {
172
+ if (ts.isArrowFunction(prop.initializer) || ts.isFunctionExpression(prop.initializer)) {
173
+ updateFn = prop.initializer;
174
+ }
175
+ break;
176
+ }
177
+ }
178
+ if (!updateFn)
179
+ return null;
180
+ // Find the switch statement in the update body
181
+ const body = ts.isBlock(updateFn.body) ? updateFn.body : null;
182
+ if (!body)
183
+ return null;
184
+ let switchStmt = null;
185
+ for (const stmt of body.statements) {
186
+ if (ts.isSwitchStatement(stmt)) {
187
+ switchStmt = stmt;
188
+ break;
189
+ }
190
+ }
191
+ if (!switchStmt)
192
+ return null;
193
+ // Check the switch discriminant is msg.type pattern
194
+ const stateParam = updateFn.parameters[0]?.name;
195
+ const msgParam = updateFn.parameters[1]?.name;
196
+ if (!stateParam || !msgParam || !ts.isIdentifier(stateParam) || !ts.isIdentifier(msgParam))
197
+ return null;
198
+ const stateName = stateParam.text;
199
+ const _msgName = msgParam.text;
200
+ // Analyze each case clause
201
+ const handlers = [];
202
+ for (const clause of switchStmt.caseBlock.clauses) {
203
+ if (!ts.isCaseClause(clause))
204
+ continue;
205
+ // Extract the case label — must be a string literal like 'select'
206
+ if (!ts.isStringLiteral(clause.expression))
207
+ continue;
208
+ const msgType = clause.expression.text;
209
+ // Collect ALL return [newState, effects] statements recursively from the
210
+ // case body. Multiple returns (from if/else branches) must all be analyzed
211
+ // so the handler's dirty mask covers every possible modified field.
212
+ const returnExprs = [];
213
+ const collectReturns = (node) => {
214
+ if (ts.isReturnStatement(node) &&
215
+ node.expression &&
216
+ ts.isArrayLiteralExpression(node.expression) &&
217
+ node.expression.elements.length >= 2) {
218
+ returnExprs.push(node.expression);
219
+ return;
220
+ }
221
+ // Don't descend into nested functions — their returns are unrelated.
222
+ if (ts.isFunctionDeclaration(node) ||
223
+ ts.isFunctionExpression(node) ||
224
+ ts.isArrowFunction(node) ||
225
+ ts.isMethodDeclaration(node)) {
226
+ return;
227
+ }
228
+ ts.forEachChild(node, collectReturns);
229
+ };
230
+ for (const stmt of clause.statements) {
231
+ collectReturns(stmt);
232
+ }
233
+ if (returnExprs.length === 0)
234
+ continue;
235
+ // Union modified fields across all return paths.
236
+ const allModified = new Set();
237
+ let bailOut = false;
238
+ for (const returnExpr of returnExprs) {
239
+ const stateExpr = returnExpr.elements[0];
240
+ const fields = analyzeModifiedFields(stateExpr, stateName, topLevelBits, topLevelBitsHi);
241
+ if (!fields) {
242
+ bailOut = true;
243
+ break;
244
+ }
245
+ for (const f of fields)
246
+ allModified.add(f);
247
+ }
248
+ if (bailOut)
249
+ continue; // at least one return path was too complex
250
+ const modifiedFields = Array.from(allModified);
251
+ // Compute the dirty mask for this case across both words. Fields
252
+ // tracked in `topLevelBitsHi` contribute to `caseDirtyHi`; fields
253
+ // tracked nowhere (`undefined` lookup in both) fall back to
254
+ // FULL_MASK in the low word — same conservative behavior as
255
+ // before, just preserved per-word now.
256
+ let caseDirty = 0;
257
+ let caseDirtyHi = 0;
258
+ for (const field of modifiedFields) {
259
+ const lo = topLevelBits.get(field);
260
+ const hi = topLevelBitsHi.get(field);
261
+ if (lo === undefined && hi === undefined) {
262
+ caseDirty |= 0xffffffff | 0;
263
+ }
264
+ else {
265
+ if (lo !== undefined)
266
+ caseDirty |= lo;
267
+ if (hi !== undefined)
268
+ caseDirtyHi |= hi;
269
+ }
270
+ }
271
+ // Detect array operation pattern for structural block optimization
272
+ const arrayOp = detectArrayOp(clause, stateName, modifiedFields, structuralMask, caseDirty);
273
+ const handler = buildCaseHandler(f, caseDirty, caseDirtyHi, arrayOp);
274
+ handlers.push(f.createPropertyAssignment(f.createStringLiteral(msgType), handler));
275
+ }
276
+ if (handlers.length === 0)
277
+ return null;
278
+ return f.createPropertyAssignment('__handlers', f.createObjectLiteralExpression(handlers, true));
279
+ }
280
+ /**
281
+ * Detect the array operation pattern in a case body.
282
+ * - 'none': no array field modified (e.g., only `selected` changes)
283
+ * - 'clear': array set to empty literal `[]`
284
+ * - 'mutate': array created via `.slice()` then mutated in place (same keys)
285
+ * - 'general': unknown pattern, use generic reconcile
286
+ */
287
+ function detectArrayOp(clause, stateName, modifiedFields, _structuralMask, _caseDirty) {
288
+ // No fields modified → no Phase 1 needed (no bindings can care if no
289
+ // state field changed). Safe to return 'none' here because it's a
290
+ // tautology: every binding mask ANDed with zero is zero.
291
+ if (modifiedFields.length === 0)
292
+ return 'none';
293
+ // The specialized methods (`reconcileClear`, `reconcileItems`,
294
+ // `reconcileRemove`, `reconcileChanged`) only exist on `each` blocks.
295
+ // Non-each blocks (`show`, `branch`, `scope`) leave them undefined,
296
+ // so a method other than 0 (general reconcile) silently no-ops on
297
+ // those blocks at runtime. If the case modifies fields BEYOND the
298
+ // array op (e.g. `{ ...state, open: true, name: '', tags: [] }`),
299
+ // any show/branch block whose mask intersects the case's dirty bits
300
+ // would be selected for reconcile but then skipped by the no-op
301
+ // method invocation — its `when`/`on` accessor never re-evaluates,
302
+ // and the component appears structurally inert after mount.
303
+ //
304
+ // Conservative correctness: only emit a non-general method when the
305
+ // array op is the SOLE field modification. With one modified field,
306
+ // the only blocks selected by mask gating are ones that read that
307
+ // single field — and the optimization is well-defined for that
308
+ // narrow case (each blocks operating on the array). Multi-field
309
+ // cases fall through to `'general'` (method=0), so every selected
310
+ // block runs the standard `reconcile` path. We trade a niche
311
+ // optimization (small benefit even when applicable) for guaranteed
312
+ // structural reconciliation across the framework's primitive set.
313
+ //
314
+ // Sister of show-helper-reconcile.test.ts, which fixed the same
315
+ // class of bug on the method=-1 path. Same architectural principle:
316
+ // the compiler can't see every block in the view, so optimizations
317
+ // that route around `reconcile` must be ironclad. When in doubt,
318
+ // emit method=0 and let `_handleMsg`'s per-block mask gate filter.
319
+ //
320
+ // Previously: if `(structuralMask & caseDirty) === 0`, return 'none'
321
+ // on the theory that no structural block's mask could intersect this
322
+ // case's dirty bits. That optimization was UNSAFE: `computeStructuralMask`
323
+ // only walks the view function's lexical AST and does not descend into
324
+ // helper function calls. A view like
325
+ //
326
+ // view: () => [
327
+ // ...show({ when: s => s.mode === 'signin', render: () => [signinFormBody()] }),
328
+ // ]
329
+ //
330
+ // where `signinFormBody()` is a helper that internally does
331
+ // ...show({ when: s => s.errors.email !== undefined, ... })
332
+ //
333
+ // produces a `structuralMask` that covers `mode` but MISSES
334
+ // `errors.email`. At runtime the inner show block is still registered
335
+ // in `inst.structuralBlocks`, and it legitimately needs to reconcile
336
+ // when `errors` changes — but the compiler was emitting `method = -1`
337
+ // (skip blocks entirely) for cases that only touch `errors`, and the
338
+ // error paragraphs would never mount.
339
+ if (modifiedFields.length !== 1)
340
+ return 'general';
341
+ const onlyField = modifiedFields[0];
342
+ // Look at the return expression's array field values
343
+ for (const stmt of clause.statements) {
344
+ const returnExpr = findReturnArray(stmt);
345
+ if (!returnExpr)
346
+ continue;
347
+ const stateExpr = returnExpr.elements[0];
348
+ if (!stateExpr || !ts.isObjectLiteralExpression(stateExpr))
349
+ continue;
350
+ for (const prop of stateExpr.properties) {
351
+ const name = ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)
352
+ ? prop.name.text
353
+ : ts.isShorthandPropertyAssignment(prop)
354
+ ? prop.name.text
355
+ : null;
356
+ if (!name)
357
+ continue;
358
+ // The optimization only applies when the array op is on the
359
+ // single tracked field. A `field: []` on a different field
360
+ // (one not in modifiedFields, e.g. an untracked field) would
361
+ // still no-op safely on each blocks via the mask gate, but to
362
+ // keep the analysis tight we require an exact match.
363
+ if (name !== onlyField)
364
+ continue;
365
+ // Check for empty array literal: `field: []`
366
+ if (ts.isPropertyAssignment(prop) &&
367
+ ts.isArrayLiteralExpression(prop.initializer) &&
368
+ prop.initializer.elements.length === 0) {
369
+ return 'clear';
370
+ }
371
+ // Check for shorthand `field` where field was assigned via `.slice()` earlier
372
+ // This catches: `const rows = state.rows.slice(); rows[i] = ...; return { ...state, rows }`
373
+ if (ts.isShorthandPropertyAssignment(prop)) {
374
+ const varName = prop.name.text;
375
+ if (hasSliceAssignment(clause, stateName, varName)) {
376
+ // Check for strided for-loop: for (let i = 0; i < arr.length; i += STRIDE)
377
+ const stride = detectStrideLoop(clause, varName);
378
+ if (stride > 1)
379
+ return { type: 'strided', stride };
380
+ return 'mutate';
381
+ }
382
+ }
383
+ // Check for property assignment with filter: `field: state.field.filter(...)`
384
+ if (ts.isPropertyAssignment(prop) && ts.isCallExpression(prop.initializer)) {
385
+ const call = prop.initializer;
386
+ if (ts.isPropertyAccessExpression(call.expression) &&
387
+ call.expression.name.text === 'filter') {
388
+ return 'remove';
389
+ }
390
+ }
391
+ }
392
+ }
393
+ return 'general';
394
+ }
395
+ function findReturnArray(stmt) {
396
+ if (ts.isReturnStatement(stmt) && stmt.expression && ts.isArrayLiteralExpression(stmt.expression))
397
+ return stmt.expression;
398
+ if (ts.isBlock(stmt)) {
399
+ for (const inner of stmt.statements) {
400
+ const result = findReturnArray(inner);
401
+ if (result)
402
+ return result;
403
+ }
404
+ }
405
+ return null;
406
+ }
407
+ /**
408
+ * Detect a strided for-loop: `for (let i = 0; i < arr.length; i += STRIDE)`
409
+ * where `arr` is the named variable. Returns the stride or 0 if not found.
410
+ */
411
+ function detectStrideLoop(clause, _arrName) {
412
+ function walk(node) {
413
+ if (ts.isForStatement(node) && node.incrementor) {
414
+ // Check incrementor: i += STRIDE
415
+ const inc = node.incrementor;
416
+ if (ts.isBinaryExpression(inc) &&
417
+ inc.operatorToken.kind === ts.SyntaxKind.PlusEqualsToken &&
418
+ ts.isNumericLiteral(inc.right)) {
419
+ const stride = parseInt(inc.right.text, 10);
420
+ if (stride > 1)
421
+ return stride;
422
+ }
423
+ }
424
+ return ts.forEachChild(node, walk) ?? 0;
425
+ }
426
+ for (const stmt of clause.statements) {
427
+ const result = walk(stmt);
428
+ if (result > 0)
429
+ return result;
430
+ }
431
+ return 0;
432
+ }
433
+ function hasSliceAssignment(clause, stateName, varName) {
434
+ function walk(node) {
435
+ // Look for: const varName = stateName.field.slice()
436
+ if (ts.isVariableDeclaration(node) &&
437
+ ts.isIdentifier(node.name) &&
438
+ node.name.text === varName &&
439
+ node.initializer) {
440
+ const init = node.initializer;
441
+ if (ts.isCallExpression(init) &&
442
+ ts.isPropertyAccessExpression(init.expression) &&
443
+ init.expression.name.text === 'slice') {
444
+ return true;
445
+ }
446
+ }
447
+ return ts.forEachChild(node, walk) ?? false;
448
+ }
449
+ for (const stmt of clause.statements) {
450
+ if (walk(stmt))
451
+ return true;
452
+ }
453
+ return false;
454
+ }
455
+ /**
456
+ * Analyze which top-level state fields are modified in a return expression.
457
+ * Returns the set of field names, or null if too complex to determine.
458
+ */
459
+ function analyzeModifiedFields(stateExpr, stateName, topLevelBits, topLevelBitsHi = new Map()) {
460
+ // Recognize fields tracked in EITHER the low-word or high-word map.
461
+ // 32..61-prefix components have their overflow paths in
462
+ // `topLevelBitsHi`; the case handler's `caseDirty` / `caseDirtyHi`
463
+ // logic depends on us recognizing those fields here.
464
+ const isTracked = (name) => topLevelBits.has(name) || topLevelBitsHi.has(name);
465
+ // Pattern: { ...state, field1: ..., field2: ... } or { field1: ..., field2: ... }
466
+ if (ts.isObjectLiteralExpression(stateExpr)) {
467
+ const modified = [];
468
+ for (const prop of stateExpr.properties) {
469
+ if (ts.isSpreadAssignment(prop)) {
470
+ // Only `...state` is safe to ignore — re-spreading state back into
471
+ // state doesn't change any field's identity. ANY other spread
472
+ // (e.g. `...msg.props`, `...someObj`) can overwrite arbitrary
473
+ // top-level fields with new references, and we cannot know which
474
+ // ones statically. Bail out so the generic Phase 2 path runs
475
+ // `__dirty` at runtime and produces a correct mask.
476
+ if (ts.isIdentifier(prop.expression) && prop.expression.text === stateName) {
477
+ continue;
478
+ }
479
+ return null;
480
+ }
481
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
482
+ const fieldName = prop.name.text;
483
+ if (isTracked(fieldName)) {
484
+ modified.push(fieldName);
485
+ }
486
+ }
487
+ // Handle shorthand: { ...state, rows } where rows is a local variable
488
+ if (ts.isShorthandPropertyAssignment(prop)) {
489
+ const fieldName = prop.name.text;
490
+ if (isTracked(fieldName)) {
491
+ modified.push(fieldName);
492
+ }
493
+ }
494
+ }
495
+ return modified.length > 0 ? modified : null;
496
+ }
497
+ // Pattern: state (no change — early return)
498
+ if (ts.isIdentifier(stateExpr) && stateExpr.text === stateName) {
499
+ return []; // no fields modified
500
+ }
501
+ return null; // too complex
502
+ }
503
+ /**
504
+ * Build a handler function for a specific message type case.
505
+ *
506
+ * Generated: (inst, msg) => {
507
+ * const [s, e] = inst.def.update(inst.state, msg)
508
+ * inst.state = s
509
+ * const bl = inst.structuralBlocks, b = inst.allBindings, p = b.length
510
+ * // Phase 1: gated by caseDirty
511
+ * for (let i = 0; i < bl.length; i++) {
512
+ * if (bl[i].mask & caseDirty) bl[i].reconcile(s, caseDirty)
513
+ * }
514
+ * // Phase 2
515
+ * __runPhase2(s, caseDirty, b, p)
516
+ * return [s, e]
517
+ * }
518
+ */
519
+ /**
520
+ * Build a handler that delegates to __handleMsg(inst, msg, dirty, method).
521
+ * method: 0=reconcile, 1=reconcileItems, 2=reconcileClear, 3=reconcileRemove, -1=skip blocks
522
+ */
523
+ function buildCaseHandler(f, caseDirty, caseDirtyHi, arrayOp) {
524
+ const method = typeof arrayOp === 'object' && arrayOp.type === 'strided'
525
+ ? 10 + arrayOp.stride // reconcileChanged with stride
526
+ : arrayOp === 'none'
527
+ ? -1
528
+ : arrayOp === 'mutate'
529
+ ? 1
530
+ : arrayOp === 'clear'
531
+ ? 2
532
+ : arrayOp === 'remove'
533
+ ? 3
534
+ : 0; // general
535
+ // (inst, msg) => __handleMsg(inst, msg, dirty, method, [dirtyHi])
536
+ const args = [
537
+ f.createIdentifier('inst'),
538
+ f.createIdentifier('msg'),
539
+ createMaskLiteral(f, caseDirty),
540
+ method >= 0
541
+ ? f.createNumericLiteral(method)
542
+ : f.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, f.createNumericLiteral(1)),
543
+ ];
544
+ // Emit the 5th positional arg only when the case touches a high-word
545
+ // field. Stale runtime bundles' _handleMsg signatures ignored that
546
+ // slot anyway; new ones (defaulted to 0) make it explicit when needed.
547
+ if (caseDirtyHi !== 0)
548
+ args.push(createMaskLiteral(f, caseDirtyHi));
549
+ return f.createArrowFunction(undefined, undefined, [
550
+ f.createParameterDeclaration(undefined, undefined, 'inst'),
551
+ f.createParameterDeclaration(undefined, undefined, 'msg'),
552
+ ], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), f.createCallExpression(f.createIdentifier('__handleMsg'), undefined, args));
553
+ }
554
+ /**
555
+ * Compute the OR of all structural block masks found in the view function.
556
+ * Returns FULL_MASK if any structural block uses FULL_MASK or if no blocks found.
557
+ */
558
+ function computeStructuralMask(configArg, fieldBits) {
559
+ const viewProp = configArg.properties.find((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'view');
560
+ if (!viewProp || !ts.isPropertyAssignment(viewProp))
561
+ return 0xffffffff | 0;
562
+ let mask = 0;
563
+ let foundStructural = false;
564
+ function walk(node) {
565
+ if (ts.isCallExpression(node)) {
566
+ const name = ts.isIdentifier(node.expression) ? node.expression.text : '';
567
+ if (['each', 'branch', 'scope', 'show'].includes(name) && node.arguments[0]) {
568
+ foundStructural = true;
569
+ const opts = node.arguments[0];
570
+ if (ts.isObjectLiteralExpression(opts)) {
571
+ // Check for __mask property (already injected by tryInjectStructuralMask)
572
+ for (const prop of opts.properties) {
573
+ if (ts.isPropertyAssignment(prop) &&
574
+ ts.isIdentifier(prop.name) &&
575
+ prop.name.text === '__mask') {
576
+ if (ts.isNumericLiteral(prop.initializer)) {
577
+ mask |= parseInt(prop.initializer.text, 10);
578
+ return;
579
+ }
580
+ if (ts.isPrefixUnaryExpression(prop.initializer)) {
581
+ // Handle negative literals like -1
582
+ mask = 0xffffffff | 0;
583
+ return;
584
+ }
585
+ }
586
+ }
587
+ // No __mask found — use driving accessor mask
588
+ const driverProp = name === 'each' ? 'items' : name === 'branch' ? 'on' : 'when';
589
+ for (const prop of opts.properties) {
590
+ if (ts.isPropertyAssignment(prop) &&
591
+ ts.isIdentifier(prop.name) &&
592
+ prop.name.text === driverProp) {
593
+ if (ts.isArrowFunction(prop.initializer) ||
594
+ ts.isFunctionExpression(prop.initializer)) {
595
+ const { mask: m } = computeAccessorMask(prop.initializer, fieldBits);
596
+ mask |= m || 0xffffffff | 0;
597
+ }
598
+ break;
599
+ }
600
+ }
601
+ }
602
+ }
603
+ }
604
+ ts.forEachChild(node, walk);
605
+ }
606
+ walk(viewProp.initializer);
607
+ return foundStructural ? mask || 0xffffffff | 0 : 0;
608
+ }
609
+ /**
610
+ * Build the __update function body:
611
+ * {
612
+ * // Phase 1 — structural reconciliation (gated by structuralMask)
613
+ * if (d & structuralMask) {
614
+ * for (let i = 0; i < bl.length; i++) {
615
+ * const bk = bl[i]
616
+ * if (!bk || (bk.mask & d) === 0) continue
617
+ * bk.reconcile(s, d)
618
+ * }
619
+ * // Compact dead bindings
620
+ * if (b.length > p || (p > 0 && b[0].dead)) {
621
+ * let w = 0
622
+ * for (let r = 0; r < b.length; r++) { if (!b[r].dead) b[w++] = b[r] }
623
+ * b.length = w
624
+ * p = Math.min(w, p)
625
+ * }
626
+ * }
627
+ * // Phase 2 — binding updates
628
+ * if (d !== 0) {
629
+ * for (let i = 0; i < p; i++) {
630
+ * const bn = b[i]
631
+ * if (bn.dead || (bn.mask & d) === 0) continue
632
+ * const v = bn.accessor(s)
633
+ * const l = bn.lastValue
634
+ * if (v === l || (v !== v && l !== l)) continue
635
+ * bn.lastValue = v
636
+ * __runPhase2(s, d, b, p)
637
+ * }
638
+ * }
639
+ * }
640
+ */
641
+ function buildUpdateBody(f, structuralMask) {
642
+ const stmts = [];
643
+ // Phase 1: structural block reconciliation, gated by aggregate mask
644
+ if (structuralMask !== 0) {
645
+ const phase1Stmts = [];
646
+ // for (let i = 0; i < bl.length; i++) {
647
+ // const bk = bl[i];
648
+ // if (!bk || !((bk.mask & d) | (bk.maskHi & dHi))) continue;
649
+ // bk.reconcile(s, d, dHi)
650
+ // }
651
+ // Two-word gate matches the runtime's `genericUpdate`: bits 0..30
652
+ // in `d`, bits 31..61 in `dHi`. For ≤31-prefix components both
653
+ // `bk.maskHi` and `dHi` are 0, so V8's inline cache collapses the
654
+ // OR back to the single-word check. >31-prefix components use the
655
+ // high word for precise gating.
656
+ //
657
+ // Re-read bl.length each iteration and null-check bk — a branch's
658
+ // reconcile may dispose the old scope, whose disposers splice child
659
+ // structural blocks out of this shared array mid-iteration.
660
+ const blockLoop = f.createForStatement(f.createVariableDeclarationList([f.createVariableDeclaration('i', undefined, undefined, f.createNumericLiteral(0))], ts.NodeFlags.Let), f.createBinaryExpression(f.createIdentifier('i'), ts.SyntaxKind.LessThanToken, f.createPropertyAccessExpression(f.createIdentifier('bl'), 'length')), f.createPostfixUnaryExpression(f.createIdentifier('i'), ts.SyntaxKind.PlusPlusToken), f.createBlock([
661
+ f.createVariableStatement(undefined, f.createVariableDeclarationList([
662
+ f.createVariableDeclaration('bk', undefined, undefined, f.createElementAccessExpression(f.createIdentifier('bl'), f.createIdentifier('i'))),
663
+ ], ts.NodeFlags.Const)),
664
+ f.createIfStatement(f.createBinaryExpression(f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createIdentifier('bk')), ts.SyntaxKind.BarBarToken, f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createParenthesizedExpression(f.createBinaryExpression(f.createParenthesizedExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'mask'), ts.SyntaxKind.AmpersandToken, f.createIdentifier('d'))), ts.SyntaxKind.BarToken, f.createParenthesizedExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'maskHi'), ts.SyntaxKind.AmpersandToken, f.createIdentifier('dHi'))))))), f.createContinueStatement()),
665
+ f.createExpressionStatement(f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('bk'), 'reconcile'), undefined, [f.createIdentifier('s'), f.createIdentifier('d'), f.createIdentifier('dHi')])),
666
+ ], true));
667
+ phase1Stmts.push(blockLoop);
668
+ // Compaction: if (b.length > p || (p > 0 && b[0].dead)) { ... }
669
+ const compactBody = f.createBlock([
670
+ // let w = 0
671
+ f.createVariableStatement(undefined, f.createVariableDeclarationList([f.createVariableDeclaration('w', undefined, undefined, f.createNumericLiteral(0))], ts.NodeFlags.Let)),
672
+ // for (let r = 0; r < b.length; r++) { if (!b[r].dead) b[w++] = b[r] }
673
+ f.createForStatement(f.createVariableDeclarationList([f.createVariableDeclaration('r', undefined, undefined, f.createNumericLiteral(0))], ts.NodeFlags.Let), f.createBinaryExpression(f.createIdentifier('r'), ts.SyntaxKind.LessThanToken, f.createPropertyAccessExpression(f.createIdentifier('b'), 'length')), f.createPostfixUnaryExpression(f.createIdentifier('r'), ts.SyntaxKind.PlusPlusToken), f.createBlock([
674
+ f.createIfStatement(f.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, f.createPropertyAccessExpression(f.createElementAccessExpression(f.createIdentifier('b'), f.createIdentifier('r')), 'dead')), f.createExpressionStatement(f.createBinaryExpression(f.createElementAccessExpression(f.createIdentifier('b'), f.createPostfixUnaryExpression(f.createIdentifier('w'), ts.SyntaxKind.PlusPlusToken)), ts.SyntaxKind.EqualsToken, f.createElementAccessExpression(f.createIdentifier('b'), f.createIdentifier('r'))))),
675
+ ], true)),
676
+ // b.length = w
677
+ f.createExpressionStatement(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('b'), 'length'), ts.SyntaxKind.EqualsToken, f.createIdentifier('w'))),
678
+ // p = Math.min(w, p)
679
+ f.createExpressionStatement(f.createBinaryExpression(f.createIdentifier('p'), ts.SyntaxKind.EqualsToken, f.createCallExpression(f.createPropertyAccessExpression(f.createIdentifier('Math'), 'min'), undefined, [f.createIdentifier('w'), f.createIdentifier('p')]))),
680
+ ], true);
681
+ const compactCondition = f.createBinaryExpression(f.createBinaryExpression(f.createPropertyAccessExpression(f.createIdentifier('b'), 'length'), ts.SyntaxKind.GreaterThanToken, f.createIdentifier('p')), ts.SyntaxKind.BarBarToken, f.createParenthesizedExpression(f.createBinaryExpression(f.createBinaryExpression(f.createIdentifier('p'), ts.SyntaxKind.GreaterThanToken, f.createNumericLiteral(0)), ts.SyntaxKind.AmpersandAmpersandToken, f.createPropertyAccessExpression(f.createElementAccessExpression(f.createIdentifier('b'), f.createNumericLiteral(0)), 'dead'))));
682
+ phase1Stmts.push(f.createIfStatement(compactCondition, compactBody));
683
+ // Wrap Phase 1 in mask gate
684
+ if (structuralMask !== (0xffffffff | 0)) {
685
+ stmts.push(f.createIfStatement(f.createBinaryExpression(f.createParenthesizedExpression(f.createBinaryExpression(f.createIdentifier('d'), ts.SyntaxKind.AmpersandToken, createMaskLiteral(f, structuralMask))), ts.SyntaxKind.ExclamationEqualsEqualsToken, f.createNumericLiteral(0)), f.createBlock(phase1Stmts, true)));
686
+ }
687
+ else {
688
+ stmts.push(...phase1Stmts);
689
+ }
690
+ }
691
+ // Phase 2: delegate to shared runtime — __runPhase2(s, d, dHi, b, p)
692
+ stmts.push(f.createExpressionStatement(f.createCallExpression(f.createIdentifier('__runPhase2'), undefined, [
693
+ f.createIdentifier('s'),
694
+ f.createIdentifier('d'),
695
+ f.createIdentifier('dHi'),
696
+ f.createIdentifier('b'),
697
+ f.createIdentifier('p'),
698
+ ])));
699
+ return f.createBlock(stmts, true);
700
+ }
701
+ /**
702
+ * Build the `__prefixes` property assignment from path → bit maps.
703
+ *
704
+ * Emits one arrow `(s) => s.<path>` per distinct path. Array index =
705
+ * the path's bit position: positions 0..30 come from `fieldBits` (low
706
+ * word), positions 31..61 from `fieldBitsHi` (high word). The runtime
707
+ * walks this array and reference-compares `prefix(prev)` vs
708
+ * `prefix(next)` per entry, fanning bits into a `(lo, hi)` pair when
709
+ * the array length exceeds 31.
710
+ *
711
+ * Returns null if no paths are present.
712
+ */
713
+ function buildPrefixesProp(fieldBits, fieldBitsHi, f) {
714
+ if (fieldBits.size === 0 && fieldBitsHi.size === 0)
715
+ return null;
716
+ // Sort paths by bit value within each word. Bits are powers of two
717
+ // inside their word (1, 2, 4, …, 1<<30), so sorting numerically gives
718
+ // ascending bit position. FULL_MASK (-1) entries from past-61
719
+ // overflow shouldn't drive a prefix entry — defensively skip them.
720
+ const orderedLo = [...fieldBits.entries()]
721
+ .filter(([, bit]) => bit > 0)
722
+ .sort(([, a], [, b]) => a - b);
723
+ const orderedHi = [...fieldBitsHi.entries()].sort(([, a], [, b]) => a - b);
724
+ const buildArrow = (path) => {
725
+ const parts = path.split('.');
726
+ const body = buildAccess(f, 's', parts);
727
+ return f.createArrowFunction(undefined, undefined, [f.createParameterDeclaration(undefined, undefined, 's')], undefined, f.createToken(ts.SyntaxKind.EqualsGreaterThanToken), body);
728
+ };
729
+ const arrows = [
730
+ ...orderedLo.map(([path]) => buildArrow(path)),
731
+ ...orderedHi.map(([path]) => buildArrow(path)),
732
+ ];
733
+ return f.createPropertyAssignment('__prefixes', f.createArrayLiteralExpression(arrows, false));
734
+ }
735
+ function buildAccess(f, root, parts) {
736
+ let expr = f.createIdentifier(root);
737
+ for (const part of parts) {
738
+ // Use optional chaining for nested paths
739
+ if (parts.length > 1) {
740
+ expr = f.createPropertyAccessChain(expr, f.createToken(ts.SyntaxKind.QuestionDotToken), part);
741
+ }
742
+ else {
743
+ expr = f.createPropertyAccessExpression(expr, part);
744
+ }
745
+ }
746
+ return expr;
747
+ }
748
+ //# sourceMappingURL=core-synthesis.js.map