@gtkx/runtime 1.5.0 → 2.0.0-beta.1

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 (101) hide show
  1. package/README.md +5 -5
  2. package/dist/application-class.d.ts +1 -1
  3. package/dist/application-class.d.ts.map +1 -1
  4. package/dist/application-class.js +1 -2
  5. package/dist/application-class.js.map +1 -1
  6. package/dist/arg.d.ts +4 -1
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js.map +1 -1
  9. package/dist/bind.d.ts.map +1 -1
  10. package/dist/bind.js +1 -9
  11. package/dist/bind.js.map +1 -1
  12. package/dist/callback.d.ts +6 -1
  13. package/dist/callback.d.ts.map +1 -1
  14. package/dist/callback.js +33 -21
  15. package/dist/callback.js.map +1 -1
  16. package/dist/closure.d.ts +3 -1
  17. package/dist/closure.d.ts.map +1 -1
  18. package/dist/closure.js +17 -6
  19. package/dist/closure.js.map +1 -1
  20. package/dist/descriptors.d.ts +36 -36
  21. package/dist/descriptors.d.ts.map +1 -1
  22. package/dist/descriptors.js +65 -26
  23. package/dist/descriptors.js.map +1 -1
  24. package/dist/element-metadata.d.ts +9 -0
  25. package/dist/element-metadata.d.ts.map +1 -0
  26. package/dist/element-metadata.js +14 -0
  27. package/dist/element-metadata.js.map +1 -0
  28. package/dist/fn.d.ts +8 -2
  29. package/dist/fn.d.ts.map +1 -1
  30. package/dist/fn.js +43 -13
  31. package/dist/fn.js.map +1 -1
  32. package/dist/folded-lengths.d.ts +2 -1
  33. package/dist/folded-lengths.d.ts.map +1 -1
  34. package/dist/folded-lengths.js +17 -7
  35. package/dist/folded-lengths.js.map +1 -1
  36. package/dist/index.d.ts +4 -26
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -26
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal.d.ts +5 -2
  41. package/dist/internal.d.ts.map +1 -1
  42. package/dist/internal.js +5 -2
  43. package/dist/internal.js.map +1 -1
  44. package/dist/native-value.d.ts +9 -1
  45. package/dist/native-value.d.ts.map +1 -1
  46. package/dist/native-value.js +41 -5
  47. package/dist/native-value.js.map +1 -1
  48. package/dist/properties.d.ts +1 -1
  49. package/dist/properties.d.ts.map +1 -1
  50. package/dist/properties.js +2 -11
  51. package/dist/properties.js.map +1 -1
  52. package/dist/register-class.d.ts +45 -249
  53. package/dist/register-class.d.ts.map +1 -1
  54. package/dist/register-class.js +38 -1
  55. package/dist/register-class.js.map +1 -1
  56. package/dist/registry.d.ts +26 -3
  57. package/dist/registry.d.ts.map +1 -1
  58. package/dist/registry.js +75 -20
  59. package/dist/registry.js.map +1 -1
  60. package/dist/signal.d.ts.map +1 -1
  61. package/dist/signal.js +9 -22
  62. package/dist/signal.js.map +1 -1
  63. package/dist/t.d.ts +5 -21
  64. package/dist/t.d.ts.map +1 -1
  65. package/dist/t.js +1 -5
  66. package/dist/t.js.map +1 -1
  67. package/dist/type.d.ts +10 -1
  68. package/dist/type.d.ts.map +1 -1
  69. package/dist/type.js +21 -2
  70. package/dist/type.js.map +1 -1
  71. package/dist/value.d.ts.map +1 -1
  72. package/dist/value.js +26 -4
  73. package/dist/value.js.map +1 -1
  74. package/dist/variant.d.ts +2 -6
  75. package/dist/variant.d.ts.map +1 -1
  76. package/dist/variant.js.map +1 -1
  77. package/dist/vfunc-call.d.ts.map +1 -1
  78. package/dist/vfunc-call.js +2 -11
  79. package/dist/vfunc-call.js.map +1 -1
  80. package/package.json +15 -6
  81. package/src/application-class.ts +2 -2
  82. package/src/arg.ts +4 -1
  83. package/src/bind.ts +1 -12
  84. package/src/callback.ts +53 -29
  85. package/src/closure.ts +31 -6
  86. package/src/descriptors.ts +126 -44
  87. package/src/element-metadata.ts +24 -0
  88. package/src/fn.ts +64 -15
  89. package/src/folded-lengths.ts +23 -10
  90. package/src/index.ts +5 -24
  91. package/src/internal.ts +9 -2
  92. package/src/native-value.ts +54 -5
  93. package/src/properties.ts +3 -16
  94. package/src/register-class.ts +104 -249
  95. package/src/registry.ts +110 -19
  96. package/src/signal.ts +9 -34
  97. package/src/t.ts +5 -21
  98. package/src/type.ts +34 -1
  99. package/src/value.ts +37 -5
  100. package/src/variant.ts +2 -6
  101. package/src/vfunc-call.ts +3 -16
