quickwin 3.0.1 → 3.0.3

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 (163) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +2 -0
  3. package/package.json +2 -3
  4. package/qwin-nowasm-x86.exe +0 -0
  5. package/qwin-nowasm.exe +0 -0
  6. package/qwin-x86.exe +0 -0
  7. package/qwin.exe +0 -0
  8. package/examples/PdfCanvas.js +0 -38
  9. package/examples/PdfCanvas.tsx +0 -54
  10. package/examples/execd/main.js +0 -23379
  11. package/examples/execd/tray.js +0 -192
  12. package/examples/execd/worker.js +0 -122
  13. package/examples/pdf_preview2.js +0 -398
  14. package/examples/pdf_preview2.ts +0 -353
  15. package/examples/pdf_viewer.js +0 -22515
  16. package/examples/pdf_viewer.tsx +0 -228
  17. package/examples/test_gallery.js +0 -18692
  18. package/examples/test_gallery.tsx +0 -291
  19. package/examples/test_react_lazy.js +0 -18560
  20. package/examples/test_react_lazy.tsx +0 -65
  21. package/examples/test_react_listbox.js +0 -18562
  22. package/examples/test_react_listbox.tsx +0 -73
  23. package/examples/test_root_window.js +0 -18532
  24. package/examples/test_root_window.tsx +0 -41
  25. package/examples/test_tab.js +0 -18563
  26. package/examples/test_tab.tsx +0 -77
  27. package/examples/tray_demo.js +0 -86
  28. package/examples/tray_demo.tsx +0 -89
  29. package/test/add.wasm +0 -0
  30. package/test/bench_ffi_callraw.js +0 -235
  31. package/test/bench_ffi_callraw.ts +0 -235
  32. package/test/bench_ffi_overhead.js +0 -139
  33. package/test/bench_ffi_overhead.ts +0 -141
  34. package/test/bench_sendmessage.js +0 -104
  35. package/test/bench_sendmessage.ts +0 -100
  36. package/test/bench_sendmessage_nonnull.js +0 -187
  37. package/test/bench_sendmessage_nonnull.ts +0 -188
  38. package/test/complex.wasm +0 -0
  39. package/test/complex_imports.wasm +0 -0
  40. package/test/debug/http_server_e2e.js +0 -31
  41. package/test/debug/minwasm.js +0 -52
  42. package/test/debug/mupdf_debug.js +0 -86
  43. package/test/debug/wasm.js +0 -122
  44. package/test/debug/wasm_export_debug.js +0 -54
  45. package/test/debug/wasm_names.js +0 -36
  46. package/test/example.pdf +0 -0
  47. package/test/global_imports.wasm +0 -0
  48. package/test/import_func.wasm +0 -0
  49. package/test/imports.wasm +0 -0
  50. package/test/mupdf-cdn.js +0 -40
  51. package/test/mupdf-cdn.ts +0 -43
  52. package/test/run.js +0 -111
  53. package/test/run.ts +0 -116
  54. package/test/sjlj.wasm +0 -0
  55. package/test/test_basic.js +0 -7
  56. package/test/test_basic.ts +0 -9
  57. package/test/test_basic_fetch.js +0 -63
  58. package/test/test_basic_fetch.ts +0 -59
  59. package/test/test_brotli.js +0 -48
  60. package/test/test_brotli.ts +0 -52
  61. package/test/test_fetch_cache.js +0 -169
  62. package/test/test_fetch_cache.ts +0 -187
  63. package/test/test_ffi.js +0 -157
  64. package/test/test_ffi.ts +0 -170
  65. package/test/test_ffi_abi.js +0 -290
  66. package/test/test_ffi_abi.ts +0 -274
  67. package/test/test_ffi_array.js +0 -116
  68. package/test/test_ffi_array.ts +0 -141
  69. package/test/test_ffi_bind.js +0 -656
  70. package/test/test_ffi_bind.ts +0 -670
  71. package/test/test_ffi_struct.js +0 -779
  72. package/test/test_ffi_struct.ts +0 -828
  73. package/test/test_ffi_struct_layout.js +0 -495
  74. package/test/test_ffi_struct_layout.ts +0 -543
  75. package/test/test_ffi_user32.js +0 -103
  76. package/test/test_ffi_user32.ts +0 -113
  77. package/test/test_ffi_windows.js +0 -172
  78. package/test/test_ffi_windows.ts +0 -187
  79. package/test/test_files.js +0 -102
  80. package/test/test_files.ts +0 -121
  81. package/test/test_frame_encoding.js +0 -128
  82. package/test/test_frame_encoding.ts +0 -132
  83. package/test/test_gdi_draw.js +0 -681
  84. package/test/test_gdi_draw.ts +0 -725
  85. package/test/test_helper.js +0 -91
  86. package/test/test_helper.ts +0 -88
  87. package/test/test_http_import.js +0 -96
  88. package/test/test_http_import.ts +0 -93
  89. package/test/test_http_server.js +0 -214
  90. package/test/test_http_server.ts +0 -211
  91. package/test/test_import_dir.js +0 -4
  92. package/test/test_import_dir.ts +0 -5
  93. package/test/test_ipv6.js +0 -134
  94. package/test/test_ipv6.ts +0 -130
  95. package/test/test_jsx.js +0 -1314
  96. package/test/test_meta_url.js +0 -3
  97. package/test/test_meta_url.ts +0 -4
  98. package/test/test_mupdf_render.js +0 -51
  99. package/test/test_mupdf_render.ts +0 -55
  100. package/test/test_mupdf_twice.js +0 -62
  101. package/test/test_mupdf_twice.ts +0 -65
  102. package/test/test_mupdf_wasm.js +0 -39
  103. package/test/test_mupdf_wasm.ts +0 -32
  104. package/test/test_net_event.js +0 -57
  105. package/test/test_net_event.ts +0 -56
  106. package/test/test_net_fetch.js +0 -384
  107. package/test/test_net_fetch.ts +0 -373
  108. package/test/test_net_websocket.js +0 -180
  109. package/test/test_net_websocket.ts +0 -164
  110. package/test/test_polyfill.js +0 -144
  111. package/test/test_polyfill.ts +0 -159
  112. package/test/test_printer.js +0 -137
  113. package/test/test_printer.ts +0 -148
  114. package/test/test_proc.js +0 -141
  115. package/test/test_proc.ts +0 -163
  116. package/test/test_react_complex.js +0 -18678
  117. package/test/test_react_counter.js +0 -18555
  118. package/test/test_react_flex.js +0 -18871
  119. package/test/test_react_hidden.js +0 -18560
  120. package/test/test_react_listview_link.js +0 -18590
  121. package/test/test_react_notify.js +0 -18797
  122. package/test/test_react_render.js +0 -18550
  123. package/test/test_registry.js +0 -74
  124. package/test/test_registry.ts +0 -95
  125. package/test/test_sysinfo.js +0 -122
  126. package/test/test_sysinfo.ts +0 -141
  127. package/test/test_url.js +0 -173
  128. package/test/test_url.ts +0 -183
  129. package/test/test_wasm_basic.js +0 -85
  130. package/test/test_wasm_basic.ts +0 -73
  131. package/test/test_wasm_import_global.js +0 -41
  132. package/test/test_wasm_import_global.ts +0 -45
  133. package/test/test_wasm_sjlj.js +0 -145
  134. package/test/test_wasm_sjlj.ts +0 -125
  135. package/test/test_wasm_types.js +0 -97
  136. package/test/test_wasm_types.ts +0 -124
  137. package/test/test_wolfssl_read.js +0 -203
  138. package/test/test_wolfssl_read.ts +0 -184
  139. package/test/test_worker.js +0 -21
  140. package/test/test_worker.ts +0 -24
  141. package/test/test_worker_concurrent.js +0 -45
  142. package/test/test_worker_concurrent.ts +0 -56
  143. package/test/test_worker_data_url.js +0 -46
  144. package/test/test_worker_data_url.ts +0 -51
  145. package/test/test_worker_fetch.js +0 -26
  146. package/test/test_worker_fetch.ts +0 -27
  147. package/test/test_worker_net.js +0 -69
  148. package/test/test_worker_net.ts +0 -66
  149. package/test/test_worker_wasm_concurrent.js +0 -44
  150. package/test/test_worker_wasm_concurrent.ts +0 -45
  151. package/test/types.wasm +0 -0
  152. package/test/worker_fetch.js +0 -13
  153. package/test/worker_fetch.ts +0 -13
  154. package/test/worker_helper.js +0 -1
  155. package/test/worker_helper.ts +0 -1
  156. package/test/worker_http.js +0 -18
  157. package/test/worker_http.ts +0 -20
  158. package/test/worker_import_lib.js +0 -12
  159. package/test/worker_import_lib.ts +0 -13
  160. package/test/worker_module.js +0 -16
  161. package/test/worker_module.ts +0 -17
  162. package/test/worker_wasm_concurrent.js +0 -22
  163. package/test/worker_wasm_concurrent.ts +0 -22
