@gtkx/runtime 1.6.0 → 2.0.0-beta.2

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 (120) 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 +22 -51
  21. package/dist/descriptors.d.ts.map +1 -1
  22. package/dist/descriptors.js +53 -27
  23. package/dist/descriptors.js.map +1 -1
  24. package/dist/fn.d.ts.map +1 -1
  25. package/dist/fn.js +23 -9
  26. package/dist/fn.js.map +1 -1
  27. package/dist/folded-lengths.d.ts +2 -1
  28. package/dist/folded-lengths.d.ts.map +1 -1
  29. package/dist/folded-lengths.js +17 -7
  30. package/dist/folded-lengths.js.map +1 -1
  31. package/dist/index.d.ts +2 -24
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +2 -24
  34. package/dist/index.js.map +1 -1
  35. package/dist/internal.d.ts +18 -2
  36. package/dist/internal.d.ts.map +1 -1
  37. package/dist/internal.js +14 -2
  38. package/dist/internal.js.map +1 -1
  39. package/dist/listeners.d.ts +3 -13
  40. package/dist/listeners.d.ts.map +1 -1
  41. package/dist/listeners.js +12 -12
  42. package/dist/listeners.js.map +1 -1
  43. package/dist/mixin.d.ts +5 -3
  44. package/dist/mixin.d.ts.map +1 -1
  45. package/dist/mixin.js +36 -9
  46. package/dist/mixin.js.map +1 -1
  47. package/dist/native-value.d.ts +9 -1
  48. package/dist/native-value.d.ts.map +1 -1
  49. package/dist/native-value.js +20 -3
  50. package/dist/native-value.js.map +1 -1
  51. package/dist/object.d.ts +11 -0
  52. package/dist/object.d.ts.map +1 -1
  53. package/dist/object.js +18 -25
  54. package/dist/object.js.map +1 -1
  55. package/dist/properties.d.ts +5 -3
  56. package/dist/properties.d.ts.map +1 -1
  57. package/dist/properties.js +67 -23
  58. package/dist/properties.js.map +1 -1
  59. package/dist/property-brand.d.ts +5 -0
  60. package/dist/property-brand.d.ts.map +1 -0
  61. package/dist/property-brand.js +5 -0
  62. package/dist/property-brand.js.map +1 -0
  63. package/dist/register-class.d.ts +142 -264
  64. package/dist/register-class.d.ts.map +1 -1
  65. package/dist/register-class.js +19 -37
  66. package/dist/register-class.js.map +1 -1
  67. package/dist/registry.d.ts +1 -1
  68. package/dist/registry.d.ts.map +1 -1
  69. package/dist/registry.js +4 -4
  70. package/dist/registry.js.map +1 -1
  71. package/dist/signal-brand.d.ts +12 -0
  72. package/dist/signal-brand.d.ts.map +1 -0
  73. package/dist/signal-brand.js +6 -0
  74. package/dist/signal-brand.js.map +1 -0
  75. package/dist/signal.d.ts +39 -2
  76. package/dist/signal.d.ts.map +1 -1
  77. package/dist/signal.js +84 -27
  78. package/dist/signal.js.map +1 -1
  79. package/dist/t.d.ts +5 -21
  80. package/dist/t.d.ts.map +1 -1
  81. package/dist/t.js +1 -5
  82. package/dist/t.js.map +1 -1
  83. package/dist/type.d.ts.map +1 -1
  84. package/dist/type.js +8 -2
  85. package/dist/type.js.map +1 -1
  86. package/dist/value.d.ts.map +1 -1
  87. package/dist/value.js +7 -1
  88. package/dist/value.js.map +1 -1
  89. package/dist/variant.d.ts +2 -6
  90. package/dist/variant.d.ts.map +1 -1
  91. package/dist/variant.js.map +1 -1
  92. package/dist/vfunc-call.d.ts.map +1 -1
  93. package/dist/vfunc-call.js +2 -11
  94. package/dist/vfunc-call.js.map +1 -1
  95. package/package.json +9 -5
  96. package/src/application-class.ts +2 -2
  97. package/src/arg.ts +4 -1
  98. package/src/bind.ts +1 -12
  99. package/src/callback.ts +53 -29
  100. package/src/closure.ts +31 -6
  101. package/src/descriptors.ts +95 -59
  102. package/src/fn.ts +31 -10
  103. package/src/folded-lengths.ts +23 -10
  104. package/src/index.ts +2 -24
  105. package/src/internal.ts +43 -2
  106. package/src/listeners.ts +21 -26
  107. package/src/mixin.ts +55 -9
  108. package/src/native-value.ts +24 -3
  109. package/src/object.ts +55 -5
  110. package/src/properties.ts +90 -26
  111. package/src/property-brand.ts +5 -0
  112. package/src/register-class.ts +338 -341
  113. package/src/registry.ts +4 -4
  114. package/src/signal-brand.ts +29 -0
  115. package/src/signal.ts +185 -39
  116. package/src/t.ts +5 -21
  117. package/src/type.ts +10 -2
  118. package/src/value.ts +9 -1
  119. package/src/variant.ts +2 -6
  120. package/src/vfunc-call.ts +3 -16
