@gtkx/codegen 1.0.0 → 1.2.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 (347) hide show
  1. package/README.md +1 -1
  2. package/dist/analysis/descriptor-render.d.ts +12 -15
  3. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  4. package/dist/analysis/descriptor-render.js +59 -38
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +25 -10
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +37 -9
  9. package/dist/analysis/descriptor.js.map +1 -1
  10. package/dist/analysis/inheritance.d.ts +4 -2
  11. package/dist/analysis/inheritance.d.ts.map +1 -1
  12. package/dist/analysis/inheritance.js +80 -38
  13. package/dist/analysis/inheritance.js.map +1 -1
  14. package/dist/analysis/interface-conflicts.d.ts +25 -0
  15. package/dist/analysis/interface-conflicts.d.ts.map +1 -0
  16. package/dist/analysis/interface-conflicts.js +139 -0
  17. package/dist/analysis/interface-conflicts.js.map +1 -0
  18. package/dist/analysis/param-capability.d.ts.map +1 -1
  19. package/dist/analysis/param-capability.js +3 -4
  20. package/dist/analysis/param-capability.js.map +1 -1
  21. package/dist/analysis/param-structure.js +2 -2
  22. package/dist/analysis/param-structure.js.map +1 -1
  23. package/dist/analysis/ts-type.d.ts +3 -2
  24. package/dist/analysis/ts-type.d.ts.map +1 -1
  25. package/dist/analysis/ts-type.js +26 -26
  26. package/dist/analysis/ts-type.js.map +1 -1
  27. package/dist/analysis/type-key.d.ts +5 -0
  28. package/dist/analysis/type-key.d.ts.map +1 -0
  29. package/dist/analysis/type-key.js +9 -0
  30. package/dist/analysis/type-key.js.map +1 -0
  31. package/dist/analysis/type-shape.d.ts +3 -2
  32. package/dist/analysis/type-shape.d.ts.map +1 -1
  33. package/dist/analysis/type-shape.js +2 -1
  34. package/dist/analysis/type-shape.js.map +1 -1
  35. package/dist/compile.d.ts +14 -6
  36. package/dist/compile.d.ts.map +1 -1
  37. package/dist/compile.js +125 -24
  38. package/dist/compile.js.map +1 -1
  39. package/dist/docs/api-reference.d.ts +2 -0
  40. package/dist/docs/api-reference.d.ts.map +1 -1
  41. package/dist/docs/api-reference.js +11 -10
  42. package/dist/docs/api-reference.js.map +1 -1
  43. package/dist/docs/element-page.d.ts.map +1 -1
  44. package/dist/docs/element-page.js +5 -3
  45. package/dist/docs/element-page.js.map +1 -1
  46. package/dist/docs/pipeline.d.ts +1 -0
  47. package/dist/docs/pipeline.d.ts.map +1 -1
  48. package/dist/docs/pipeline.js +93 -19
  49. package/dist/docs/pipeline.js.map +1 -1
  50. package/dist/docs/render.d.ts +2 -1
  51. package/dist/docs/render.d.ts.map +1 -1
  52. package/dist/docs/render.js +5 -4
  53. package/dist/docs/render.js.map +1 -1
  54. package/dist/docs/symbol-page.d.ts.map +1 -1
  55. package/dist/docs/symbol-page.js +10 -9
  56. package/dist/docs/symbol-page.js.map +1 -1
  57. package/dist/fingerprint.d.ts +20 -3
  58. package/dist/fingerprint.d.ts.map +1 -1
  59. package/dist/fingerprint.js +56 -37
  60. package/dist/fingerprint.js.map +1 -1
  61. package/dist/gi.d.ts +1 -0
  62. package/dist/gi.d.ts.map +1 -1
  63. package/dist/gi.js +8 -2
  64. package/dist/gi.js.map +1 -1
  65. package/dist/gir/ancestry.d.ts +7 -1
  66. package/dist/gir/ancestry.d.ts.map +1 -1
  67. package/dist/gir/ancestry.js +9 -1
  68. package/dist/gir/ancestry.js.map +1 -1
  69. package/dist/gir/annotations.d.ts +1 -2
  70. package/dist/gir/annotations.d.ts.map +1 -1
  71. package/dist/gir/annotations.js +1 -1
  72. package/dist/gir/annotations.js.map +1 -1
  73. package/dist/gir/cursor-overrides.d.ts +8 -0
  74. package/dist/gir/cursor-overrides.d.ts.map +1 -0
  75. package/dist/gir/cursor-overrides.js +6 -0
  76. package/dist/gir/cursor-overrides.js.map +1 -0
  77. package/dist/gir/emittable.d.ts +7 -0
  78. package/dist/gir/emittable.d.ts.map +1 -0
  79. package/dist/gir/emittable.js +3 -0
  80. package/dist/gir/emittable.js.map +1 -0
  81. package/dist/gir/function.d.ts.map +1 -1
  82. package/dist/gir/function.js +28 -2
  83. package/dist/gir/function.js.map +1 -1
  84. package/dist/gir/gir-path.d.ts +1 -1
  85. package/dist/gir/gir-path.js +1 -1
  86. package/dist/gir/gir-path.js.map +1 -1
  87. package/dist/gir/library.d.ts +5 -1
  88. package/dist/gir/library.d.ts.map +1 -1
  89. package/dist/gir/library.js +9 -2
  90. package/dist/gir/library.js.map +1 -1
  91. package/dist/gir/namespace.d.ts +5 -1
  92. package/dist/gir/namespace.d.ts.map +1 -1
  93. package/dist/gir/namespace.js +4 -2
  94. package/dist/gir/namespace.js.map +1 -1
  95. package/dist/gir/parameter.d.ts +13 -1
  96. package/dist/gir/parameter.d.ts.map +1 -1
  97. package/dist/gir/parameter.js +1 -0
  98. package/dist/gir/parameter.js.map +1 -1
  99. package/dist/gir/parse.d.ts.map +1 -1
  100. package/dist/gir/parse.js +6 -6
  101. package/dist/gir/parse.js.map +1 -1
  102. package/dist/gir/primitives.js +1 -1
  103. package/dist/gir/primitives.js.map +1 -1
  104. package/dist/guards.d.ts +16 -0
  105. package/dist/guards.d.ts.map +1 -0
  106. package/dist/guards.js +11 -0
  107. package/dist/guards.js.map +1 -0
  108. package/dist/internal.d.ts +2 -0
  109. package/dist/internal.d.ts.map +1 -1
  110. package/dist/internal.js +2 -0
  111. package/dist/internal.js.map +1 -1
  112. package/dist/json.d.ts +3 -0
  113. package/dist/json.d.ts.map +1 -0
  114. package/dist/json.js +11 -0
  115. package/dist/json.js.map +1 -0
  116. package/dist/khronos/args.d.ts.map +1 -1
  117. package/dist/khronos/args.js +8 -6
  118. package/dist/khronos/args.js.map +1 -1
  119. package/dist/khronos/modules.d.ts.map +1 -1
  120. package/dist/khronos/modules.js +22 -7
  121. package/dist/khronos/modules.js.map +1 -1
  122. package/dist/khronos/parse.d.ts.map +1 -1
  123. package/dist/khronos/parse.js +3 -4
  124. package/dist/khronos/parse.js.map +1 -1
  125. package/dist/khronos/pipeline.d.ts +1 -1
  126. package/dist/khronos/pipeline.d.ts.map +1 -1
  127. package/dist/khronos/pipeline.js.map +1 -1
  128. package/dist/khronos/plan.d.ts +1 -7
  129. package/dist/khronos/plan.d.ts.map +1 -1
  130. package/dist/khronos/plan.js +1 -1
  131. package/dist/khronos/plan.js.map +1 -1
  132. package/dist/khronos/render.d.ts.map +1 -1
  133. package/dist/khronos/render.js +8 -4
  134. package/dist/khronos/render.js.map +1 -1
  135. package/dist/runner.d.ts +7 -1
  136. package/dist/runner.d.ts.map +1 -1
  137. package/dist/runner.js +21 -3
  138. package/dist/runner.js.map +1 -1
  139. package/dist/staging.d.ts +5 -0
  140. package/dist/staging.d.ts.map +1 -0
  141. package/dist/staging.js +57 -0
  142. package/dist/staging.js.map +1 -0
  143. package/dist/store/compile-store.d.ts +0 -1
  144. package/dist/store/compile-store.d.ts.map +1 -1
  145. package/dist/store/compile-store.js +7 -32
  146. package/dist/store/compile-store.js.map +1 -1
  147. package/dist/store/gi/callables.d.ts +6 -3
  148. package/dist/store/gi/callables.d.ts.map +1 -1
  149. package/dist/store/gi/callables.js +40 -30
  150. package/dist/store/gi/callables.js.map +1 -1
  151. package/dist/store/gi/callback.d.ts.map +1 -1
  152. package/dist/store/gi/callback.js +9 -5
  153. package/dist/store/gi/callback.js.map +1 -1
  154. package/dist/store/gi/class.d.ts.map +1 -1
  155. package/dist/store/gi/class.js +104 -44
  156. package/dist/store/gi/class.js.map +1 -1
  157. package/dist/store/gi/companion.d.ts.map +1 -1
  158. package/dist/store/gi/companion.js +6 -6
  159. package/dist/store/gi/companion.js.map +1 -1
  160. package/dist/store/gi/constant.d.ts.map +1 -1
  161. package/dist/store/gi/constant.js +11 -3
  162. package/dist/store/gi/constant.js.map +1 -1
  163. package/dist/store/gi/constructor-props.js +1 -1
  164. package/dist/store/gi/constructor-props.js.map +1 -1
  165. package/dist/store/gi/enum.d.ts.map +1 -1
  166. package/dist/store/gi/enum.js +29 -12
  167. package/dist/store/gi/enum.js.map +1 -1
  168. package/dist/store/gi/function.d.ts +1 -3
  169. package/dist/store/gi/function.d.ts.map +1 -1
  170. package/dist/store/gi/function.js +24 -18
  171. package/dist/store/gi/function.js.map +1 -1
  172. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  173. package/dist/store/gi/gtype-binding.js +4 -1
  174. package/dist/store/gi/gtype-binding.js.map +1 -1
  175. package/dist/store/gi/interface.d.ts.map +1 -1
  176. package/dist/store/gi/interface.js +34 -19
  177. package/dist/store/gi/interface.js.map +1 -1
  178. package/dist/store/gi/method.d.ts +7 -6
  179. package/dist/store/gi/method.d.ts.map +1 -1
  180. package/dist/store/gi/method.js +13 -24
  181. package/dist/store/gi/method.js.map +1 -1
  182. package/dist/store/gi/param-marshal.d.ts +3 -2
  183. package/dist/store/gi/param-marshal.d.ts.map +1 -1
  184. package/dist/store/gi/param-marshal.js +50 -5
  185. package/dist/store/gi/param-marshal.js.map +1 -1
  186. package/dist/store/gi/pipeline.d.ts.map +1 -1
  187. package/dist/store/gi/pipeline.js +21 -10
  188. package/dist/store/gi/pipeline.js.map +1 -1
  189. package/dist/store/gi/properties.d.ts +2 -1
  190. package/dist/store/gi/properties.d.ts.map +1 -1
  191. package/dist/store/gi/properties.js +6 -3
  192. package/dist/store/gi/properties.js.map +1 -1
  193. package/dist/store/gi/property-accessor.d.ts +10 -8
  194. package/dist/store/gi/property-accessor.d.ts.map +1 -1
  195. package/dist/store/gi/property-accessor.js +49 -91
  196. package/dist/store/gi/property-accessor.js.map +1 -1
  197. package/dist/store/gi/record-constructor.d.ts +8 -1
  198. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  199. package/dist/store/gi/record-constructor.js +34 -13
  200. package/dist/store/gi/record-constructor.js.map +1 -1
  201. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  202. package/dist/store/gi/record-field-accessor.js +20 -4
  203. package/dist/store/gi/record-field-accessor.js.map +1 -1
  204. package/dist/store/gi/record-layout.d.ts +6 -1
  205. package/dist/store/gi/record-layout.d.ts.map +1 -1
  206. package/dist/store/gi/record-layout.js +8 -1
  207. package/dist/store/gi/record-layout.js.map +1 -1
  208. package/dist/store/gi/record.d.ts.map +1 -1
  209. package/dist/store/gi/record.js +19 -8
  210. package/dist/store/gi/record.js.map +1 -1
  211. package/dist/store/gi/registration.js +2 -2
  212. package/dist/store/gi/registration.js.map +1 -1
  213. package/dist/store/gi/return-wrap.js +1 -1
  214. package/dist/store/gi/return-wrap.js.map +1 -1
  215. package/dist/store/gi/signal.d.ts +2 -1
  216. package/dist/store/gi/signal.d.ts.map +1 -1
  217. package/dist/store/gi/signal.js +55 -28
  218. package/dist/store/gi/signal.js.map +1 -1
  219. package/dist/store/gi/value-marshalable.d.ts.map +1 -1
  220. package/dist/store/gi/value-marshalable.js +2 -1
  221. package/dist/store/gi/value-marshalable.js.map +1 -1
  222. package/dist/store/gi/vtable.d.ts +4 -1
  223. package/dist/store/gi/vtable.d.ts.map +1 -1
  224. package/dist/store/gi/vtable.js +60 -17
  225. package/dist/store/gi/vtable.js.map +1 -1
  226. package/dist/store/gi-store.d.ts +1 -0
  227. package/dist/store/gi-store.d.ts.map +1 -1
  228. package/dist/store/gi-store.js +15 -20
  229. package/dist/store/gi-store.js.map +1 -1
  230. package/dist/store/jsx/generated-elements.d.ts +2 -1
  231. package/dist/store/jsx/generated-elements.d.ts.map +1 -1
  232. package/dist/store/jsx/generated-elements.js +13 -7
  233. package/dist/store/jsx/generated-elements.js.map +1 -1
  234. package/dist/store/jsx/jsx.js +3 -3
  235. package/dist/store/jsx/jsx.js.map +1 -1
  236. package/dist/store/jsx/metadata.d.ts.map +1 -1
  237. package/dist/store/jsx/metadata.js +3 -1
  238. package/dist/store/jsx/metadata.js.map +1 -1
  239. package/dist/store/jsx-store.d.ts.map +1 -1
  240. package/dist/store/jsx-store.js +3 -4
  241. package/dist/store/jsx-store.js.map +1 -1
  242. package/dist/store/resolve-store.d.ts +14 -3
  243. package/dist/store/resolve-store.d.ts.map +1 -1
  244. package/dist/store/resolve-store.js +98 -15
  245. package/dist/store/resolve-store.js.map +1 -1
  246. package/dist/store/store-fs.d.ts +9 -7
  247. package/dist/store/store-fs.d.ts.map +1 -1
  248. package/dist/store/store-fs.js +54 -25
  249. package/dist/store/store-fs.js.map +1 -1
  250. package/dist/store/store-links.d.ts +3 -0
  251. package/dist/store/store-links.d.ts.map +1 -0
  252. package/dist/store/store-links.js +39 -0
  253. package/dist/store/store-links.js.map +1 -0
  254. package/dist/writer/comparison-context.d.ts +4 -0
  255. package/dist/writer/comparison-context.d.ts.map +1 -0
  256. package/dist/writer/comparison-context.js +13 -0
  257. package/dist/writer/comparison-context.js.map +1 -0
  258. package/dist/writer/context.d.ts +3 -3
  259. package/dist/writer/context.d.ts.map +1 -1
  260. package/dist/writer/context.js +11 -6
  261. package/dist/writer/context.js.map +1 -1
  262. package/dist/writer/module.d.ts +13 -5
  263. package/dist/writer/module.d.ts.map +1 -1
  264. package/dist/writer/module.js +68 -11
  265. package/dist/writer/module.js.map +1 -1
  266. package/dist/xml.d.ts +8 -1
  267. package/dist/xml.d.ts.map +1 -1
  268. package/dist/xml.js +70 -1
  269. package/dist/xml.js.map +1 -1
  270. package/overrides/gobject/object.ts.ejs +2 -0
  271. package/package.json +14 -3
  272. package/src/analysis/descriptor-render.ts +106 -52
  273. package/src/analysis/descriptor.ts +76 -15
  274. package/src/analysis/inheritance.ts +134 -44
  275. package/src/analysis/interface-conflicts.ts +235 -0
  276. package/src/analysis/param-capability.ts +3 -6
  277. package/src/analysis/param-structure.ts +2 -2
  278. package/src/analysis/ts-type.ts +43 -28
  279. package/src/analysis/type-key.ts +14 -0
  280. package/src/analysis/type-shape.ts +5 -2
  281. package/src/compile.ts +192 -27
  282. package/src/docs/api-reference.ts +13 -10
  283. package/src/docs/element-page.ts +5 -2
  284. package/src/docs/pipeline.ts +140 -24
  285. package/src/docs/render.ts +7 -3
  286. package/src/docs/symbol-page.ts +12 -11
  287. package/src/fingerprint.ts +95 -47
  288. package/src/gi.ts +10 -2
  289. package/src/gir/ancestry.ts +17 -1
  290. package/src/gir/annotations.ts +1 -1
  291. package/src/gir/cursor-overrides.ts +12 -0
  292. package/src/gir/emittable.ts +8 -0
  293. package/src/gir/function.ts +45 -9
  294. package/src/gir/gir-path.ts +1 -1
  295. package/src/gir/library.ts +10 -2
  296. package/src/gir/namespace.ts +8 -2
  297. package/src/gir/parameter.ts +15 -0
  298. package/src/gir/parse.ts +10 -7
  299. package/src/gir/primitives.ts +1 -1
  300. package/src/guards.ts +23 -0
  301. package/src/internal.ts +2 -0
  302. package/src/json.ts +11 -0
  303. package/src/khronos/args.ts +9 -6
  304. package/src/khronos/modules.ts +25 -9
  305. package/src/khronos/parse.ts +3 -4
  306. package/src/khronos/pipeline.ts +1 -1
  307. package/src/khronos/plan.ts +0 -1
  308. package/src/khronos/render.ts +8 -6
  309. package/src/runner.ts +26 -4
  310. package/src/staging.ts +67 -0
  311. package/src/store/compile-store.ts +7 -38
  312. package/src/store/gi/callables.ts +60 -33
  313. package/src/store/gi/callback.ts +9 -9
  314. package/src/store/gi/class.ts +140 -53
  315. package/src/store/gi/companion.ts +7 -6
  316. package/src/store/gi/constant.ts +13 -3
  317. package/src/store/gi/constructor-props.ts +1 -1
  318. package/src/store/gi/enum.ts +36 -26
  319. package/src/store/gi/function.ts +30 -24
  320. package/src/store/gi/gtype-binding.ts +7 -2
  321. package/src/store/gi/interface.ts +39 -20
  322. package/src/store/gi/method.ts +26 -32
  323. package/src/store/gi/param-marshal.ts +77 -6
  324. package/src/store/gi/pipeline.ts +25 -15
  325. package/src/store/gi/properties.ts +8 -4
  326. package/src/store/gi/property-accessor.ts +73 -161
  327. package/src/store/gi/record-constructor.ts +53 -21
  328. package/src/store/gi/record-field-accessor.ts +26 -4
  329. package/src/store/gi/record-layout.ts +11 -1
  330. package/src/store/gi/record.ts +20 -12
  331. package/src/store/gi/registration.ts +2 -2
  332. package/src/store/gi/return-wrap.ts +1 -1
  333. package/src/store/gi/signal.ts +66 -42
  334. package/src/store/gi/value-marshalable.ts +3 -1
  335. package/src/store/gi/vtable.ts +97 -20
  336. package/src/store/gi-store.ts +26 -31
  337. package/src/store/jsx/generated-elements.ts +17 -8
  338. package/src/store/jsx/jsx.ts +3 -3
  339. package/src/store/jsx/metadata.ts +4 -1
  340. package/src/store/jsx-store.ts +3 -4
  341. package/src/store/resolve-store.ts +133 -18
  342. package/src/store/store-fs.ts +82 -35
  343. package/src/store/store-links.ts +49 -0
  344. package/src/writer/comparison-context.ts +18 -0
  345. package/src/writer/context.ts +12 -6
  346. package/src/writer/module.ts +101 -14
  347. package/src/xml.ts +103 -1
