@gtkx/runtime 1.0.0-rc.4 → 1.1.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 (156) hide show
  1. package/README.md +5 -5
  2. package/dist/application-class.d.ts +37 -0
  3. package/dist/application-class.d.ts.map +1 -0
  4. package/dist/application-class.js +78 -0
  5. package/dist/application-class.js.map +1 -0
  6. package/dist/arg.d.ts +2 -1
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js +2 -1
  9. package/dist/arg.js.map +1 -1
  10. package/dist/bind.d.ts +11 -0
  11. package/dist/bind.d.ts.map +1 -1
  12. package/dist/bind.js +11 -0
  13. package/dist/bind.js.map +1 -1
  14. package/dist/callback.d.ts +4 -3
  15. package/dist/callback.d.ts.map +1 -1
  16. package/dist/callback.js +124 -29
  17. package/dist/callback.js.map +1 -1
  18. package/dist/closure.d.ts +26 -0
  19. package/dist/closure.d.ts.map +1 -0
  20. package/dist/closure.js +99 -0
  21. package/dist/closure.js.map +1 -0
  22. package/dist/default-application.d.ts +4 -0
  23. package/dist/default-application.d.ts.map +1 -0
  24. package/dist/default-application.js +17 -0
  25. package/dist/default-application.js.map +1 -0
  26. package/dist/descriptors.d.ts +98 -4
  27. package/dist/descriptors.d.ts.map +1 -1
  28. package/dist/descriptors.js +72 -6
  29. package/dist/descriptors.js.map +1 -1
  30. package/dist/error.d.ts +1 -0
  31. package/dist/error.d.ts.map +1 -1
  32. package/dist/error.js.map +1 -1
  33. package/dist/exit-hook.js +13 -0
  34. package/dist/exit-hook.js.map +1 -1
  35. package/dist/fn.d.ts +24 -2
  36. package/dist/fn.d.ts.map +1 -1
  37. package/dist/fn.js +67 -32
  38. package/dist/fn.js.map +1 -1
  39. package/dist/folded-lengths.d.ts +16 -0
  40. package/dist/folded-lengths.d.ts.map +1 -0
  41. package/dist/folded-lengths.js +38 -0
  42. package/dist/folded-lengths.js.map +1 -0
  43. package/dist/index.d.ts +14 -6
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +12 -4
  46. package/dist/index.js.map +1 -1
  47. package/dist/internal.d.ts +2 -0
  48. package/dist/internal.d.ts.map +1 -1
  49. package/dist/internal.js +2 -0
  50. package/dist/internal.js.map +1 -1
  51. package/dist/library.d.ts +3 -1
  52. package/dist/library.d.ts.map +1 -1
  53. package/dist/library.js +3 -1
  54. package/dist/library.js.map +1 -1
  55. package/dist/lifecycle.d.ts +58 -11
  56. package/dist/lifecycle.d.ts.map +1 -1
  57. package/dist/lifecycle.js +95 -22
  58. package/dist/lifecycle.js.map +1 -1
  59. package/dist/listeners.d.ts.map +1 -1
  60. package/dist/listeners.js +19 -5
  61. package/dist/listeners.js.map +1 -1
  62. package/dist/mixin.d.ts +3 -2
  63. package/dist/mixin.d.ts.map +1 -1
  64. package/dist/mixin.js +13 -1
  65. package/dist/mixin.js.map +1 -1
  66. package/dist/native-value.d.ts.map +1 -1
  67. package/dist/native-value.js +4 -3
  68. package/dist/native-value.js.map +1 -1
  69. package/dist/object.d.ts +40 -7
  70. package/dist/object.d.ts.map +1 -1
  71. package/dist/object.js +95 -26
  72. package/dist/object.js.map +1 -1
  73. package/dist/param-spec.d.ts +11 -0
  74. package/dist/param-spec.d.ts.map +1 -0
  75. package/dist/param-spec.js +147 -0
  76. package/dist/param-spec.js.map +1 -0
  77. package/dist/promisify.d.ts.map +1 -1
  78. package/dist/promisify.js +3 -2
  79. package/dist/promisify.js.map +1 -1
  80. package/dist/properties.d.ts +39 -7
  81. package/dist/properties.d.ts.map +1 -1
  82. package/dist/properties.js +310 -43
  83. package/dist/properties.js.map +1 -1
  84. package/dist/register-class.d.ts +147 -12
  85. package/dist/register-class.d.ts.map +1 -1
  86. package/dist/register-class.js +129 -83
  87. package/dist/register-class.js.map +1 -1
  88. package/dist/registry.d.ts +80 -21
  89. package/dist/registry.d.ts.map +1 -1
  90. package/dist/registry.js +86 -33
  91. package/dist/registry.js.map +1 -1
  92. package/dist/signal.d.ts +11 -4
  93. package/dist/signal.d.ts.map +1 -1
  94. package/dist/signal.js +85 -25
  95. package/dist/signal.js.map +1 -1
  96. package/dist/t.d.ts +2 -0
  97. package/dist/t.d.ts.map +1 -1
  98. package/dist/t.js +1 -0
  99. package/dist/t.js.map +1 -1
  100. package/dist/tuple.d.ts +6 -3
  101. package/dist/tuple.d.ts.map +1 -1
  102. package/dist/tuple.js +9 -7
  103. package/dist/tuple.js.map +1 -1
  104. package/dist/type.d.ts +2 -1
  105. package/dist/type.d.ts.map +1 -1
  106. package/dist/type.js +2 -0
  107. package/dist/type.js.map +1 -1
  108. package/dist/value.d.ts +10 -1
  109. package/dist/value.d.ts.map +1 -1
  110. package/dist/value.js +99 -24
  111. package/dist/value.js.map +1 -1
  112. package/dist/vfunc-call.d.ts +37 -0
  113. package/dist/vfunc-call.d.ts.map +1 -0
  114. package/dist/vfunc-call.js +172 -0
  115. package/dist/vfunc-call.js.map +1 -0
  116. package/dist/vfunc-seeds.d.ts +12 -0
  117. package/dist/vfunc-seeds.d.ts.map +1 -0
  118. package/dist/vfunc-seeds.js +19 -0
  119. package/dist/vfunc-seeds.js.map +1 -0
  120. package/dist/vfunc.d.ts +8 -0
  121. package/dist/vfunc.d.ts.map +1 -0
  122. package/dist/vfunc.js +29 -0
  123. package/dist/vfunc.js.map +1 -0
  124. package/package.json +14 -4
  125. package/src/application-class.ts +126 -0
  126. package/src/arg.ts +2 -1
  127. package/src/bind.ts +11 -0
  128. package/src/callback.ts +178 -34
  129. package/src/closure.ts +136 -0
  130. package/src/default-application.ts +21 -0
  131. package/src/descriptors.ts +150 -34
  132. package/src/error.ts +1 -0
  133. package/src/exit-hook.ts +18 -0
  134. package/src/fn.ts +107 -36
  135. package/src/folded-lengths.ts +64 -0
  136. package/src/index.ts +21 -6
  137. package/src/internal.ts +2 -0
  138. package/src/library.ts +3 -1
  139. package/src/lifecycle.ts +138 -25
  140. package/src/listeners.ts +25 -6
  141. package/src/mixin.ts +15 -2
  142. package/src/native-value.ts +5 -3
  143. package/src/object.ts +134 -35
  144. package/src/param-spec.ts +234 -0
  145. package/src/promisify.ts +4 -2
  146. package/src/properties.ts +473 -46
  147. package/src/register-class.ts +386 -114
  148. package/src/registry.ts +169 -49
  149. package/src/signal.ts +116 -42
  150. package/src/t.ts +3 -0
  151. package/src/tuple.ts +18 -12
  152. package/src/type.ts +3 -1
  153. package/src/value.ts +143 -23
  154. package/src/vfunc-call.ts +247 -0
  155. package/src/vfunc-seeds.ts +36 -0
  156. package/src/vfunc.ts +38 -0