package/src/registry.ts CHANGED
@@ -10,11 +10,12 @@ import {
10
10
  setWrapper,
11
11
  } from "@gtkx/native";
12
12
  import { type AnyClass, walkClassChain } from "@gtkx/utils";
13
- import { copyLayerMembers, type Mixin, type MixinReceiver } from "./mixin.js";
13
+ import { copyLayerMembers, installMixins, type Mixin, type MixinReceiver } from "./mixin.js";
14
14
  import {
15
15
  TYPE_INVALID,
16
16
  TYPE_OBJECT,
17
17
  type TypedClass,
18
+ typeFundamental,
18
19
  typeInterfaces,
19
20
  typeIsA,
20
21
  typeName,
@@ -31,6 +32,22 @@ import {
31
32
  type StaticBase<C, K extends PropertyKey> = Omit<C, K> &
32
33
  (C extends abstract new (...args: infer A) => infer R ? abstract new (...args: A) => R : never);
33
34
 
35
+ /**
36
+ * Static side of class `C` with its construct signature retargeted to produce `I`. A generated
37
+ * wrapper class is declared locally and exported as a registered constant; its instance type is
38
+ * exported as an interface extending the local class so declaration merging and module
39
+ * augmentation keep working, and this type makes constructing the constant produce that interface.
40
+ */
41
+ type WrapperClass<C, I> = Omit<C, "prototype"> & {
42
+ /** Prototype retyped to the exported instance interface, so `instanceof` narrows to it. */
43
+ prototype: I;
44
+ } &
45
+ (C extends new (...args: infer A) => unknown
46
+ ? new (...args: A) => I
47
+ : C extends abstract new (...args: infer A) => unknown
48
+ ? abstract new (...args: A) => I
49
+ : never);
50
+
34
51
  /** One overridable vtable slot: where it sits in the vtable struct and how it is marshalled. */
35
52
  type VfuncDescriptor = {
36
53
  /** GIR name of the type struct holding the slot, without its namespace, such as `WidgetClass`. */
@@ -374,6 +391,31 @@ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, layout?: I
374
391
  }
375
392
  }
376
393
 
394
+ /**
395
+ * Copies the members of registered interfaces onto a wrapper class, resolving each interface's
396
+ * mixin through the registry rather than taking the mixin itself, so a class states which
397
+ * interfaces it implements by referencing their classes. The interfaces must already be
398
+ * registered through `registerInterface`; generated code guarantees that by declaring interfaces
399
+ * ahead of the classes that implement them.
400
+ *
401
+ * @param cls Wrapper class adopting the interfaces.
402
+ * @param interfaces Registered interface classes to adopt, in order.
403
+ * @throws If an entry is not a registered interface.
404
+ */
405
+ function installInterfaces(cls: AnyClass, interfaces: AnyClass[]): void {
406
+ const mixins = interfaces.map((iface) => {
407
+ const mixin = getInterfaceMixin(getClassType(iface));
408
+
409
+ if (mixin === undefined) {
410
+ throw new Error(`installInterfaces: ${iface.name} is not a registered interface`);
411
+ }
412
+
413
+ return mixin;
414
+ });
415
+
416
+ installMixins(cls, mixins);
417
+ }
418
+
377
419
  /**
378
420
  * Wraps a native handle in a JS wrapper instance. With no class, resolves and
379
421
  * reuses the wrapper for the handle's runtime GType (composing interface mixins),
@@ -443,6 +485,23 @@ function getWrapperClass(type: bigint): AnyClass {
443
485
  return cls;
444
486
  }
445
487
 
488
+ function getExactWrapperClass(type: bigint, label?: string): AnyClass {
489
+ if (type === TYPE_INVALID) {
490
+ throw new Error(`No GType is registered under '${label ?? String(type)}'`);
491
+ }
492
+
493
+ const cls = classRegistry.get(type);
494
+
495
+ if (cls === undefined) {
496
+ throw new Error(
497
+ `No wrapper class is registered for '${label ?? typeName(type) ?? String(type)}': ` +
498
+ "its module was dropped from the bundle or never imported",
499
+ );
500
+ }
501
+
502
+ return cls;
503
+ }
504
+
446
505
  function resolveWrapperClass(type: bigint): AnyClass | null {
447
506
  let currentType = type;
448
507
 
@@ -491,7 +550,35 @@ function createComposedClass(base: AnyClass, runtimeType: bigint): AnyClass {
491
550
  return applied.size === 0 ? base : cls;
492
551
  }
493
552
 
494
- function resolveComposedClass(runtimeType: bigint): AnyClass | null {
553
+ function isWrappableBase(fallbackType: bigint): boolean {
554
+ return fallbackType === TYPE_INVALID || typeFundamental(fallbackType) === TYPE_OBJECT;
555
+ }
556
+
557
+ function isBetweenWalkAndRuntime(fallbackType: bigint, walkedType: bigint, runtimeType: bigint): boolean {
558
+ return fallbackType !== walkedType && typeIsA(fallbackType, walkedType) && typeIsA(runtimeType, fallbackType);
559
+ }
560
+
561
+ function chooseWrapBase(walked: AnyClass | null, fallback: AnyClass | undefined, runtimeType: bigint): AnyClass | null {
562
+ if (fallback === undefined || !isWrappableBase(getClassType(fallback))) {
563
+ return walked;
564
+ }
565
+
566
+ if (walked === null || isBetweenWalkAndRuntime(getClassType(fallback), getClassType(walked), runtimeType)) {
567
+ return fallback;
568
+ }
569
+
570
+ return walked;
571
+ }
572
+
573
+ function stampComposedClass(composed: AnyClass, runtimeType: bigint): AnyClass {
574
+ setClassType(composed, runtimeType);
575
+ wrapperClasses.add(composed);
576
+ composedClassRegistry.set(runtimeType, composed);
577
+
578
+ return composed;
579
+ }
580
+
581
+ function resolveComposedClass(runtimeType: bigint, fallbackClass?: () => AnyClass): AnyClass | null {
495
582
  const exact = classRegistry.get(runtimeType);
496
583
 
497
584
  if (exact) {
@@ -504,7 +591,8 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
504
591
  return cached;
505
592
  }
506
593
 
507
- const base = resolveWrapperClass(runtimeType);
594
+ const walked = resolveWrapperClass(runtimeType);
595
+ const base = chooseWrapBase(walked, fallbackClass?.(), runtimeType);
508
596
 
509
597
  if (base === null) {
510
598
  return null;
@@ -512,37 +600,37 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
512
600
 
513
601
  const composed = createComposedClass(base, runtimeType);
514
602
 
515
- if (composed === base) {
516
- return base;
603
+ if (composed !== base) {
604
+ return stampComposedClass(composed, runtimeType);
517
605
  }
518
606
 
519
- setClassType(composed, runtimeType);
520
- wrapperClasses.add(composed);
521
- composedClassRegistry.set(runtimeType, composed);
607
+ if (base === walked) {
608
+ return base;
609
+ }
522
610
 
523
- return composed;
611
+ return stampComposedClass(class extends base {}, runtimeType);
524
612
  }
525
613
 
526
- function wrapObject(value: unknown): object | null {
527
- return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>);
614
+ function wrapObject(value: unknown, fallbackClass?: () => AnyClass): object | null {
615
+ return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>, fallbackClass);
528
616
  }
529
617
 
530
- function wrapCallScopedObject(value: unknown): object | null {
531
- return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper);
618
+ function wrapCallScopedObject(value: unknown, fallbackClass?: () => AnyClass): object | null {
619
+ return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper, fallbackClass);
532
620
  }
533
621
 
534
622
  function existingWrapperFor(handle: ExternalObject<Handle>): object | null {
535
623
  return handleMap.has(handle) ? handle : getWrapper(handle);
536
624
  }
537
625
 
538
- function createWrapper(handle: ExternalObject<Handle>): object {
626
+ function createWrapper(handle: ExternalObject<Handle>, fallbackClass?: () => AnyClass): object {
539
627
  const runtimeType: bigint = getType(handle);
540
628
 
541
629
  if (runtimeType === TYPE_INVALID) {
542
630
  throw new Error("Cannot resolve runtime GLib type from handle");
543
631
  }
544
632
 
545
- const cls = resolveComposedClass(runtimeType);
633
+ const cls = resolveComposedClass(runtimeType, fallbackClass);
546
634
 
547
635
  if (!cls) {
548
636
  throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
@@ -551,21 +639,21 @@ function createWrapper(handle: ExternalObject<Handle>): object {
551
639
  return Object.create(cls.prototype) as object;
552
640
  }
553
641
 
554
- function wrapperFor(handle: ExternalObject<Handle>, bind: WrapperBinding): object {
642
+ function wrapperFor(handle: ExternalObject<Handle>, bind: WrapperBinding, fallbackClass?: () => AnyClass): object {
555
643
  const existing = existingWrapperFor(handle);
556
644
 
557
645
  if (existing) {
558
646
  return existing;
559
647
  }
560
648
 
561
- const instance = createWrapper(handle);
649
+ const instance = createWrapper(handle, fallbackClass);
562
650
  bind(handle, instance);
563
651
 
564
652
  return instance;
565
653
  }
566
654
 
567
- function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
568
- return wrapperFor(handle, registerWrapper);
655
+ function getOrCreateWrapper(handle: ExternalObject<Handle>, fallbackClass?: () => AnyClass): object {
656
+ return wrapperFor(handle, registerWrapper, fallbackClass);
569
657
  }
570
658
 
571
659
  function instanceClassName(instance: object): string {
@@ -639,8 +727,10 @@ export {
639
727
  registerWrapperClass,
640
728
  registerWrapperClassResolver,
641
729
  registerInterface,
730
+ installInterfaces,
642
731
  wrapFundamentalHandle,
643
732
  wrapHandle,
733
+ getExactWrapperClass,
644
734
  getWrapperClass,
645
735
  resolveWrapperClass,
646
736
  getHandle,
@@ -657,5 +747,6 @@ export {
657
747
  type InterfaceProperty,
658
748
  type StaticBase,
659
749
  type VfuncDescriptor,
750
+ type WrapperClass,
660
751
  type WrapperClassResolver,
661
752
  };
package/src/signal.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { Descriptor, ExternalObject, Handle } from "@gtkx/native";
2
- import { getOrInsert, toCamelIdentifier, upperFirst } from "@gtkx/utils";
2
+ import { toCamelIdentifier, upperFirst } from "@gtkx/utils";
3
3
  import { type Arg, isCallerAllocatedArg, isInoutArg, isOutputArg } from "./arg.js";
4
- import { bind, createBindCache } from "./bind.js";
4
+ import { bind } from "./bind.js";
5
5
  import { wrapCallback } from "./callback.js";
6
- import { toClosure } from "./closure.js";
6
+ import { newCCallbackClosure, toClosure } from "./closure.js";
7
7
  import {
8
8
  arrayT,
9
9
  biguint64T,
@@ -69,7 +69,6 @@ type EmitArg = Arg & {
69
69
  value?: unknown;
70
70
  };
71
71
 
72
- const connectCache = createBindCache();
73
72
  const connectionTable: WeakMap<object, Map<string, Set<number>>> = new WeakMap();
74
73
  const gQuarkFromString = bind(LIB, "g_quark_from_string", [stringT("borrowed")], uint32T);
75
74
  const gSignalLookup = bind(LIB, "g_signal_lookup", [stringT("borrowed"), biguint64T], uint32T);
@@ -129,21 +128,8 @@ const isSignalHandlerConnected = (instance: object, handlerId: number): boolean
129
128
  gSignalHandlerIsConnected(getHandle(instance), handlerId) as boolean;
130
129
 
131
130
  const trackConnection = (instance: object, signal: string, handlerId: number): void => {
132
- let bySignal = connectionTable.get(instance);
133
-
134
- if (!bySignal) {
135
- bySignal = new Map();
136
- connectionTable.set(instance, bySignal);
137
- }
138
-
139
- let handlerIds = bySignal.get(signal);
140
-
141
- if (!handlerIds) {
142
- handlerIds = new Set();
143
- bySignal.set(signal, handlerIds);
144
- }
145
-
146
- handlerIds.add(handlerId);
131
+ const bySignal = connectionTable.getOrInsertComputed(instance, () => new Map<string, Set<number>>());
132
+ bySignal.getOrInsertComputed(signal, () => new Set<number>()).add(handlerId);
147
133
  };
148
134
 
149
135
  const untrackConnection = (instance: object, handlerId: number): void => {
@@ -238,23 +224,11 @@ const buildSignalNames = (type: bigint): string[] => {
238
224
  return [...names];
239
225
  };
240
226
 
241
- const signalNamesFor = (type: bigint): string[] => getOrInsert(signalNameCache, type, buildSignalNames);
227
+ const signalNamesFor = (type: bigint): string[] => signalNameCache.getOrInsertComputed(type, buildSignalNames);
242
228
 
243
229
  const getSignalId = (instance: object, signal: string): number =>
244
230
  signalIdFor((instance as TypedClass).__type__, signal);
245
231
 
246
- function connectBind(type: bigint, signal: string, callback: CallbackDescriptor): (...values: unknown[]) => unknown {
247
- const key = `${String(type)}\0${getSignalBaseName(signal)}`;
248
-
249
- return connectCache(
250
- key,
251
- LIB,
252
- "g_signal_connect_data",
253
- [objectT("borrowed"), stringT("borrowed"), callback, uint32T],
254
- uint64T,
255
- );
256
- }
257
-
258
232
  /**
259
233
  * Connects a handler to a GObject signal on an instance and returns the handler id.
260
234
  * @param instance Emitter to connect to.
@@ -265,8 +239,9 @@ function connectSignal(instance: object, signal: string, spec: SignalConnectSpec
265
239
  const { callback, handler, isAfter } = spec;
266
240
  const wrapped = wrapCallback(handler, callback, "signal");
267
241
  const type: bigint = (instance as TypedClass).__type__;
268
- const connect = connectBind(type, signal, callback);
269
- const handlerId = connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
242
+ const key = `${String(type)}\0${getSignalBaseName(signal)}`;
243
+ const closure = newCCallbackClosure(key, callback, wrapped);
244
+ const handlerId = gSignalConnectClosure(getHandle(instance), signal, closure, isAfter) as number;
270
245
  trackConnection(instance, getSignalBaseName(signal), handlerId);
271
246
 
272
247
  return handlerId;
package/src/t.ts CHANGED
@@ -41,10 +41,7 @@ type T = {
41
41
  unichar: typeof helpers.unicharT;
42
42
  /** Descriptor for an opaque `gpointer` argument, taken from a typed array's memory or a numeric address. */
43
43
  buffer: typeof helpers.bufferT;
44
- /**
45
- * Builds a descriptor for a C string, whose optional length sizes the caller-allocated buffer
46
- * used when the string is passed by reference.
47
- */
44
+ /** Builds a C-string descriptor with optional caller-allocated length. */
48
45
  string: typeof helpers.stringT;
49
46
  /** Builds a descriptor for a `GObject`, wrapped in the class registered for its runtime GType. */
50
47
  object: typeof helpers.objectT;
@@ -82,28 +79,15 @@ type T = {
82
79
  cursorArray: typeof helpers.cursorArrayT;
83
80
  /** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */
84
81
  callback: typeof helpers.callbackT;
85
- /**
86
- * Binds a native function, wiring up argument directions, `GError` checking, and packing output
87
- * arguments into the result.
88
- */
82
+ /** Binds a native function with argument directions, errors, and packed outputs. */
89
83
  fn: typeof fn;
90
- /**
91
- * Binds a struct field at a fixed offset, compiling its descriptor once into an accessor that
92
- * reads and writes it.
93
- */
84
+ /** Binds a struct field at a fixed offset. */
94
85
  field: typeof field;
95
- /**
96
- * Binds a struct field whose offset is supplied per access, for walking records stored at a
97
- * stride in a buffer.
98
- */
86
+ /** Binds a struct field whose offset is supplied per access. */
99
87
  fieldAt: typeof fieldAt;
100
88
  };
101
89
 
102
- /**
103
- * Type descriptor builder: a collection of factory helpers that describe C types for
104
- * FFI marshalling, plus `bind` to bind a native function and `fn` to describe a
105
- * function signature.
106
- */
90
+ /** FFI descriptor factories and native function binders. */
107
91
  const t: T = {
108
92
  bind,
109
93
  int8: helpers.int8T,
package/src/type.ts CHANGED
@@ -29,6 +29,14 @@ const gTypeParent = bind(LIB, "g_type_parent", [biguint64T], biguint64T);
29
29
  const gTypeFundamental = bind(LIB, "g_type_fundamental", [biguint64T], biguint64T);
30
30
  const gTypeName = bind(LIB, "g_type_name", [biguint64T], stringT("borrowed"));
31
31
  const gTypeInterfaces = bind(LIB, "g_type_interfaces", [biguint64T, refT(uint32T)], sizedArrayT(biguint64T, 1, "full"));
32
+
33
+ const gTypeInterfacePrerequisites = bind(
34
+ LIB,
35
+ "g_type_interface_prerequisites",
36
+ [biguint64T, refT(uint32T)],
37
+ sizedArrayT(biguint64T, 1, "full"),
38
+ );
39
+
32
40
  /** GType tag for an invalid or uninitialized type. */
33
41
  const TYPE_INVALID = 0n;
34
42
  /** GType tag for the absence of a value (`void`). */
@@ -137,11 +145,26 @@ function typeInterfaces(type: bigint): bigint[] {
137
145
  return gTypeInterfaces(type, nInterfacesRef) as bigint[];
138
146
  }
139
147
 
148
+ function typeInterfacePrerequisites(type: bigint): bigint[] {
149
+ const nPrerequisitesRef = { value: 0 };
150
+
151
+ return gTypeInterfacePrerequisites(type, nPrerequisitesRef) as bigint[];
152
+ }
153
+
140
154
  /** Returns the GType registered under the given name, or `TYPE_INVALID` if none exists. */
141
155
  function typeFromName(name: string): bigint {
142
156
  return gTypeFromName(name) as bigint;
143
157
  }
144
158
 
159
+ /**
160
+ * Keeps the given wrapper classes in a tree-shaken bundle: a generated bootstrap names the classes
161
+ * its namespace cannot operate without as arguments here, which is a reference a bundler retains.
162
+ *
163
+ * @param wrappers Wrapper classes to keep.
164
+ * @returns The same classes, untouched.
165
+ */
166
+ const retainWrapperClasses = (wrappers: readonly unknown[]): readonly unknown[] => wrappers;
167
+
145
168
  function typeFundamental(type: bigint): bigint {
146
169
  return gTypeFundamental(type) as bigint;
147
170
  }
@@ -221,6 +244,14 @@ function isResolvableDescriptor(descriptor: Descriptor): descriptor is Resolvabl
221
244
  return RESOLVABLE_DESCRIPTOR_KINDS.has(descriptor.kind);
222
245
  }
223
246
 
247
+ function resolveEnumOrFlagsType(descriptor: Extract<Descriptor, { kind: "enum" | "flags" }>): bigint {
248
+ if (descriptor.getTypeFnName === "") {
249
+ return descriptor.isSigned ? TYPE_INT : TYPE_UINT;
250
+ }
251
+
252
+ return resolveType(descriptor.sharedLibrary, descriptor.getTypeFnName);
253
+ }
254
+
224
255
  function resolveDescriptorType(descriptor: Descriptor): bigint {
225
256
  if (descriptor.kind === "biguint64" && "type" in descriptor) {
226
257
  return TYPE_GTYPE;
@@ -239,7 +270,7 @@ function resolveDescriptorType(descriptor: Descriptor): bigint {
239
270
  switch (descriptor.kind) {
240
271
  case "enum":
241
272
  case "flags": {
242
- return resolveType(descriptor.sharedLibrary, descriptor.getTypeFnName);
273
+ return resolveEnumOrFlagsType(descriptor);
243
274
  }
244
275
  case "boxed": {
245
276
  return resolveBoxedType(descriptor);
@@ -283,8 +314,10 @@ export {
283
314
  getStrvType,
284
315
  isResolvableDescriptor,
285
316
  isTypedClass,
317
+ retainWrapperClasses,
286
318
  typeIsA,
287
319
  typeParent,
320
+ typeInterfacePrerequisites,
288
321
  typeInterfaces,
289
322
  typeFromName,
290
323
  typeFundamental,
package/src/value.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  fundamentalT,
16
16
  int8T,
17
17
  int32T,
18
+ type ObjectDescriptor,
18
19
  objectT,
19
20
  stringT,
20
21
  uint8T,
@@ -128,6 +129,7 @@ const stringValueType = bindValueType("string", stringT("borrowed"));
128
129
  const enumValueType = bindValueType("enum", int32T);
129
130
  const flagsValueType = bindValueType("flags", uint32T);
130
131
  const objectValueType = bindValueType("object", objectT("borrowed"));
132
+ const objectValueFundamentals: Set<bigint> = new Set([TYPE_OBJECT, TYPE_INTERFACE]);
131
133
  const paramValueType = bindValueType("param", PARAM_T);
132
134
  const variantValueType = bindValueType("variant", VARIANT_T);
133
135
  const pointerValueType = bindValueType("pointer", uint64T);
@@ -420,6 +422,14 @@ const arrayValueType = (descriptor: ArrayDescriptor): ValueType => {
420
422
  throw new Error(`Unsupported array type ${descriptor.arrayKind} of ${descriptor.itemDescriptor.kind}`);
421
423
  };
422
424
 
425
+ const resolveEnumOrFlagsValueType = (descriptor: Extract<Descriptor, { kind: "enum" | "flags" }>): ValueType => {
426
+ if (descriptor.getTypeFnName === "") {
427
+ return descriptor.kind === "flags" ? flagsValueType : enumValueType;
428
+ }
429
+
430
+ return enumOrFlagsValueType(resolveType(descriptor.sharedLibrary, descriptor.getTypeFnName));
431
+ };
432
+
423
433
  const resolveValueType = (descriptor: Descriptor): ValueType => {
424
434
  if (descriptor.kind === "biguint64" && "type" in descriptor) {
425
435
  return typeValueType;
@@ -438,7 +448,7 @@ const resolveValueType = (descriptor: Descriptor): ValueType => {
438
448
  switch (descriptor.kind) {
439
449
  case "enum":
440
450
  case "flags": {
441
- return enumOrFlagsValueType(resolveType(descriptor.sharedLibrary, descriptor.getTypeFnName));
451
+ return resolveEnumOrFlagsValueType(descriptor);
442
452
  }
443
453
  case "boxed": {
444
454
  return boxedValueType(resolveBoxedType(descriptor));
@@ -718,10 +728,32 @@ function fromValue(value: ExternalObject<Handle>): unknown {
718
728
  return get(value);
719
729
  }
720
730
 
721
- const fromValueForDescriptor = (descriptor: Descriptor, value: ExternalObject<Handle>): unknown =>
722
- descriptor.kind === "array" && descriptor.arrayKind === "gbytearray"
723
- ? byteArrayValueGetterFor(descriptor.isBytes === true)(value)
724
- : fromValue(value);
731
+ const objectValueWithFallback = (
732
+ descriptor: ObjectDescriptor,
733
+ value: ExternalObject<Handle>,
734
+ ): { wrapped: unknown } | null => {
735
+ if (descriptor.fallbackClass === undefined || !objectValueFundamentals.has(typeFundamental(getValueType(value)))) {
736
+ return null;
737
+ }
738
+
739
+ return { wrapped: wrapObject(objectValueType.get(value), descriptor.fallbackClass) };
740
+ };
741
+
742
+ const fromValueForDescriptor = (descriptor: Descriptor, value: ExternalObject<Handle>): unknown => {
743
+ if (descriptor.kind === "array" && descriptor.arrayKind === "gbytearray") {
744
+ return byteArrayValueGetterFor(descriptor.isBytes === true)(value);
745
+ }
746
+
747
+ if (descriptor.kind === "object") {
748
+ const withFallback = objectValueWithFallback(descriptor, value);
749
+
750
+ if (withFallback !== null) {
751
+ return withFallback.wrapped;
752
+ }
753
+ }
754
+
755
+ return fromValue(value);
756
+ };
725
757
 
726
758
  const inferredValueGuard: ValueGuard = (jsValue) => inferValueGType(jsValue, wrapperGType(jsValue)) !== TYPE_INVALID;
727
759
 
package/src/variant.ts CHANGED
@@ -34,10 +34,7 @@ type BasicValueMap<Nested> = {
34
34
 
35
35
  /** Single-character code of one of the GVariant basic types in {@link BasicValueMap}. */
36
36
  type BasicCode = keyof BasicValueMap<unknown>;
37
- /**
38
- * JavaScript type a byte array (`ay`) unpacks to: the same one the generated bindings use for byte sequences, so a
39
- * `Uint8Array` under the `v2ByteArrays` future flag and a `number[]` otherwise.
40
- */
37
+ /** JavaScript type a byte array (`ay`) unpacks to. */
41
38
  type ByteArray = ReturnType<GLib.Bytes["unrefToArray"]>;
42
39
  /** Values a byte array (`ay`) packs from. */
43
40
  type ByteArrayInput = Uint8Array | number[];
@@ -503,8 +500,7 @@ const toVariant = <S extends string>(typeString: S, value: VariantInput<S>): GLi
503
500
  /**
504
501
  * Unpacks a `GLib.Variant` into the JavaScript value its GVariant type describes, the inverse of
505
502
  * {@link toVariant}. A dictionary keyed by strings unpacks to a record and one keyed by anything
506
- * else to a `Map`, an array to an array, a byte array (`ay`) to the byte array type the generated
507
- * bindings use (`Uint8Array` under the `v2ByteArrays` future flag, `number[]` otherwise), a tuple
503
+ * else to a `Map`, an array to an array, a byte array (`ay`) to a `Uint8Array`, a tuple
508
504
  * to an array of its members, a maybe to its value or `null`, and a nested variant to the
509
505
  * `GLib.Variant` itself unless `recursive` is set, in which case it is unwrapped all the way down.
510
506
  *
package/src/vfunc-call.ts CHANGED
@@ -1,6 +1,5 @@
1
- import type { BindVfuncOptions } from "@gtkx/native";
2
1
  import type { AnyClass } from "@gtkx/utils";
3
- import { bindVfunc } from "@gtkx/native";
2
+ import { bindVfunc, type BindVfuncOptions } from "@gtkx/native";
4
3
  import { type Arg, isCallerAllocatedArg, requiresInputArg } from "./arg.js";
5
4
  import { buildNativeArgTypes, fromNativeCallable } from "./fn.js";
6
5
  import { toNative } from "./native-value.js";
@@ -106,21 +105,9 @@ function buildInvoker(slot: ResolvedSlot, instanceType: bigint | undefined, call
106
105
  }
107
106
 
108
107
  function cachedInvoker(cache: InvokerCache, owner: AnyClass, key: string, build: () => Invoker): Invoker {
109
- let byKey = cache.get(owner);
108
+ const byKey = cache.getOrInsertComputed(owner, () => new Map<string, Invoker>());
110
109
 
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;
110
+ return byKey.getOrInsertComputed(key, build);
124
111
  }
125
112
 
126
113
  function resolveParentSlot(klass: AnyClass, parentType: bigint, methodName: string): ResolvedSlot | undefined {