@@ -1,16 +1,18 @@
1
+ import { sanitizeTypeIdentifier } from "@gtkx/utils";
1
2
  import type { GirClass } from "../../gir/class.js";
2
3
  import type { Library } from "../../gir/library.js";
3
4
  import type { GirAlias, GirNamespace } from "../../gir/namespace.js";
4
5
  import { renderTsType } from "../../analysis/ts-type.js";
6
+ import { getParentRef } from "../../gir/ancestry.js";
7
+ import { isEmittableEntity } from "../../gir/emittable.js";
5
8
  import { PRIMITIVE_TS_TYPE, primitiveCategory } from "../../gir/primitives.js";
6
- import { splitOptionalNamespace } from "../../gir/type-ref.js";
7
9
  import { ModuleContext } from "../../writer/context.js";
8
10
  import { generateCallback } from "./callback.js";
9
11
  import { generateClass } from "./class.js";
10
12
  import { generateConstant } from "./constant.js";
11
13
  import { getDoc } from "./doc-spec.js";
12
14
  import { generateEnum } from "./enum.js";
13
- import { generateNamespaceBootstrap, generateNamespaceFunction } from "./function.js";
15
+ import { generateNamespaceFunction } from "./function.js";
14
16
  import { generateInterface } from "./interface.js";