package/src/type.ts CHANGED
@@ -15,6 +15,7 @@ import { LIB } from "./library.js";
15
15
 
16
16
  /** Object tagged with its GLib type through a `__type__` GType field. */
17
17
  type TypedClass = {
18
+ /** GType the object's class is registered under. */
18
19
  __type__: bigint;
19
20
  };
20
21
 
@@ -119,6 +120,7 @@ const isTypedClass = (value: unknown): value is TypedClass =>
119
120
  typeof value === "object" && value !== null && "__type__" in value && typeof value.__type__ === "bigint";
120
121
 
121
122
  /** Returns whether `type` is `ancestorType` or descends from it. */
123
+ /* eslint-disable-next-line unicorn/consistent-boolean-name -- mirrors g_type_is_a from the C API */
122
124
  function typeIsA(type: bigint, ancestorType: bigint): boolean {
123
125
  return gTypeIsA(type, ancestorType) as boolean;
124
126
  }
@@ -150,6 +152,7 @@ function typeName(type: bigint): string | null {
150
152
  }
151
153
 
152
154
  /** Returns whether `value` is a typed wrapper whose GType is or descends from `gtype`. */
155
+ /* eslint-disable-next-line unicorn/consistent-boolean-name -- mirrors g_type_is_a from the C API */
153
156
  function valueIsA(value: unknown, gtype: bigint): boolean {
154
157
  return isTypedClass(value) && typeIsA(value.__type__, gtype);
155
158
  }
@@ -284,6 +287,5 @@ export {
284
287
  resolveBoxedType,
285
288
  resolveFundamentalType,
286
289
  resolveDescriptorType,
287
- type ResolvableDescriptor,
288
290
  type TypedClass,
289
291
  };
package/src/value.ts CHANGED
@@ -9,16 +9,21 @@ import {
9
9
  boxedT,
10
10
  float32T,
11
11
  float64T,
12
+ type FundamentalDescriptor,
13
+ fundamentalLifecycleFor,
14
+ fundamentalT,
15
+ int8T,
12
16
  int32T,
13
17
  objectT,
14
18
  stringT,
19
+ uint8T,
15
20
  uint32T,
16
21
  uint64T,
17
22
  voidT,
18
23
  } from "./descriptors.js";
