@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,178 @@
1
+ import { toCamelCase, toIdentifier } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import type { GirFunction } from "../gir/function.js";
4
+ import { type GirParameter, isCallerAllocatedOut, isOutParameter } from "../gir/parameter.js";
5
+ import type { GirTypeRef } from "../gir/type-ref.js";
6
+
7
+ /**
8
+ * Parameter classification shared across the callable writers: which positions
9
+ * a TypeScript signature exposes, which marshal through a `{ value }` ref cell,
10
+ * which cross the FFI as native handles, and how a parameter is named. Depends
11
+ * only on the GIR model and the module context.
12
+ */
13
+
14
+ /**
15
+ * A callable input parameter paired with its original GIR position.
16
+ */
17
+ export type InputParameter = {
18
+ /** The GIR parameter. */
19
+ readonly parameter: GirParameter;
20
+ /** The parameter's index in the callable's full parameter list. */
21
+ readonly index: number;
22
+ };
23
+
24
+ /**
25
+ * The input parameters a callable's TypeScript signature exposes.
26
+ *
27
+ * Drops `<varargs>` slots, out-only and caller-allocated-out parameters,
28
+ * array-length parameters computed from a sibling array, and the
29
+ * `user_data`/`GDestroyNotify` slots folded into a callback descriptor. Each
30
+ * surviving parameter keeps its original index so callers can recover argument
31
+ * names.
32
+ *
33
+ * @param fn - The callable
34
+ */
35
+ export const inputParameters = (fn: GirFunction): readonly InputParameter[] => {
36
+ const lengthIndices = arrayLengthIndices(fn);
37
+ const closureIndices = closureAndDestroyIndices(fn);
38
+ const result: InputParameter[] = [];
39
+ fn.parameters.forEach((parameter, index) => {
40
+ if (parameter.isVarargs) return;
41
+ if (isOutParameter(parameter)) return;
42
+ if (isCallerAllocatedOut(parameter)) return;
43
+ if (lengthIndices.has(index)) return;
44
+ if (closureIndices.has(index)) return;
45
+ result.push({ parameter, index });
46
+ });
47
+ return result;
48
+ };
49
+
50
+ /**
51
+ * Indices of the `gpointer user_data` and `GDestroyNotify` parameters paired
52
+ * to a callback parameter. These slots are folded into the callback's
53
+ * trampoline descriptor and are not emitted as standalone FFI arguments.
54
+ *
55
+ * @param fn - The callable
56
+ */
57
+ export const closureAndDestroyIndices = (fn: GirFunction): ReadonlySet<number> => {
58
+ const indices = new Set<number>();
59
+ for (const parameter of fn.parameters) {
60
+ if (parameter.closureIndex !== undefined) indices.add(parameter.closureIndex);
61
+ if (parameter.destroyIndex !== undefined) indices.add(parameter.destroyIndex);
62
+ }
63
+ return indices;
64
+ };
65
+
66
+ const arrayLengthIndices = (fn: GirFunction): ReadonlySet<number> => {
67
+ const map = arrayLengthSources(fn);
68
+ return new Set(map.keys());
69
+ };
70
+
71
+ /**
72
+ * Maps each array-length parameter's index to the array parameter whose
73
+ * `.length` supplies it.
74
+ *
75
+ * @param fn - The callable
76
+ */
77
+ export const arrayLengthSources = (fn: GirFunction): ReadonlyMap<number, number> => {
78
+ const map = new Map<number, number>();
79
+ fn.parameters.forEach((parameter, index) => {
80
+ if (parameter.type?.kind !== "array") return;
81
+ const lengthIndex = parameter.type.lengthParameterIndex;
82
+ if (lengthIndex === undefined) return;
83
+ map.set(lengthIndex, index);
84
+ });
85
+ return map;
86
+ };
87
+
88
+ /**
89
+ * Indices of parameters consumed as the length of the return array (and so
90
+ * dropped from the return tuple).
91
+ *
92
+ * @param fn - The callable
93
+ */
94
+ export const returnArrayLengthIndices = (fn: GirFunction): ReadonlySet<number> => {
95
+ if (fn.returnValue.type?.kind !== "array") return new Set();
96
+ const lengthIndex = fn.returnValue.type.lengthParameterIndex;
97
+ if (lengthIndex === undefined) return new Set();
98
+ return new Set([lengthIndex]);
99
+ };
100
+
101
+ /**
102
+ * Whether a parameter is passed to the FFI binding as a `t.ref(...)` cell.
103
+ *
104
+ * Pure out parameters marshal through a `{ value }` ref cell the native layer
105
+ * writes into. Inout parameters do too — except handle-passing ones
106
+ * (objects, interfaces, boxed), which are passed by their existing handle
107
+ * and mutated in place, not through a pointer-to-pointer cell.
108
+ * Caller-allocated outs pass a pre-built handle and are excluded.
109
+ *
110
+ * @param context - The module context
111
+ * @param parameter - The parameter to test
112
+ */
113
+ export const needsRefArg = (context: ModuleContext, parameter: GirParameter): boolean => {
114
+ if (parameter.direction !== "out" && parameter.direction !== "inout") return false;
115
+ const passesHandleDirectly =
116
+ (parameter.callerAllocates || parameter.direction === "inout") &&
117
+ parameter.type !== undefined &&
118
+ isHandlePassing(context, parameter.type);
119
+ return !passesHandleDirectly;
120
+ };
121
+
122
+ /**
123
+ * Whether a caller-allocated-out parameter is one the body can materialize and
124
+ * collect into the return — a boxed record or class the runtime can allocate
125
+ * via its wrapper constructor. Array and other caller-out buffers cannot be
126
+ * allocated here, so they are excluded from both the body and the return type.
127
+ *
128
+ * @param context - The module context
129
+ * @param parameter - The parameter to test
130
+ */
131
+ export const isCollectibleCallerOut = (context: ModuleContext, parameter: GirParameter): boolean => {
132
+ if (parameter.type === undefined || parameter.type.kind !== "named") return false;
133
+ const resolved = context.repository.resolveNamed(
134
+ parameter.type.namespaceName ?? context.namespace.name,
135
+ parameter.type.typeName,
136
+ );
137
+ return resolved?.kind === "boxed" || resolved?.kind === "class";
138
+ };
139
+
140
+ /**
141
+ * Whether a value of `ref` is passed across the FFI boundary as a native
142
+ * handle (object, interface, boxed, or an alias to one) instead of by value.
143
+ *
144
+ * @param context - The module context
145
+ * @param ref - The type reference to test
146
+ */
147
+ export const isHandlePassing = (context: ModuleContext, ref: GirTypeRef): boolean => {
148
+ if (ref.kind !== "named") return false;
149
+ const owner = ref.namespaceName ?? context.namespace.name;
150
+ const resolved = context.repository.resolveNamed(owner, ref.typeName);
151
+ if (resolved === undefined) return true;
152
+ switch (resolved.kind) {
153
+ case "class":
154
+ case "interface":
155
+ case "boxed":
156
+ return true;
157
+ case "alias": {
158
+ const target = resolved.targetRef;
159
+ if (target === undefined || target.kind !== "named") return false;
160
+ return isHandlePassing(context, target);
161
+ }
162
+ case "enum":
163
+ case "callback":
164
+ return false;
165
+ }
166
+ };
167
+
168
+ /**
169
+ * The JavaScript identifier for a parameter: the camelCased GIR name, or
170
+ * `arg<index>` for an unnamed position.
171
+ *
172
+ * @param parameter - The GIR parameter
173
+ * @param index - The parameter's position in the callable
174
+ */
175
+ export const parameterIdentifier = (parameter: GirParameter, index: number): string => {
176
+ if (parameter.name.length === 0) return `arg${index}`;
177
+ return toIdentifier(toCamelCase(parameter.name));
178
+ };
@@ -0,0 +1,139 @@
1
+ import { quote, toCamelCase, toIdentifier } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import type { GirFunction } from "../gir/function.js";
5
+ import type { GirProperty } from "../gir/property.js";
6
+ import type { GirTypeRef } from "../gir/type-ref.js";
7
+ import { renderMethodReturnType } from "./method.js";
8
+ import { renderTsType } from "./ts-type.js";
9
+
10
+ /**
11
+ * Whether a GObject property of `type` can hold `null`.
12
+ *
13
+ * Reference values — objects, boxed records, interfaces, and containers —
14
+ * marshal to `null` when unset; scalar value types (numbers, booleans,
15
+ * strings, `unichar`, enums) are surfaced non-null to match their typed
16
+ * setters. Aliases resolve to their target.
17
+ *
18
+ * @param context - The module context
19
+ * @param type - The property's value type
20
+ */
21
+ const isNullablePropertyType = (context: ModuleContext, type: GirTypeRef | undefined): boolean => {
22
+ if (type === undefined) return false;
23
+ if (type.kind === "primitive") return false;
24
+ if (type.kind !== "named") return true;
25
+ const resolved = context.repository.resolveNamed(type.namespaceName ?? context.namespace.name, type.typeName);
26
+ if (resolved === undefined) return true;
27
+ if (resolved.kind === "enum") return false;
28
+ if (resolved.kind === "alias") return isNullablePropertyType(context, resolved.targetRef);
29
+ return true;
30
+ };
31
+
32
+ /**
33
+ * Renders the `get` / `set` accessor pair for a single GObject property
34
+ * on a class declaration.
35
+ *
36
+ * Read-only properties get only a getter; readonly + non-writable
37
+ * properties are skipped entirely. Properties whose name has already
38
+ * been claimed by an emitted method (its camelCase form clashes) are
39
+ * skipped, since the method takes precedence — the runtime accessor is
40
+ * always reachable via `this.getProperty(girName)` in that case.
41
+ *
42
+ * @param context - The module context
43
+ * @param property - The property to surface
44
+ * @param claimedNames - Names already used by emitted methods
45
+ */
46
+ export const renderPropertyAccessor = (
47
+ context: ModuleContext,
48
+ property: GirProperty,
49
+ claimedNames: ReadonlySet<string>,
50
+ methodByName: ReadonlyMap<string, GirFunction>,
51
+ ): string | undefined => {
52
+ const jsName = toIdentifier(toCamelCase(property.name));
53
+ if (claimedNames.has(jsName)) return undefined;
54
+ if (jsName === "constructor") return undefined;
55
+
56
+ const writable = property.writable || property.construct || property.constructOnly;
57
+ const getterMember = delegateMember(property.getter, jsName, claimedNames);
58
+ const getMethod =
59
+ getterMember !== undefined && property.getter !== undefined ? methodByName.get(property.getter) : undefined;
60
+ const setterMember = writable ? delegateMember(property.setter, jsName, claimedNames) : undefined;
61
+ const setMethod =
62
+ setterMember !== undefined && property.setter !== undefined ? methodByName.get(property.setter) : undefined;
63
+ const setParam = setMethod?.parameters[0];
64
+
65
+ const tsType =
66
+ setParam !== undefined
67
+ ? renderTsType(context, setParam.type, setParam.nullable || setParam.optional)
68
+ : getMethod !== undefined
69
+ ? renderMethodReturnType(context, getMethod)
70
+ : renderTsType(context, property.type, isNullablePropertyType(context, property.type));
71
+
72
+ const blocks: string[] = [];
73
+ const getBody = renderGetterBody({ context, property, getterMember, getMethod, tsType });
74
+ blocks.push(`get ${jsName}(): ${tsType} {\n${indent(getBody, 1)}\n}`);
75
+
76
+ if (writable) {
77
+ const setBody =
78
+ setterMember !== undefined
79
+ ? `this.${setterMember}(value);`
80
+ : `this.setProperty(${quote(property.name)}, value);`;
81
+ blocks.push(`set ${jsName}(value: ${tsType}) {\n${indent(setBody, 1)}\n}`);
82
+ }
83
+ return blocks.join("\n\n");
84
+ };
85
+
86
+ /**
87
+ * Inputs for {@link renderGetterBody}.
88
+ */
89
+ type GetterBodyOptions = {
90
+ readonly context: ModuleContext;
91
+ readonly property: GirProperty;
92
+ readonly getterMember: string | undefined;
93
+ readonly getMethod: GirFunction | undefined;
94
+ readonly tsType: string;
95
+ };
96
+
97
+ /**
98
+ * Renders a property getter body.
99
+ *
100
+ * The property type follows the setter's parameter (what callers may assign),
101
+ * so a getter whose own GIR nullability differs is narrowed to it with a single
102
+ * assertion; matching nullability needs no cast. Properties with no typed
103
+ * getter read through the generic `getProperty` GValue path.
104
+ *
105
+ * @param options - {@link GetterBodyOptions}
106
+ */
107
+ const renderGetterBody = (options: GetterBodyOptions): string => {
108
+ const { context, property, getterMember, getMethod, tsType } = options;
109
+ if (getterMember === undefined) return `return this.getProperty(${quote(property.name)}) as ${tsType};`;
110
+ if (getMethod === undefined) return `return this.${getterMember}() as ${tsType};`;
111
+ const getType = renderMethodReturnType(context, getMethod);
112
+ return getType === tsType ? `return this.${getterMember}();` : `return this.${getterMember}() as ${tsType};`;
113
+ };
114
+
115
+ /**
116
+ * Resolves a property's GIR `getter`/`setter` method name to the camelCase
117
+ * member to delegate the accessor to, or `undefined` to fall back to the
118
+ * generic `getProperty`/`setProperty` GValue path.
119
+ *
120
+ * Delegation is used only when the named method was actually emitted on the
121
+ * class (so object, interface, and boxed values marshal through their typed
122
+ * setter rather than the GValue `valueFromJS` path) and the member name does
123
+ * not collide with the accessor itself, which would recurse.
124
+ *
125
+ * @param accessorName - The accessor's own camelCase member name
126
+ * @param attribute - The GIR `getter`/`setter` attribute, if present
127
+ * @param claimedNames - Names already emitted as methods on the class
128
+ */
129
+ const delegateMember = (
130
+ attribute: string | undefined,
131
+ accessorName: string,
132
+ claimedNames: ReadonlySet<string>,
133
+ ): string | undefined => {
134
+ if (attribute === undefined) return undefined;
135
+ const member = toCamelCase(attribute);
136
+ if (member === accessorName) return undefined;
137
+ if (!claimedNames.has(member)) return undefined;
138
+ return member;
139
+ };
@@ -0,0 +1,42 @@
1
+ import { quote } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+
5
+ /**
6
+ * The pre-rendered pieces of a single `registerNativeClass(...)` descriptor.
7
+ *
8
+ * Each metadata field is the already-rendered object-literal fragment for that
9
+ * descriptor key, or `undefined` when the type contributes nothing for it.
10
+ */
11
+ export type NativeClassRegistration = {
12
+ readonly className: string;
13
+ readonly role: "class" | "interface" | "boxed";
14
+ readonly getTypeRef?: string | undefined;
15
+ readonly construction?: string | undefined;
16
+ readonly vfuncs?: string | undefined;
17
+ };
18
+
19
+ /**
20
+ * Appends the single `registerNativeClass(Class, { … })` registration that
21
+ * collapses a type's GType, construction, and vfunc metadata into one
22
+ * module-load call.
23
+ *
24
+ * Emits nothing when the registration carries no metadata at all, so callers
25
+ * can pass the result of optional sub-renderers without pre-checking.
26
+ *
27
+ * @param context - The module context
28
+ * @param registration - The pre-rendered descriptor pieces
29
+ */
30
+ export const appendNativeClassRegistration = (context: ModuleContext, registration: NativeClassRegistration): void => {
31
+ const { className, role, getTypeRef, construction, vfuncs } = registration;
32
+ if (getTypeRef === undefined && construction === undefined && vfuncs === undefined) {
33
+ return;
34
+ }
35
+ context.addRuntimeImport("registerNativeClass");
36
+ const lines: string[] = [`role: ${quote(role)},`];
37
+ if (getTypeRef !== undefined) lines.push(`gtype: ${getTypeRef},`);
38
+ if (construction !== undefined) lines.push(`construction: ${construction},`);
39
+ if (vfuncs !== undefined) lines.push(`vfuncs: ${vfuncs},`);
40
+ const body = lines.map((line) => indent(line, 1)).join("\n");
41
+ context.module.appendRegistration(`registerNativeClass(${className}, {\n${body}\n});`);
42
+ };
@@ -0,0 +1,180 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import type { ParameterTransfer } from "../gir/parameter.js";
3
+ import type { ResolvedNamed } from "../gir/repository.js";
4
+ import type { GirTypeRef, NamedTypeRef, PrimitiveTypeRef } from "../gir/type-ref.js";
5
+ import { renderTsType } from "./ts-type.js";
6
+
7
+ /**
8
+ * Lifting raw FFI call results into their typed JavaScript form: objects to
9
+ * their runtime-registered wrappers, boxed values to typed wrappers,
10
+ * collections per element, and primitives with the appropriate coercion.
11
+ * Shared by method-body return handling, signal/callback argument marshalling,
12
+ * and boxed field accessors.
13
+ */
14
+
15
+ /**
16
+ * Casts a raw FFI call result to a native handle so the registry wrappers
17
+ * (`getNativeObject` / `getNativeObjectAsInterface`) accept it. The cast widens
18
+ * to `NativeHandle | null` for a GIR-nullable value so the wrapper result type
19
+ * stays nullable, matching the declared return.
20
+ */
21
+ export const handleCast = (context: ModuleContext, valueExpression: string, nullable: boolean): string => {
22
+ context.addNativeTypeImport("NativeHandle");
23
+ return `${valueExpression} as NativeHandle${nullable ? " | null" : ""}`;
24
+ };
25
+
26
+ /**
27
+ * Inputs for {@link wrapReturnValue}.
28
+ */
29
+ export type WrapReturnOptions = {
30
+ readonly ref: GirTypeRef | undefined;
31
+ readonly transfer: ParameterTransfer;
32
+ readonly nullable: boolean;
33
+ readonly valueExpression: string;
34
+ };
35
+
36
+ /**
37
+ * Wraps a raw FFI value into its typed JavaScript form.
38
+ *
39
+ * Objects resolve to their runtime-registered wrapper, interfaces to the
40
+ * interface wrapper, boxed values to a typed wrapper, collections recurse per
41
+ * element, and primitives pass through with the appropriate coercion. Shared
42
+ * by return-value handling and signal-handler argument marshalling.
43
+ *
44
+ * @param context - The module context
45
+ * @param options - {@link WrapReturnOptions}
46
+ */
47
+ export const wrapReturnValue = (context: ModuleContext, options: WrapReturnOptions): string => {
48
+ const { ref, nullable, valueExpression } = options;
49
+ if (ref === undefined) return valueExpression;
50
+ switch (ref.kind) {
51
+ case "primitive":
52
+ return wrapPrimitive(ref, nullable, valueExpression);
53
+ case "named":
54
+ return wrapNamed(context, ref, valueExpression, nullable);
55
+ case "array":
56
+ return wrapCollection(context, ref.element, valueExpression, nullable);
57
+ case "list":
58
+ return ref.flavor === "gbytearray"
59
+ ? `(${valueExpression} as number[]${nullable ? " | null" : ""})`
60
+ : wrapCollection(context, ref.element, valueExpression, nullable);
61
+ case "hashtable":
62
+ return nullable
63
+ ? `(${valueExpression} === null ? null : new globalThis.Map(${valueExpression} as Iterable<readonly [unknown, unknown]>))`
64
+ : `new globalThis.Map(${valueExpression} as Iterable<readonly [unknown, unknown]>)`;
65
+ case "callback":
66
+ case "varargs":
67
+ return `(${valueExpression} as unknown[])`;
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Wraps a collection return value, mapping each element through the runtime
73
+ * wrapper its type requires.
74
+ *
75
+ * The native layer hands collection returns back as arrays of raw element
76
+ * values; object, interface, and boxed elements must be lifted into their
77
+ * typed JavaScript wrappers (matching the per-element wrapping a scalar
78
+ * return of the same type receives) while primitive and enum elements pass
79
+ * through untouched.
80
+ */
81
+ const wrapCollection = (
82
+ context: ModuleContext,
83
+ element: GirTypeRef | undefined,
84
+ valueExpression: string,
85
+ nullable: boolean,
86
+ ): string => {
87
+ const itemExpression = collectionItemWrap(context, element);
88
+ if (itemExpression === undefined) {
89
+ const elementTs = element === undefined ? "unknown" : renderTsType(context, element, false);
90
+ return `(${valueExpression} as ${elementTs}[]${nullable ? " | null" : ""})`;
91
+ }
92
+ return nullable
93
+ ? `((${valueExpression} as unknown[] | null)?.map((item) => ${itemExpression}) ?? null)`
94
+ : `(${valueExpression} as unknown[]).map((item) => ${itemExpression})`;
95
+ };
96
+
97
+ const collectionItemWrap = (context: ModuleContext, element: GirTypeRef | undefined): string | undefined => {
98
+ if (element === undefined || element.kind !== "named") return undefined;
99
+ const owner = element.namespaceName ?? context.namespace.name;
100
+ const resolved = context.repository.resolveNamed(owner, element.typeName);
101
+ if (resolved === undefined) {
102
+ context.addRuntimeImport("getNativeObject");
103
+ return `getNativeObject(${handleCast(context, "item", false)})`;
104
+ }
105
+ switch (resolved.kind) {
106
+ case "class":
107
+ case "boxed":
108
+ context.addRuntimeImport("getNativeObject");
109
+ return `getNativeObject(${handleCast(context, "item", false)})`;
110
+ case "interface": {
111
+ context.addRuntimeImport("getNativeObjectAsInterface");
112
+ return `getNativeObjectAsInterface(${handleCast(context, "item", false)}, ${context.qualify(owner, element.typeName)})`;
113
+ }
114
+ case "alias":
115
+ return resolved.target === undefined
116
+ ? undefined
117
+ : collectionItemWrap(context, {
118
+ kind: "named",
119
+ namespaceName: resolved.namespace.name,
120
+ typeName: resolved.target,
121
+ cType: undefined,
122
+ });
123
+ case "enum":
124
+ case "callback":
125
+ return undefined;
126
+ }
127
+ };
128
+
129
+ const wrapPrimitive = (ref: PrimitiveTypeRef, nullable: boolean, valueExpression: string): string => {
130
+ const category = ref.category;
131
+ if (category === "void") return valueExpression;
132
+ if (category === "string") return `(${valueExpression} as ${nullable ? "string | null" : "string"})`;
133
+ if (category === "boolean") return `Boolean(${valueExpression})`;
134
+ return `(${valueExpression} as number)`;
135
+ };
136
+
137
+ const wrapNamed = (context: ModuleContext, ref: NamedTypeRef, valueExpression: string, nullable: boolean): string => {
138
+ const owner = ref.namespaceName ?? context.namespace.name;
139
+ const resolved = context.repository.resolveNamed(owner, ref.typeName);
140
+ if (resolved === undefined) {
141
+ context.addRuntimeImport("getNativeObject");
142
+ return `getNativeObject(${handleCast(context, valueExpression, nullable)})`;
143
+ }
144
+ return wrapResolved(context, resolved, { namespaceName: owner, typeName: ref.typeName, valueExpression, nullable });
145
+ };
146
+
147
+ type WrapResolvedOptions = {
148
+ readonly namespaceName: string;
149
+ readonly typeName: string;
150
+ readonly valueExpression: string;
151
+ readonly nullable: boolean;
152
+ };
153
+
154
+ const wrapResolved = (context: ModuleContext, resolved: ResolvedNamed, options: WrapResolvedOptions): string => {
155
+ const { namespaceName, typeName, valueExpression, nullable } = options;
156
+ switch (resolved.kind) {
157
+ case "class": {
158
+ context.addRuntimeImport("getNativeObject");
159
+ return `getNativeObject(${handleCast(context, valueExpression, nullable)})`;
160
+ }
161
+ case "interface": {
162
+ const classExpression = context.qualify(namespaceName, typeName);
163
+ context.addRuntimeImport("getNativeObjectAsInterface");
164
+ return `getNativeObjectAsInterface(${handleCast(context, valueExpression, nullable)}, ${classExpression})`;
165
+ }
166
+ case "boxed": {
167
+ const classExpression = context.qualify(namespaceName, typeName);
168
+ context.addRuntimeImport("getNativeObject");
169
+ return `getNativeObject(${handleCast(context, valueExpression, nullable)}, ${classExpression})`;
170
+ }
171
+ case "enum":
172
+ return `(${valueExpression} as number)`;
173
+ case "callback":
174
+ return valueExpression;
175
+ case "alias":
176
+ return resolved.targetRef === undefined
177
+ ? valueExpression
178
+ : wrapReturnValue(context, { ref: resolved.targetRef, transfer: "full", nullable, valueExpression });
179
+ }
180
+ };
@@ -0,0 +1,59 @@
1
+ import type { GirFunction } from "../gir/function.js";
2
+
3
+ /**
4
+ * Shape of a method that codegen cannot marshal and that the FFI runtime
5
+ * replaces on the prototype at module load.
6
+ */
7
+ type RuntimeOverride = {
8
+ /** Optional generic parameter list, including angle brackets. */
9
+ readonly generics?: string;
10
+ /** Comma-separated TypeScript parameter list. */
11
+ readonly signature: string;
12
+ /** TypeScript return-type annotation. */
13
+ readonly returnType: string;
14
+ /** Body executed when the runtime override has not been installed yet. */
15
+ readonly body: string;
16
+ };
17
+
18
+ const RUNTIME_OVERRIDES: ReadonlyMap<string, RuntimeOverride> = new Map([
19
+ [
20
+ "g_value_get_boxed",
21
+ {
22
+ generics: "<T = unknown>",
23
+ signature: "",
24
+ returnType: "T",
25
+ body: 'throw new Error("g_value_get_boxed: runtime override not installed");',
26
+ },
27
+ ],
28
+ [
29
+ "g_value_set_boxed",
30
+ {
31
+ signature: "boxed: object | null",
32
+ returnType: "void",
33
+ body: 'throw new Error("g_value_set_boxed: runtime override not installed");',
34
+ },
35
+ ],
36
+ ]);
37
+
38
+ /**
39
+ * Renders the method body for a callable whose marshaling is provided by a
40
+ * hand-written runtime override.
41
+ *
42
+ * `gpointer` exchanges (`GValue` boxed accessors and similar) cannot be
43
+ * marshaled from GIR alone; the FFI runtime replaces them on the prototype
44
+ * at module load. The codegen emits a stub with the override's signature
45
+ * so the consumer types resolve to the runtime shape and the runtime's
46
+ * `prototype` assignment type-checks.
47
+ *
48
+ * Returns `undefined` for callables without a registered override.
49
+ *
50
+ * @param callable - The callable to consider
51
+ * @param memberName - The camelCase JS method name
52
+ */
53
+ export const renderRuntimeOverride = (callable: GirFunction, memberName: string): string | undefined => {
54
+ if (callable.cIdentifier === undefined) return undefined;
55
+ const override = RUNTIME_OVERRIDES.get(callable.cIdentifier);
56
+ if (override === undefined) return undefined;
57
+ const generics = override.generics ?? "";
58
+ return `${memberName}${generics}(${override.signature}): ${override.returnType} {\n ${override.body}\n}`;
59
+ };