15
17
  import { generateRecord } from "./record.js";
16
18
 
@@ -28,6 +30,14 @@ const generateNamespaceModule = (namespace: GirNamespace, library: Library): str
28
30
  generateNamespaceTypes(context, namespace);
29
31
  generateNamespaceMembers(context, namespace);
30
32
 
33
+ if (!context.module.hasExports()) {
34
+ throw new Error(
35
+ `GIR file at ${namespace.girFile} has nothing to generate: its ${namespace.name} namespace produces a ` +
36
+ "module with no exports, so the file is empty, truncated, or declares only entries GIR marks as not " +
37
+ "introspectable",
38
+ );
39
+ }
40
+
31
41
  return context.module.toSource();
32
42
  };
33
43
 
@@ -58,8 +68,6 @@ const generateNamespaceMembers = (context: ModuleContext, namespace: GirNamespac
58
68
  generateNamespaceFunction(context, fn);
59
69
  }
60
70
 
61
- generateNamespaceBootstrap(context, namespace);
62
-
63
71
  for (const constant of namespace.constants) {
64
72
  generateConstant(context, constant);
65
73
  }
@@ -70,14 +78,20 @@ const generateNamespaceMembers = (context: ModuleContext, namespace: GirNamespac
70
78
  };
71
79
 