19
24
  import { LIB, PARAM_T, VALUE_SIZE, VALUE_T, VARIANT_T } from "./library.js";
20
25
  import { toNative } from "./native-value.js";
21
- import { getHandle, getWrapperClass, wrapHandle } from "./registry.js";
26
+ import { getHandle, getWrapperClass, wrapHandle, wrapObject } from "./registry.js";
22
27
  import {
23
28
  getStrvType,
24
29
  isResolvableDescriptor,
@@ -28,6 +33,7 @@ import {
28
33
  resolveType,
29
34
  TYPE_BOOLEAN,
30
35
  TYPE_BOXED,
36
+ TYPE_CHAR,
31
37
  TYPE_DOUBLE,
32
38
  TYPE_ENUM,
33
39
  TYPE_FLAGS,
@@ -35,13 +41,17 @@ import {
35
41
  TYPE_GTYPE,
36
42
  TYPE_INT,
37
43
  TYPE_INT64,
44
+ TYPE_INTERFACE,
38
45
  TYPE_INVALID,
46
+ TYPE_LONG,
39
47
  TYPE_OBJECT,
40
48
  TYPE_PARAM,
41
49
  TYPE_POINTER,
42
50
  TYPE_STRING,
51
+ TYPE_UCHAR,
43
52
  TYPE_UINT,
44
53
  TYPE_UINT64,
54
+ TYPE_ULONG,
45
55
  TYPE_VARIANT,
46
56
  typeFundamental,
47
57
  typeName,
@@ -53,16 +63,24 @@ type ValueType = {
53
63
  };
54
64
 
55
65
  type ValueGetter = (value: ExternalObject<Handle>) => unknown;
66
+ type ValueWriter = ValueType["set"];
67
+ type ValueNarrower = (jsValue: unknown) => unknown;
56
68
 
57
69
  const setBoxedCache = createBindCache();
58
70
  const setStaticBoxedCache = createBindCache();
59
71
  const dupBoxedCache = createBindCache();
72
+ const setInstanceCache = createBindCache();
73
+ const peekPointerCache = createBindCache();
60
74
  const gValueInit = bind(LIB, "g_value_init", [VALUE_T, biguint64T], voidT);
61
75
  const gValueCopy = bind(LIB, "g_value_copy", [VALUE_T, VALUE_T], voidT);
62
76
  const booleanValueType = bindValueType("boolean", booleanT);
63
77
  const typeValueType = bindValueType("gtype", biguint64T);
78
+ const scharValueType = bindValueType("schar", int8T);
79
+ const ucharValueType = bindValueType("uchar", uint8T);
64
80
  const intValueType = bindValueType("int", int32T);
65
81
  const uintValueType = bindValueType("uint", uint32T);
82
+ const longValueType = bindValueType("long", bigint64T);
83
+ const ulongValueType = bindValueType("ulong", biguint64T);
66
84
  const int64ValueType = bindValueType("int64", bigint64T);
67
85
  const uint64ValueType = bindValueType("uint64", biguint64T);
68
86
  const floatValueType = bindValueType("float", float32T);
@@ -74,9 +92,10 @@ const objectValueType = bindValueType("object", objectT("borrowed"));
74
92
  const paramValueType = bindValueType("param", PARAM_T);
75
93
  const variantValueType = bindValueType("variant", VARIANT_T);
76
94
  const pointerValueType = bindValueType("pointer", uint64T);
95
+ const setStrvBoxed = bind(LIB, "g_value_set_boxed", [VALUE_T, arrayT(stringT("borrowed"))], voidT);
77
96
 
78
97
  const strvValueType: ValueType = {
79
- set: bind(LIB, "g_value_set_boxed", [VALUE_T, arrayT(stringT("borrowed"))], voidT),
98
+ set: setStrvValue,
80
99
  get: bind(LIB, "g_value_get_boxed", [VALUE_T], arrayT(stringT("borrowed"))),
81
100
  };
82
101
 
@@ -100,9 +119,12 @@ const PLAIN_VALUE_TYPES: Partial<Record<Descriptor["kind"], ValueType>> = {
100
119
 
101
120
  const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
102
121
  [TYPE_BOOLEAN, booleanValueType.get],
103
- [TYPE_GTYPE, typeValueType.get],
122
+ [TYPE_CHAR, scharValueType.get],
123
+ [TYPE_UCHAR, ucharValueType.get],
104
124
  [TYPE_INT, intValueType.get],
105
125
  [TYPE_UINT, uintValueType.get],
126
+ [TYPE_LONG, longValueType.get],
127
+ [TYPE_ULONG, ulongValueType.get],
106
128
  [TYPE_INT64, int64ValueType.get],
107
129
  [TYPE_UINT64, uint64ValueType.get],
108
130
  [TYPE_FLOAT, floatValueType.get],
@@ -113,9 +135,12 @@ const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
113
135
 
114
136
  const PLAIN_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
115
137
  [TYPE_BOOLEAN, booleanValueType.set],
116
- [TYPE_GTYPE, typeValueType.set],
138
+ [TYPE_CHAR, scharValueType.set],
139
+ [TYPE_UCHAR, ucharValueType.set],
117
140
  [TYPE_INT, intValueType.set],
118
141
  [TYPE_UINT, uintValueType.set],
142
+ [TYPE_LONG, longValueType.set],
143
+ [TYPE_ULONG, ulongValueType.set],
119
144
  [TYPE_INT64, int64ValueType.set],
120
145
  [TYPE_UINT64, uint64ValueType.set],
121
146
  [TYPE_FLOAT, floatValueType.set],
@@ -127,9 +152,11 @@ const PLAIN_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
127
152
  const WRAPPED_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
128
153
  [TYPE_STRING, setStringValue],
129
154
  [TYPE_OBJECT, handleSetter(objectValueType)],
155
+ [TYPE_INTERFACE, handleSetter(objectValueType)],
130
156
  [TYPE_PARAM, handleSetter(paramValueType)],
131
157
  [TYPE_VARIANT, handleSetter(variantValueType)],
132
158
  [TYPE_BOXED, setBoxedFromValue],
159
+ [TYPE_POINTER, setPointerValue],
133
160
  ]);
134
161
 
135
162
  const getBoxedTypeName = (type: bigint): string => typeName(type) ?? "GBoxed";
@@ -160,6 +187,46 @@ const boxedValueType = (type: bigint): ValueType => {
160
187
  const enumOrFlagsValueType = (type: bigint): ValueType =>
161
188
  typeFundamental(type) === TYPE_FLAGS ? flagsValueType : enumValueType;
162
189
 
190
+ function unsupportedFundamental(type: bigint): never {
191
+ throw new Error(`Unsupported fundamental type '${typeName(type) ?? String(type)}' for value`);
192
+ }
193
+
194
+ const setInstanceBind = (fundamental: bigint, descriptor: FundamentalDescriptor) =>
195
+ setInstanceCache(String(fundamental), LIB, "g_value_set_instance", [VALUE_T, descriptor], voidT);
196
+
197
+ const peekPointerBind = (fundamental: bigint, descriptor: FundamentalDescriptor) =>
198
+ peekPointerCache(String(fundamental), LIB, "g_value_peek_pointer", [VALUE_T], descriptor);
199
+
200
+ const customFundamentalDescriptor = (fundamental: bigint): FundamentalDescriptor | undefined => {
201
+ const name = typeName(fundamental);
202
+
203
+ if (name === null) {
204
+ return undefined;
205
+ }
206
+
207
+ const lifecycle = fundamentalLifecycleFor(name);
208
+
209
+ if (lifecycle === undefined) {
210
+ return undefined;
211
+ }
212
+
213
+ return fundamentalT(lifecycle.sharedLibrary, lifecycle.refFnName, lifecycle.unrefFnName, {
214
+ ownership: "borrowed",
215
+ typeName: name,
216
+ });
217
+ };
218
+
219
+ const customFundamentalValueType = (type: bigint): ValueType | undefined => {
220
+ const fundamental = typeFundamental(type);
221
+ const descriptor = customFundamentalDescriptor(fundamental);
222
+
223
+ if (descriptor === undefined) {
224
+ return undefined;
225
+ }
226
+
227
+ return { set: setInstanceBind(fundamental, descriptor), get: peekPointerBind(fundamental, descriptor) };
228
+ };
229
+
163
230
  const fundamentalValueType = (type: bigint): ValueType => {
164
231
  switch (typeFundamental(type)) {
165
232
  case TYPE_PARAM: {
@@ -172,7 +239,7 @@ const fundamentalValueType = (type: bigint): ValueType => {
172
239
  return boxedValueType(type);
173
240
  }
174
241
  default: {
175
- throw new Error(`Unsupported fundamental type '${typeName(type) ?? String(type)}' for value`);
242
+ return customFundamentalValueType(type) ?? unsupportedFundamental(type);
176
243
  }
177
244
  }
178
245
  };
@@ -189,7 +256,7 @@ function copyValue(dest: ExternalObject<Handle>, src: ExternalObject<Handle>): v
189
256
  gValueCopy(src, dest);
190
257
  }
191
258
 
192
- const newTypedValue = (type: bigint): ExternalObject<Handle> => {
259
+ const newValueForType = (type: bigint): ExternalObject<Handle> => {
193
260
  const value = newValue();
194
261
  gValueInit(value, type);
195
262
 
@@ -202,7 +269,7 @@ const newBoxedValue = (
202
269
  resolveSetBind: (name: string) => ValueType["set"],
203
270
  ): ExternalObject<Handle> => {
204
271
  const type = resolveDescriptorType(descriptor);
205
- const value = newTypedValue(type);
272
+ const value = newValueForType(type);
206
273
  resolveSetBind(getBoxedTypeName(type))(value, getHandle(boxed));
207
274
 
208
275
  return value;
@@ -276,8 +343,9 @@ const wrappedValueGetter = (fundamental: bigint): ValueGetter | undefined => {
276
343
  case TYPE_STRING: {
277
344
  return (value) => stringValueType.get(value) ?? null;
278
345
  }
346
+ case TYPE_INTERFACE:
279
347
  case TYPE_OBJECT: {
280
- return (value) => wrapHandle(objectValueType.get(value) as ExternalObject<Handle> | null);
348
+ return (value) => wrapObject(objectValueType.get(value));
281
349
  }
282
350
  case TYPE_PARAM: {
283
351
  return (value) =>
@@ -309,6 +377,18 @@ function setStringValue(value: ExternalObject<Handle>, nativeValue: unknown): vo
309
377
  stringValueType.set(value, nativeValue ?? null);
310
378
  }
311
379
 
380
+ function setStrvValue(value: ExternalObject<Handle>, nativeValue: unknown): void {
381
+ setStrvBoxed(value, nativeValue ?? null);
382
+ }
383
+
384
+ function setPointerValue(value: ExternalObject<Handle>, nativeValue: unknown): void {
385
+ if (nativeValue != null) {
386
+ throw new Error("G_TYPE_POINTER non-null values cannot be marshalled from JS");
387
+ }
388
+
389
+ pointerValueType.set(value, 0);
390
+ }
391
+
312
392
  function handleSetter(target: ValueType): ValueType["set"] {
313
393
  return (value, nativeValue) => {
314
394
  target.set(value, nativeValue == null ? null : getHandle(nativeValue));
@@ -319,21 +399,61 @@ function setBoxedFromValue(value: ExternalObject<Handle>, nativeValue: unknown):
319
399
  setBoxedValue(value, nativeValue ?? null);
320
400
  }
321
401
 
322
- const resolveValueGetter = (fundamental: bigint): ValueGetter | undefined =>
402
+ const customFundamentalGetter = (type: bigint): ValueGetter | undefined => {
403
+ const valueType = customFundamentalValueType(type);
404
+
405
+ if (valueType === undefined) {
406
+ return undefined;
407
+ }
408
+
409
+ return (value) => wrapHandle(valueType.get(value) as ExternalObject<Handle> | null, getWrapperClass(type));
410
+ };
411
+
412
+ const customFundamentalSetter = (type: bigint): ValueType["set"] | undefined => {
413
+ const valueType = customFundamentalValueType(type);
414
+
415
+ return valueType === undefined ? undefined : handleSetter(valueType);
416
+ };
417
+
418
+ const builtInValueGetter = (fundamental: bigint): ValueGetter | undefined =>
323
419
  PLAIN_VALUE_GETTERS.get(fundamental) ?? wrappedValueGetter(fundamental);
324
420
 
325
- const resolveValueSetter = (fundamental: bigint): ValueType["set"] | undefined =>
421
+ const builtInValueSetter = (fundamental: bigint): ValueType["set"] | undefined =>
326
422
  PLAIN_VALUE_SETTERS.get(fundamental) ?? WRAPPED_VALUE_SETTERS.get(fundamental);
327
423
 
328
- function intoValue(value: ExternalObject<Handle>, jsValue: unknown): void {
329
- const type = getValueType(value);
330
- const set = resolveValueSetter(typeFundamental(type));
424
+ const exactValueType = (type: bigint): ValueType | undefined => {
425
+ if (type === TYPE_GTYPE) {
426
+ return typeValueType;
427
+ }
428
+
429
+ return type === getStrvType() ? strvValueType : undefined;
430
+ };
431
+
432
+ const resolveValueGetter = (type: bigint): ValueGetter | undefined =>
433
+ exactValueType(type)?.get ?? builtInValueGetter(typeFundamental(type)) ?? customFundamentalGetter(type);
434
+
435
+ const resolveValueSetter = (type: bigint): ValueType["set"] | undefined =>
436
+ exactValueType(type)?.set ?? builtInValueSetter(typeFundamental(type)) ?? customFundamentalSetter(type);
437
+
438
+ const holdsUnchanged: ValueNarrower = (jsValue) => jsValue;
439
+ const holdsAsFloat: ValueNarrower = (jsValue) => Math.fround(jsValue as number);
440
+
441
+ function valueNarrowerFor(type: bigint): ValueNarrower {
442
+ return typeFundamental(type) === TYPE_FLOAT ? holdsAsFloat : holdsUnchanged;
443
+ }
444
+
445
+ function valueWriterFor(type: bigint): ValueWriter {
446
+ const set = resolveValueSetter(type);
331
447
 
332
448
  if (set === undefined) {
333
449
  throw new Error(`Unsupported type for intoValue: ${typeName(type) ?? String(type)}`);
334
450
  }
335
451
 
336
- set(value, jsValue);
452
+ return set;
453
+ }
454
+
455
+ function intoValue(value: ExternalObject<Handle>, jsValue: unknown): void {
456
+ valueWriterFor(getValueType(value))(value, jsValue);
337
457
  }
338
458
 
339
459
  const resolveNativeValue = (descriptor: Descriptor, value: unknown): unknown => {
@@ -357,7 +477,7 @@ const resolveValueGType = (descriptor: Descriptor, nativeValue: unknown): bigint
357
477
  function toValue(descriptor: Descriptor, value: unknown): ExternalObject<Handle> {
358
478
  const nativeValue = resolveNativeValue(descriptor, value);
359
479
  const type = resolveValueGType(descriptor, nativeValue);
360
- const gValue = newTypedValue(type);
480
+ const gValue = newValueForType(type);
361
481
  resolveValueType(descriptor).set(gValue, nativeValue);
362
482
 
363
483
  return gValue;
@@ -365,12 +485,7 @@ function toValue(descriptor: Descriptor, value: unknown): ExternalObject<Handle>
365
485
 
366
486
  function fromValue(value: ExternalObject<Handle>): unknown {
367
487
  const type = getValueType(value);
368
-
369
- if (type === getStrvType()) {
370
- return strvValueType.get(value);
371
- }
372
-
373
- const get = resolveValueGetter(typeFundamental(type));
488
+ const get = resolveValueGetter(type);
374
489
 
375
490
  if (get === undefined) {
376
491
  throw new Error(`Unsupported type for fromValue: ${typeName(type) ?? String(type)}`);
@@ -380,7 +495,7 @@ function fromValue(value: ExternalObject<Handle>): unknown {
380
495
  }
381
496
 
382
497
  function newValueForDescriptor(descriptor: Descriptor): ExternalObject<Handle> {
383
- return newTypedValue(resolveDescriptorType(descriptor));
498
+ return newValueForType(resolveDescriptorType(descriptor));
384
499
  }
385
500
 
386
501
  function outValueForDescriptor(
@@ -394,7 +509,7 @@ function outValueForDescriptor(
394
509
  write(storage, descriptor, 0, initial);
395
510
  }
396
511
 
397
- const value = newTypedValue(TYPE_POINTER);
512
+ const value = newValueForType(TYPE_POINTER);
398
513
  pointerValueType.set(value, storage);
399
514
 
400
515
  return { value, read: () => read(storage, descriptor, 0) };
@@ -417,7 +532,12 @@ export {
417
532
  toValue,
418
533
  fromValue,
419
534
  newValueForDescriptor,
535
+ newValueForType,
420
536
  outValueForDescriptor,
421
537
  outValueForBoxedDescriptor,
422
538
  inoutValueForBoxedDescriptor,
539
+ type ValueNarrower,
540
+ valueNarrowerFor,
541
+ type ValueWriter,
542
+ valueWriterFor,
423
543
  };
@@ -0,0 +1,247 @@
1
+ import type { BindVfuncOptions } from "@gtkx/native";
2
+ import type { AnyClass } from "@gtkx/utils";
3
+ import { bindVfunc } from "@gtkx/native";
4
+ import { type Arg, isCallerAllocatedArg, requiresInputArg } from "./arg.js";
5
+ import { buildNativeArgTypes, fromNativeCallable } from "./fn.js";
6
+ import { toNative } from "./native-value.js";
7
+ import {
8
+ getClassType,
9
+ getHandle,
10
+ getInterfaceVfuncRegistry,
11
+ getVfuncRegistry,
12
+ instanceClassName,
13
+ resolveWrapperType,
14
+ type VfuncDescriptor,
15
+ } from "./registry.js";
16
+ import { TYPE_INVALID, typeInterfaces, typeIsA, typeParent } from "./type.js";
17
+ import { type RefSeeds, seedsFor } from "./vfunc-seeds.js";
18
+ import { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs } from "./vfunc.js";
19
+
20
+ type Invoker = (instance: object, inputs: unknown[]) => unknown;
21
+ type InvokerCache = WeakMap<AnyClass, Map<string, Invoker>>;
22
+ type ResolvedSlot = { descriptor: VfuncDescriptor; interfaceType?: bigint };
23
+
24
+ const NO_BASELINE = -1;
25
+
26
+ const SEEDED_SLOTS: Record<string, RefSeeds> = {
27
+ "LayoutManagerClass.measure": new Map([[6, NO_BASELINE], [7, NO_BASELINE]]),
28
+ "WidgetClass.measure": new Map([[5, NO_BASELINE], [6, NO_BASELINE]]),
29
+ };
30
+
31
+ const parentInvokers: InvokerCache = new WeakMap();
32
+ const vfuncInvokers: InvokerCache = new WeakMap();
33
+
34
+ function toNativeInput(arg: Arg, input: unknown): unknown {
35
+ return isCallerAllocatedArg(arg) ? input : toNative(arg.type, input);
36
+ }
37
+
38
+ function bindOptionsFor(
39
+ slot: ResolvedSlot,
40
+ instanceType: bigint | undefined,
41
+ args: Arg[],
42
+ label: string,
43
+ ): BindVfuncOptions {
44
+ const { descriptor, interfaceType } = slot;
45
+
46
+ const options: BindVfuncOptions = {
47
+ byteOffset: descriptor.byteOffset,
48
+ label,
49
+ argDescriptors: buildNativeArgTypes(args, descriptor.canThrow === true),
50
+ returnDescriptor: descriptor.returnDescriptor,
51
+ };
52
+
53
+ if (descriptor.vtableSize !== undefined) {
54
+ options.vtableSize = descriptor.vtableSize;
55
+ }
56
+
57
+ if (instanceType !== undefined) {
58
+ options.instanceType = instanceType;
59
+ }
60
+
61
+ if (interfaceType !== undefined) {
62
+ options.interfaceType = interfaceType;
63
+ }
64
+
65
+ return options;
66
+ }
67
+
68
+ function buildInvoker(slot: ResolvedSlot, instanceType: bigint | undefined, caller: string): Invoker {
69
+ const { descriptor } = slot;
70
+ const args = vfuncArgs(descriptor);
71
+ const canThrow = descriptor.canThrow === true;
72
+ const label = `${descriptor.className}.${descriptor.vfuncName}`;
73
+ const [, ...inputArgs] = args.filter(requiresInputArg);
74
+ let pendingSeeds: RefSeeds | undefined;
75
+
76
+ const takeRefSeeds = (): RefSeeds | undefined => {
77
+ const seeds = pendingSeeds;
78
+ pendingSeeds = undefined;
79
+
80
+ return seeds;
81
+ };
82
+
83
+ const shaped = fromNativeCallable(
84
+ bindVfunc(bindOptionsFor(slot, instanceType, args, label)),
85
+ {
86
+ args,
87
+ returns: descriptor.returnDescriptor,
88
+ canThrow,
89
+ },
90
+ takeRefSeeds,
91
+ );
92
+
93
+ return (instance, inputs) => {
94
+ if (inputs.length !== inputArgs.length) {
95
+ throw new Error(
96
+ `${caller}: ${label} expects ${String(inputArgs.length)} arguments, ` +
97
+ `received ${String(inputs.length)}`,
98
+ );
99
+ }
100
+
101
+ const nativeInputs = inputArgs.map((arg, index) => toNativeInput(arg, inputs[index]));
102
+ pendingSeeds = SEEDED_SLOTS[label] ?? seedsFor(descriptor.argDescriptors, instance);
103
+
104
+ return shaped(getHandle(instance), ...nativeInputs);
105
+ };
106
+ }
107
+
108
+ function cachedInvoker(cache: InvokerCache, owner: AnyClass, key: string, build: () => Invoker): Invoker {
109
+ let byKey = cache.get(owner);
110
+
111
+ if (byKey === undefined) {
112
+ byKey = new Map();
113
+ cache.set(owner, byKey);
114
+ }
115
+
116
+ let invoker = byKey.get(key);
117
+
118
+ if (invoker === undefined) {
119
+ invoker = build();
120
+ byKey.set(key, invoker);
121
+ }
122
+
123
+ return invoker;
124
+ }
125
+
126
+ function resolveParentSlot(klass: AnyClass, parentType: bigint, methodName: string): ResolvedSlot | undefined {
127
+ const classDescriptor = findClassVfuncDescriptor(klass, methodName);
128
+
129
+ if (classDescriptor) {
130
+ return { descriptor: classDescriptor };
131
+ }
132
+
133
+ for (const interfaceType of typeInterfaces(parentType)) {
134
+ const descriptor = findInterfaceVfuncDescriptor(interfaceType, methodName);
135
+
136
+ if (descriptor) {
137
+ return { descriptor, interfaceType };
138
+ }
139
+ }
140
+
141
+ return undefined;
142
+ }
143
+
144
+ function resolveParentType(klass: AnyClass, methodName: string): bigint {
145
+ const gtype = getClassType(klass);
146
+
147
+ if (gtype === TYPE_INVALID) {
148
+ throw new Error(
149
+ `callParent: cannot call '${methodName}' because ${klass.name} was never passed to registerClass`,
150
+ );
151
+ }
152
+
153
+ return typeParent(gtype);
154
+ }
155
+
156
+ function buildParentInvoker(klass: AnyClass, methodName: string): Invoker {
157
+ const parentType = resolveParentType(klass, methodName);
158
+ const slot = resolveParentSlot(klass, parentType, methodName);
159
+
160
+ if (!slot) {
161
+ throw new Error(`callParent: ${klass.name} inherits no '${methodName}' vtable slot`);
162
+ }
163
+
164
+ return buildInvoker(slot, parentType, "callParent");
165
+ }
166
+
167
+ /**
168
+ * Calls the implementation of a virtual function that the parent type of `klass` provides, so an
169
+ * override can chain up to the behavior it replaces. `klass` is the class whose override is
170
+ * running, named lexically rather than taken from `instance`, so each level of a hierarchy reaches
171
+ * exactly one level up. Arguments and the return value follow the same convention the override
172
+ * itself uses: pure out parameters are left out and returned instead, and a slot with several
173
+ * outputs returns them as a tuple. Throws if `klass` was never registered, inherits no such slot,
174
+ * or the parent type leaves the slot empty.
175
+ *
176
+ * @param klass The registered class whose override is chaining up.
177
+ * @param methodName Name of the overridden method, such as `vfuncMeasure`.
178
+ * @param instance The instance the override was invoked on.
179
+ * @param inputs The arguments the override received.
180
+ */
181
+ function callParent(klass: AnyClass, methodName: string, instance: object, ...inputs: unknown[]): unknown {
182
+ const invoker = cachedInvoker(parentInvokers, klass, methodName, () => buildParentInvoker(klass, methodName));
183
+
184
+ return invoker(instance, inputs);
185
+ }
186
+
187
+ function resolveOwnerSlot(owner: AnyClass, key: string): ResolvedSlot {
188
+ const classDescriptor = getVfuncRegistry(owner)?.[key];
189
+
190
+ if (classDescriptor !== undefined) {
191
+ return { descriptor: classDescriptor };
192
+ }
193
+
194
+ const interfaceType = getClassType(owner);
195
+ const descriptor = getInterfaceVfuncRegistry(interfaceType)?.[key];
196
+
197
+ if (descriptor === undefined) {
198
+ throw new Error(`callVfunc: ${owner.name} declares no '${key}' vtable slot`);
199
+ }
200
+
201
+ return { descriptor, interfaceType };
202
+ }
203
+
204
+ function requiresDefaultVtable(slot: ResolvedSlot, instanceType: bigint): boolean {
205
+ return slot.interfaceType !== undefined && !typeIsA(instanceType, slot.interfaceType);
206
+ }
207
+
208
+ function resolveInstanceType(key: string, instance: object, slot: ResolvedSlot): bigint | undefined {
209
+ const instanceType = resolveWrapperType(instance);
210
+
211
+ if (instanceType === TYPE_INVALID) {
212
+ const name = instanceClassName(instance);
213
+ throw new Error(`callVfunc: cannot call '${key}' because ${name} descends from no registered wrapper class`);
214
+ }
215
+
216
+ return requiresDefaultVtable(slot, instanceType) ? undefined : instanceType;
217
+ }
218
+
219
+ /**
220
+ * Calls the implementation of a virtual function a wrapper class or interface declares, backing the
221
+ * `vfunc`-prefixed members the generated bindings emit. The slot is read from the nearest generated
222
+ * wrapper class in the instance's class chain rather than from the instance's own type, so an
223
+ * override reaching it through `super` runs the implementation it replaced instead of re-entering
224
+ * itself. When that wrapper class does not carry the interface the slot belongs to, because a class
225
+ * registered below it adopted the interface, the slot comes from the interface's own default vtable
226
+ * instead, which holds exactly what the adopting class replaced. Arguments and the return value
227
+ * follow the same convention an override uses: pure out parameters are left out and returned
228
+ * instead, and a slot with several outputs returns them as a tuple. Throws if the owner declares no
229
+ * such slot, the instance descends from no wrapper class, or the resolved vtable leaves the slot
230
+ * empty.
231
+ *
232
+ * @param owner The wrapper class or interface declaring the slot.
233
+ * @param key Name of the generated member, such as `vfuncMeasure`.
234
+ * @param instance The instance to invoke the slot on.
235
+ * @param inputs The arguments the slot receives.
236
+ */
237
+ function callVfunc(owner: AnyClass, key: string, instance: object, inputs: unknown[]): unknown {
238
+ const slot = resolveOwnerSlot(owner, key);
239
+ const instanceType = resolveInstanceType(key, instance, slot);
240
+
241
+ const invoker = cachedInvoker(vfuncInvokers, owner, `${key}:${String(instanceType)}`, () =>
242
+ buildInvoker(slot, instanceType, "callVfunc"));
243
+
244
+ return invoker(instance, inputs);
245
+ }
246
+
247
+ export { callParent, callVfunc };
@@ -0,0 +1,36 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+
3
+ type RefSeeds = Map<number, unknown>;
4
+
5
+ type SeedFrame = {
6
+ argDescriptors: Descriptor[];
7
+ instance: unknown;
8
+ seeds: RefSeeds;
9
+ };
10
+
11
+ const frames: SeedFrame[] = [];
12
+
13
+ const pushSeedFrame = (frame: SeedFrame): void => {
14
+ frames.push(frame);
15
+ };
16
+
17
+ const popSeedFrame = (): void => {
18
+ frames.pop();
19
+ };
20
+
21
+ const isMatchingFrame = (frame: SeedFrame, argDescriptors: Descriptor[], instance: unknown): boolean =>
22
+ frame.argDescriptors === argDescriptors && frame.instance === instance;
23
+
24
+ const seedsFor = (argDescriptors: Descriptor[], instance: unknown): RefSeeds | undefined => {
25
+ for (let index = frames.length - 1; index >= 0; index--) {
26
+ const frame = frames[index];
27
+
28
+ if (frame !== undefined && isMatchingFrame(frame, argDescriptors, instance)) {
29
+ return frame.seeds;
30
+ }
31
+ }
32
+
33
+ return undefined;
34
+ };
35
+
36
+ export { popSeedFrame, pushSeedFrame, type RefSeeds, seedsFor };