@gtkx/codegen 0.21.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 (340) hide show
  1. package/LICENSE +373 -0
  2. package/dist/dsl/context.d.ts +104 -0
  3. package/dist/dsl/context.d.ts.map +1 -0
  4. package/dist/dsl/context.js +140 -0
  5. package/dist/dsl/context.js.map +1 -0
  6. package/dist/dsl/emit.d.ts +28 -0
  7. package/dist/dsl/emit.d.ts.map +1 -0
  8. package/dist/dsl/emit.js +41 -0
  9. package/dist/dsl/emit.js.map +1 -0
  10. package/dist/dsl/identifier.d.ts +28 -0
  11. package/dist/dsl/identifier.d.ts.map +1 -0
  12. package/dist/dsl/identifier.js +45 -0
  13. package/dist/dsl/identifier.js.map +1 -0
  14. package/dist/dsl/imports.d.ts +58 -0
  15. package/dist/dsl/imports.d.ts.map +1 -0
  16. package/dist/dsl/imports.js +107 -0
  17. package/dist/dsl/imports.js.map +1 -0
  18. package/dist/dsl/module.d.ts +53 -0
  19. package/dist/dsl/module.d.ts.map +1 -0
  20. package/dist/dsl/module.js +76 -0
  21. package/dist/dsl/module.js.map +1 -0
  22. package/dist/ffi/pipeline.d.ts +19 -0
  23. package/dist/ffi/pipeline.d.ts.map +1 -0
  24. package/dist/ffi/pipeline.js +107 -0
  25. package/dist/ffi/pipeline.js.map +1 -0
  26. package/dist/fingerprint.d.ts +30 -0
  27. package/dist/fingerprint.d.ts.map +1 -0
  28. package/dist/fingerprint.js +33 -0
  29. package/dist/fingerprint.js.map +1 -0
  30. package/dist/gi-store.d.ts +34 -0
  31. package/dist/gi-store.d.ts.map +1 -0
  32. package/dist/gi-store.js +154 -0
  33. package/dist/gi-store.js.map +1 -0
  34. package/dist/gir/boxed.d.ts +66 -0
  35. package/dist/gir/boxed.d.ts.map +1 -0
  36. package/dist/gir/boxed.js +49 -0
  37. package/dist/gir/boxed.js.map +1 -0
  38. package/dist/gir/callback.d.ts +15 -0
  39. package/dist/gir/callback.d.ts.map +1 -0
  40. package/dist/gir/callback.js +17 -0
  41. package/dist/gir/callback.js.map +1 -0
  42. package/dist/gir/class.d.ts +49 -0
  43. package/dist/gir/class.d.ts.map +1 -0
  44. package/dist/gir/class.js +42 -0
  45. package/dist/gir/class.js.map +1 -0
  46. package/dist/gir/enum.d.ts +39 -0
  47. package/dist/gir/enum.d.ts.map +1 -0
  48. package/dist/gir/enum.js +24 -0
  49. package/dist/gir/enum.js.map +1 -0
  50. package/dist/gir/field.d.ts +27 -0
  51. package/dist/gir/field.d.ts.map +1 -0
  52. package/dist/gir/field.js +19 -0
  53. package/dist/gir/field.js.map +1 -0
  54. package/dist/gir/function.d.ts +47 -0
  55. package/dist/gir/function.d.ts.map +1 -0
  56. package/dist/gir/function.js +28 -0
  57. package/dist/gir/function.js.map +1 -0
  58. package/dist/gir/namespace.d.ts +65 -0
  59. package/dist/gir/namespace.d.ts.map +1 -0
  60. package/dist/gir/namespace.js +60 -0
  61. package/dist/gir/namespace.js.map +1 -0
  62. package/dist/gir/parameter.d.ts +82 -0
  63. package/dist/gir/parameter.d.ts.map +1 -0
  64. package/dist/gir/parameter.js +66 -0
  65. package/dist/gir/parameter.js.map +1 -0
  66. package/dist/gir/parse.d.ts +78 -0
  67. package/dist/gir/parse.d.ts.map +1 -0
  68. package/dist/gir/parse.js +155 -0
  69. package/dist/gir/parse.js.map +1 -0
  70. package/dist/gir/primitives.d.ts +36 -0
  71. package/dist/gir/primitives.d.ts.map +1 -0
  72. package/dist/gir/primitives.js +109 -0
  73. package/dist/gir/primitives.js.map +1 -0
  74. package/dist/gir/property.d.ts +29 -0
  75. package/dist/gir/property.d.ts.map +1 -0
  76. package/dist/gir/property.js +16 -0
  77. package/dist/gir/property.js.map +1 -0
  78. package/dist/gir/qualified-name.d.ts +37 -0
  79. package/dist/gir/qualified-name.d.ts.map +1 -0
  80. package/dist/gir/qualified-name.js +39 -0
  81. package/dist/gir/qualified-name.js.map +1 -0
  82. package/dist/gir/qualify.d.ts +32 -0
  83. package/dist/gir/qualify.d.ts.map +1 -0
  84. package/dist/gir/qualify.js +60 -0
  85. package/dist/gir/qualify.js.map +1 -0
  86. package/dist/gir/repository.d.ts +82 -0
  87. package/dist/gir/repository.d.ts.map +1 -0
  88. package/dist/gir/repository.js +122 -0
  89. package/dist/gir/repository.js.map +1 -0
  90. package/dist/gir/signal.d.ts +22 -0
  91. package/dist/gir/signal.d.ts.map +1 -0
  92. package/dist/gir/signal.js +20 -0
  93. package/dist/gir/signal.js.map +1 -0
  94. package/dist/gir/size.d.ts +67 -0
  95. package/dist/gir/size.d.ts.map +1 -0
  96. package/dist/gir/size.js +87 -0
  97. package/dist/gir/size.js.map +1 -0
  98. package/dist/gir/type-ref.d.ts +79 -0
  99. package/dist/gir/type-ref.d.ts.map +1 -0
  100. package/dist/gir/type-ref.js +101 -0
  101. package/dist/gir/type-ref.js.map +1 -0
  102. package/dist/index.d.ts +12 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +12 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/jsx-store.d.ts +22 -0
  107. package/dist/jsx-store.d.ts.map +1 -0
  108. package/dist/jsx-store.js +40 -0
  109. package/dist/jsx-store.js.map +1 -0
  110. package/dist/react/compounds-meta.d.ts +18 -0
  111. package/dist/react/compounds-meta.d.ts.map +1 -0
  112. package/dist/react/compounds-meta.js +93 -0
  113. package/dist/react/compounds-meta.js.map +1 -0
  114. package/dist/react/compounds.d.ts +27 -0
  115. package/dist/react/compounds.d.ts.map +1 -0
  116. package/dist/react/compounds.js +164 -0
  117. package/dist/react/compounds.js.map +1 -0
  118. package/dist/react/internal.d.ts +14 -0
  119. package/dist/react/internal.d.ts.map +1 -0
  120. package/dist/react/internal.js +79 -0
  121. package/dist/react/internal.js.map +1 -0
  122. package/dist/react/jsx.d.ts +22 -0
  123. package/dist/react/jsx.d.ts.map +1 -0
  124. package/dist/react/jsx.js +134 -0
  125. package/dist/react/jsx.js.map +1 -0
  126. package/dist/react/pipeline.d.ts +13 -0
  127. package/dist/react/pipeline.d.ts.map +1 -0
  128. package/dist/react/pipeline.js +25 -0
  129. package/dist/react/pipeline.js.map +1 -0
  130. package/dist/react/props.d.ts +38 -0
  131. package/dist/react/props.d.ts.map +1 -0
  132. package/dist/react/props.js +231 -0
  133. package/dist/react/props.js.map +1 -0
  134. package/dist/react/slot-props.d.ts +10 -0
  135. package/dist/react/slot-props.d.ts.map +1 -0
  136. package/dist/react/slot-props.js +56 -0
  137. package/dist/react/slot-props.js.map +1 -0
  138. package/dist/react/widgets.d.ts +83 -0
  139. package/dist/react/widgets.d.ts.map +1 -0
  140. package/dist/react/widgets.js +152 -0
  141. package/dist/react/widgets.js.map +1 -0
  142. package/dist/runner.d.ts +55 -0
  143. package/dist/runner.d.ts.map +1 -0
  144. package/dist/runner.js +60 -0
  145. package/dist/runner.js.map +1 -0
  146. package/dist/store-fs.d.ts +109 -0
  147. package/dist/store-fs.d.ts.map +1 -0
  148. package/dist/store-fs.js +98 -0
  149. package/dist/store-fs.js.map +1 -0
  150. package/dist/transpile.d.ts +32 -0
  151. package/dist/transpile.d.ts.map +1 -0
  152. package/dist/transpile.js +63 -0
  153. package/dist/transpile.js.map +1 -0
  154. package/dist/typecheck-store.d.ts +41 -0
  155. package/dist/typecheck-store.d.ts.map +1 -0
  156. package/dist/typecheck-store.js +102 -0
  157. package/dist/typecheck-store.js.map +1 -0
  158. package/dist/writers/alias.d.ts +13 -0
  159. package/dist/writers/alias.d.ts.map +1 -0
  160. package/dist/writers/alias.js +17 -0
  161. package/dist/writers/alias.js.map +1 -0
  162. package/dist/writers/async.d.ts +22 -0
  163. package/dist/writers/async.d.ts.map +1 -0
  164. package/dist/writers/async.js +48 -0
  165. package/dist/writers/async.js.map +1 -0
  166. package/dist/writers/boxed-constructor.d.ts +26 -0
  167. package/dist/writers/boxed-constructor.d.ts.map +1 -0
  168. package/dist/writers/boxed-constructor.js +85 -0
  169. package/dist/writers/boxed-constructor.js.map +1 -0
  170. package/dist/writers/boxed-field-accessor.d.ts +18 -0
  171. package/dist/writers/boxed-field-accessor.d.ts.map +1 -0
  172. package/dist/writers/boxed-field-accessor.js +260 -0
  173. package/dist/writers/boxed-field-accessor.js.map +1 -0
  174. package/dist/writers/boxed-layout.d.ts +27 -0
  175. package/dist/writers/boxed-layout.d.ts.map +1 -0
  176. package/dist/writers/boxed-layout.js +128 -0
  177. package/dist/writers/boxed-layout.js.map +1 -0
  178. package/dist/writers/boxed.d.ts +20 -0
  179. package/dist/writers/boxed.d.ts.map +1 -0
  180. package/dist/writers/boxed.js +71 -0
  181. package/dist/writers/boxed.js.map +1 -0
  182. package/dist/writers/callables.d.ts +124 -0
  183. package/dist/writers/callables.d.ts.map +1 -0
  184. package/dist/writers/callables.js +283 -0
  185. package/dist/writers/callables.js.map +1 -0
  186. package/dist/writers/callback.d.ts +16 -0
  187. package/dist/writers/callback.d.ts.map +1 -0
  188. package/dist/writers/callback.js +38 -0
  189. package/dist/writers/callback.js.map +1 -0
  190. package/dist/writers/class-struct-record.d.ts +31 -0
  191. package/dist/writers/class-struct-record.d.ts.map +1 -0
  192. package/dist/writers/class-struct-record.js +67 -0
  193. package/dist/writers/class-struct-record.js.map +1 -0
  194. package/dist/writers/class-struct.d.ts +22 -0
  195. package/dist/writers/class-struct.d.ts.map +1 -0
  196. package/dist/writers/class-struct.js +85 -0
  197. package/dist/writers/class-struct.js.map +1 -0
  198. package/dist/writers/class.d.ts +16 -0
  199. package/dist/writers/class.d.ts.map +1 -0
  200. package/dist/writers/class.js +307 -0
  201. package/dist/writers/class.js.map +1 -0
  202. package/dist/writers/constant.d.ts +15 -0
  203. package/dist/writers/constant.d.ts.map +1 -0
  204. package/dist/writers/constant.js +23 -0
  205. package/dist/writers/constant.js.map +1 -0
  206. package/dist/writers/constants.d.ts +15 -0
  207. package/dist/writers/constants.d.ts.map +1 -0
  208. package/dist/writers/constants.js +23 -0
  209. package/dist/writers/constants.js.map +1 -0
  210. package/dist/writers/constructor-props.d.ts +32 -0
  211. package/dist/writers/constructor-props.d.ts.map +1 -0
  212. package/dist/writers/constructor-props.js +110 -0
  213. package/dist/writers/constructor-props.js.map +1 -0
  214. package/dist/writers/enum.d.ts +21 -0
  215. package/dist/writers/enum.d.ts.map +1 -0
  216. package/dist/writers/enum.js +48 -0
  217. package/dist/writers/enum.js.map +1 -0
  218. package/dist/writers/function.d.ts +40 -0
  219. package/dist/writers/function.d.ts.map +1 -0
  220. package/dist/writers/function.js +127 -0
  221. package/dist/writers/function.js.map +1 -0
  222. package/dist/writers/gtype-binding.d.ts +19 -0
  223. package/dist/writers/gtype-binding.d.ts.map +1 -0
  224. package/dist/writers/gtype-binding.js +40 -0
  225. package/dist/writers/gtype-binding.js.map +1 -0
  226. package/dist/writers/inheritance.d.ts +48 -0
  227. package/dist/writers/inheritance.d.ts.map +1 -0
  228. package/dist/writers/inheritance.js +90 -0
  229. package/dist/writers/inheritance.js.map +1 -0
  230. package/dist/writers/interface.d.ts +16 -0
  231. package/dist/writers/interface.d.ts.map +1 -0
  232. package/dist/writers/interface.js +155 -0
  233. package/dist/writers/interface.js.map +1 -0
  234. package/dist/writers/method.d.ts +150 -0
  235. package/dist/writers/method.d.ts.map +1 -0
  236. package/dist/writers/method.js +510 -0
  237. package/dist/writers/method.js.map +1 -0
  238. package/dist/writers/param-classify.d.ts +93 -0
  239. package/dist/writers/param-classify.d.ts.map +1 -0
  240. package/dist/writers/param-classify.js +163 -0
  241. package/dist/writers/param-classify.js.map +1 -0
  242. package/dist/writers/property-accessor.d.ts +19 -0
  243. package/dist/writers/property-accessor.d.ts.map +1 -0
  244. package/dist/writers/property-accessor.js +117 -0
  245. package/dist/writers/property-accessor.js.map +1 -0
  246. package/dist/writers/registration.d.ts +27 -0
  247. package/dist/writers/registration.d.ts.map +1 -0
  248. package/dist/writers/registration.js +30 -0
  249. package/dist/writers/registration.js.map +1 -0
  250. package/dist/writers/return-wrap.d.ts +39 -0
  251. package/dist/writers/return-wrap.d.ts.map +1 -0
  252. package/dist/writers/return-wrap.js +152 -0
  253. package/dist/writers/return-wrap.js.map +1 -0
  254. package/dist/writers/runtime-override.d.ts +18 -0
  255. package/dist/writers/runtime-override.d.ts.map +1 -0
  256. package/dist/writers/runtime-override.js +44 -0
  257. package/dist/writers/runtime-override.js.map +1 -0
  258. package/dist/writers/signal.d.ts +21 -0
  259. package/dist/writers/signal.d.ts.map +1 -0
  260. package/dist/writers/signal.js +260 -0
  261. package/dist/writers/signal.js.map +1 -0
  262. package/dist/writers/ts-type.d.ts +50 -0
  263. package/dist/writers/ts-type.d.ts.map +1 -0
  264. package/dist/writers/ts-type.js +75 -0
  265. package/dist/writers/ts-type.js.map +1 -0
  266. package/dist/writers/types-ts.d.ts +27 -0
  267. package/dist/writers/types-ts.d.ts.map +1 -0
  268. package/dist/writers/types-ts.js +97 -0
  269. package/dist/writers/types-ts.js.map +1 -0
  270. package/dist/writers/value.d.ts +89 -0
  271. package/dist/writers/value.d.ts.map +1 -0
  272. package/dist/writers/value.js +402 -0
  273. package/dist/writers/value.js.map +1 -0
  274. package/package.json +55 -0
  275. package/src/dsl/context.ts +146 -0
  276. package/src/dsl/emit.ts +42 -0
  277. package/src/dsl/identifier.ts +52 -0
  278. package/src/dsl/imports.ts +109 -0
  279. package/src/dsl/module.ts +75 -0
  280. package/src/ffi/pipeline.ts +113 -0
  281. package/src/fingerprint.ts +50 -0
  282. package/src/gi-store.ts +199 -0
  283. package/src/gir/boxed.ts +94 -0
  284. package/src/gir/callback.ts +26 -0
  285. package/src/gir/class.ts +79 -0
  286. package/src/gir/enum.ts +55 -0
  287. package/src/gir/field.ts +40 -0
  288. package/src/gir/function.ts +67 -0
  289. package/src/gir/namespace.ts +118 -0
  290. package/src/gir/parameter.ts +120 -0
  291. package/src/gir/parse.ts +171 -0
  292. package/src/gir/primitives.ts +137 -0
  293. package/src/gir/property.ts +39 -0
  294. package/src/gir/qualified-name.ts +55 -0
  295. package/src/gir/qualify.ts +64 -0
  296. package/src/gir/repository.ts +149 -0
  297. package/src/gir/signal.ts +36 -0
  298. package/src/gir/size.ts +129 -0
  299. package/src/gir/type-ref.ts +180 -0
  300. package/src/index.ts +16 -0
  301. package/src/jsx-store.ts +53 -0
  302. package/src/react/compounds-meta.ts +99 -0
  303. package/src/react/compounds.ts +204 -0
  304. package/src/react/internal.ts +91 -0
  305. package/src/react/jsx.ts +155 -0
  306. package/src/react/pipeline.ts +29 -0
  307. package/src/react/props.ts +307 -0
  308. package/src/react/slot-props.ts +57 -0
  309. package/src/react/widgets.ts +185 -0
  310. package/src/runner.ts +89 -0
  311. package/src/store-fs.ts +157 -0
  312. package/src/transpile.ts +80 -0
  313. package/src/typecheck-store.ts +136 -0
  314. package/src/writers/alias.ts +19 -0
  315. package/src/writers/async.ts +44 -0
  316. package/src/writers/boxed-constructor.ts +105 -0
  317. package/src/writers/boxed-field-accessor.ts +306 -0
  318. package/src/writers/boxed-layout.ts +166 -0
  319. package/src/writers/boxed.ts +77 -0
  320. package/src/writers/callables.ts +339 -0
  321. package/src/writers/callback.ts +40 -0
  322. package/src/writers/class-struct-record.ts +75 -0
  323. package/src/writers/class-struct.ts +96 -0
  324. package/src/writers/class.ts +384 -0
  325. package/src/writers/constant.ts +29 -0
  326. package/src/writers/constructor-props.ts +130 -0
  327. package/src/writers/enum.ts +53 -0
  328. package/src/writers/function.ts +136 -0
  329. package/src/writers/gtype-binding.ts +48 -0
  330. package/src/writers/inheritance.ts +104 -0
  331. package/src/writers/interface.ts +168 -0
  332. package/src/writers/method.ts +678 -0
  333. package/src/writers/param-classify.ts +178 -0
  334. package/src/writers/property-accessor.ts +139 -0
  335. package/src/writers/registration.ts +42 -0
  336. package/src/writers/return-wrap.ts +180 -0
  337. package/src/writers/runtime-override.ts +59 -0
  338. package/src/writers/signal.ts +318 -0
  339. package/src/writers/ts-type.ts +115 -0
  340. package/src/writers/value.ts +485 -0