72
80
  const generateAlias = (context: ModuleContext, alias: GirAlias): void => {
81
+ if (!isEmittableEntity(alias)) {
82
+ return;
83
+ }
84
+
73
85
  const category = alias.cType === undefined ? undefined : primitiveCategory(alias.cType);
74
86
  const targetType = category === "gtype" ? PRIMITIVE_TS_TYPE.gtype : renderTsType(context, alias.target);
75
87
  const doc = getDoc(alias);
88
+ const name = sanitizeTypeIdentifier(alias.name);
76
89
 
77
- context.module.appendDeclaration(
78
- `${doc}export type ${alias.name} = ${targetType};`,
79
- context.declaredType(alias.name),
80
- );
90
+ context.declare({
91
+ name,
92
+ code: `${doc}export type ${name} = ${targetType};`,
93
+ owner: alias.name,
94
+ });
81
95
  };
82
96
 
83
97
  const visitClass = (state: TopologicalState, klass: GirClass): void => {
@@ -124,17 +138,13 @@ const sameNamespaceParent = (
124
138
  namespaceName: string,
125
139
  byLocalName: Map<string, GirClass>,
126
140
  ): GirClass | undefined => {
127
- if (klass.parent === undefined) {
128
- return undefined;
129
- }
130
-
131
- const [parentNamespace, typeName] = splitOptionalNamespace(klass.parent);
141
+ const parent = getParentRef(klass);
132
142
 
133
- if (parentNamespace !== undefined && parentNamespace !== namespaceName) {
143
+ if (parent === undefined || (parent.namespaceName !== undefined && parent.namespaceName !== namespaceName)) {
134
144
  return undefined;
135
145
  }
136
146
 
137
- return byLocalName.get(typeName);
147
+ return byLocalName.get(parent.typeName);
138
148
  };
139
149
 
140
150
  export { generateNamespaceModule };
@@ -1,5 +1,6 @@
1
1
  import type { GirClass } from "../../gir/class.js";
2
2
  import type { ModuleContext } from "../../writer/context.js";
3
+ import type { Declaration } from "../../writer/module.js";
3
4
  import { collectInterfaceProperties } from "../../analysis/inheritance.js";
4
5
  import { renderBracedOrEmpty } from "../../writer/emit.js";
5
6
  import { parentCompanionRef } from "./companion.js";
@@ -8,7 +9,7 @@ import { propertyDoc, type ResolvedAccessor, resolveOwnerAccessor } from "./prop
8
9
  const PROPERTIES_SUFFIX = "Properties";
9
10
 
10
11
  const propertyEntry = (accessor: ResolvedAccessor): string =>
11
- `${propertyDoc(accessor.property)}${accessor.jsName}: ${accessor.tsType};`;
12
+ `${propertyDoc(accessor.property)}${accessor.jsName}: ${accessor.readType};`;
12
13
 
13
14
  const interfaceEntries = (context: ModuleContext, klass: GirClass): string[] => {
14
15
  const entries: string[] = [];
@@ -31,7 +32,7 @@ const renderPropertyDeclarations = (
31
32
  klass: GirClass,
32
33
  className: string,
33
34
  accessors: ResolvedAccessor[],
34
- ): string[] => {
35
+ ): Declaration[] => {
35
36
  const parentRef = parentCompanionRef(context, klass, PROPERTIES_SUFFIX);
36
37
  const extendsClause = parentRef === undefined ? "" : ` extends ${parentRef}`;
37
38
 
@@ -43,8 +44,11 @@ const renderPropertyDeclarations = (
43
44
  const map = `${className}${PROPERTIES_SUFFIX}`;
44
45
 
45
46
  return [
46
- renderBracedOrEmpty(`export interface ${map}${extendsClause}`, entries.join("\n")),
47
- renderBracedOrEmpty(`export interface ${className}`, `__properties__: ${map};`),
47
+ { name: map, code: renderBracedOrEmpty(`export interface ${map}${extendsClause}`, entries.join("\n")) },
48
+ {
49
+ name: className,
50
+ code: renderBracedOrEmpty(`export interface ${className}`, `__properties__: ${map};`),
51
+ },
48
52
  ];
49
53
  };
50
54
 
@@ -1,44 +1,38 @@
1
- import { camelCase, sourceStringLiteral, toCamelIdentifier } from "@gtkx/utils";
1
+ import { sourceStringLiteral, toCamelIdentifier } from "@gtkx/utils";
2
2
  import type { GirFunction } from "../../gir/function.js";
3
3
  import type { TypeId } from "../../gir/type-id.js";
4
4
  import type { ModuleContext } from "../../writer/context.js";
5
5
  import { renderDescriptor } from "../../analysis/descriptor-render.js";
6
- import { inputParameters } from "../../analysis/param-structure.js";
7
6
  import { renderTsType } from "../../analysis/ts-type.js";
8
7
  import { type GirProperty, isConstructableProperty } from "../../gir/property.js";
9
8
  import { renderBlock } from "../../writer/emit.js";
10
9
  import { isEmittableCallable } from "./callables.js";
11
10
  import { getDoc } from "./doc-spec.js";
12
- import { methodExportName, renderMethodReturnType } from "./method.js";
11
+ import { methodExportName } from "./method.js";
12
+
13
+ type InheritedAccessorTypes = {
14
+ readType: string | undefined;
15
+ writeType: string | undefined;
16
+ };
13
17
 
14
18
  type ResolvedAccessor = {
15
19
  property: GirProperty;
16
20
  jsName: string;
17
- tsType: string;
21
+ readType: string;
22
+ writeType: string;
18
23
  hasGetter: boolean;
19
24
  isWritable: boolean;
20
- getterMember: string | undefined;
21
- getterMethod: GirFunction | undefined;
22
- setterMember: string | undefined;
23
25
  };
24
26
 
25
27
  type PropertyAccessorArgs = {
26
28
  context: ModuleContext;
27
29
  property: GirProperty;
28
30
  claimedNames: Set<string>;
29
- methodByName: Map<string, GirFunction>;
30
- inheritedType?: string | undefined;
31
+ inheritedTypes?: InheritedAccessorTypes | undefined;
32
+ inheritedNames?: Set<string> | undefined;
31
33
  };
32
34
 
33
- type AccessorDelegate = { member: string | undefined; method: GirFunction | undefined };
34
-
35
- type GetterBodyOptions = {
36
- context: ModuleContext;
37
- property: GirProperty;
38
- getterMember: string | undefined;
39
- getterMethod: GirFunction | undefined;
40
- tsType: string;
41
- };
35
+ const NON_NULLABLE_KINDS: Set<string> = new Set(["primitive", "enum"]);
42
36
 
43
37
  const isNullablePropertyType = (context: ModuleContext, type: TypeId | undefined): boolean => {
44
38
  if (type === undefined) {
@@ -51,105 +45,49 @@ const isNullablePropertyType = (context: ModuleContext, type: TypeId | undefined
51
45
  return true;
52
46
  }
53
47
 
54
- if (resolved.kind === "primitive") {
55
- return false;
56
- }
57
-
58
- if (resolved.kind === "enum") {
59
- return false;
60
- }
61
-
62
48
  if (resolved.kind === "alias") {
63
49
  return isNullablePropertyType(context, resolved.value.target);
64
50
  }
65
51
 
66
- return true;
67
- };
68
-
69
- const resolveDelegate = (
70
- args: PropertyAccessorArgs,
71
- attribute: string | undefined,
72
- member: string | undefined,
73
- isDelegatable: (method: GirFunction) => boolean,
74
- ): AccessorDelegate => {
75
- const method = member !== undefined && attribute !== undefined ? args.methodByName.get(attribute) : undefined;
76
- const canDelegate = method === undefined || isDelegatable(method);
77
-
78
- return canDelegate ? { member, method } : { member: undefined, method: undefined };
79
- };
80
-
81
- const resolveGetterDelegate = (args: PropertyAccessorArgs, jsName: string): AccessorDelegate => {
82
- const { context, property, claimedNames } = args;
83
- const member = delegateMember(property.getter, jsName, claimedNames);
84
-
85
- return resolveDelegate(
86
- args,
87
- property.getter,
88
- member,
89
- (method) =>
90
- inputParameters(context.library, method).length === 0 &&
91
- !renderMethodReturnType(context, method).startsWith("["),
92
- );
52
+ return !NON_NULLABLE_KINDS.has(resolved.kind);
93
53
  };
94
54
 
95
- const resolveSetterDelegate = (args: PropertyAccessorArgs, jsName: string, isWritable: boolean): AccessorDelegate => {
96
- const { context, property, claimedNames } = args;
97
- const member = isWritable ? delegateMember(property.setter, jsName, claimedNames) : undefined;
98
-
99
- return resolveDelegate(
100
- args,
101
- property.setter,
102
- member,
103
- (method) => inputParameters(context.library, method).length === 1,
55
+ const declaredPropertyType = (context: ModuleContext, property: GirProperty): string =>
56
+ renderTsType(
57
+ context,
58
+ property.type,
59
+ property.defaultValue === "NULL" || isNullablePropertyType(context, property.type),
104
60
  );
105
- };
106
-
107
- const resolveOwnType = (
108
- context: ModuleContext,
109
- property: GirProperty,
110
- getterMethod: GirFunction | undefined,
111
- setterMethod: GirFunction | undefined,
112
- ): string => {
113
- const setterParam = setterMethod?.parameters[0];
114
-
115
- if (setterParam !== undefined) {
116
- return renderTsType(context, setterParam.type, setterParam.nullable || setterParam.optional);
117
- }
118
-
119
- if (getterMethod !== undefined) {
120
- return renderMethodReturnType(context, getterMethod);
121
- }
122
-
123
- return renderTsType(context, property.type, isNullablePropertyType(context, property.type));
124
- };
125
61
 
126
- const isSkippedAccessor = (property: GirProperty, jsName: string, claimedNames: Set<string>): boolean =>
127
- !property.introspectable || claimedNames.has(jsName) || jsName === "constructor";
62
+ const isSkippedAccessor = (args: PropertyAccessorArgs, jsName: string): boolean =>
63
+ !args.property.introspectable ||
64
+ args.claimedNames.has(jsName) ||
65
+ args.inheritedNames?.has(jsName) === true ||
66
+ jsName === "constructor";
128
67
 
129
- const resolveTsType = (inheritedType: string | undefined, ownType: string): string =>
130
- inheritedType !== undefined && inheritedType !== ownType ? inheritedType : ownType;
68
+ const isAccessorEmittable = (args: PropertyAccessorArgs, jsName: string, isWritable: boolean): boolean =>
69
+ !isSkippedAccessor(args, jsName) && (args.property.readable || isWritable);
131
70
 
132
71
  const resolveAccessor = (args: PropertyAccessorArgs): ResolvedAccessor | undefined => {
133
- const { context, property, claimedNames } = args;
72
+ const { property } = args;
134
73
  const jsName = toCamelIdentifier(property.name);
135
-
136
- if (isSkippedAccessor(property, jsName, claimedNames)) {
137
- return undefined;
138
- }
139
-
140
74
  const isWritable = isConstructableProperty(property) && !property.constructOnly;
141
- const { member: getterMember, method: getterMethod } = resolveGetterDelegate(args, jsName);
142
- const { member: setterMember, method: setterMethod } = resolveSetterDelegate(args, jsName, isWritable);
143
- const hasGetter = property.readable || getterMember !== undefined;
144
75
 
145
- if (!hasGetter && !isWritable) {
76
+ if (!isAccessorEmittable(args, jsName, isWritable)) {
146
77
  return undefined;
147
78
  }
148
79
 
149
- const ownType = resolveOwnType(context, property, getterMethod, setterMethod);
150
- const tsType = resolveTsType(args.inheritedType, ownType);
80
+ const declared = declaredPropertyType(args.context, property);
81
+ const hasGetter = property.readable;
151
82
 
152
- return { property, jsName, tsType, hasGetter, isWritable, getterMember, getterMethod, setterMember };
83
+ return {
84
+ property,
85
+ jsName,
86
+ readType: args.inheritedTypes?.readType ?? declared,
87
+ writeType: args.inheritedTypes?.writeType ?? declared,
88
+ hasGetter,
89
+ isWritable,
90
+ };
153
91
  };
154
92
 
155
93
  const resolveOwnerAccessor = (
@@ -157,7 +95,6 @@ const resolveOwnerAccessor = (
157
95
  property: GirProperty,
158
96
  methods: GirFunction[],
159
97
  ): ResolvedAccessor | undefined => {
160
- const methodByName: Map<string, GirFunction> = new Map();
161
98
  const claimedNames: Set<string> = new Set();
162
99
 
163
100
  for (const method of methods) {
@@ -165,18 +102,28 @@ const resolveOwnerAccessor = (
165
102
  continue;
166
103
  }
167
104
 
168
- methodByName.set(method.name, method);
169
105
  claimedNames.add(methodExportName(method));
170
106
  }
171
107
 
172
- return resolveAccessor({ context, property, claimedNames, methodByName });
108
+ return resolveAccessor({ context, property, claimedNames });
173
109
  };
174
110
 
175
- const resolveAccessorType = (
111
+ const resolveAccessorTypes = (
176
112
  context: ModuleContext,
177
113
  property: GirProperty,
178
114
  methods: GirFunction[],
179
- ): string | undefined => resolveOwnerAccessor(context, property, methods)?.tsType;
115
+ ): InheritedAccessorTypes | undefined => {
116
+ const accessor = resolveOwnerAccessor(context, property, methods);
117
+
118
+ if (accessor === undefined) {
119
+ return undefined;
120
+ }
121
+
122
+ return {
123
+ readType: accessor.hasGetter ? accessor.readType : undefined,
124
+ writeType: accessor.isWritable ? accessor.writeType : undefined,
125
+ };
126
+ };
180
127
 
181
128
  const withAccessor = (
182
129
  args: PropertyAccessorArgs,
@@ -196,19 +143,15 @@ const renderResolvedPropertyAccessor = (
196
143
  property: GirProperty,
197
144
  accessor: ResolvedAccessor,
198
145
  ): string => {
199
- const { jsName, tsType, hasGetter, isWritable, getterMember, getterMethod, setterMember } = accessor;
146
+ const { jsName, readType, writeType, hasGetter, isWritable } = accessor;
200
147
  const blocks: string[] = [];
201
148
 
202
149
  if (hasGetter) {
203
- const getterBody = renderGetterBody({ context, property, getterMember, getterMethod, tsType });
204
- blocks.push(renderBlock(`get ${jsName}(): ${tsType}`, getterBody));
150
+ blocks.push(renderBlock(`get ${jsName}(): ${readType}`, renderGenericGetBody(context, property, readType)));
205
151
  }
206
152
 
207
153
  if (isWritable) {
208
- const setterBody =
209
- setterMember === undefined ? renderGenericSetBody(context, property) : `this.${setterMember}(value);`;
210
-
211
- blocks.push(renderBlock(`set ${jsName}(value: ${tsType})`, setterBody));
154
+ blocks.push(renderBlock(`set ${jsName}(value: ${writeType})`, renderGenericSetBody(context, property)));
212
155
  }
213
156
 
214
157
  return `${propertyDoc(property)}${blocks.join("\n\n")}`;
@@ -220,20 +163,26 @@ const propertyDoc = (property: GirProperty): string =>
220
163
  const renderPropertyAccessor = (args: PropertyAccessorArgs): string | undefined =>
221
164
  withAccessor(args, (accessor) => renderResolvedPropertyAccessor(args.context, args.property, accessor));
222
165
 
223
- const renderPropertyAccessorSignature = (args: PropertyAccessorArgs): string | undefined =>
224
- withAccessor(args, ({ jsName, tsType, hasGetter, isWritable }) => {
225
- const doc = propertyDoc(args.property);
166
+ const renderAccessorPairSignature = (accessor: ResolvedAccessor): string => {
167
+ const { jsName, readType, writeType, hasGetter, isWritable } = accessor;
226
168
 
227
- if (hasGetter && isWritable) {
228
- return `${doc}${jsName}: ${tsType};`;
229
- }
169
+ if (!hasGetter) {
170
+ return `set ${jsName}(value: ${writeType});`;
171
+ }
230
172
 
231
- if (hasGetter) {
232
- return `${doc}get ${jsName}(): ${tsType};`;
233
- }
173
+ if (!isWritable) {
174
+ return `get ${jsName}(): ${readType};`;
175
+ }
234
176
 
235
- return `${doc}set ${jsName}(value: ${tsType});`;
236
- });
177
+ if (readType === writeType) {
178
+ return `${jsName}: ${readType};`;
179
+ }
180
+
181
+ return `get ${jsName}(): ${readType};\nset ${jsName}(value: ${writeType});`;
182
+ };
183
+
184
+ const renderPropertyAccessorSignature = (args: PropertyAccessorArgs): string | undefined =>
185
+ withAccessor(args, (accessor) => `${propertyDoc(args.property)}${renderAccessorPairSignature(accessor)}`);
237
186
 
238
187
  const renderPropertyDescriptor = (context: ModuleContext, property: GirProperty): string =>
239
188
  renderDescriptor(context, property.type, property.transferOwnership);
@@ -254,52 +203,15 @@ const renderGenericSetBody = (context: ModuleContext, property: GirProperty): st
254
203
  return `setObjectProperty(this, ${sourceStringLiteral(property.name)}, ${descriptor}, value);`;
255
204
  };
256
205
 
257
- const renderGetterBody = (options: GetterBodyOptions): string => {
258
- const { context, property, getterMember, getterMethod, tsType } = options;
259
-
260
- if (getterMember === undefined) {
261
- return renderGenericGetBody(context, property, tsType);
262
- }
263
-
264
- if (getterMethod === undefined) {
265
- return `return this.${getterMember}() as ${tsType};`;
266
- }
267
-
268
- const getterType = renderMethodReturnType(context, getterMethod);
269
-
270
- return getterType === tsType ? `return this.${getterMember}();` : `return this.${getterMember}() as ${tsType};`;
271
- };
272
-
273
- const delegateMember = (
274
- attribute: string | undefined,
275
- accessorName: string,
276
- claimedNames: Set<string>,
277
- ): string | undefined => {
278
- if (attribute === undefined) {
279
- return undefined;
280
- }
281
-
282
- const member = camelCase(attribute);
283
-
284
- if (member === accessorName) {
285
- return undefined;
286
- }
287
-
288
- if (!claimedNames.has(member)) {
289
- return undefined;
290
- }
291
-
292
- return member;
293
- };
294
-
295
206
  export {
296
207
  propertyDoc,
297
208
  resolveAccessor,
298
209
  resolveOwnerAccessor,
299
- resolveAccessorType,
210
+ resolveAccessorTypes,
300
211
  renderResolvedPropertyAccessor,
301
212
  renderPropertyAccessor,
302
213
  renderPropertyAccessorSignature,
214
+ type InheritedAccessorTypes,
303
215
  type ResolvedAccessor,
304
216
  type PropertyAccessorArgs,
305
217
  };
@@ -1,11 +1,14 @@
1
1
  import { sourceStringLiteral, toCamelIdentifier } from "@gtkx/utils";
2
2
  import type { GirField } from "../../gir/field.js";
3
+ import type { GirFunction } from "../../gir/function.js";
3
4
  import type { GirRecord } from "../../gir/record.js";
4
5
  import type { TypeId } from "../../gir/type-id.js";
5
6
  import type { ModuleContext } from "../../writer/context.js";
6
7
  import { renderDescriptor } from "../../analysis/descriptor-render.js";
8
+ import { inputParameters, parameterIdentifier } from "../../analysis/param-structure.js";
7
9
  import { renderTsType } from "../../analysis/ts-type.js";
8
10
  import { renderBlock, renderBracedOrEmpty } from "../../writer/emit.js";
11
+ import { type Callables, staticMembers } from "./callables.js";
9
12
  import { getDoc } from "./doc-spec.js";
10
13
  import { renderSourceGtype } from "./gtype-binding.js";
11
14
  import { emitFieldWrite, isEmittableField, isInlineField } from "./record-field-accessor.js";
@@ -14,22 +17,22 @@ import { isConstructibleRecord } from "./value-marshalable.js";
14
17
 
15
18
  type WritableFieldSlot = RecordFieldSlot & { field: GirField & { type: TypeId } };
16
19
 
20
+ type RecordConstructorSpec = {
21
+ record: GirRecord;
22
+ className: string;
23
+ callables: Callables;
24
+ isErrorSubclass: boolean;
25
+ };
26
+
17
27
  const isWritableFieldSlot = (context: ModuleContext, entry: RecordFieldSlot): entry is WritableFieldSlot =>
18
28
  entry.field.writable && isEmittableField(context, entry.field);
19
29
 
20
- const isOpaque = (record: GirRecord): boolean => record.opaque || record.disguised;
21
-
22
30
  const renderRecordConstructorPropsInterface = (
23
31
  context: ModuleContext,
24
32
  record: GirRecord,
25
33
  className: string,
26
34
  ): string => {
27
35
  const head = `export interface ${className}ConstructorProps`;
28
-
29
- if (isOpaque(record) || !isConstructibleRecord(context, context.namespace.name, record)) {
30
- return renderBracedOrEmpty(head, "");
31
- }
32
-
33
36
  const { slots } = computeRecordFieldSlots(context, record.fields, record.isUnion);
34
37
 
35
38
  const lines = slots
@@ -43,8 +46,41 @@ const renderRecordConstructorPropsInterface = (
43
46
  return renderBracedOrEmpty(head, lines.join("\n"));
44
47
  };
45
48
 
46
- const renderOpaqueConstructor = (className: string, superCall: string[]): string => {
47
- const message = sourceStringLiteral(`Cannot construct ${className}: opaque boxed type with no known layout`);
49
+ const isSelfReturning = (context: ModuleContext, record: GirRecord, callable: GirFunction): boolean => {
50
+ const ref = callable.returnValue.type;
51
+ const name = ref === undefined ? undefined : context.library.nameFor(ref);
52
+
53
+ return name?.namespaceName === context.namespace.name && name.typeName === record.name;
54
+ };
55
+
56
+ const renderStaticCall = (context: ModuleContext, callable: GirFunction, name: string): string => {
57
+ const args = inputParameters(context.library, callable).map(({ parameter, index }) =>
58
+ parameterIdentifier(parameter, index));
59
+
60
+ return `${name}(${args.join(", ")})`;
61
+ };
62
+
63
+ const constructionHint = (context: ModuleContext, spec: RecordConstructorSpec): string | undefined => {
64
+ const candidates = staticMembers(context, spec.callables).filter(({ callable }) =>
65
+ isSelfReturning(context, spec.record, callable));
66
+
67
+ const candidate = candidates.find(({ name }) => name === "new") ?? candidates[0];
68
+
69
+ return candidate === undefined ? undefined : renderStaticCall(context, candidate.callable, candidate.name);
70
+ };
71
+
72
+ const unconstructibleMessage = (context: ModuleContext, spec: RecordConstructorSpec): string => {
73
+ const qualified = `${context.namespace.name}.${spec.className}`;
74
+ const hint = constructionHint(context, spec);
75
+
76
+ return hint === undefined
77
+ ? `Cannot construct ${qualified} with new: its instances come from the functions that return them.`
78
+ : `Cannot construct ${qualified} with new: use ${qualified}.${hint} instead.`;
79
+ };
80
+
81
+ const renderUnconstructibleGuard = (context: ModuleContext, spec: RecordConstructorSpec): string => {
82
+ const superCall = spec.isErrorSubclass ? ["super();"] : [];
83
+ const message = sourceStringLiteral(unconstructibleMessage(context, spec));
48
84
 
49
85
  return renderBlock("constructor()", [...superCall, `throw new globalThis.Error(${message});`].join("\n"));
50
86
  };
@@ -66,18 +102,14 @@ const renderFieldWrites = (context: ModuleContext, slots: RecordFieldSlot[]): st
66
102
  return statements;
67
103
  };
68
104
 
69
- const renderRecordConstructor = (
70
- context: ModuleContext,
71
- record: GirRecord,
72
- className: string,
73
- isErrorSubclass = false,
74
- ): string => {
75
- const superCall = isErrorSubclass ? ["super();"] : [];
105
+ const renderRecordConstructor = (context: ModuleContext, spec: RecordConstructorSpec): string => {
106
+ const { record, className, isErrorSubclass } = spec;
76
107
 
77
- if (isOpaque(record) || !isConstructibleRecord(context, context.namespace.name, record)) {
78
- return renderOpaqueConstructor(className, superCall);
108
+ if (!isConstructibleRecord(context, context.namespace.name, record)) {
109
+ return renderUnconstructibleGuard(context, spec);
79
110
  }
80
111
 
112
+ const superCall = isErrorSubclass ? ["super();"] : [];
81
113
  const { slots, size } = computeRecordFieldSlots(context, record.fields, record.isUnion);
82
114
 
83
115
  if (size === 0) {
@@ -89,9 +121,9 @@ const renderRecordConstructor = (
89
121
 
90
122
  const statements = [
91
123
  ...superCall,
92
- `const handle = alloc(${allocArgs(context, record, size).join(", ")});`,
124
+ `const __handle = alloc(${allocArgs(context, record, size).join(", ")});`,
93
125
  ...renderFieldWrites(context, slots),
94
- "setHandle(this, handle);",
126
+ "setHandle(this, __handle);",
95
127
  ];
96
128
 
97
129
  return renderBlock(`constructor(props: ${className}ConstructorProps = {})`, statements.join("\n"));
@@ -119,7 +151,7 @@ const renderFieldWrite = (context: ModuleContext, entry: WritableFieldSlot): str
119
151
  const write = emitFieldWrite(context, {
120
152
  descriptor,
121
153
  slot: entry.slot,
122
- targetExpr: "handle",
154
+ targetExpr: "__handle",
123
155
  valueExpr: `props.${name}`,
124
156
  });
125
157
 
@@ -1,6 +1,7 @@
1
1
  import { toCamelIdentifier } from "@gtkx/utils";
2
2
  import type { GirAnnotations } from "../../gir/annotations.js";
3
3
  import type { GirField } from "../../gir/field.js";
4
+ import type { GirRecord } from "../../gir/record.js";
4
5
  import type { FieldSlot } from "../../gir/size.js";
5
6
  import type { TypeId } from "../../gir/type-id.js";
6
7
  import type { GirType } from "../../gir/type.js";
@@ -365,6 +366,21 @@ const visitInlineStructFields = (
365
366
  }
366
367
  };
367
368
 
369
+ const inlineLeafCount = (context: ModuleContext, fields: GirField[]): number => {
370
+ let count = 0;
371
+
372
+ visitInlineStructFields(context, fields, 0, {
373
+ leaf: () => {
374
+ count += 1;
375
+ },
376
+ nested: (_jsName, nested) => {
377
+ count += inlineLeafCount(context, nested);
378
+ },
379
+ });
380
+
381
+ return count;
382
+ };
383
+
368
384
  const elementAccessExpr = (descriptor: string, offset: number): string =>
369
385
  `read(__array, ${descriptor}, __base + ${String(offset)})`;
370
386
 
@@ -451,7 +467,7 @@ const structArrayGetterBlock = (options: StructArrayAccessorOptions): string =>
451
467
  };
452
468
 
453
469
  const structArraySetterStatements = (context: ModuleContext, options: StructArrayAccessorOptions): string => {
454
- const { elementDescriptor, offset, elementSize, elementFields } = options;
470
+ const { elementDescriptor, offset, elementSize, elementFields, lengthExpr } = options;
455
471
  const writes: string[] = [];
456
472
 
457
473
  appendElementWriteStatements(context, {
@@ -467,7 +483,7 @@ const structArraySetterStatements = (context: ModuleContext, options: StructArra
467
483
  `const __descriptor = ${elementDescriptor};`,
468
484
  `const __array = read(getHandle(this), __descriptor, ${String(offset)}) as ReturnType<typeof getHandle>;`,
469
485
  "for (const [__index, __element] of __value.entries()) {",
470
- indent(loop, 1),
486
+ indent(`if (__index >= ${lengthExpr}) {\n break;\n}\n\n${loop}`, 1),
471
487
  "}",
472
488
  `write(getHandle(this), __descriptor, ${String(offset)}, __array);`,
473
489
  ].join("\n");
@@ -479,6 +495,12 @@ const structArraySetterBlock = (options: StructArrayAccessorOptions): string =>
479
495
  structArraySetterStatements(options.context, options),
480
496
  );
481
497
 
498
+ const isPlainDataRecord = (record: GirRecord): boolean =>
499
+ record.glibGetType === undefined &&
500
+ record.constructors.length === 0 &&
501
+ record.functions.length === 0 &&
502
+ record.methods.length === 0;
503
+
482
504
  const resolveStructArrayElements = (context: ModuleContext, fieldType: TypeId): StructArrayElements | undefined => {
483
505
  const arrayType = context.library.typeFor(fieldType);
484
506
 
@@ -488,7 +510,7 @@ const resolveStructArrayElements = (context: ModuleContext, fieldType: TypeId):
488
510
 
489
511
  const elementType = context.library.typeFor(arrayType.element);
490
512
 
491
- if (elementType?.kind === "record" && elementType.value.glibGetType !== undefined) {
513
+ if (elementType?.kind === "record" && !isPlainDataRecord(elementType.value)) {
492
514
  return undefined;
493
515
  }
494
516
 
@@ -536,7 +558,7 @@ const resolveStructArray = (context: ModuleContext, target: StructArrayTarget):
536
558
 
537
559
  const shape = resolveStructArrayShape(context, elements, siblingFields);
538
560
 
539
- if (shape === undefined) {
561
+ if (shape === undefined || inlineLeafCount(context, elements.elementFields) === 0) {
540
562
  return undefined;
541
563
  }
542
564