@@ -1,779 +0,0 @@
1
- import * as os from 'os';
2
- import { struct, union } from '../lib/ffi/struct.js';
3
- import { bind, } from '../lib/ffi/bind.js';
4
- import { PtrArrayBuffer } from '../lib/ffi/ctype.js';
5
- function expectType(_value) { }
6
- // Ptr 定义:'' 判右侧(不作 naked 分布)——字面品牌照旧收窄、Ptr<''> 归一 number、
7
- // never 不再塌缩成 never(旧定义下 Ptr<never> 即 never,decode(… | Ptr<N>) 会在 N=never 时堵死)
8
- expectType(true);
9
- expectType(true);
10
- const RECT = /* @__PURE__ */ struct('RECT', {
11
- left: 'i32',
12
- top: 'i32',
13
- right: 'i32',
14
- bottom: 'i32',
15
- });
16
- const TVITEM_DECL = {
17
- '#': 'struct',
18
- mask: 'u32',
19
- hItem: '<>ptr',
20
- state: 'u32',
21
- stateMask: 'u32',
22
- pszText: '<>ptr',
23
- cchTextMax: 'i32',
24
- iImage: 'i32',
25
- iSelectedImage: 'i32',
26
- cChildren: 'i32',
27
- lParam: '<>ptr',
28
- };
29
- const TVITEM = /* @__PURE__ */ struct('TVITEM', TVITEM_DECL);
30
- const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
31
- hParent: '<>ptr',
32
- hInsertAfter: '<>ptr',
33
- item: TVITEM_DECL,
34
- });
35
- export const suite = {
36
- name: 'ffi-struct',
37
- run: (t) => {
38
- const is64 = os.arch === 'x64';
39
- t.section('RECT layout & roundtrip');
40
- t.check('RECT.size == 16', 16, RECT.size);
41
- const rbuf = RECT.encode({ left: 10, top: 20, right: 30, bottom: 40 });
42
- const r = RECT.decode(rbuf);
43
- t.check('left', 10, r.left);
44
- t.check('top', 20, r.top);
45
- t.check('right', 30, r.right);
46
- t.check('bottom', 40, r.bottom);
47
- t.check('offsetOf left', 0, RECT.offsetOf('left'));
48
- t.check('offsetOf bottom', 12, RECT.offsetOf('bottom'));
49
- // decode 入参随 N 品牌化:命名 struct 收 ArrayBuffer | Ptr<其名字>,
50
- // 未命名(N='')才归一 ArrayBuffer | number
51
- expectType(true);
52
- expectType(true);
53
- if (false) {
54
- // @ts-expect-error plain number is not assignable to ArrayBuffer | Ptr<'RECT'>
55
- RECT.decode(123);
56
- }
57
- t.section('GetWindowRect fills RECT buffer');
58
- const getWindowRect = bind('user32.dll', 'GetWindowRect', '<>ptr <BYTE>ptr -> i32');
59
- const getWindowRectLayout = bind('user32.dll', 'GetWindowRect', '<>ptr <RECT>ptr -> i32', { RECT });
60
- const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> <>ptr');
61
- const hwnd = getDesktopWindow();
62
- const wrect = new PtrArrayBuffer(16);
63
- const ok = getWindowRect(hwnd, wrect);
64
- t.checkTrue('GetWindowRect succeeds', ok !== 0);
65
- const wr = RECT.decode(wrect);
66
- t.checkTrue('screen RECT non-empty', wr.right > 0 && wr.bottom > 0);
67
- // out 参数:encode() 新建零 buffer 返回 PtrArrayBuffer<'RECT'> → .ptr 直接喂
68
- // <RECT>ptr,读回走 def.decode(实例)(ArrayBuffer 分支直读)
69
- t.section('RECT.alloc() out-param → PtrArrayBuffer');
70
- const out = RECT.alloc();
71
- t.checkTrue('encode() exposes ArrayBuffer subclass + ptr', out instanceof ArrayBuffer && typeof out.ptr === 'number');
72
- t.checkTrue('GetWindowRect(hwnd, out.ptr) succeeds', getWindowRectLayout(hwnd, out.ptr) !== 0);
73
- const or = RECT.decode(out);
74
- t.checkTrue('RECT.decode(encode()) decodes out-param', or.right > 0 && or.bottom > 0);
75
- t.section('ptr layout matches arch');
76
- t.check(`TVITEM.size (${os.arch})`, is64 ? 56 : 40, TVITEM.size);
77
- t.check('TVITEM.offsetOf pszText', is64 ? 24 : 16, TVITEM.offsetOf('pszText'));
78
- t.check('TVINSERTSTRUCT.size', is64 ? 72 : 48, TVINSERTSTRUCT.size);
79
- t.check('TVINSERTSTRUCT.offsetOf item', is64 ? 16 : 8, TVINSERTSTRUCT.offsetOf('item'));
80
- t.section('nested struct roundtrip');
81
- const ins = TVINSERTSTRUCT.encode({
82
- hParent: 0x11111111,
83
- hInsertAfter: 0x22222222,
84
- item: {
85
- mask: 1, hItem: 0x33333333, state: 0, stateMask: 0,
86
- pszText: 0x44444444, cchTextMax: 100,
87
- iImage: 0, iSelectedImage: 0, cChildren: 5, lParam: 0x55555555,
88
- },
89
- });
90
- const got = TVINSERTSTRUCT.decode(ins);
91
- t.check('nested hParent', 0x11111111, got.hParent);
92
- t.check('nested hInsertAfter', 0x22222222, got.hInsertAfter);
93
- t.check('nested cChildren', 5, got.item.cChildren);
94
- t.check('nested ptr pszText', 0x44444444, got.item.pszText);
95
- t.section('mixed scalar layout (i32/u32/i16/<>ptr;别名机制已移除)');
96
- const M = struct('M', {
97
- n: 'i32',
98
- d: 'u32',
99
- w: '<>ptr',
100
- s: 'i16',
101
- q: '<>ptr',
102
- });
103
- t.check(`mixed size (${os.arch})`, is64 ? 32 : 20, M.size);
104
- const mb = M.encode({ n: -5, d: 0xFFFFFFFF, w: 0xAABBCCDD, s: -7, q: 0x11223344 });
105
- const m = M.decode(mb);
106
- t.check('i32 -5', -5, m.n);
107
- t.check('u32 0xFFFFFFFF', 0xFFFFFFFF, m.d);
108
- // ptr 位有符号读(对齐 C 版 JS_NewInt64):0xAABBCCDD 在 ia32 按 4 字节
109
- // 存取 → 符号扩展读成负;x64 8 字节高位 0 → 仍为正
110
- t.check('<>ptr 符号读', is64 ? 0xAABBCCDD : -1430532899, m.w);
111
- t.check('i16 -7', -7, m.s);
112
- t.check('<>ptr 0x11223344', 0x11223344, m.q);
113
- // 别名机制已移除:typedef 名塌出词汇表 —— struct 字段值只收规范 token
114
- // (仅类型层断言,包成函数体不执行,避免运行时 fail-loud 崩掉 suite)
115
- // @ts-expect-error typedef 别名 'DWORD' 已移除
116
- void (() => struct('XD', { d: 'DWORD' }));
117
- t.section('array sugar + trailing bitfield(数组糖与尾随位域单元并存)');
118
- const AS = struct('AS', {
119
- v: 'u32[4]',
120
- f: 'u32:3',
121
- });
122
- t.check('size == 20 (16 + 位域单元)', 20, AS.size);
123
- t.check('f offset == 16', 16, AS.offsetOf('f'));
124
- const asd = AS.decode(AS.encode({ v: [1, 2, 3, 4], f: 5 }));
125
- t.check('u32[4][1]', 2, asd.v[1]);
126
- t.check('u32:3', 5, asd.f);
127
- expectType();
128
- t.section('union() entry roundtrip');
129
- const U = union({ a: 'u8', b: 'u32' });
130
- t.check('union size == 4', 4, U.size);
131
- t.check('union offsetOf a', 0, U.offsetOf('a'));
132
- t.check('union offsetOf b', 0, U.offsetOf('b'));
133
- const ud = U.decode(U.encode({ a: 0, b: 0x01020304 }));
134
- t.check('union a 与 b 同偏移(低字节别名)', 0x04, ud.a);
135
- t.check('union b', 0x01020304, ud.b);
136
- t.section('union 位域完全别名 roundtrip(mingw 实测语义)');
137
- {
138
- const UB = union({ a: 'u32:1', b: 'u32:1' });
139
- t.check('union bitfield size == 4', 4, UB.size);
140
- const ud1 = UB.decode(UB.encode({ a: 1, b: 1 }));
141
- t.check('a=1,b=1 roundtrip a', 1, ud1.a);
142
- t.check('a=1,b=1 roundtrip b', 1, ud1.b);
143
- // 同位别名:字段按声明序写入,后写字段覆盖先写字段
144
- const ud2 = UB.decode(UB.encode({ a: 1, b: 0 }));
145
- t.check('别名覆盖:b=0 后写 → a 读 0', 0, ud2.a);
146
- t.check('别名覆盖:b == 0', 0, ud2.b);
147
- // 溢出形状(30+4>32):union 每成员回 offset 0,曾开新单元越出 size
148
- const OF = union({ a: 'u32:30', b: 'u32:4' });
149
- t.check('overflow union size == 4', 4, OF.size);
150
- const od1 = OF.decode(OF.encode({ a: 0x3FFFFFFF, b: 0xF }));
151
- t.check('overflow roundtrip a', 0x3FFFFFFF, od1.a);
152
- t.check('overflow roundtrip b', 0xF, od1.b);
153
- const od2 = OF.decode(OF.encode({ a: 0x3FFFFFFF, b: 0 }));
154
- t.check('overflow 别名覆盖 → a 低 4 位被清', 0x3FFFFFF0, od2.a);
155
- // 位域 + 普通成员混合:b 曾被 flushUnit 推到 offset 4(越出 size 4)
156
- const MX = union({ a: 'u32:1', b: 'u8' });
157
- t.check('mixed union size == 4', 4, MX.size);
158
- t.check('mixed offsetOf b == 0(曾为 4)', 0, MX.offsetOf('b'));
159
- const md = MX.decode(MX.encode({ a: 1, b: 0x54 }));
160
- t.check('mixed: b 覆盖 a 所在字节 → a 读 0', 0, md.a);
161
- t.check('mixed: b == 0x54', 0x54, md.b);
162
- // 嵌套回归:struct 内嵌该 union(raw '#' 形式;union() 装饰对象不能嵌套,
163
- // 嵌套 union 的解码类型推导会 TS2589,沿用旧行例 as any——本条是运行时断言),
164
- // tail 不被 union 内错位写污染
165
- const nestedU = { '#': 'union', a: 'u32:1', b: 'u8' };
166
- const NS = struct({ tag: 'u32', u: nestedU, tail: 'u16' });
167
- t.check('nested size == 12', 12, NS.size);
168
- const nd = NS.decode(NS.encode({ tag: 0xAABBCCDD, u: { a: 1, b: 0x54 }, tail: 0x1234 }));
169
- t.check('nested tag', 0xAABBCCDD, nd.tag);
170
- t.check('nested u.b == 0x54', 0x54, nd.u.b);
171
- t.check('nested tail 不被污染', 0x1234, nd.tail);
172
- }
173
- t.section('struct() entry: pack 选项 + 键尾 @N(alignas)');
174
- const P = struct('P', { a: 'u8', b: 'u32', c: 'u8' }, { pack: 1 });
175
- t.check('pack(1) size == 6', 6, P.size);
176
- t.check('pack(1) offsetOf b', 1, P.offsetOf('b'));
177
- const AL = struct('AL', { a: 'u8', 'b@8': 'u32' });
178
- t.check('@N offsetOf a', 0, AL.offsetOf('a'));
179
- t.check('@N offsetOf b(字段名已剥离 @N)', 8, AL.offsetOf('b'));
180
- t.section('utf-16le string[8] roundtrip & truncation');
181
- const W = struct('W', { name: 'u16[8]@utf-16le' });
182
- t.check('utf16[8].size == 16', 16, W.size);
183
- const wb = W.encode({ name: 'hello' });
184
- t.check('read back "hello"', 'hello', W.decode(wb).name);
185
- const lb = W.encode({ name: 'a very long string over' });
186
- t.check('utf16[8]: payload 7 + NUL', 'a very ', W.decode(lb).name);
187
- t.check('utf16 terminator[14..15] == 0', 0, new DataView(lb).getUint16(14, true));
188
- t.section("utf-8 string[8] roundtrip & truncation");
189
- const C = struct('C', { name: 'u8[8]@utf-8' });
190
- t.check('char[8].size == 8', 8, C.size);
191
- const cb = C.encode({ name: 'hi' });
192
- t.check('read back "hi"', 'hi', C.decode(cb).name);
193
- t.check('NUL at [2]', 0, new DataView(cb).getUint8(2));
194
- const ctrunc = C.encode({ name: '1234567890' });
195
- t.check('char[8]: payload 7 + NUL', '1234567', C.decode(ctrunc).name);
196
- t.check('char terminator[7] == 0', 0, new DataView(ctrunc).getUint8(7));
197
- t.section('string sugar: 布局与解释正交(错配 roundtrip)');
198
- const XS = struct('XS', { buf: 'u8[8]@utf-16le' });
199
- t.check('u8[8]@utf-16le size == 8(encoding 不参与布局)', 8, XS.size);
200
- const xsd = XS.decode(XS.encode({ buf: 'AB' }));
201
- t.check('8 字节按 utf-16le 解出 "AB"', 'AB', xsd.buf);
202
- t.section('string sugar as array element');
203
- const SA = struct('SA', { rows: { '#': 'array', element: 'u16[4]@utf-16le', length: 3 } });
204
- t.check('string[4]×3 size == 24', 24, SA.size);
205
- const sad = SA.decode(SA.encode({ rows: ['ab', 'cd', 'ef'] }));
206
- t.check('rows[0]', 'ab', sad.rows[0]);
207
- t.check('rows[1]', 'cd', sad.rows[1]);
208
- expectType();
209
- t.section('键侧 alignas @N 与值侧 encoding @ 并存');
210
- const KV = struct('KV', { a: 'u8', 's@8': 'u16[4]@utf-8' });
211
- t.check('offsetOf a', 0, KV.offsetOf('a'));
212
- t.check('offsetOf s(键尾 @8 生效)', 8, KV.offsetOf('s'));
213
- t.check('KV size == 16', 16, KV.size);
214
- t.section('numeric arrays roundtrip (CArray)');
215
- const A = struct('A', {
216
- v: 'u16[4]',
217
- k: 'i32[2]',
218
- });
219
- t.check('size == 16', 16, A.size);
220
- const ab = A.encode({ v: [1, 2, 3, 4], k: [-1, 300000] });
221
- const ad = A.decode(ab);
222
- t.check('u16[0]', 1, ad.v[0]);
223
- t.check('u16[3]', 4, ad.v[3]);
224
- t.check('i32[0] -1', -1, ad.k[0]);
225
- t.check('i32[1]', 300000, ad.k[1]);
226
- t.section('T[1] stays an array (no scalar degradation)');
227
- const ONE = struct('ONE', { v: 'u16[1]' });
228
- t.check('size == 2', 2, ONE.size);
229
- const one = ONE.decode(ONE.encode({ v: [42] }));
230
- t.check('length 1', 1, one.v.length);
231
- t.check('[0]', 42, one.v[0]);
232
- t.section('array of struct roundtrip (stride = child size)');
233
- const PT_DECL = {
234
- '#': 'struct',
235
- x: 'i32',
236
- y: 'i32',
237
- };
238
- const PT = /* @__PURE__ */ struct('PT', PT_DECL);
239
- const POLY = struct('POLY', {
240
- count: 'u32',
241
- pts: { '#': 'array', element: PT_DECL, length: 3 },
242
- });
243
- t.check('POLY.size == 28', 28, POLY.size);
244
- t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'));
245
- const poly = POLY.decode(POLY.encode({ count: 3, pts: [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }] }));
246
- t.check('count', 3, poly.count);
247
- t.check('pts[0].x', 1, poly.pts[0].x);
248
- t.check('pts[2].y', 6, poly.pts[2].y);
249
- // 编译期断言:CType IR 推导 — string / 定长数组元组 / 嵌套 struct 递归
250
- expectType();
251
- expectType();
252
- expectType();
253
- expectType();
254
- expectType();
255
- expectType();
256
- let rejected = false;
257
- try {
258
- struct({ v: 'i32[0]' });
259
- }
260
- catch {
261
- rejected = true;
262
- }
263
- t.check('array length 0 rejected', true, rejected);
264
- let rejectedBare = false;
265
- try {
266
- struct({ p: 'ptr' });
267
- }
268
- catch {
269
- rejectedBare = true;
270
- }
271
- t.check('bare "ptr" member rejected', true, rejectedBare);
272
- // '#' 必填:字段值漏写 '#'(裸嵌套 map)不再默认 struct,直接 throw
273
- let rejectedMap = false;
274
- try {
275
- struct({ outer: { inner: 'u32' } });
276
- }
277
- catch {
278
- rejectedMap = true;
279
- }
280
- t.check('bare nested map rejected(漏写 #)', true, rejectedMap);
281
- // 顶层 '#' 与函数名 kind 不符 → throw(union DECL 误传 struct() 防线);
282
- // 一致('#': 'struct' 的 DECL 直传形态)→ 按 kind 正常布局
283
- let rejectedKind = false;
284
- try {
285
- struct({ '#': 'union', a: 'u8', b: 'u32' });
286
- }
287
- catch {
288
- rejectedKind = true;
289
- }
290
- t.check('top-level # kind mismatch rejected', true, rejectedKind);
291
- const H = struct('H', { '#': 'struct', a: 'u8', b: 'u32' });
292
- t.check('top-level # matching kind accepted(DECL 直传)', 8, H.size);
293
- // 类型层 kind 锁(参数交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }`):DECL 的
294
- // '#' 字面与函数名 kind 不符 → 交叉 never,编译期报错。@ts-expect-error 断言该
295
- // 报错存在(锁失效则指令未使用、tsc 报错);仅类型期检查,函数不调用
296
- const _kindLock = () => {
297
- // @ts-expect-error —— union DECL 传 struct()
298
- struct('BAD_U', { '#': 'union', a: 'u8', b: 'u32' });
299
- // @ts-expect-error —— struct DECL 传 union()
300
- union('BAD_S', { '#': 'struct', a: 'u8', b: 'u32' });
301
- };
302
- void _kindLock;
303
- t.section("pointer member '<>ptr' raw vs '<T>ptr' branded");
304
- const PTR = struct('PTR', {
305
- raw: '<>ptr',
306
- r: '<RECT>ptr',
307
- tag: 'u32',
308
- });
309
- t.check('PTR.size', is64 ? 24 : 12, PTR.size);
310
- t.check('offsetOf raw', 0, PTR.offsetOf('raw'));
311
- t.check('offsetOf r', is64 ? 8 : 4, PTR.offsetOf('r'));
312
- t.check('offsetOf tag', is64 ? 16 : 8, PTR.offsetOf('tag'));
313
- const rawOut = RECT.alloc();
314
- const pd = PTR.decode(PTR.encode({ raw: rawOut.ptr, r: rawOut.ptr, tag: 7 }));
315
- t.check('raw roundtrip', rawOut.ptr, pd.raw);
316
- t.check('r roundtrip', rawOut.ptr, pd.r);
317
- t.check('tag', 7, pd.tag);
318
- // 编译期:'<>ptr' → MaybePtr<''>(NULL|number,与 number 双向可赋值,NULL 即 0 保真);
319
- // '<RECT>ptr' → MaybePtr<'RECT'>(NULL|Ptr,0 保真,传参前收窄)。
320
- expectType();
321
- expectType();
322
- expectType();
323
- t.section('f32/f64 layout (MSVC: f@0, double@8)');
324
- const FL = struct('FL', {
325
- f: 'f32', // float
326
- d: 'f64', // double
327
- });
328
- t.check('size == 16 (f@0, double aligned @8)', 16, FL.size);
329
- const fb = FL.encode({ f: 1.5, d: -2.25 });
330
- const fr = FL.decode(fb);
331
- t.check('float', 1.5, fr.f);
332
- t.check('double', -2.25, fr.d);
333
- t.section('comctl dialog structs track 32/64-bit ABI');
334
- const TCITEMW = struct('TCITEMW', {
335
- mask: 'u32',
336
- dwState: 'u32',
337
- dwStateMask: 'u32',
338
- pszText: '<>ptr',
339
- cchTextMax: 'i32',
340
- iImage: 'i32',
341
- lParam: '<>ptr',
342
- });
343
- t.check('TCITEMW.size', is64 ? 40 : 28, TCITEMW.size);
344
- t.check('TCITEMW.offsetOf pszText', is64 ? 16 : 12, TCITEMW.offsetOf('pszText'));
345
- t.check('TCITEMW.offsetOf cchTextMax', is64 ? 24 : 16, TCITEMW.offsetOf('cchTextMax'));
346
- const TTTOOLINFOW = struct('TTTOOLINFOW', {
347
- cbSize: 'u32',
348
- uFlags: 'u32',
349
- hwnd: '<>ptr',
350
- uId: '<>ptr',
351
- rect: 'i32[4]',
352
- hinst: '<>ptr',
353
- lpszText: '<>ptr',
354
- lParam: '<>ptr',
355
- lpReserved: '<>ptr', // WinXP+ 追加
356
- });
357
- t.check('TTTOOLINFOW.size', is64 ? 72 : 48, TTTOOLINFOW.size);
358
- t.check('TTTOOLINFOW.offsetOf lpszText', is64 ? 48 : 36, TTTOOLINFOW.offsetOf('lpszText'));
359
- const OPENFILENAMEW = struct('OPENFILENAMEW', {
360
- lStructSize: 'u32',
361
- hwndOwner: '<>ptr',
362
- hInstance: '<>ptr',
363
- lpstrFilter: '<>ptr',
364
- lpstrCustomFilter: '<>ptr',
365
- nMaxCustFilter: 'u32',
366
- nFilterIndex: 'u32',
367
- lpstrFile: '<>ptr',
368
- nMaxFile: 'u32',
369
- lpstrFileTitle: '<>ptr',
370
- nMaxFileTitle: 'u32',
371
- lpstrInitialDir: '<>ptr',
372
- lpstrTitle: '<>ptr',
373
- Flags: 'u32',
374
- nFileOffset: 'u16',
375
- nFileExtension: 'u16',
376
- lpstrDefExt: '<>ptr',
377
- lCustData: '<>ptr',
378
- lpfnHook: '<>ptr',
379
- lpTemplateName: '<>ptr',
380
- pvReserved: '<>ptr',
381
- dwReserved: 'u32',
382
- FlagsEx: 'u32', // Win2000+ 追加
383
- });
384
- t.check('OPENFILENAMEW.size', is64 ? 152 : 88, OPENFILENAMEW.size);
385
- t.check('OPENFILENAMEW.offsetOf lpstrFile', is64 ? 48 : 28, OPENFILENAMEW.offsetOf('lpstrFile'));
386
- t.check('OPENFILENAMEW.offsetOf Flags', is64 ? 96 : 52, OPENFILENAMEW.offsetOf('Flags'));
387
- t.check('OPENFILENAMEW.offsetOf lpstrTitle', is64 ? 88 : 48, OPENFILENAMEW.offsetOf('lpstrTitle'));
388
- const BROWSEINFOW = struct('BROWSEINFOW', {
389
- hwndOwner: '<>ptr',
390
- pidlRoot: '<>ptr',
391
- pszDisplayName: '<>ptr',
392
- lpszTitle: '<>ptr',
393
- ulFlags: 'u32',
394
- lpfn: '<>ptr',
395
- lParam: '<>ptr',
396
- iImage: 'i32',
397
- });
398
- t.check('BROWSEINFOW.size', is64 ? 64 : 32, BROWSEINFOW.size);
399
- t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'));
400
- t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'));
401
- t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)');
402
- const NMHDR_DECL = {
403
- '#': 'struct',
404
- hwndFrom: '<>ptr',
405
- idFrom: '<>ptr',
406
- code: 'i32',
407
- };
408
- const NMHDR = /* @__PURE__ */ struct('NMHDR', NMHDR_DECL);
409
- t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size);
410
- t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'));
411
- t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)');
412
- const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
413
- hdr: NMHDR_DECL,
414
- dwDrawStage: 'u32',
415
- hdc: '<>ptr',
416
- rc: 'i32[4]',
417
- dwItemSpec: '<>ptr',
418
- uItemState: 'u32',
419
- lItemlParam: '<>ptr',
420
- });
421
- t.check('NMCUSTOMDRAW.size', is64 ? 80 : 48, NMCUSTOMDRAW.size);
422
- t.check('NMCUSTOMDRAW.offsetOf dwDrawStage', is64 ? 24 : 12, NMCUSTOMDRAW.offsetOf('dwDrawStage'));
423
- t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'));
424
- t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'));
425
- const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
426
- hdr: NMHDR_DECL,
427
- dwDrawStage: 'u32',
428
- hdc: '<>ptr',
429
- rc: 'i32[4]',
430
- dwItemSpec: '<>ptr',
431
- uItemState: 'u32',
432
- lItemlParam: '<>ptr',
433
- clrText: 'u32',
434
- clrTextBk: 'u32',
435
- iSubItem: 'i32',
436
- dwItemType: 'u32',
437
- });
438
- t.check('NMLVCUSTOMDRAW.size', is64 ? 96 : 64, NMLVCUSTOMDRAW.size);
439
- t.check('NMLVCUSTOMDRAW.offsetOf clrText', is64 ? 80 : 48, NMLVCUSTOMDRAW.offsetOf('clrText'));
440
- t.check('NMLVCUSTOMDRAW.offsetOf clrTextBk', is64 ? 84 : 52, NMLVCUSTOMDRAW.offsetOf('clrTextBk'));
441
- t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'));
442
- t.section('NMLISTVIEW / NMITEMACTIVATE');
443
- const NMLISTVIEW = struct('NMLISTVIEW', {
444
- hdr: NMHDR_DECL,
445
- iItem: 'i32',
446
- iSubItem: 'i32',
447
- uNewState: 'u32',
448
- uOldState: 'u32',
449
- uChanged: 'u32',
450
- ptAction: 'i32[2]',
451
- lParam: '<>ptr',
452
- });
453
- t.check('NMLISTVIEW.size', is64 ? 64 : 44, NMLISTVIEW.size);
454
- t.check('NMLISTVIEW.offsetOf iItem', is64 ? 24 : 12, NMLISTVIEW.offsetOf('iItem'));
455
- t.check('NMLISTVIEW.offsetOf iSubItem', is64 ? 28 : 16, NMLISTVIEW.offsetOf('iSubItem'));
456
- t.check('NMLISTVIEW.offsetOf uNewState', is64 ? 32 : 20, NMLISTVIEW.offsetOf('uNewState'));
457
- t.check('NMLISTVIEW.offsetOf uOldState', is64 ? 36 : 24, NMLISTVIEW.offsetOf('uOldState'));
458
- t.section('LVITEMW / LVCOLUMNW');
459
- const LVITEMW = struct('LVITEMW', {
460
- mask: 'u32',
461
- iItem: 'i32',
462
- iSubItem: 'i32',
463
- state: 'u32',
464
- stateMask: 'u32',
465
- pszText: '<>ptr',
466
- cchTextMax: 'i32',
467
- iImage: 'i32',
468
- lParam: '<>ptr',
469
- iIndent: 'i32',
470
- iGroupId: 'i32',
471
- cColumns: 'u32',
472
- puColumns: '<>ptr',
473
- piColFmt: '<>ptr',
474
- iGroup: 'i32',
475
- });
476
- t.check('LVITEMW.size', is64 ? 88 : 60, LVITEMW.size);
477
- t.check('LVITEMW.offsetOf iItem', 4, LVITEMW.offsetOf('iItem'));
478
- t.check('LVITEMW.offsetOf pszText', is64 ? 24 : 20, LVITEMW.offsetOf('pszText'));
479
- t.check('LVITEMW.offsetOf iImage', is64 ? 36 : 28, LVITEMW.offsetOf('iImage'));
480
- t.check('LVITEMW.offsetOf lParam', is64 ? 40 : 32, LVITEMW.offsetOf('lParam'));
481
- const LVCOLUMNW = struct('LVCOLUMNW', {
482
- mask: 'u32',
483
- fmt: 'i32',
484
- cx: 'i32',
485
- pszText: '<>ptr',
486
- cchTextMax: 'i32',
487
- iSubItem: 'i32',
488
- iImage: 'i32',
489
- iOrder: 'i32',
490
- cxMin: 'i32',
491
- cxDefault: 'i32',
492
- cxIdeal: 'i32',
493
- });
494
- t.check('LVCOLUMNW.size', is64 ? 56 : 44, LVCOLUMNW.size);
495
- t.check('LVCOLUMNW.offsetOf pszText', is64 ? 16 : 12, LVCOLUMNW.offsetOf('pszText'));
496
- t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'));
497
- t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'));
498
- t.section('NMDATETIMECHANGE');
499
- const SYSTEMTIME_DECL = {
500
- '#': 'struct',
501
- wYear: 'u16',
502
- wMonth: 'u16',
503
- wDayOfWeek: 'u16',
504
- wDay: 'u16',
505
- wHour: 'u16',
506
- wMinute: 'u16',
507
- wSecond: 'u16',
508
- wMilliseconds: 'u16',
509
- };
510
- const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
511
- hdr: NMHDR_DECL,
512
- dwFlags: 'u32',
513
- st: SYSTEMTIME_DECL,
514
- });
515
- t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size);
516
- t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'));
517
- t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'));
518
- t.section('NMLINK szUrl offset');
519
- const LITEM_DECL = {
520
- '#': 'struct',
521
- mask: 'u32',
522
- iLink: 'i32',
523
- state: 'u32',
524
- stateMask: 'u32',
525
- szID: 'u16[48]@utf-16le',
526
- };
527
- const LITEM = /* @__PURE__ */ struct('LITEM', LITEM_DECL);
528
- const NMLINK = struct('NMLINK', {
529
- hdr: NMHDR_DECL,
530
- item: LITEM_DECL,
531
- szUrl: 'u16[2084]@utf-16le',
532
- });
533
- t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'));
534
- t.check('NMLINK.offsetOf szUrl', is64 ? 136 : 124, NMLINK.offsetOf('szUrl'));
535
- // === 位域读/写往返 ===
536
- // 布局 ground truth 在 test_ffi_struct_layout.ts(mingw 交叉实测),这里只测读写。
537
- t.section('bitfield roundtrip: LSB→MSB 打包 + 读改写隔离');
538
- {
539
- // 字段按序写入同一 buffer:写 d 时单元里已有 a/b/c 的位,
540
- // 若 writeBitfield 不做读改写(直接 val<<bit)会把 a/b/c 清零。
541
- const BF = struct('BF', {
542
- a: 'u32:3',
543
- b: 'u32:5',
544
- c: 'u32:4',
545
- d: 'u32:10', // 3+5+4+10=22 <= 32
546
- tail: 'u32',
547
- });
548
- t.check('BF.size == 8', 8, BF.size);
549
- const bd = BF.decode(BF.encode({ a: 5, b: 31, c: 15, d: 1023, tail: 0xDEAD }));
550
- t.check('a (LSB)', 5, bd.a);
551
- t.check('b', 31, bd.b);
552
- t.check('c', 15, bd.c);
553
- t.check('d (max 10-bit)', 1023, bd.d);
554
- t.check('tail survives bitfield unit', 0xDEAD, bd.tail);
555
- // 位域写不破坏同单元的邻居:a=5 后写 b=31,a 仍为 5
556
- t.check('a survives b write', 5, bd.a);
557
- }
558
- t.section('bitfield roundtrip: signed sign extension');
559
- {
560
- const SB = struct('SB', {
561
- s: 'i32:8',
562
- u: 'u32:4',
563
- });
564
- t.check('SB.size == 4 (同宽不分签别,共单元)', 4, SB.size);
565
- const sbd = SB.decode(SB.encode({ s: -5, u: 9 }));
566
- t.check('signed -5', -5, sbd.s);
567
- t.check('unsigned 9', 9, sbd.u);
568
- t.check('signed -1', -1, SB.decode(SB.encode({ s: -1, u: 0 })).s);
569
- t.check('signed 127 (min int8)', 127, SB.decode(SB.encode({ s: 127, u: 0 })).s);
570
- t.check('signed 128 → -128', -128, SB.decode(SB.encode({ s: 128, u: 0 })).s);
571
- }
572
- t.section('bitfield roundtrip: overflow starts a new unit');
573
- {
574
- const OV = struct('OV', {
575
- a: 'u32:30',
576
- b: 'u32:4', // 30+4=34 > 32 → 新单元,剩余 2 位废弃
577
- c: 'u32',
578
- });
579
- t.check('OV.size == 12', 12, OV.size);
580
- t.check('OV.offsetOf b (new unit)', 4, OV.offsetOf('b'));
581
- t.check('OV.offsetOf c', 8, OV.offsetOf('c'));
582
- const ovd = OV.decode(OV.encode({ a: 0x3FFFFFFF, b: 15, c: 7 }));
583
- t.check('OV a (max 30-bit)', 0x3FFFFFFF, ovd.a);
584
- t.check('OV b', 15, ovd.b);
585
- t.check('OV c', 7, ovd.c);
586
- }
587
- t.section('bitfield: DCB roundtrip (Windows serial port config)');
588
- {
589
- const DCB = struct('DCB', {
590
- DCBlength: 'u32',
591
- BaudRate: 'u32',
592
- fBinary: 'u32:1',
593
- fParity: 'u32:1',
594
- fOutxCtsFlow: 'u32:1',
595
- fOutxDsrFlow: 'u32:1',
596
- fDtrControl: 'u32:2',
597
- fDsrSensitivity: 'u32:1',
598
- fTXContinueOnXoff: 'u32:1',
599
- fOutX: 'u32:1',
600
- fInX: 'u32:1',
601
- fErrorChar: 'u32:1',
602
- fNull: 'u32:1',
603
- fRtsControl: 'u32:2',
604
- fAbortOnError: 'u32:1',
605
- fDummy2: 'u32:17',
606
- wReserved: 'u16',
607
- XonLim: 'u16',
608
- XoffLim: 'u16',
609
- ByteSize: 'u8',
610
- Parity: 'u8',
611
- StopBits: 'u8',
612
- XonChar: 'i8',
613
- XoffChar: 'i8',
614
- ErrorChar: 'i8',
615
- EofChar: 'i8',
616
- EvtChar: 'i8',
617
- wReserved1: 'u16',
618
- });
619
- t.check('DCB.size == 28', 28, DCB.size);
620
- const d = DCB.decode(DCB.encode({
621
- DCBlength: 28, BaudRate: 0x1C200,
622
- fBinary: 1, fParity: 0, fOutxCtsFlow: 0, fOutxDsrFlow: 0,
623
- fDtrControl: 2, fDsrSensitivity: 1, fTXContinueOnXoff: 0,
624
- fOutX: 1, fInX: 0, fErrorChar: 1, fNull: 0,
625
- fRtsControl: 2, fAbortOnError: 1, fDummy2: 0,
626
- wReserved: 0, XonLim: 200, XoffLim: 100,
627
- ByteSize: 8, Parity: 0, StopBits: 0,
628
- XonChar: 3, XoffChar: 4, ErrorChar: 0xFF,
629
- EofChar: 3, EvtChar: 0x4, wReserved1: 0,
630
- }));
631
- t.check('DCBlength', 28, d.DCBlength);
632
- t.check('BaudRate', 0x1C200, d.BaudRate);
633
- t.check('fBinary', 1, d.fBinary);
634
- t.check('fParity', 0, d.fParity);
635
- t.check('fDtrControl', 2, d.fDtrControl);
636
- t.check('fDsrSensitivity', 1, d.fDsrSensitivity);
637
- t.check('fOutX', 1, d.fOutX);
638
- t.check('fRtsControl', 2, d.fRtsControl);
639
- t.check('fAbortOnError', 1, d.fAbortOnError);
640
- t.check('fDummy2', 0, d.fDummy2);
641
- t.check('wReserved', 0, d.wReserved);
642
- t.check('XonLim', 200, d.XonLim);
643
- t.check('XoffLim', 100, d.XoffLim);
644
- t.check('ByteSize', 8, d.ByteSize);
645
- t.check('Parity', 0, d.Parity);
646
- t.check('StopBits', 0, d.StopBits);
647
- t.check('XonChar', 3, d.XonChar);
648
- t.check('XoffChar', 4, d.XoffChar);
649
- t.check('ErrorChar (i8)', -1, d.ErrorChar);
650
- t.check('EofChar', 3, d.EofChar);
651
- t.check('EvtChar', 4, d.EvtChar);
652
- t.check('wReserved1', 0, d.wReserved1);
653
- }
654
- // 类型层:位域成员值类型是 number
655
- {
656
- const BF = struct('BF', {
657
- a: 'u32:3',
658
- s: 'i32:8',
659
- });
660
- expectType();
661
- expectType();
662
- }
663
- // 位域 width>53:值域装不进安全整数 → bigint(与类型层 BitShape 同界)
664
- t.section('bitfield width>53: bigint roundtrip (u64/i64)');
665
- {
666
- // u64:60 + u64:4 同单元混合形态:>53 的字段 bigint、≤53 的字段 number
667
- const W64 = struct('W64', {
668
- u: 'u64:60',
669
- v: 'u64:4',
670
- });
671
- t.check('u64:60+u64:4 size == 8(同单元)', 8, W64.size);
672
- t.check('v 与 u 同单元 offset == 0', 0, W64.offsetOf('v'));
673
- const big = 0x0fffffffffffffffn; // 60 位内大值 > 2^53
674
- const d1 = W64.decode(W64.encode({ u: big, v: 15 }));
675
- t.check('u64:60 大值精确往返', big, d1.u);
676
- t.check('u64:4 同单元往返', 15, d1.v);
677
- expectType();
678
- expectType();
679
- // i64:60 负值符号扩展 → 精确 bigint(回归:曾被有符号分支的 Number() 截断)
680
- const I64 = struct('I64', { s: 'i64:60' });
681
- const neg = -12345678901234567n;
682
- t.check('i64:60 负值精确往返', neg, I64.decode(I64.encode({ s: neg })).s);
683
- // 符号位为 0 的正值:width>53 → 依然 bigint
684
- t.check('i64:60 正值 bigint', 100n, I64.decode(I64.encode({ s: 100n })).s);
685
- expectType();
686
- if (false) {
687
- // @ts-expect-error width>53 位域收 bigint,number 被类型层拒绝
688
- I64.encode({ s: 42 });
689
- }
690
- // 分界:53 → number(2^53-1 精确),54 → bigint(2^54-1 精确)
691
- const B53 = struct('B53', { a: 'u64:53' });
692
- expectType();
693
- t.check('u64:53 边界 number 精确', 9007199254740991, B53.decode(B53.encode({ a: 9007199254740991 })).a);
694
- const B54 = struct('B54', { a: 'u64:54' });
695
- expectType();
696
- t.check('u64:54 边界 bigint 精确', 18014398509481983n, B54.decode(B54.encode({ a: 18014398509481983n })).a);
697
- }
698
- // ============ token @Enum:struct 字段值域 ============
699
- t.section('struct 字段 @Enum: 词汇定义点拒绝 + 值域收窄 + 必填判定');
700
- {
701
- // 定义点拒绝(∉ SimpleValue —— 未知枚举名与拼错 token 同一道闸)。if(false)
702
- // 纯类型层断言:位域反例运行时 normToken 也会 throw,不执行。
703
- if (false) {
704
- // @ts-expect-error 未知枚举名(TypoEnum ∉ keyof EnumMap)
705
- struct('Bad1', { m: 'u32@TypoEnum' });
706
- // @ts-expect-error 位域单元不收 @(值域标注对位域无意义,词汇拒)
707
- struct('Bad2', { m: 'u32@WmMsg:3' });
708
- // @ts-expect-error ptr 位未知枚举名
709
- struct('Bad3', { p: '<>ptr@TypoEnum' });
710
- // @ts-expect-error 数组糖元素同样受词汇守卫
711
- struct('Bad4', { a: 'u32@TypoEnum[4]' });
712
- }
713
- // 合法定义:数字位 + ptr 位 @ 标注
714
- const EV = struct('EV', {
715
- msg: 'u32@WmMsg', // WM_NULL = 0 ∈ 值域 → encode 可省
716
- key: 'u32@MouseKeyFlag', // 仅 MK_SHIFT = 4,0 ∉ 值域 → encode 必填
717
- hp: '<>ptr@SetWindowPosHwnd', // ptr 位(HWND_TOP = 0 ∈ → 可省)
718
- });
719
- // decode:值域收窄(与 bind 签名位同构;异种枚举/域外字面量由枚举 nominal 拦)
720
- const dev = EV.decode(EV.encode({ key: 4 }));
721
- expectType();
722
- expectType();
723
- expectType();
724
- t.check('数字位 @ 字段归一往返', 4, dev.key);
725
- // encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
726
- // 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
727
- EV.encode({ key: 4 /* MouseKeyFlag.MK_SHIFT */ });
728
- // @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
729
- EV.encode({ msg: 0 });
730
- if (false) {
731
- // @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
732
- EV.encode({ key: 3 });
733
- // @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
734
- EV.encode({ key: 1 /* WmMsg.CREATE */ });
735
- }
736
- // 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
737
- const EA = struct('EA', { msgs: 'u32@WmMsg[4]' });
738
- const dea = EA.decode(EA.encode({ msgs: [0, 1, 2, 0] }));
739
- expectType();
740
- t.check('数组 @ 元素运行时归一往返', 2, dea.msgs[2]);
741
- }
742
- // ============ EncodeIn 后序穿透:嵌套/数组子树含必填 ⇒ 父键必给 ============
743
- t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)');
744
- {
745
- // 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
746
- const NSUB_DECL = {
747
- '#': 'struct',
748
- hp: '<X>ptr!',
749
- rest: 'u32',
750
- s: 'u16[4]@utf-8',
751
- };
752
- const NSUB = /* @__PURE__ */ struct('NSUB', NSUB_DECL);
753
- const NOUT = struct('NOUT', { top: 'u32', sub: NSUB_DECL });
754
- // decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
755
- const nbuf = new PtrArrayBuffer(NOUT.size);
756
- new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true);
757
- const dn = NOUT.decode(nbuf);
758
- NOUT.encode(dn); // 全量回喂 ✓
759
- NOUT.encode({ sub: dn.sub }); // 顶层 top 可省 ✓
760
- // B 形态关键钉:内层非必填兄弟 rest/s 可省(A「全量严格形」形态此行编译错)
761
- NOUT.encode({ sub: { hp: dn.sub.hp } });
762
- // @ts-expect-error 嵌套穿透:sub 子树含 '!' ⇒ sub 必填 —— 省略即编译错
763
- NOUT.encode({ top: 1 });
764
- // @ts-expect-error 内层 '!' 必填:sub.hp 省略即编译错,不静默写 0
765
- NOUT.encode({ sub: { rest: 1 } });
766
- // 数组穿透:元素 0 ∉ 值域 ⇒ 整组必填(省 = 元素全 0 静默违约)
767
- const ARMAND = struct('ARMAND', { keys: 'u32@MouseKeyFlag[2]' });
768
- ARMAND.encode({ keys: [4, 4] });
769
- // @ts-expect-error 数组含必填元素:整组省略即编译错
770
- ARMAND.encode({});
771
- // weak-type 防线在「无必填」分支保持:裸 number 与全可选形无公共键(TS2559)。
772
- // if(false) 纯类型层断言(运行时 encode(123) 会炸,不执行)。
773
- if (false) {
774
- // @ts-expect-error 无必填 struct 的入参退化纯 DeepPartial,裸 number 被拦
775
- RECT.encode(123);
776
- }
777
- }
778
- },
779
- };