@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,485 @@
1
+ import { quote } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { joinArgs } from "../dsl/emit.js";
4
+ import { callbackFromNode, type GirCallback } from "../gir/callback.js";
5
+ import type { GirNamespace } from "../gir/namespace.js";
6
+ import { type GirParameter, isInoutParameter, isOutParameter, type ParameterTransfer } from "../gir/parameter.js";
7
+ import { qualifyTypeRef } from "../gir/qualify.js";
8
+ import type { GirRepository, ResolvedNamed } from "../gir/repository.js";
9
+ import type { ArrayTypeRef, GirTypeRef, NamedTypeRef } from "../gir/type-ref.js";
10
+ import { computeBoxedFieldSlots } from "./boxed-layout.js";
11
+
12
+ /**
13
+ * Inline byte sizes for boxed value types whose GIR record is opaque, so the
14
+ * field layout cannot be computed but the type is still passed by value in
15
+ * arrays (e.g. the `GValue` cells `g_signal_emitv` consumes).
16
+ */
17
+ const HARDCODED_INLINE_ELEMENT_SIZES: ReadonlyMap<string, number> = new Map([["GObject.Value", 24]]);
18
+
19
+ /**
20
+ * Maps a GIR transfer-ownership value to the FFI runtime's ownership
21
+ * vocabulary.
22
+ *
23
+ * The runtime treats `"none"` as `"borrowed"` for incoming values and as
24
+ * `"full"` for return values that already own the pointer. Callers pass
25
+ * the GIR string directly; this helper does the translation.
26
+ *
27
+ * @param transfer - GIR transfer-ownership (`"none"`, `"full"`, `"container"`)
28
+ */
29
+ const ffiOwnership = (transfer: ParameterTransfer): "borrowed" | "full" | "none" => {
30
+ if (transfer === "full") return "full";
31
+ if (transfer === "container") return "full";
32
+ return "borrowed";
33
+ };
34
+
35
+ /**
36
+ * Derives the transfer of a container's elements from the container's own
37
+ * transfer. A `"container"` transfer owns the container but borrows its
38
+ * elements, so they marshal as `"none"`; otherwise elements inherit the
39
+ * container's transfer.
40
+ *
41
+ * @param transfer - The container's GIR transfer-ownership
42
+ */
43
+ const deriveElementTransfer = (transfer: ParameterTransfer): ParameterTransfer =>
44
+ transfer === "container" ? "none" : transfer;
45
+
46
+ /**
47
+ * Renders a TypeScript expression that materialises the FFI type
48
+ * descriptor for `ref`.
49
+ *
50
+ * @param context - The module context (used for cross-namespace imports)
51
+ * @param ref - The GIR type reference, or `undefined` for void
52
+ * @param transfer - GIR transfer-ownership conveyed onto the descriptor
53
+ * @param argIndexOffset - Shift applied to a sized array's length-parameter
54
+ * index so it addresses the FFI argument list (which includes the
55
+ * instance receiver) rather than the GIR parameter list
56
+ */
57
+ export const renderFfiType = (
58
+ context: ModuleContext,
59
+ ref: GirTypeRef | undefined,
60
+ transfer: ParameterTransfer = "none",
61
+ argIndexOffset = 0,
62
+ ): string => {
63
+ if (ref === undefined) return "t.void";
64
+ const ownership = ffiOwnership(transfer);
65
+ switch (ref.kind) {
66
+ case "primitive":
67
+ return primitiveExpression(ref.category, ownership);
68
+ case "varargs":
69
+ return "t.void";
70
+ case "callback":
71
+ return "t.void";
72
+ case "named":
73
+ return namedExpression(context, ref, ownership);
74
+ case "array":
75
+ return arrayExpression(context, ref, transfer, argIndexOffset);
76
+ case "list": {
77
+ if (ref.flavor === "gbytearray") return `t.byteArray(${quote(ownership)})`;
78
+ const element = renderFfiType(context, ref.element, deriveElementTransfer(transfer), argIndexOffset);
79
+ const helper = LIST_HELPERS[ref.flavor];
80
+ return `t.${helper}(${element}, ${quote(ownership)})`;
81
+ }
82
+ case "hashtable": {
83
+ const elementTransfer = deriveElementTransfer(transfer);
84
+ const key = renderFfiType(context, ref.key, elementTransfer, argIndexOffset);
85
+ const value = renderFfiType(context, ref.value, elementTransfer, argIndexOffset);
86
+ return `t.hashTable(${key}, ${value}, ${quote(ownership)})`;
87
+ }
88
+ }
89
+ };
90
+
91
+ /**
92
+ * A callback declaration resolved from a parameter type, together with the
93
+ * namespace its parameter and return references belong to.
94
+ */
95
+ export type ResolvedCallback = {
96
+ readonly callback: GirCallback;
97
+ readonly namespaceName: string;
98
+ };
99
+
100
+ /**
101
+ * Resolves a parameter type reference to its callback declaration, whether
102
+ * declared inline (`<callback>` child) or by name (a namespace-level
103
+ * `<callback>`). Returns `undefined` for non-callback references.
104
+ *
105
+ * @param context - The module context
106
+ * @param ref - The parameter type reference
107
+ */
108
+ export const resolveCallbackType = (
109
+ context: ModuleContext,
110
+ ref: GirTypeRef | undefined,
111
+ ): ResolvedCallback | undefined => {
112
+ if (ref === undefined) return undefined;
113
+ if (ref.kind === "callback")
114
+ return { callback: callbackFromNode(ref.callback), namespaceName: context.namespace.name };
115
+ if (ref.kind !== "named") return undefined;
116
+ const owner = ref.namespaceName ?? context.namespace.name;
117
+ const resolved = context.repository.resolveNamed(owner, ref.typeName);
118
+ if (resolved === undefined || resolved.kind !== "callback") return undefined;
119
+ return { callback: resolved.value, namespaceName: resolved.namespace.name };
120
+ };
121
+
122
+ /**
123
+ * Whether a type reference is a scalar the native trampoline can read and
124
+ * write through a `{ value }` cell: a non-string, non-void primitive, an enum or
125
+ * flags type, or an alias resolving to one. Pointer-shaped types (strings,
126
+ * objects, boxed records, arrays) are excluded — their out-parameter slots may
127
+ * be uninitialized, so reading the incoming value would be unsound.
128
+ *
129
+ * @param repository - The GIR repository
130
+ * @param namespaceName - The namespace the reference is resolved against
131
+ * @param ref - The type reference
132
+ */
133
+ export const isScalarRef = (repository: GirRepository, namespaceName: string, ref: GirTypeRef | undefined): boolean => {
134
+ if (ref === undefined) return false;
135
+ if (ref.kind === "primitive") return ref.category !== "string" && ref.category !== "void";
136
+ if (ref.kind !== "named") return false;
137
+ const resolved = repository.resolveNamed(ref.namespaceName ?? namespaceName, ref.typeName);
138
+ if (resolved === undefined) return false;
139
+ if (resolved.kind === "enum") return true;
140
+ if (resolved.kind === "alias") {
141
+ return resolved.targetRef !== undefined && isScalarRef(repository, resolved.namespace.name, resolved.targetRef);
142
+ }
143
+ return false;
144
+ };
145
+
146
+ /**
147
+ * Whether an inout parameter marshals through a readable-and-writable
148
+ * `{ value }` cell rather than being passed by handle and mutated in place.
149
+ *
150
+ * True for scalar inout parameters (see {@link isScalarRef}): the native
151
+ * trampoline seeds the cell with the incoming value, the handler returns its
152
+ * replacement in the tuple, and the cell is flushed back through the pointer.
153
+ * Handle-passing inout parameters (objects, interfaces, boxed records) stay
154
+ * plain arguments the handler mutates directly.
155
+ *
156
+ * @param context - The module context
157
+ * @param parameter - The parameter to test
158
+ */
159
+ export const isCellInout = (context: ModuleContext, parameter: GirParameter): boolean =>
160
+ isInoutParameter(parameter) && isScalarRef(context.repository, context.namespace.name, parameter.type);
161
+
162
+ /**
163
+ * Renders the `t.trampoline(...)` FFI descriptor for a callback parameter.
164
+ *
165
+ * The trampoline carries the callback's own argument and return FFI types,
166
+ * the index of its `user_data` slot, and the owning parameter's `scope` plus
167
+ * whether a paired `GDestroyNotify` is present. The native marshaller folds
168
+ * the C-level callback, `user_data`, and destroy arguments into this single
169
+ * descriptor. Returns `undefined` when `ref` is not a callback.
170
+ *
171
+ * @param context - The module context
172
+ * @param ref - The parameter type reference
173
+ * @param owningParameter - The parameter that carries the callback, for scope/destroy
174
+ */
175
+ export const renderTrampolineType = (
176
+ context: ModuleContext,
177
+ ref: GirTypeRef | undefined,
178
+ owningParameter: GirParameter,
179
+ ): string | undefined => {
180
+ const resolved = resolveCallbackType(context, ref);
181
+ if (resolved === undefined) return undefined;
182
+ const { callback, namespaceName } = resolved;
183
+ const argTypes = callback.parameters.map((parameter) => {
184
+ const ffi = renderFfiType(context, qualifyTypeRef(parameter.type, namespaceName), parameter.transferOwnership);
185
+ return isOutParameter(parameter) || isCellInout(context, parameter) ? `t.ref(${ffi})` : ffi;
186
+ });
187
+ let userDataIndex: number | undefined;
188
+ callback.parameters.forEach((parameter, index) => {
189
+ if (parameter.name === "user_data" || parameter.name === "data") userDataIndex = index;
190
+ });
191
+ const returnRef = qualifyTypeRef(callback.returnValue.type, namespaceName);
192
+ const isVoid = returnRef === undefined || (returnRef.kind === "primitive" && returnRef.category === "void");
193
+ const returnType = isVoid ? "t.void" : renderFfiType(context, returnRef, callback.returnValue.transferOwnership);
194
+ const options: string[] = [];
195
+ if (owningParameter.destroyIndex !== undefined) options.push("hasDestroy: true");
196
+ if (userDataIndex !== undefined) options.push(`userDataIndex: ${userDataIndex}`);
197
+ if (owningParameter.scope !== undefined) options.push(`scope: ${quote(owningParameter.scope)}`);
198
+ const optionsArg = options.length > 0 ? `, { ${options.join(", ")} }` : "";
199
+ return `t.trampoline([${argTypes.join(", ")}], ${returnType}${optionsArg})`;
200
+ };
201
+
202
+ const LIST_HELPERS: Readonly<Record<"glist" | "gslist" | "gptrarray" | "garray" | "gbytearray", string>> = {
203
+ glist: "list",
204
+ gslist: "slist",
205
+ gptrarray: "ptrArray",
206
+ garray: "gArray",
207
+ gbytearray: "byteArray",
208
+ };
209
+
210
+ const primitiveExpression = (
211
+ category:
212
+ | "void"
213
+ | "boolean"
214
+ | "int8"
215
+ | "uint8"
216
+ | "int16"
217
+ | "uint16"
218
+ | "int32"
219
+ | "uint32"
220
+ | "int64"
221
+ | "uint64"
222
+ | "float32"
223
+ | "float64"
224
+ | "string"
225
+ | "unichar"
226
+ | "pointer",
227
+ ownership: "borrowed" | "full" | "none",
228
+ ): string => {
229
+ if (category === "void") return "t.void";
230
+ if (category === "string") return `t.string(${quote(ownership)})`;
231
+ if (category === "pointer") return "t.uint64";
232
+ return `t.${category}`;
233
+ };
234
+
235
+ const namedExpression = (
236
+ context: ModuleContext,
237
+ ref: NamedTypeRef,
238
+ ownership: "borrowed" | "full" | "none",
239
+ ): string => {
240
+ const namespaceName = ref.namespaceName ?? context.namespace.name;
241
+ const resolved = context.repository.resolveNamed(namespaceName, ref.typeName);
242
+ if (resolved === undefined) {
243
+ return `t.object(${quote(ownership)})`;
244
+ }
245
+ return expressionForResolved(context, resolved, ownership);
246
+ };
247
+
248
+ /**
249
+ * Ref/unref functions for fundamental types whose GIR record omits
250
+ * `glib:ref-func`/`glib:unref-func` but which are nonetheless ref-counted
251
+ * fundamentals (their `GType` is not a boxed type, so `g_boxed_free` aborts).
252
+ * Keyed by GLib type name.
253
+ */
254
+ const INTRINSIC_FUNDAMENTAL_FUNCS: ReadonlyMap<string, { readonly ref: string; readonly unref: string }> = new Map([
255
+ ["GVariant", { ref: "g_variant_ref_sink", unref: "g_variant_unref" }],
256
+ ]);
257
+
258
+ type FundamentalDescriptor = {
259
+ readonly lib: string;
260
+ readonly refFunc: string;
261
+ readonly unrefFunc: string;
262
+ readonly glibTypeName: string | undefined;
263
+ readonly ownership: "borrowed" | "full" | "none";
264
+ };
265
+
266
+ /**
267
+ * Translates a GLib ref-func into the function the FFI runtime calls to add an
268
+ * independent reference.
269
+ *
270
+ * The runtime references a fundamental to make a defensive copy of a borrowed
271
+ * return, to clone a handle, or to hand a callee its own reference for a
272
+ * transfer-full argument — each must *add* a reference. GObject registers
273
+ * `*_ref_sink` (e.g. `g_param_spec_ref_sink`, `g_variant_ref_sink`) as the
274
+ * ref-func for floating types, but a sink only clears the floating flag on a
275
+ * floating instance and adds no reference, so a wrapper built from it would
276
+ * later unref a reference it never took and abort. The plain `*_ref`
277
+ * counterpart adds a reference for floating and non-floating instances alike.
278
+ */
279
+ const referenceAddingFunc = (refFunc: string): string =>
280
+ refFunc.endsWith("_ref_sink") ? refFunc.slice(0, -"_sink".length) : refFunc;
281
+
282
+ const renderFundamental = (descriptor: FundamentalDescriptor): string => {
283
+ const { lib, refFunc, unrefFunc, glibTypeName, ownership } = descriptor;
284
+ const parts = [`ownership: ${quote(ownership)}`];
285
+ if (glibTypeName !== undefined) parts.push(`typeName: ${quote(glibTypeName)}`);
286
+ return `t.fundamental(${quote(lib)}, ${quote(referenceAddingFunc(refFunc))}, ${quote(unrefFunc)}, { ${parts.join(", ")} })`;
287
+ };
288
+
289
+ const classOrInterfaceExpression = (
290
+ resolved: Extract<ResolvedNamed, { kind: "class" | "interface" }>,
291
+ ownership: "borrowed" | "full" | "none",
292
+ ): string => {
293
+ const cls = resolved.value;
294
+ if (cls.glibRefFunc === undefined || cls.glibUnrefFunc === undefined) {
295
+ return `t.object(${quote(ownership)})`;
296
+ }
297
+ return renderFundamental({
298
+ lib: resolved.namespace.sharedLibrary ?? "",
299
+ refFunc: cls.glibRefFunc,
300
+ unrefFunc: cls.glibUnrefFunc,
301
+ glibTypeName: cls.glibTypeName,
302
+ ownership,
303
+ });
304
+ };
305
+
306
+ type AncestorFundamental = {
307
+ readonly lib: string;
308
+ readonly refFunc: string;
309
+ readonly unrefFunc: string;
310
+ readonly glibTypeName: string | undefined;
311
+ };
312
+
313
+ const fundamentalAncestor = (
314
+ context: ModuleContext,
315
+ namespaceName: string,
316
+ typeName: string,
317
+ ): AncestorFundamental | undefined => {
318
+ const seen = new Set<string>();
319
+ let owner = namespaceName;
320
+ let name = typeName;
321
+ while (!seen.has(`${owner}.${name}`)) {
322
+ seen.add(`${owner}.${name}`);
323
+ const resolved = context.repository.resolveNamed(owner, name);
324
+ if (resolved === undefined || (resolved.kind !== "class" && resolved.kind !== "interface")) return undefined;
325
+ const cls = resolved.value;
326
+ if (cls.fundamental && cls.glibRefFunc !== undefined && cls.glibUnrefFunc !== undefined) {
327
+ return {
328
+ lib: resolved.namespace.sharedLibrary ?? "",
329
+ refFunc: cls.glibRefFunc,
330
+ unrefFunc: cls.glibUnrefFunc,
331
+ glibTypeName: cls.glibTypeName,
332
+ };
333
+ }
334
+ if (cls.parent === undefined) return undefined;
335
+ const dot = cls.parent.indexOf(".");
336
+ owner = dot === -1 ? resolved.namespace.name : cls.parent.slice(0, dot);
337
+ name = dot === -1 ? cls.parent : cls.parent.slice(dot + 1);
338
+ }
339
+ return undefined;
340
+ };
341
+
342
+ /**
343
+ * Renders the FFI descriptor for a method's instance (`self`) parameter.
344
+ *
345
+ * The receiver is always passed as a borrowed pointer regardless of the GIR
346
+ * instance-parameter transfer (`transfer="full"` on e.g. `gdk_event_unref`
347
+ * describes C-side consumption, not FFI ownership). A receiver whose ancestry
348
+ * reaches a ref-counted fundamental (e.g. `GskRenderNode`) is marshalled as
349
+ * `t.fundamental`; every other receiver — GObject subclasses and opaque
350
+ * records alike — is a borrowed `t.object`.
351
+ *
352
+ * @param context - The module context
353
+ * @param instance - The instance parameter
354
+ */
355
+ export const renderSelfFfiType = (context: ModuleContext, instance: GirParameter): string => {
356
+ const ref = instance.type;
357
+ if (ref === undefined || ref.kind !== "named") return `t.object("borrowed")`;
358
+ const owner = ref.namespaceName ?? context.namespace.name;
359
+ const resolved = context.repository.resolveNamed(owner, ref.typeName);
360
+ if (resolved === undefined) return `t.object("borrowed")`;
361
+ if (resolved.kind === "class" || resolved.kind === "interface") {
362
+ const ancestor = fundamentalAncestor(context, owner, ref.typeName);
363
+ return ancestor === undefined
364
+ ? `t.object("borrowed")`
365
+ : renderFundamental({ ...ancestor, ownership: "borrowed" });
366
+ }
367
+ if (resolved.kind === "boxed" && isReferenceableBoxed(resolved.value)) {
368
+ return boxedExpression(resolved, ffiOwnership(instance.transferOwnership));
369
+ }
370
+ return `t.object("borrowed")`;
371
+ };
372
+
373
+ const isReferenceableBoxed = (boxed: Extract<ResolvedNamed, { kind: "boxed" }>["value"]): boolean => {
374
+ const hasRefPair =
375
+ (boxed.glibRefFunc ?? boxed.copyFunc) !== undefined && (boxed.glibUnrefFunc ?? boxed.freeFunc) !== undefined;
376
+ const hasIntrinsic = boxed.glibTypeName !== undefined && INTRINSIC_FUNDAMENTAL_FUNCS.has(boxed.glibTypeName);
377
+ return hasRefPair || hasIntrinsic || boxed.glibGetType !== undefined;
378
+ };
379
+
380
+ const boxedExpression = (
381
+ resolved: Extract<ResolvedNamed, { kind: "boxed" }>,
382
+ ownership: "borrowed" | "full" | "none",
383
+ ): string => {
384
+ const boxed = resolved.value;
385
+ const intrinsic =
386
+ boxed.glibTypeName === undefined ? undefined : INTRINSIC_FUNDAMENTAL_FUNCS.get(boxed.glibTypeName);
387
+ const refFunc = boxed.glibRefFunc ?? boxed.copyFunc ?? intrinsic?.ref;
388
+ const unrefFunc = boxed.glibUnrefFunc ?? boxed.freeFunc ?? intrinsic?.unref;
389
+ if (refFunc !== undefined && unrefFunc !== undefined) {
390
+ const lib = resolved.namespace.sharedLibrary ?? "";
391
+ return renderFundamental({ lib, refFunc, unrefFunc, glibTypeName: boxed.glibTypeName, ownership });
392
+ }
393
+ if (boxed.glibGetType === undefined) {
394
+ return `t.struct(${quote(ownership)})`;
395
+ }
396
+ const glibName = boxed.glibTypeName ?? boxed.cType ?? boxed.name;
397
+ const lib = resolved.namespace.sharedLibrary;
398
+ const libExpr = lib === undefined ? "undefined" : quote(lib);
399
+ return `t.boxed(${joinArgs([quote(glibName), quote(ownership), libExpr, quote(boxed.glibGetType)])})`;
400
+ };
401
+
402
+ const expressionForResolved = (
403
+ context: ModuleContext,
404
+ resolved: ResolvedNamed,
405
+ ownership: "borrowed" | "full" | "none",
406
+ ): string => {
407
+ switch (resolved.kind) {
408
+ case "class":
409
+ case "interface":
410
+ return classOrInterfaceExpression(resolved, ownership);
411
+ case "boxed":
412
+ return boxedExpression(resolved, ownership);
413
+ case "enum": {
414
+ const getter = resolved.value.glibGetType;
415
+ const signed = resolved.value.members.some((member) => member.value.startsWith("-"));
416
+ if (getter === undefined || getter === "") return signed ? "t.int32" : "t.uint32";
417
+ const lib = resolved.namespace.sharedLibrary ?? "";
418
+ const helper = resolved.value.kind === "bitfield" ? "flags" : "enum";
419
+ return `t.${helper}(${quote(lib)}, ${quote(getter)}, ${String(signed)})`;
420
+ }
421
+ case "callback":
422
+ return "t.void";
423
+ case "alias":
424
+ return aliasExpression(context, {
425
+ namespace: resolved.namespace,
426
+ targetRef: resolved.targetRef,
427
+ ownership,
428
+ });
429
+ }
430
+ };
431
+
432
+ const arrayExpression = (
433
+ context: ModuleContext,
434
+ ref: ArrayTypeRef,
435
+ transfer: ParameterTransfer,
436
+ argIndexOffset: number,
437
+ ): string => {
438
+ const ownership = ffiOwnership(transfer);
439
+ const element = renderFfiType(context, ref.element, deriveElementTransfer(transfer), argIndexOffset);
440
+ const size = inlineElementSize(context, ref.element);
441
+ const sizeArg = size === undefined ? "" : `, ${size}`;
442
+ if (ref.lengthParameterIndex !== undefined) {
443
+ return `t.sizedArray(${element}, ${ref.lengthParameterIndex + argIndexOffset}, ${quote(ownership)}${sizeArg})`;
444
+ }
445
+ if (ref.fixedSize !== undefined) {
446
+ return `t.fixedArray(${element}, ${ref.fixedSize}, ${quote(ownership)}${sizeArg})`;
447
+ }
448
+ const optionsArg = size === undefined ? "" : `, { elementSize: ${size} }`;
449
+ return `t.array(${element}, "array", ${quote(ownership)}${optionsArg})`;
450
+ };
451
+
452
+ /**
453
+ * Computes the inline byte size of an array element when it is a by-value
454
+ * boxed struct, so the native marshaller can lay out a contiguous element
455
+ * buffer. Returns `undefined` for pointer elements (objects, strings, boxed
456
+ * pointers) and primitives, which the native layer already sizes.
457
+ */
458
+ const inlineElementSize = (context: ModuleContext, element: GirTypeRef | undefined): number | undefined => {
459
+ if (element === undefined || element.kind !== "named") return undefined;
460
+ const owner = element.namespaceName ?? context.namespace.name;
461
+ const hardcoded = HARDCODED_INLINE_ELEMENT_SIZES.get(`${owner}.${element.typeName}`);
462
+ if (hardcoded !== undefined) return hardcoded;
463
+ if (element.cType?.includes("*")) return undefined;
464
+ const resolved = context.repository.resolveNamed(owner, element.typeName);
465
+ if (resolved === undefined || resolved.kind !== "boxed") return undefined;
466
+ const boxed = resolved.value;
467
+ if (boxed.opaque || boxed.disguised || boxed.fields.length === 0) return undefined;
468
+ const { size } = computeBoxedFieldSlots(context, boxed.fields, boxed.isUnion);
469
+ return size > 0 ? size : undefined;
470
+ };
471
+
472
+ type AliasExpressionOptions = {
473
+ readonly namespace: GirNamespace;
474
+ readonly targetRef: GirTypeRef | undefined;
475
+ readonly ownership: "borrowed" | "full" | "none";
476
+ };
477
+
478
+ const aliasExpression = (context: ModuleContext, options: AliasExpressionOptions): string => {
479
+ const { namespace, targetRef, ownership } = options;
480
+ const qualified = qualifyTypeRef(targetRef, namespace.name);
481
+ if (qualified === undefined) {
482
+ return `t.object(${quote(ownership)})`;
483
+ }
484
+ return renderFfiType(context, qualified, ownership === "full" ? "full" : "none");
485
+ };