@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,339 @@
1
+ import { toCamelCase } 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 { callableReferencesClassStruct } from "./class-struct-record.js";
6
+ import { renderFnExpression } from "./function.js";
7
+ import { methodExportName, renderMethodBody, renderMethodReturnType, renderMethodSignature } from "./method.js";
8
+ import { renderRuntimeOverride } from "./runtime-override.js";
9
+
10
+ /**
11
+ * Constructors, static functions, and instance methods of a class, interface,
12
+ * or boxed record.
13
+ */
14
+ export type Callables = {
15
+ readonly constructors: readonly GirFunction[];
16
+ readonly functions: readonly GirFunction[];
17
+ readonly methods: readonly GirFunction[];
18
+ };
19
+
20
+ /**
21
+ * Drops callables that lack a `c:identifier` or repeat one already retained.
22
+ *
23
+ * GIR sometimes lists the same callable twice (e.g. as both a class function
24
+ * and a constructor); the C identifier is the canonical key.
25
+ *
26
+ * @param callables - The raw GIR callables
27
+ */
28
+ export const dedupeCallables = (callables: readonly GirFunction[]): readonly GirFunction[] => {
29
+ const seen = new Set<string>();
30
+ const result: GirFunction[] = [];
31
+ for (const callable of callables) {
32
+ if (callable.cIdentifier === undefined) continue;
33
+ if (seen.has(callable.cIdentifier)) continue;
34
+ seen.add(callable.cIdentifier);
35
+ result.push(callable);
36
+ }
37
+ return result;
38
+ };
39
+
40
+ /**
41
+ * Appends the top-level `const fn = t.fn(...)` binding for every introspectable
42
+ * callable that has a C identifier and is not shadowed.
43
+ *
44
+ * @param context - The module context
45
+ * @param callables - The callables to bind
46
+ */
47
+ /**
48
+ * Appends the top-level `const fn = t.fn(...)` binding for a single callable,
49
+ * deduplicated by its C identifier. Used when flattening interface and
50
+ * prerequisite methods onto a type one at a time.
51
+ *
52
+ * @param context - The module context
53
+ * @param method - The callable to bind
54
+ */
55
+ export const appendMethodBinding = (context: ModuleContext, method: GirFunction): void => {
56
+ if (method.cIdentifier === undefined) return;
57
+ if (callableReferencesClassStruct(context, method)) return;
58
+ const expression = renderFnExpression(context, method);
59
+ if (expression === undefined) return;
60
+ context.module.appendBinding(`const ${method.cIdentifier} = ${expression};`, method.cIdentifier);
61
+ };
62
+
63
+ export const emitBindings = (context: ModuleContext, callables: Callables): void => {
64
+ const all = [...callables.constructors, ...callables.functions, ...callables.methods];
65
+ for (const callable of all) {
66
+ if (!callable.introspectable) continue;
67
+ if (callable.shadowedBy !== undefined) continue;
68
+ if (callable.cIdentifier === undefined) continue;
69
+ if (callableReferencesClassStruct(context, callable)) continue;
70
+ const expression = renderFnExpression(context, callable);
71
+ if (expression === undefined) continue;
72
+ context.module.appendBinding(`const ${callable.cIdentifier} = ${expression};`, callable.cIdentifier);
73
+ }
74
+ };
75
+
76
+ /**
77
+ * How a type's static `<constructor>` factories wrap their result:
78
+ *
79
+ * - `"gobject"` — resolve the wrapper from the runtime GLib type (a class).
80
+ * - `"interface"` — resolve the runtime type, falling back to the closest
81
+ * registered ancestor that implements the interface, then the interface itself.
82
+ * - `"boxed"` — wrap as the exact value-type class (boxed records).
83
+ */
84
+ export type ConstructorWrap = "gobject" | "interface" | "boxed";
85
+
86
+ /**
87
+ * Renders a `static <name>(...): Owner` factory method for a GIR `<constructor>`.
88
+ *
89
+ * Returns `undefined` for non-introspectable, shadowed, identifier-less, or
90
+ * `constructor`-named entries.
91
+ *
92
+ * @param context - The module context
93
+ * @param callable - The GIR constructor callable
94
+ * @param ownerClassName - The local class name that wraps the result
95
+ * @param wrap - How the result is lifted to its JavaScript wrapper
96
+ */
97
+ const renderConstructorStatic = (
98
+ context: ModuleContext,
99
+ callable: GirFunction,
100
+ ownerClassName: string,
101
+ wrap: ConstructorWrap,
102
+ ): string | undefined => {
103
+ if (!isEmittableCallable(context, callable)) return undefined;
104
+ const name = constructorMemberName(callable.name);
105
+ if (name === undefined) return undefined;
106
+ const cIdentifier = callable.cIdentifier;
107
+ if (cIdentifier === undefined) return undefined;
108
+ const signature = renderMethodSignature(context, callable);
109
+ const body = renderMethodBody(context, callable, {
110
+ bindingExpression: cIdentifier,
111
+ isStatic: true,
112
+ returnAs: wrap === "gobject" ? { via: "gobject" } : { via: wrap, className: ownerClassName },
113
+ });
114
+ return `static ${name}(${signature}): ${ownerClassName} {\n${indent(body, 1)}\n}`;
115
+ };
116
+
117
+ /**
118
+ * Renders a `static <name>(...): Return` member for a GIR `<function>`.
119
+ *
120
+ * Returns `undefined` when the callable cannot be emitted on a class body.
121
+ *
122
+ * @param context - The module context
123
+ * @param callable - The GIR function callable
124
+ */
125
+ const renderStaticMember = (context: ModuleContext, callable: GirFunction): string | undefined => {
126
+ if (!isEmittableCallable(context, callable)) return undefined;
127
+ const cIdentifier = callable.cIdentifier;
128
+ if (cIdentifier === undefined) return undefined;
129
+ const name = toCamelCase(callable.name);
130
+ if (name === "constructor") return undefined;
131
+ const signature = renderMethodSignature(context, callable);
132
+ const returnType = renderMethodReturnType(context, callable);
133
+ const body = renderMethodBody(context, callable, { bindingExpression: cIdentifier, isStatic: true });
134
+ return `static ${name}(${signature}): ${returnType} {\n${indent(body, 1)}\n}`;
135
+ };
136
+
137
+ /**
138
+ * Renders an instance method body for a GIR `<method>`.
139
+ *
140
+ * Hand-written runtime overrides (e.g. `g_value_get_boxed`) short-circuit the
141
+ * usual FFI call body with a throwing stub that the runtime replaces on the
142
+ * prototype at module load.
143
+ *
144
+ * Returns `undefined` for non-emittable callables.
145
+ *
146
+ * @param context - The module context
147
+ * @param callable - The GIR method callable
148
+ */
149
+ export const renderInstanceMethod = (
150
+ context: ModuleContext,
151
+ callable: GirFunction,
152
+ nameOverride?: string,
153
+ ): string | undefined => {
154
+ if (!isEmittableCallable(context, callable)) return undefined;
155
+ const cIdentifier = callable.cIdentifier;
156
+ if (cIdentifier === undefined) return undefined;
157
+ const name = nameOverride ?? methodExportName(callable);
158
+ if (name === "constructor") return undefined;
159
+ const override = renderRuntimeOverride(callable, name);
160
+ if (override !== undefined) return override;
161
+ const signature = renderMethodSignature(context, callable);
162
+ const returnType = renderMethodReturnType(context, callable);
163
+ const body = renderMethodBody(context, callable, { bindingExpression: cIdentifier, isStatic: false });
164
+ return `${name}(${signature}): ${returnType} {\n${indent(body, 1)}\n}`;
165
+ };
166
+
167
+ /**
168
+ * Renders the legacy-name alias of a method that has been shadowed by a
169
+ * variadic or kebab-cased successor.
170
+ *
171
+ * Returns `undefined` when the alias collides with `constructor` or the
172
+ * shadower lacks a C identifier.
173
+ *
174
+ * @param context - The module context
175
+ * @param original - The shadowed callable whose name should remain reachable
176
+ * @param shadower - The callable the alias dispatches into
177
+ */
178
+ const renderInstanceAlias = (
179
+ context: ModuleContext,
180
+ original: GirFunction,
181
+ shadower: GirFunction,
182
+ ): string | undefined => {
183
+ if (shadower.cIdentifier === undefined) return undefined;
184
+ const aliasName = toCamelCase(original.name);
185
+ if (aliasName === "constructor") return undefined;
186
+ const signature = renderMethodSignature(context, shadower);
187
+ const returnType = renderMethodReturnType(context, shadower);
188
+ const body = renderMethodBody(context, shadower, { bindingExpression: shadower.cIdentifier, isStatic: false });
189
+ return `${aliasName}(${signature}): ${returnType} {\n${indent(body, 1)}\n}`;
190
+ };
191
+
192
+ /**
193
+ * Inputs for {@link appendShadowedAliases}.
194
+ */
195
+ export type ShadowedAliasOptions = {
196
+ readonly context: ModuleContext;
197
+ /** The full list of method callables. */
198
+ readonly methods: readonly GirFunction[];
199
+ /** Lookup table for shadower resolution. */
200
+ readonly methodByName: ReadonlyMap<string, GirFunction>;
201
+ /** The accumulating members list to append to. */
202
+ readonly members: string[];
203
+ /** Set of names already emitted on the class body. */
204
+ readonly claimedNames: Set<string>;
205
+ };
206
+
207
+ /**
208
+ * Appends shadowed-method aliases for every callable in `methods` whose
209
+ * shadower is introspectable.
210
+ *
211
+ * Records each emitted alias name in `claimedNames` so subsequent renderers
212
+ * can detect collisions.
213
+ *
214
+ * @param options - {@link ShadowedAliasOptions}
215
+ */
216
+ export const appendShadowedAliases = (options: ShadowedAliasOptions): void => {
217
+ const { context, methods, methodByName, members, claimedNames } = options;
218
+ for (const callable of methods) {
219
+ if (callable.shadowedBy === undefined) continue;
220
+ if (callable.introspectable) continue;
221
+ const shadower = methodByName.get(callable.shadowedBy);
222
+ if (shadower === undefined || !shadower.introspectable) continue;
223
+ const aliasName = toCamelCase(callable.name);
224
+ if (claimedNames.has(aliasName)) continue;
225
+ const block = renderInstanceAlias(context, callable, shadower);
226
+ if (block !== undefined) {
227
+ members.push(block);
228
+ claimedNames.add(aliasName);
229
+ }
230
+ }
231
+ };
232
+
233
+ /**
234
+ * Indexes a list of method callables by their GIR name for shadower lookup.
235
+ */
236
+ export const indexMethodsByName = (methods: readonly GirFunction[]): ReadonlyMap<string, GirFunction> => {
237
+ const map = new Map<string, GirFunction>();
238
+ for (const callable of methods) map.set(callable.name, callable);
239
+ return map;
240
+ };
241
+
242
+ const isEmittableCallable = (context: ModuleContext, callable: GirFunction): boolean =>
243
+ callable.introspectable &&
244
+ callable.shadowedBy === undefined &&
245
+ callable.cIdentifier !== undefined &&
246
+ !callableReferencesClassStruct(context, callable);
247
+
248
+ const constructorMemberName = (girName: string): string | undefined => {
249
+ const camel = toCamelCase(girName);
250
+ if (camel === "constructor") return undefined;
251
+ return camel;
252
+ };
253
+
254
+ /**
255
+ * Renders the shared head of a class/interface/boxed body: every `static`
256
+ * constructor factory and every `static` namespace function on the same type.
257
+ *
258
+ * @param context - The module context
259
+ * @param callables - The class's emittable callables
260
+ * @param ownerClassName - The local PascalCase class name
261
+ * @param wrap - How static constructors lift their result to a wrapper
262
+ */
263
+ export const renderStaticHead = (
264
+ context: ModuleContext,
265
+ callables: Callables,
266
+ ownerClassName: string,
267
+ wrap: ConstructorWrap,
268
+ ): readonly string[] => {
269
+ const blocks: string[] = [];
270
+ for (const callable of callables.constructors) {
271
+ const block = renderConstructorStatic(context, callable, ownerClassName, wrap);
272
+ if (block !== undefined) blocks.push(block);
273
+ }
274
+ for (const callable of callables.functions) {
275
+ const block = renderStaticMember(context, callable);
276
+ if (block !== undefined) blocks.push(block);
277
+ }
278
+ return blocks;
279
+ };
280
+
281
+ /**
282
+ * Renders the plain instance methods of a class/interface/boxed body, the
283
+ * variant that does NOT consult inherited-signature conflicts. Used for
284
+ * interfaces and boxed records that do not extend other widget shapes.
285
+ *
286
+ * Records each emitted method name in `claimedNames` so subsequent property
287
+ * and signal-method renderers can detect collisions.
288
+ */
289
+ const renderPlainInstanceMethods = (
290
+ context: ModuleContext,
291
+ methods: readonly GirFunction[],
292
+ claimedNames: Set<string>,
293
+ ): readonly string[] => {
294
+ const blocks: string[] = [];
295
+ for (const callable of methods) {
296
+ const block = renderInstanceMethod(context, callable);
297
+ if (block === undefined) continue;
298
+ blocks.push(block);
299
+ claimedNames.add(methodExportName(callable));
300
+ }
301
+ return blocks;
302
+ };
303
+
304
+ /**
305
+ * Inputs for {@link renderPlainTypeMembers}.
306
+ */
307
+ export type PlainTypeMembersOptions = {
308
+ readonly context: ModuleContext;
309
+ readonly className: string;
310
+ readonly callables: Callables;
311
+ /** Whether to prepend `declare __gtype__: number;`. */
312
+ readonly hasGType: boolean;
313
+ /** How static constructors lift their result to a wrapper. */
314
+ readonly wrap: ConstructorWrap;
315
+ };
316
+
317
+ /**
318
+ * Renders the shared head of an interface or boxed class body — `__gtype__`
319
+ * declaration (optional), statics, plain instance methods, and
320
+ * shadowed-method aliases — plus the `claimedNames` set populated by those
321
+ * renderers.
322
+ *
323
+ * Callers append the constructor, property accessors, field accessors, and
324
+ * signal members on top of the returned members list. The class writer takes
325
+ * a different path because it has to consult inherited signatures.
326
+ */
327
+ export const renderPlainTypeMembers = (
328
+ options: PlainTypeMembersOptions,
329
+ ): { readonly members: string[]; readonly claimedNames: Set<string> } => {
330
+ const { context, className, callables, hasGType, wrap } = options;
331
+ const members: string[] = [];
332
+ if (hasGType) members.push("declare __gtype__: number;");
333
+ const claimedNames = new Set<string>();
334
+ members.push(...renderStaticHead(context, callables, className, wrap));
335
+ members.push(...renderPlainInstanceMethods(context, callables.methods, claimedNames));
336
+ const methodByName = indexMethodsByName(callables.methods);
337
+ appendShadowedAliases({ context, methods: callables.methods, methodByName, members, claimedNames });
338
+ return { members, claimedNames };
339
+ };
@@ -0,0 +1,40 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import type { GirCallback } from "../gir/callback.js";
3
+ import type { GirFunction } from "../gir/function.js";
4
+ import { renderMethodReturnType, renderMethodSignature } from "./method.js";
5
+
6
+ const callbackAsFunction = (callback: GirCallback): GirFunction => ({
7
+ kind: "function",
8
+ name: callback.name,
9
+ cIdentifier: undefined,
10
+ throws: false,
11
+ introspectable: callback.introspectable,
12
+ shadows: undefined,
13
+ shadowedBy: undefined,
14
+ movedTo: undefined,
15
+ instance: undefined,
16
+ parameters: callback.parameters,
17
+ returnValue: callback.returnValue,
18
+ invoker: undefined,
19
+ });
20
+
21
+ /**
22
+ * Emits an `export type` alias for a top-level GIR `<callback>`.
23
+ *
24
+ * The callback is surfaced under its GIR `name` verbatim with a fully typed
25
+ * call signature: input parameters follow {@link renderMethodSignature} (the
26
+ * folded `user_data`/`GDestroyNotify` slots are dropped) and the return follows
27
+ * {@link renderMethodReturnType}, so out-parameters tuple into the return the
28
+ * same way a method's would.
29
+ *
30
+ * @param context - The module context
31
+ * @param callback - The callback to emit
32
+ */
33
+ export const emitCallback = (context: ModuleContext, callback: GirCallback): void => {
34
+ if (!callback.introspectable) return;
35
+ if (callback.name.length === 0) return;
36
+ const fn = callbackAsFunction(callback);
37
+ const signature = renderMethodSignature(context, fn);
38
+ const returnType = renderMethodReturnType(context, fn);
39
+ context.module.appendDeclaration(`export type ${callback.name} = (${signature}) => ${returnType};`);
40
+ };
@@ -0,0 +1,75 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import type { GirBoxed } from "../gir/boxed.js";
3
+ import type { GirFunction } from "../gir/function.js";
4
+ import type { GirTypeRef } from "../gir/type-ref.js";
5
+
6
+ /**
7
+ * GObject's class- and interface-struct roots. Every concrete class struct
8
+ * (`GEnumClass`, `GFlagsClass`, …) embeds one of these as its first field; the
9
+ * roots themselves open with a bare `GType` and are matched by name.
10
+ */
11
+ const GTYPE_STRUCT_ROOTS = new Set(["GObject.TypeClass", "GObject.TypeInterface"]);
12
+
13
+ /**
14
+ * Class/interface-struct records that carry no embedded `GTypeClass`/
15
+ * `GTypeInterface` marker and therefore have to be named outright.
16
+ */
17
+ const EXPLICIT_CLASS_STRUCTS = new Set(["Pango.AttrClass"]);
18
+
19
+ const qualify = (namespaceName: string, name: string): string => `${namespaceName}.${name}`;
20
+
21
+ /**
22
+ * Reports whether a `<record>` is a GObject class- or interface-struct — the
23
+ * runtime vtable of a type rather than a marshallable value. These are never
24
+ * constructed or passed by value from JavaScript, so codegen emits neither the
25
+ * record nor any binding that references it.
26
+ *
27
+ * @param namespaceName - The namespace the record lives in
28
+ * @param boxed - The record under consideration
29
+ */
30
+ export const isClassStructRecord = (namespaceName: string, boxed: GirBoxed): boolean => {
31
+ const qualified = qualify(namespaceName, boxed.name);
32
+ if (GTYPE_STRUCT_ROOTS.has(qualified) || EXPLICIT_CLASS_STRUCTS.has(qualified)) return true;
33
+ const first = boxed.fields[0];
34
+ if (first === undefined || first.type === undefined || first.type.kind !== "named") return false;
35
+ const fieldNamespace = first.type.namespaceName ?? namespaceName;
36
+ return GTYPE_STRUCT_ROOTS.has(qualify(fieldNamespace, first.type.typeName));
37
+ };
38
+
39
+ const refIsClassStruct = (context: ModuleContext, ref: GirTypeRef | undefined): boolean => {
40
+ if (ref === undefined) return false;
41
+ switch (ref.kind) {
42
+ case "named": {
43
+ const namespaceName = ref.namespaceName ?? context.namespace.name;
44
+ const resolved = context.repository.resolveNamed(namespaceName, ref.typeName);
45
+ if (resolved === undefined || resolved.kind !== "boxed") return false;
46
+ return isClassStructRecord(resolved.namespace.name, resolved.value);
47
+ }
48
+ case "array":
49
+ case "list":
50
+ return refIsClassStruct(context, ref.element);
51
+ default:
52
+ return false;
53
+ }
54
+ };
55
+
56
+ /**
57
+ * Reports whether a type reference resolves to a class/interface-struct record,
58
+ * following array and list element types.
59
+ *
60
+ * @param context - The module context
61
+ * @param ref - The type reference, or `undefined`
62
+ */
63
+ export const typeRefIsClassStruct = (context: ModuleContext, ref: GirTypeRef | undefined): boolean =>
64
+ refIsClassStruct(context, ref);
65
+
66
+ /**
67
+ * Reports whether a callable's return type or any parameter resolves to a
68
+ * class/interface-struct record, so its binding and wrapper can be dropped.
69
+ *
70
+ * @param context - The module context
71
+ * @param fn - The callable
72
+ */
73
+ export const callableReferencesClassStruct = (context: ModuleContext, fn: GirFunction): boolean =>
74
+ refIsClassStruct(context, fn.returnValue.type) ||
75
+ fn.parameters.some((parameter) => refIsClassStruct(context, parameter.type));
@@ -0,0 +1,96 @@
1
+ import { quote, toCamelCase, toIdentifier, toPascalCase } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import { callbackFromNode, type GirCallback } from "../gir/callback.js";
5
+ import type { GirClass } from "../gir/class.js";
6
+ import type { GirField } from "../gir/field.js";
7
+ import type { GirTypeRef } from "../gir/type-ref.js";
8
+ import { computeBoxedFieldSlots } from "./boxed-layout.js";
9
+ import { renderFfiType } from "./value.js";
10
+
11
+ type VtableKind = "class" | "interface";
12
+
13
+ /**
14
+ * Renders the `{ … }` vtable descriptor literal for a class or interface
15
+ * descriptor's `vfuncs` field, or `undefined` when the type exposes no
16
+ * marshallable slots.
17
+ *
18
+ * Each entry describes one overridable vtable slot — keyed by the slot's
19
+ * camelCase name and carrying `{ kind, className, vfuncName, byteOffset,
20
+ * argTypes, returnType }` — so the runtime `registerClass` can auto-discover
21
+ * a subclass method, locate the matching slot by byte offset, and register a
22
+ * trampoline with the correct marshalling. Slots whose signature cannot be
23
+ * marshalled (non-introspectable, variadic, out-parameters without
24
+ * caller-allocates, or nested callbacks) are omitted. The `kind` discriminator
25
+ * lets the runtime register an interface vfunc mapping in addition to the class
26
+ * one when the descriptor's role is `"interface"`.
27
+ *
28
+ * @param context - The module context
29
+ * @param klass - The class or interface
30
+ */
31
+ export const renderVfuncMetadata = (context: ModuleContext, klass: GirClass): string | undefined => {
32
+ if (klass.glibTypeStruct === undefined) return undefined;
33
+ const structName = toPascalCase(klass.glibTypeStruct);
34
+ const kind: VtableKind = klass.isInterface ? "interface" : "class";
35
+ const entries = vtableEntries(context, structName, kind, klass.glibTypeStruct);
36
+ if (entries.length === 0) return undefined;
37
+ return `{\n${entries.map((entry) => indent(entry, 1)).join("\n")}\n}`;
38
+ };
39
+
40
+ const vtableEntries = (context: ModuleContext, structName: string, kind: VtableKind, typeStruct: string): string[] => {
41
+ const resolved = context.repository.resolveNamed(context.namespace.name, typeStruct);
42
+ if (resolved === undefined || resolved.kind !== "boxed") return [];
43
+ const { slots } = computeBoxedFieldSlots(context, resolved.value.fields, resolved.value.isUnion);
44
+ const entries: string[] = [];
45
+ const claimedNames = new Set<string>();
46
+ for (const { field, slot } of slots) {
47
+ if (field.callback === undefined) continue;
48
+ const key = toIdentifier(toCamelCase(field.name));
49
+ if (key === "constructor" || claimedNames.has(key)) continue;
50
+ const callback = callbackFromNode(field.callback);
51
+ if (!isVtableSlotEligible(callback)) continue;
52
+ claimedNames.add(key);
53
+ entries.push(
54
+ renderDescriptor(context, { key, structName, kind, field, callback, byteOffset: slot.byteOffset }),
55
+ );
56
+ }
57
+ return entries;
58
+ };
59
+
60
+ const isVtableSlotEligible = (callback: GirCallback): boolean => {
61
+ if (!callback.introspectable) return false;
62
+ for (const param of callback.parameters) {
63
+ if (param.isVarargs) return false;
64
+ if ((param.direction === "out" || param.direction === "inout") && !param.callerAllocates) return false;
65
+ if (isCallbackRef(param.type)) return false;
66
+ }
67
+ return !isCallbackRef(callback.returnValue.type);
68
+ };
69
+
70
+ const isCallbackRef = (ref: GirTypeRef | undefined): boolean => ref?.kind === "callback";
71
+
72
+ type RenderDescriptorOptions = {
73
+ readonly key: string;
74
+ readonly structName: string;
75
+ readonly kind: VtableKind;
76
+ readonly field: GirField;
77
+ readonly callback: GirCallback;
78
+ readonly byteOffset: number;
79
+ };
80
+
81
+ const renderDescriptor = (context: ModuleContext, options: RenderDescriptorOptions): string => {
82
+ const { key, structName, kind, field, callback, byteOffset } = options;
83
+ const argTypes = callback.parameters
84
+ .map((param) => renderFfiType(context, param.type, param.transferOwnership))
85
+ .join(", ");
86
+ const returnType = renderFfiType(context, callback.returnValue.type, callback.returnValue.transferOwnership);
87
+ const lines = [
88
+ `kind: ${quote(kind)},`,
89
+ `className: ${quote(structName)},`,
90
+ `vfuncName: ${quote(field.name)},`,
91
+ `byteOffset: ${byteOffset},`,
92
+ `argTypes: [${argTypes}],`,
93
+ `returnType: ${returnType},`,
94
+ ];
95
+ return `${key}: {\n${lines.map((line) => indent(line, 1)).join("\n")}\n},`;
96
+ };