package/src/mixin.ts CHANGED
@@ -17,6 +17,12 @@ type MixinReceiver = {
17
17
  */
18
18
  type Mixin = (base: AnyClass<MixinReceiver>) => AnyClass;
19
19
 
20
+ const syntheticSignalMembers: WeakMap<object, ReadonlySet<string>> = new WeakMap();
21
+
22
+ function markSyntheticSignalMembers(klass: AnyClass, names: readonly string[]): void {
23
+ syntheticSignalMembers.set(klass.prototype, new Set(names));
24
+ }
25
+
20
26
  function isDefinedInClassChain(prototype: object, key: string): boolean {
21
27
  let current: object | null = prototype;
22
28
 
@@ -31,12 +37,28 @@ function isDefinedInClassChain(prototype: object, key: string): boolean {
31
37
  return false;
32
38
  }
33
39
 
34
- function copyLayerMember(target: AnyClass, layer: object, key: string): void {
40
+ function isNaturalInClassChain(prototype: object, key: string): boolean {
41
+ let current: object | null = prototype;
42
+
43
+ while (current !== null && current !== Object.prototype) {
44
+ if (Object.hasOwn(current, key)) {
45
+ return syntheticSignalMembers.get(current)?.has(key) !== true;
46
+ }
47
+
48
+ current = Reflect.getPrototypeOf(current);
49
+ }
50
+
51
+ return false;
52
+ }
53
+
54
+ function copyLayerMember(target: AnyClass, layer: object, key: string, inheritedOverrides: Set<string>): void {
35
55
  if (key === "constructor") {
36
56
  return;
37
57
  }
38
58
 
39
- if (isDefinedInClassChain(target.prototype, key)) {
59
+ const isProtectedInherited = !inheritedOverrides.has(key) && isDefinedInClassChain(target.prototype, key);
60
+
61
+ if (isProtectedInherited || Object.hasOwn(target.prototype, key)) {
40
62
  return;
41
63
  }
42
64
 
@@ -47,9 +69,9 @@ function copyLayerMember(target: AnyClass, layer: object, key: string): void {
47
69
  }
48
70
  }
49
71
 
50
- function copyLayerMembers(target: AnyClass, layer: object): void {
72
+ function copyLayerMembers(target: AnyClass, layer: object, inheritedOverrides: Set<string> = new Set()): void {
51
73
  for (const key of Object.getOwnPropertyNames(layer)) {
52
- copyLayerMember(target, layer, key);
74
+ copyLayerMember(target, layer, key, inheritedOverrides);
53
75
  }
54
76
  }
55
77
 
@@ -60,7 +82,8 @@ function copyLayerMembers(target: AnyClass, layer: object): void {
60
82
  * @param target The class whose prototype receives the mixin members.
61
83
  * @param mixins The mixins to apply, in order.
62
84
  */
63
- function installMixins(target: AnyClass, mixins: Mixin[]): void {
85
+ function installMixins(target: AnyClass, mixins: Mixin[], inheritedOverrideNames: Iterable<string> = []): void {
86
+ const inheritedOverrides = new Set(inheritedOverrideNames);
64
87
  const empty: AnyClass<MixinReceiver> = class {
65
88
  connect(): number {
66
89
  return 0;
@@ -72,7 +95,7 @@ function installMixins(target: AnyClass, mixins: Mixin[]): void {
72
95
  };
73
96
 
74
97
  for (const mixin of mixins) {
75
- copyLayerMembers(target, mixin(empty).prototype);
98
+ copyLayerMembers(target, mixin(empty).prototype, inheritedOverrides);
76
99
  }
77
100
  }
78
101
 
@@ -82,11 +105,34 @@ function dropLayerMembers(layer: AnyClass, names: Set<string>): void {
82
105
  }
83
106
  }
84
107
 
108
+ function createMixinLayer(parent: AnyClass<MixinReceiver>, mixin: Mixin, inheritedNames: Set<string>): AnyClass {
109
+ const layer = mixin(parent);
110
+ const protectedNames = new Set(inheritedNames);
111
+
112
+ for (const key of Object.getOwnPropertyNames(layer.prototype)) {
113
+ if (key !== "constructor" && isNaturalInClassChain(parent.prototype, key)) {
114
+ protectedNames.add(key);
115
+ }
116
+ }
117
+
118
+ dropLayerMembers(layer, protectedNames);
119
+
120
+ return layer;
121
+ }
122
+
85
123
  function insertMixinLayer(target: AnyClass, mixin: Mixin, inheritedNames: Set<string>): void {
86
- const layer = mixin(getParentClass(target) as AnyClass<MixinReceiver>);
87
- dropLayerMembers(layer, inheritedNames);
124
+ const parent = getParentClass(target) as AnyClass<MixinReceiver>;
125
+ const layer = createMixinLayer(parent, mixin, inheritedNames);
88
126
  Object.setPrototypeOf(target.prototype, layer.prototype);
89
127
  Object.setPrototypeOf(target, layer);
90
128
  }
91
129
 
92
- export { copyLayerMembers, insertMixinLayer, installMixins, type MixinReceiver, type Mixin };
130
+ export {
131
+ copyLayerMembers,
132
+ createMixinLayer,
133
+ insertMixinLayer,
134
+ installMixins,
135
+ markSyntheticSignalMembers,
136
+ type MixinReceiver,
137
+ type Mixin,
138
+ };
@@ -255,12 +255,33 @@ function collectionToNative(descriptor: ArrayDescriptor, value: unknown): unknow
255
255
  return value == null ? null : mapCollection(descriptor, value, toNative);
256
256
  }
257
257
 
258
- function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unknown {
258
+ /**
259
+ * Normalises the JavaScript form of a hash table argument into its entry list. A `Map` is the
260
+ * expected form and any other iterable of pairs is accepted; anything else is rejected, a plain
261
+ * object above all, which `Array.from` would quietly flatten into an empty table.
262
+ *
263
+ * @param value The JavaScript value passed for a hash table argument.
264
+ */
265
+ function toHashTableEntries(value: unknown): [unknown, unknown][] | null {
259
266
  if (value == null) {
260
267
  return null;
261
268
  }
262
269
 
263
- return [...(value as Map<unknown, unknown>)].map(([key, val]): [unknown, unknown] => [
270
+ if (typeof (value as Partial<Iterable<unknown>>)[Symbol.iterator] !== "function") {
271
+ throw new TypeError("A hash table argument must be a Map or an iterable of [key, value] pairs");
272
+ }
273
+
274
+ return [...(value as Iterable<[unknown, unknown]>)];
275
+ }
276
+
277
+ function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unknown {
278
+ const entries = toHashTableEntries(value);
279
+
280
+ if (entries === null) {
281
+ return null;
282
+ }
283
+
284
+ return entries.map(([key, val]): [unknown, unknown] => [
264
285
  toNative(descriptor.keyDescriptor, key),
265
286
  toNative(descriptor.valueDescriptor, val),
266
287
  ]);
@@ -302,4 +323,4 @@ function toNative(descriptor: Descriptor, value: unknown): unknown {
302
323
  }
303
324
  }
304
325
 
305
- export { fromNative, toNative };
326
+ export { fromNative, toHashTableEntries, toNative };
package/src/object.ts CHANGED
@@ -3,9 +3,16 @@ import { type AnyClass, getParentClass } from "@gtkx/utils";
3
3
  import { bind } from "./bind.js";
4
4
  import { objectT, stringT, voidT } from "./descriptors.js";
5
5
  import { LIB, VALUE_T } from "./library.js";
6
- import { coercePropertyValue, type ConstructProperty, constructPropertyFor } from "./properties.js";
6
+ import {
7
+ coercePropertyValue,
8
+ type ConstructProperty,
9
+ constructPropertyFor,
10
+ readableObjectPropertyFor,
11
+ writableObjectPropertyFor,
12
+ } from "./properties.js";
13
+ import { propertyMapOverride, writablePropertyMapOverride } from "./property-brand.js";
7
14
  import { getHandle, registerWrapper } from "./registry.js";
8
- import { fromValueForDescriptor, newValueForDescriptor, toValue } from "./value.js";
15
+ import { fromValue, fromValueForDescriptor, newValueForDescriptor, toValue } from "./value.js";
9
16
 
10
17
  /**
11
18
  * One construct property a wrapper class accepts: the canonical `GObject` name it is set under,
@@ -172,7 +179,28 @@ function newObjectWithProperties<T extends object>(gtype: bigint, props: object,
172
179
  * @param propertyName The property name.
173
180
  * @param descriptor Describes the property's type.
174
181
  */
175
- function getObjectProperty(obj: object, propertyName: string, descriptor: Descriptor): unknown {
182
+ function getObjectProperty<
183
+ TObject extends { __properties__: object },
184
+ TPropertyMap extends object = TObject extends { [propertyMapOverride]?: infer TResolver }
185
+ ? TResolver extends () => infer TMap
186
+ ? Extract<NonNullable<TMap>, object>
187
+ : TObject["__properties__"]
188
+ : TObject["__properties__"],
189
+ TName extends Extract<keyof NoInfer<TPropertyMap>, string> = Extract<keyof NoInfer<TPropertyMap>, string>,
190
+ >(obj: TObject, propertyName: TName): NoInfer<TPropertyMap>[TName];
191
+ function getObjectProperty(obj: object, propertyName: string, descriptor: Descriptor): unknown;
192
+ function getObjectProperty(obj: object, propertyName: string, descriptor?: Descriptor): unknown {
193
+ if (arguments.length === 2) {
194
+ const property = readableObjectPropertyFor(obj, propertyName);
195
+ gObjectGetProperty(getHandle(obj), property.name, property.value);
196
+
197
+ return fromValue(property.value);
198
+ }
199
+
200
+ if (descriptor === undefined) {
201
+ throw new TypeError("getObjectProperty requires a property descriptor when called with three arguments");
202
+ }
203
+
176
204
  const value = newValueForDescriptor(descriptor);
177
205
  gObjectGetProperty(getHandle(obj), propertyName, value);
178
206
 
@@ -192,8 +220,30 @@ function getObjectProperty(obj: object, propertyName: string, descriptor: Descri
192
220
  * @param descriptor Describes the property's type.
193
221
  * @param jsValue The value to set.
194
222
  */
195
- function setObjectProperty(obj: object, propertyName: string, descriptor: Descriptor, jsValue: unknown): void {
196
- gObjectSetProperty(getHandle(obj), propertyName, toValue(descriptor, jsValue));
223
+ function setObjectProperty<
224
+ TObject extends { __writableProperties__: object },
225
+ TPropertyMap extends object = TObject extends { [writablePropertyMapOverride]?: infer TResolver }
226
+ ? TResolver extends () => infer TMap
227
+ ? Extract<NonNullable<TMap>, object>
228
+ : TObject["__writableProperties__"]
229
+ : TObject["__writableProperties__"],
230
+ TName extends Extract<keyof NoInfer<TPropertyMap>, string> = Extract<keyof NoInfer<TPropertyMap>, string>,
231
+ >(obj: TObject, propertyName: TName, jsValue: NoInfer<TPropertyMap>[TName]): void;
232
+ function setObjectProperty(obj: object, propertyName: string, descriptor: Descriptor, jsValue: unknown): void;
233
+ function setObjectProperty(
234
+ obj: object,
235
+ propertyName: string,
236
+ descriptorOrValue: unknown,
237
+ jsValue?: unknown,
238
+ ): void {
239
+ if (arguments.length === 3) {
240
+ const property = writableObjectPropertyFor(obj, propertyName, descriptorOrValue);
241
+ gObjectSetProperty(getHandle(obj), property.name, property.value);
242
+
243
+ return;
244
+ }
245
+
246
+ gObjectSetProperty(getHandle(obj), propertyName, toValue(descriptorOrValue as Descriptor, jsValue));
197
247
  }
198
248
 
199
249
  export {
package/src/properties.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
2
- import type { AnyClass } from "@gtkx/utils";
3
- import { camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
2
+ import { type AnyClass, camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
4
3
  import { bind } from "./bind.js";
5
4
  import { biguint64T, fundamentalT, refT, sizedArrayT, stringT, structT, uint32T, voidT } from "./descriptors.js";
6
5
  import { LIB, PARAM_T, VALUE_T } from "./library.js";
@@ -56,7 +55,7 @@ type AccessorBase = PropertyCheck & {
56
55
  type DeclaredAccessor = AccessorBase & {
57
56
  isInterfaceProperty?: false;
58
57
  memberName: string;
59
- hasGeneratedMember: boolean;
58
+ hasMemberAccessor: boolean;
60
59
  };
61
60
 
62
61
  type InterfaceAccessor = AccessorBase & { isInterfaceProperty: true };
@@ -101,7 +100,7 @@ const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM
101
100
  const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
102
101
  const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
103
102
  const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
104
- const coercionChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
103
+ const propertyChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
105
104
  const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
106
105
 
107
106
  const classListProperties = bind(
@@ -215,8 +214,11 @@ const findSourceSpec = (source: bigint | AnyClass, name: string): ExternalObject
215
214
  * @param source The GType, wrapper class or interface declaring the property.
216
215
  * @returns Handle of the override spec.
217
216
  */
218
- const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> =>
219
- paramSpecOverrideNew(name, findSourceSpec(source, name)) as ExternalObject<Handle>;
217
+ const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> => {
218
+ const canonicalName = canonicalCase(name);
219
+
220
+ return paramSpecOverrideNew(canonicalName, findSourceSpec(source, canonicalName)) as ExternalObject<Handle>;
221
+ };
220
222
 
221
223
  function describeValue(value: unknown): string {
222
224
  if (typeof value === "string") {
@@ -284,6 +286,24 @@ function assertWritable(instance: object, check: PropertyCheck, value: unknown):
284
286
  throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
285
287
  }
286
288
 
289
+ function assertReadable(instance: object, check: PropertyCheck): void {
290
+ if (isParamReadable(check.flags)) {
291
+ return;
292
+ }
293
+
294
+ throw new TypeError(
295
+ propertyMessage(instance, check, `cannot read property '${check.propertyName}'; the property is write-only`),
296
+ );
297
+ }
298
+
299
+ function assertMutable(instance: object, check: PropertyCheck, value: unknown): void {
300
+ assertWritable(instance, check, value);
301
+
302
+ if (isParamConstructOnly(check.flags)) {
303
+ throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, CONSTRUCT_ONLY_REASON)));
304
+ }
305
+ }
306
+
287
307
  function writeHeld(check: PropertyCheck, gValue: ExternalObject<Handle>, value: unknown): void {
288
308
  check.write ??= valueWriterFor(check.valueType);
289
309
  check.write(gValue, check.narrowValue(value));
@@ -334,7 +354,7 @@ function isReadableProperty(gtype: bigint, propertyName: string): boolean {
334
354
  }
335
355
  }
336
356
 
337
- function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null {
357
+ function lookupPropertyCheck(gtype: bigint, name: string): PropertyCheck | null {
338
358
  const klass = typeClassRef(gtype) as ExternalObject<Handle>;
339
359
 
340
360
  try {
@@ -346,24 +366,37 @@ function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null
346
366
  }
347
367
  }
348
368
 
349
- function coercionCheckFor(gtype: bigint, name: string): PropertyCheck | null {
350
- let checks = coercionChecks.get(gtype);
369
+ function propertyCheckFor(gtype: bigint, name: string): PropertyCheck | null {
370
+ const checks = propertyChecks.getOrInsertComputed(gtype, () => new Map<string, PropertyCheck | null>());
351
371
 
352
- if (checks === undefined) {
353
- checks = new Map();
354
- coercionChecks.set(gtype, checks);
355
- }
372
+ return checks.getOrInsertComputed(name, () => lookupPropertyCheck(gtype, name));
373
+ }
356
374
 
357
- let check = checks.get(name);
375
+ function objectPropertyCheckFor(instance: object, name: string): PropertyCheck {
376
+ const gtype = getInstanceType(instance);
377
+ const check = typeIsA(gtype, TYPE_OBJECT) ? propertyCheckFor(gtype, name) : null;
358
378
 
359
- if (check === undefined) {
360
- check = lookupCoercionCheck(gtype, name);
361
- checks.set(name, check);
379
+ if (check === null) {
380
+ throw new TypeError(`${instanceClassName(instance)} has no GObject property named '${name}'`);
362
381
  }
363
382
 
364
383
  return check;
365
384
  }
366
385
 
386
+ function readableObjectPropertyFor(instance: object, name: string): ConstructProperty {
387
+ const check = objectPropertyCheckFor(instance, name);
388
+ assertReadable(instance, check);
389
+
390
+ return { name: check.propertyName, value: newValueForType(check.valueType) };
391
+ }
392
+
393
+ function writableObjectPropertyFor(instance: object, name: string, value: unknown): ConstructProperty {
394
+ const check = objectPropertyCheckFor(instance, name);
395
+ assertMutable(instance, check, value);
396
+
397
+ return { name: check.propertyName, value: checkedValueFor(instance, check, value) };
398
+ }
399
+
367
400
  function truncateToWhole(check: PropertyCheck, value: number): number {
368
401
  const range = WHOLE_NUMBER_RANGES.get(typeFundamental(check.valueType));
369
402
 
@@ -397,7 +430,7 @@ function coercePropertyValue(gtype: bigint, propertyName: string, value: unknown
397
430
  return value;
398
431
  }
399
432
 
400
- const check = coercionCheckFor(gtype, propertyName);
433
+ const check = propertyCheckFor(gtype, propertyName);
401
434
 
402
435
  return check === null ? value : coerceNumber(check, value);
403
436
  }
@@ -512,12 +545,35 @@ function checkedSetter(accessor: DeclaredAccessor): (this: object, value: unknow
512
545
  };
513
546
  }
514
547
 
515
- function hasOwnMember(prototype: object, alias: string): boolean {
516
- return Object.getOwnPropertyDescriptor(prototype, alias) !== undefined;
548
+ function memberDescriptorFor(prototype: object, name: string): PropertyDescriptor | undefined {
549
+ let current: object | null = prototype;
550
+
551
+ while (current !== null) {
552
+ const descriptor = Object.getOwnPropertyDescriptor(current, name);
553
+
554
+ if (descriptor !== undefined) {
555
+ return descriptor;
556
+ }
557
+
558
+ current = Reflect.getPrototypeOf(current);
559
+ }
560
+
561
+ return undefined;
562
+ }
563
+
564
+ function isMemberAccessor(descriptor: PropertyDescriptor | undefined): boolean {
565
+ return descriptor !== undefined && ("get" in descriptor || "set" in descriptor);
517
566
  }
518
567
 
519
568
  function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias: string): void {
520
- if (hasOwnMember(prototype, alias)) {
569
+ if (Object.getOwnPropertyDescriptor(prototype, alias) !== undefined) {
570
+ return;
571
+ }
572
+
573
+ const parent = Reflect.getPrototypeOf(prototype);
574
+ const inherited = parent === null ? undefined : memberDescriptorFor(parent, alias);
575
+
576
+ if (inherited !== undefined && !isMemberAccessor(inherited)) {
521
577
  return;
522
578
  }
523
579
 
@@ -526,13 +582,13 @@ function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias
526
582
 
527
583
  function installAccessors(klass: AnyClass, accessor: DeclaredAccessor): void {
528
584
  const prototype = (klass as { prototype: object }).prototype;
529
- accessor.hasGeneratedMember = !hasOwnMember(prototype, accessor.memberName);
585
+ accessor.hasMemberAccessor = isMemberAccessor(Object.getOwnPropertyDescriptor(prototype, accessor.memberName));
530
586
 
531
587
  const descriptor: PropertyDescriptor = {
532
588
  configurable: true,
533
589
  enumerable: true,
534
- get: accessor.hasGeneratedMember ? storedGetter(accessor) : memberGetter(accessor),
535
- set: accessor.hasGeneratedMember ? checkedSetter(accessor) : memberSetter(accessor),
590
+ get: accessor.hasMemberAccessor ? memberGetter(accessor) : storedGetter(accessor),
591
+ set: accessor.hasMemberAccessor ? memberSetter(accessor) : checkedSetter(accessor),
536
592
  };
537
593
 
538
594
  for (const alias of accessorNames(accessor.name)) {
@@ -599,7 +655,13 @@ function callMember(instance: object, member: string, args: unknown[]): unknown
599
655
  }
600
656
 
601
657
  function backingMemberFor(instance: object, accessor: DeclaredAccessor): string | undefined {
602
- if (accessor.hasGeneratedMember && !Object.hasOwn(instance, accessor.memberName)) {
658
+ if (accessor.hasMemberAccessor) {
659
+ return accessor.memberName;
660
+ }
661
+
662
+ const descriptor = Object.getOwnPropertyDescriptor(instance, accessor.memberName);
663
+
664
+ if (descriptor === undefined || ("value" in descriptor && typeof descriptor.value === "function")) {
603
665
  return undefined;
604
666
  }
605
667
 
@@ -699,7 +761,7 @@ function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): Decl
699
761
  ...checkFor(getHandle(pspec), name),
700
762
  memberName: camelCase(name),
701
763
  storage: Symbol(`gtkx:property:${name}`),
702
- hasGeneratedMember: false,
764
+ hasMemberAccessor: false,
703
765
  };
704
766
 
705
767
  installAccessors(klass, accessor);
@@ -783,8 +845,10 @@ export {
783
845
  makeGetProperty,
784
846
  makeSetProperty,
785
847
  newParamSpecOverride,
848
+ readableObjectPropertyFor,
786
849
  SET_PROPERTY_VFUNC,
787
850
  toNativeProperties,
851
+ writableObjectPropertyFor,
788
852
  type ConstructProperty,
789
853
  type PropertyDispatch,
790
854
  type PropertySpec,
@@ -0,0 +1,5 @@
1
+ const propertyMapOverride: unique symbol = Symbol("gtkx.propertyMapOverride");
2
+ const writablePropertyMapOverride: unique symbol = Symbol("gtkx.writablePropertyMapOverride");
3
+ const descriptorFreePropertySpec: unique symbol = Symbol("gtkx.descriptorFreePropertySpec");
4
+
5
+ export { descriptorFreePropertySpec, propertyMapOverride, writablePropertyMapOverride };