@@ -0,0 +1,318 @@
1
+ import { quote, toCamelCase } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import type { GirClass } from "../gir/class.js";
5
+ import type { GirParameter } from "../gir/parameter.js";
6
+ import { isOutParameter } from "../gir/parameter.js";
7
+ import { qualifyTypeRef } from "../gir/qualify.js";
8
+ import type { GirSignal } from "../gir/signal.js";
9
+ import type { GirTypeRef, PrimitiveTypeRef } from "../gir/type-ref.js";
10
+ import { forEachAncestor, resolveImplementedInterface } from "./inheritance.js";
11
+ import { planTrampolineArgs, renderTupleWriteback } from "./method.js";
12
+ import { isHandlePassing } from "./param-classify.js";
13
+ import { isCellInout, renderFfiType } from "./value.js";
14
+
15
+ const SIGNAL_HANDLER_TYPE = "(...args: any[]) => any";
16
+
17
+ /**
18
+ * A signal together with the namespace its parameter and return references
19
+ * belong to. Own signals carry the class's namespace; signals flattened from
20
+ * an implemented interface carry the interface's namespace so their references
21
+ * resolve correctly.
22
+ */
23
+ type CollectedSignal = {
24
+ readonly signal: GirSignal;
25
+ readonly namespaceName: string;
26
+ };
27
+
28
+ /** The generated marshalling references a class's inline `emit` switch uses. */
29
+ type GObjectRefs = {
30
+ readonly Value: string;
31
+ readonly signalEmitv: string;
32
+ readonly signalLookup: string;
33
+ };
34
+
35
+ /**
36
+ * Renders the `connect` and `emit` instance methods for a class that owns or
37
+ * inherits-by-interface at least one signal.
38
+ *
39
+ * Each method is a `switch` over the type's own signals. `connect` resolves the
40
+ * per-signal trampoline, wraps the handler, and hands it to the thin
41
+ * `connectSignal` wrapper around the non-introspectable `g_signal_connect_data`.
42
+ * `emit` marshals the arguments into `GValue`s with `valueFromFfi` and calls the
43
+ * generated `signalLookup` / `signalEmitv` directly. Unknown signals route up
44
+ * the class chain via `super.connect` / `super.emit`; the root `GObject.Object`
45
+ * throws because it bottoms out the hierarchy.
46
+ *
47
+ * Returns an empty array when the class contributes no signals of its own.
48
+ *
49
+ * @param context - The module context
50
+ * @param klass - The class whose signal methods to render
51
+ */
52
+ export const renderSignalMembers = (context: ModuleContext, klass: GirClass): readonly string[] => {
53
+ if (klass.glibGetType === undefined) return [];
54
+ const signals = collectClassSignals(context, klass);
55
+ if (signals.length === 0) return [];
56
+ const isRootObject = context.namespace.name === "GObject" && klass.name === "Object";
57
+
58
+ context.addRuntimeImport("connectSignal");
59
+ context.addRuntimeImport("signalBaseName");
60
+ context.addRuntimeImport("t");
61
+
62
+ const connectCases = signals.map((collected) => renderConnectCase(context, collected));
63
+ const emitCases = signals.map((collected) => renderEmitCase(context, collected));
64
+ const connectDefault = isRootObject
65
+ ? `default:\n throw new globalThis.Error("Unknown signal '" + signal + "'");`
66
+ : "default:\n return super.connect(signal, handler, after);";
67
+ const emitDefault = isRootObject
68
+ ? `default:\n throw new globalThis.Error("Unknown signal '" + sigName + "'");`
69
+ : "default:\n super.emit(sigName, ...args);";
70
+
71
+ const connectSwitch = `switch (signalBaseName(signal)) {\n${indent([...connectCases, connectDefault].join("\n"), 1)}\n}`;
72
+ const emitSwitch = `switch (sigName) {\n${indent([...emitCases, emitDefault].join("\n"), 1)}\n}`;
73
+
74
+ return [
75
+ `connect(signal: string, handler: ${SIGNAL_HANDLER_TYPE}, after?: boolean): number {\n${indent(connectSwitch, 1)}\n}`,
76
+ `emit(sigName: string, ...args: unknown[]): void {\n${indent(emitSwitch, 1)}\n}`,
77
+ ];
78
+ };
79
+
80
+ /**
81
+ * Renders one `connect` switch case: it resolves the signal's typed trampoline,
82
+ * wraps the user handler with the per-signal in-parameter marshalling closure,
83
+ * and dispatches `g_signal_connect_data` through {@link connectSignal} with the
84
+ * full detailed signal name.
85
+ */
86
+ const renderConnectCase = (context: ModuleContext, collected: CollectedSignal): string => {
87
+ const { signal } = collected;
88
+ const { trampoline, invoke } = renderTrampolineAndInvoke(context, collected);
89
+ const body = [
90
+ `const invoke = ${invoke};`,
91
+ "const handlerWrapper = (...args: unknown[]): unknown => invoke(handler, args);",
92
+ `return connectSignal(this, signal, ${trampoline}, handlerWrapper, after ?? false);`,
93
+ ].join("\n");
94
+ return `case ${quote(signal.name)}: {\n${indent(body, 1)}\n}`;
95
+ };
96
+
97
+ /**
98
+ * Renders one `emit` switch case: it marshals the emitting instance and each
99
+ * signal parameter into a `GValue` with `valueFromFfi`, resolves the signal id
100
+ * from the instance's runtime `GType`, and dispatches `g_signal_emitv`. Signals
101
+ * with a non-void return supply an initialized return `GValue`.
102
+ */
103
+ const renderEmitCase = (context: ModuleContext, collected: CollectedSignal): string => {
104
+ const { signal, namespaceName } = collected;
105
+ const params = signal.parameters.filter((parameter) => !parameter.isVarargs);
106
+ const paramFfi = params.map((parameter) =>
107
+ renderFfiType(context, qualifyTypeRef(parameter.type, namespaceName), parameter.transferOwnership),
108
+ );
109
+ context.addValueFromFfiImport();
110
+ const refs = gobjectRefs(context);
111
+ const valueArgs = [
112
+ 'valueFromFfi(t.object("full"), this)',
113
+ ...paramFfi.map((ffi, index) => `valueFromFfi(${ffi}, args[${index}])`),
114
+ ];
115
+ const values = `[${valueArgs.join(", ")}]`;
116
+ const lookup = `${refs.signalLookup}(${quote(signal.name)}, this.__gtype__)`;
117
+ const returnRef = qualifyTypeRef(signal.returnValue.type, namespaceName);
118
+ const emitStatements =
119
+ returnRef === undefined || isVoidRef(returnRef)
120
+ ? `${refs.signalEmitv}(${values}, ${lookup}, 0);`
121
+ : [
122
+ `const returnValue = new ${refs.Value}();`,
123
+ `returnValue.init(${renderReturnGType(context, returnRef)});`,
124
+ `${refs.signalEmitv}(${values}, ${lookup}, 0, returnValue);`,
125
+ ].join("\n");
126
+ return `case ${quote(signal.name)}: {\n${indent(`${emitStatements}\nreturn;`, 1)}\n}`;
127
+ };
128
+
129
+ /**
130
+ * Resolves the `GObject` marshalling references the inline `emit` switch needs.
131
+ * Within the `GObject` namespace the generated `Value`, `signalEmitv`, and
132
+ * `signalLookup` are local; elsewhere they are reached through the cross-
133
+ * namespace `GObject` alias.
134
+ */
135
+ const gobjectRefs = (context: ModuleContext): GObjectRefs => {
136
+ if (context.namespace.name === "GObject") {
137
+ return { Value: "Value", signalEmitv: "signalEmitv", signalLookup: "signalLookup" };
138
+ }
139
+ const alias = context.addCrossNamespaceImport("GObject");
140
+ return { Value: `${alias}.Value`, signalEmitv: `${alias}.signalEmitv`, signalLookup: `${alias}.signalLookup` };
141
+ };
142
+
143
+ /**
144
+ * Renders the trampoline FFI descriptor used to connect a handler and the
145
+ * `invoke` closure that marshals the trampoline arguments into the user handler.
146
+ *
147
+ * Out-parameter cells are wrapped in `t.ref(...)` so the native trampoline can
148
+ * write them back; the `invoke` closure mirrors the out-parameter tuple
149
+ * convention used by method out-parameters.
150
+ */
151
+ const renderTrampolineAndInvoke = (
152
+ context: ModuleContext,
153
+ collected: CollectedSignal,
154
+ ): { readonly trampoline: string; readonly invoke: string } => {
155
+ const { signal, namespaceName } = collected;
156
+ const params = signal.parameters.filter((parameter) => !parameter.isVarargs);
157
+ const paramFfi = params.map((parameter) =>
158
+ renderFfiType(context, qualifyTypeRef(parameter.type, namespaceName), parameter.transferOwnership),
159
+ );
160
+ const trampolineParamFfi = params.map((parameter, index) =>
161
+ isOutParameter(parameter) || isCellInout(context, parameter) ? `t.ref(${paramFfi[index]})` : paramFfi[index],
162
+ );
163
+ const returnRef = qualifyTypeRef(signal.returnValue.type, namespaceName);
164
+ const isVoid = isVoidRef(returnRef);
165
+ const returnFfi = isVoid ? "t.void" : renderFfiType(context, returnRef, signal.returnValue.transferOwnership);
166
+ const trampolineArgs = ['t.object("borrowed")', ...trampolineParamFfi, "t.void"].join(", ");
167
+ const trampoline = `t.trampoline([${trampolineArgs}], ${returnFfi}, { hasDestroy: true, userDataIndex: ${params.length + 1} })`;
168
+ const invoke = renderInvokeClosure(context, collected, params, isVoid ? undefined : returnRef);
169
+ return { trampoline, invoke };
170
+ };
171
+
172
+ const collectClassSignals = (context: ModuleContext, klass: GirClass): readonly CollectedSignal[] => {
173
+ const inheritedNames = collectInheritedSignalNames(context, klass);
174
+ const seen = new Set<string>();
175
+ const result: CollectedSignal[] = [];
176
+ for (const signal of klass.signals) {
177
+ const name = toCamelCase(signal.name);
178
+ if (inheritedNames.has(name) || seen.has(name)) continue;
179
+ seen.add(name);
180
+ result.push({ signal, namespaceName: context.namespace.name });
181
+ }
182
+ for (const implementName of klass.implements) {
183
+ const iface = resolveImplementedInterface(context, implementName);
184
+ if (iface === undefined) continue;
185
+ for (const signal of iface.klass.signals) {
186
+ const name = toCamelCase(signal.name);
187
+ if (inheritedNames.has(name) || seen.has(name)) continue;
188
+ seen.add(name);
189
+ result.push({ signal, namespaceName: iface.namespaceName });
190
+ }
191
+ }
192
+ return result;
193
+ };
194
+
195
+ const collectInheritedSignalNames = (context: ModuleContext, klass: GirClass): ReadonlySet<string> => {
196
+ const names = new Set<string>();
197
+ forEachAncestor(context, klass, (ancestor) => {
198
+ for (const signal of ancestor.klass.signals) names.add(toCamelCase(signal.name));
199
+ for (const implementName of ancestor.klass.implements) {
200
+ const iface = resolveImplementedInterface(context, implementName, ancestor.namespaceName);
201
+ if (iface === undefined) continue;
202
+ for (const signal of iface.klass.signals) names.add(toCamelCase(signal.name));
203
+ }
204
+ });
205
+ return names;
206
+ };
207
+
208
+ const renderInvokeClosure = (
209
+ context: ModuleContext,
210
+ collected: CollectedSignal,
211
+ params: readonly GirParameter[],
212
+ returnRef: GirTypeRef | undefined,
213
+ ): string => {
214
+ const { namespaceName } = collected;
215
+ const { callArgs, outArgIndices } = planTrampolineArgs(context, params, namespaceName, 1);
216
+ if (outArgIndices.length === 0) {
217
+ if (returnRef !== undefined && isHandlePassing(context, returnRef)) {
218
+ context.addRuntimeImport("tryGetHandle");
219
+ return `(handler, args) => {\n const _result = handler(${callArgs});\n return tryGetHandle(_result);\n}`;
220
+ }
221
+ return `(handler, args) => handler(${callArgs})`;
222
+ }
223
+ return renderOutParamInvoke(context, callArgs, outArgIndices, returnRef);
224
+ };
225
+
226
+ /**
227
+ * Renders the invoke closure for a signal with out-parameters.
228
+ *
229
+ * The handler returns its results as the tuple {@link renderMethodReturnType}
230
+ * describes (`[primary, ...outs]` when both exist, the scalar out alone for a
231
+ * void return with a single out, or an out-only tuple otherwise). The shared
232
+ * {@link renderTupleWriteback} destructures that tuple and writes each out
233
+ * value into its trampoline cell's `value` slot — the native side flushes
234
+ * those cells through the matching C out-pointers, so the tuple convention
235
+ * stays entirely in generated code.
236
+ *
237
+ * @param context - The module context
238
+ * @param callArgs - The rendered in-parameter call arguments
239
+ * @param outArgIndices - Trampoline-arg indices of the out-parameter cells
240
+ * @param returnRef - The signal's return type, or `undefined` for void
241
+ */
242
+ const renderOutParamInvoke = (
243
+ context: ModuleContext,
244
+ callArgs: string,
245
+ outArgIndices: readonly number[],
246
+ returnRef: GirTypeRef | undefined,
247
+ ): string => {
248
+ const body = renderTupleWriteback(context, `handler(${callArgs})`, outArgIndices, returnRef);
249
+ return `(handler, args) => {\n ${body}\n}`;
250
+ };
251
+
252
+ const renderReturnGType = (context: ModuleContext, ref: GirTypeRef): string => {
253
+ if (ref.kind === "primitive") {
254
+ return `${typeFromNameReference(context)}(${quote(primitiveGTypeName(ref.category))})`;
255
+ }
256
+ if (ref.kind === "named") {
257
+ const owner = ref.namespaceName ?? context.namespace.name;
258
+ const resolved = context.repository.resolveNamed(owner, ref.typeName);
259
+ if (resolved !== undefined && resolved.kind === "enum") {
260
+ context.addNativeImport("call");
261
+ context.addRuntimeImport("t");
262
+ const lib = resolved.namespace.sharedLibrary ?? "";
263
+ const getter = resolved.value.glibGetType ?? "";
264
+ return `call(${quote(lib)}, ${quote(getter)}, [], t.uint64)`;
265
+ }
266
+ if (
267
+ resolved !== undefined &&
268
+ (resolved.kind === "class" || resolved.kind === "interface" || resolved.kind === "boxed")
269
+ ) {
270
+ const glibTypeName = glibTypeNameOf(resolved.value) ?? ref.typeName;
271
+ return `${typeFromNameReference(context)}(${quote(glibTypeName)})`;
272
+ }
273
+ }
274
+ return `${typeFromNameReference(context)}("GObject")`;
275
+ };
276
+
277
+ const isVoidRef = (ref: GirTypeRef | undefined): boolean =>
278
+ ref === undefined || (ref.kind === "primitive" && ref.category === "void");
279
+
280
+ const glibTypeNameOf = (value: {
281
+ readonly glibTypeName?: string | undefined;
282
+ readonly cType?: string | undefined;
283
+ }): string | undefined => value.glibTypeName ?? value.cType;
284
+
285
+ const typeFromNameReference = (context: ModuleContext): string => {
286
+ if (context.namespace.name === "GObject") return "typeFromName";
287
+ const alias = context.addCrossNamespaceImport("GObject");
288
+ return `${alias}.typeFromName`;
289
+ };
290
+
291
+ const primitiveGTypeName = (category: PrimitiveTypeRef["category"]): string => {
292
+ switch (category) {
293
+ case "boolean":
294
+ return "gboolean";
295
+ case "int8":
296
+ case "int16":
297
+ case "int32":
298
+ return "gint";
299
+ case "uint8":
300
+ case "uint16":
301
+ case "uint32":
302
+ case "unichar":
303
+ return "guint";
304
+ case "int64":
305
+ return "gint64";
306
+ case "uint64":
307
+ case "pointer":
308
+ return "guint64";
309
+ case "float32":
310
+ return "gfloat";
311
+ case "float64":
312
+ return "gdouble";
313
+ case "string":
314
+ return "gchararray";
315
+ case "void":
316
+ return "void";
317
+ }
318
+ };
@@ -0,0 +1,115 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import { aliasExportName } from "../dsl/identifier.js";
3
+ import { PRIMITIVE_TS_TYPE } from "../gir/primitives.js";
4
+ import type { ResolvedNamed } from "../gir/repository.js";
5
+ import type { GirTypeRef, NamedTypeRef } from "../gir/type-ref.js";
6
+
7
+ /**
8
+ * TypeScript type annotations the writers emit alongside runtime values.
9
+ *
10
+ * Generated `.ts` files are consumed both as runtime modules (transpiled to
11
+ * `.js`) and as declaration sources (`ts.transpileDeclaration` extracts the
12
+ * `.d.ts` shape). Every exported value, parameter, and return position
13
+ * therefore carries an explicit annotation so isolated declaration emit
14
+ * succeeds without inference. The helpers below produce those annotations
15
+ * from the same `GirTypeRef` shapes the runtime FFI writers consume,
16
+ * importing referenced cross-namespace classes on demand.
17
+ */
18
+
19
+ /**
20
+ * Strategy describing the type-ref shapes the FFI and React generators render
21
+ * differently: container style (`Map` vs `Record`), the callback placeholder,
22
+ * whether a `GByteArray` collapses to `number[]`, and how a named reference is
23
+ * qualified and imported. {@link renderBaseTypeFor} renders every other shape
24
+ * identically for both.
25
+ */
26
+ export type TsTypeTarget = {
27
+ /** `"map"` → `Map<K, V>`; `"record"` → `Record<K, V>`. */
28
+ readonly containerStyle: "map" | "record";
29
+ /** The placeholder emitted for a callback type. */
30
+ readonly callbackType: string;
31
+ /** Whether a `GByteArray` renders as `number[]` instead of `element[]`. */
32
+ readonly byteArrayAsNumber: boolean;
33
+ /** Renders a named reference (class/interface/boxed/enum/callback/alias). */
34
+ readonly renderNamed: (ref: NamedTypeRef) => string;
35
+ };
36
+
37
+ /**
38
+ * Renders the TypeScript annotation for a `GirTypeRef`, dispatching the shapes
39
+ * both generators share and delegating the divergent ones to {@link target}.
40
+ *
41
+ * @param target - The per-generator rendering strategy
42
+ * @param ref - The type reference, or `undefined` for void
43
+ */
44
+ export const renderBaseTypeFor = (target: TsTypeTarget, ref: GirTypeRef | undefined): string => {
45
+ if (ref === undefined) return "void";
46
+ switch (ref.kind) {
47
+ case "primitive":
48
+ return PRIMITIVE_TS_TYPE[ref.category];
49
+ case "varargs":
50
+ return "unknown[]";
51
+ case "callback":
52
+ return target.callbackType;
53
+ case "named":
54
+ return target.renderNamed(ref);
55
+ case "array":
56
+ return `${renderBaseTypeFor(target, ref.element)}[]`;
57
+ case "list":
58
+ if (ref.flavor === "gbytearray" && target.byteArrayAsNumber) return "number[]";
59
+ return `${renderBaseTypeFor(target, ref.element)}[]`;
60
+ case "hashtable": {
61
+ const key = renderBaseTypeFor(target, ref.key);
62
+ const value = renderBaseTypeFor(target, ref.value);
63
+ return target.containerStyle === "record" ? `Record<${key}, ${value}>` : `Map<${key}, ${value}>`;
64
+ }
65
+ }
66
+ };
67
+
68
+ const moduleTarget = (context: ModuleContext): TsTypeTarget => ({
69
+ containerStyle: "map",
70
+ callbackType: "((...args: any[]) => any)",
71
+ byteArrayAsNumber: true,
72
+ renderNamed: (ref) => namedTsType(context, ref),
73
+ });
74
+
75
+ /**
76
+ * Renders a TypeScript type annotation for a `GirTypeRef` in an FFI module:
77
+ * classes/interfaces/boxeds/enums/callbacks as their qualified identifier,
78
+ * arrays and lists as `T[]`, hashtables as `Map<K, V>`, primitives to their TS
79
+ * counterpart, and `undefined` to `void`.
80
+ *
81
+ * @param context - The module context
82
+ * @param ref - The type reference, or `undefined` for void
83
+ * @param isNullable - Whether the slot accepts `null`
84
+ */
85
+ export const renderTsType = (context: ModuleContext, ref: GirTypeRef | undefined, isNullable = false): string => {
86
+ const base = renderBaseTypeFor(moduleTarget(context), ref);
87
+ return isNullable ? `${base} | null` : base;
88
+ };
89
+
90
+ const namedTsType = (context: ModuleContext, ref: NamedTypeRef): string => {
91
+ const namespaceName = ref.namespaceName ?? context.namespace.name;
92
+ const resolved = context.repository.resolveNamed(namespaceName, ref.typeName);
93
+ if (resolved === undefined) {
94
+ return context.qualify(namespaceName, ref.typeName);
95
+ }
96
+ return resolvedTsType(context, namespaceName, ref.typeName, resolved);
97
+ };
98
+
99
+ const resolvedTsType = (
100
+ context: ModuleContext,
101
+ namespaceName: string,
102
+ typeName: string,
103
+ resolved: ResolvedNamed,
104
+ ): string => {
105
+ switch (resolved.kind) {
106
+ case "class":
107
+ case "interface":
108
+ case "boxed":
109
+ case "enum":
110
+ case "callback":
111
+ return context.qualify(namespaceName, typeName);
112
+ case "alias":
113
+ return context.qualify(namespaceName, aliasExportName(namespaceName, typeName));
114
+ }
115
+ };