@gtkx/runtime 2.0.0-beta.1 → 2.0.0-beta.10

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 (74) hide show
  1. package/README.md +43 -40
  2. package/dist/descriptors.d.ts +2 -1
  3. package/dist/descriptors.d.ts.map +1 -1
  4. package/dist/descriptors.js +5 -1
  5. package/dist/descriptors.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal.d.ts +17 -0
  11. package/dist/internal.d.ts.map +1 -1
  12. package/dist/internal.js +13 -0
  13. package/dist/internal.js.map +1 -1
  14. package/dist/lifecycle.js +1 -1
  15. package/dist/lifecycle.js.map +1 -1
  16. package/dist/listeners.d.ts +3 -13
  17. package/dist/listeners.d.ts.map +1 -1
  18. package/dist/listeners.js +12 -12
  19. package/dist/listeners.js.map +1 -1
  20. package/dist/mixin.d.ts +5 -3
  21. package/dist/mixin.d.ts.map +1 -1
  22. package/dist/mixin.js +36 -9
  23. package/dist/mixin.js.map +1 -1
  24. package/dist/object.d.ts +14 -3
  25. package/dist/object.d.ts.map +1 -1
  26. package/dist/object.js +20 -27
  27. package/dist/object.js.map +1 -1
  28. package/dist/properties.d.ts +4 -2
  29. package/dist/properties.d.ts.map +1 -1
  30. package/dist/properties.js +69 -16
  31. package/dist/properties.js.map +1 -1
  32. package/dist/property-brand.d.ts +5 -0
  33. package/dist/property-brand.d.ts.map +1 -0
  34. package/dist/property-brand.js +5 -0
  35. package/dist/property-brand.js.map +1 -0
  36. package/dist/register-class.d.ts +102 -20
  37. package/dist/register-class.d.ts.map +1 -1
  38. package/dist/register-class.js +19 -37
  39. package/dist/register-class.js.map +1 -1
  40. package/dist/registry.d.ts +1 -1
  41. package/dist/registry.d.ts.map +1 -1
  42. package/dist/registry.js +4 -4
  43. package/dist/registry.js.map +1 -1
  44. package/dist/signal-brand.d.ts +12 -0
  45. package/dist/signal-brand.d.ts.map +1 -0
  46. package/dist/signal-brand.js +6 -0
  47. package/dist/signal-brand.js.map +1 -0
  48. package/dist/signal.d.ts +39 -2
  49. package/dist/signal.d.ts.map +1 -1
  50. package/dist/signal.js +75 -5
  51. package/dist/signal.js.map +1 -1
  52. package/dist/type.d.ts.map +1 -1
  53. package/dist/type.js +1 -1
  54. package/dist/type.js.map +1 -1
  55. package/dist/value.d.ts +2 -1
  56. package/dist/value.d.ts.map +1 -1
  57. package/dist/value.js +28 -7
  58. package/dist/value.js.map +1 -1
  59. package/package.json +7 -5
  60. package/src/descriptors.ts +7 -0
  61. package/src/index.ts +2 -2
  62. package/src/internal.ts +42 -0
  63. package/src/lifecycle.ts +1 -1
  64. package/src/listeners.ts +21 -26
  65. package/src/mixin.ts +55 -9
  66. package/src/object.ts +57 -7
  67. package/src/properties.ts +94 -17
  68. package/src/property-brand.ts +5 -0
  69. package/src/register-class.ts +296 -95
  70. package/src/registry.ts +4 -4
  71. package/src/signal-brand.ts +29 -0
  72. package/src/signal.ts +177 -6
  73. package/src/type.ts +1 -1
  74. package/src/value.ts +40 -5
package/src/listeners.ts CHANGED
@@ -1,22 +1,18 @@
1
- import { isSignalHandlerConnected, type SignalHandler, untrackConnection } from "./signal.js";
2
-
3
- /**
4
- * The connect and disconnect surface {@link onSignal}, {@link onceSignal} and {@link offSignal}
5
- * drive on an emitter.
6
- */
7
- type SignalConnectable = {
8
- /** Connects a handler to a signal and returns its handler id. */
9
- connect(signal: string, handler: SignalHandler, isAfter?: boolean): number;
10
- /** Disconnects the handler previously connected under the given id. */
11
- disconnect(handlerId: number): void;
12
- };
1
+ import {
2
+ canonicalDetailedSignalName,
3
+ connectSignalByName,
4
+ disconnectSignal,
5
+ isSignalHandlerConnected,
6
+ type SignalHandler,
7
+ } from "./signal.js";
13
8
 
14
9
  const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number[]>>> = new WeakMap();
15
10
 
16
11
  const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
17
- listenerTable.get(instance)?.get(signal)?.get(handler)?.at(-1);
12
+ listenerTable.get(instance)?.get(canonicalDetailedSignalName(signal))?.get(handler)?.at(-1);
18
13
 
19
14
  const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
15
+ const key = canonicalDetailedSignalName(signal);
20
16
  let bySignal = listenerTable.get(instance);
21
17
 
22
18
  if (!bySignal) {
@@ -24,11 +20,11 @@ const trackListener = (instance: object, signal: string, handler: SignalHandler,
24
20
  listenerTable.set(instance, bySignal);
25
21
  }
26
22
 
27
- let byHandler = bySignal.get(signal);
23
+ let byHandler = bySignal.get(key);
28
24
 
29
25
  if (!byHandler) {
30
26
  byHandler = new Map();
31
- bySignal.set(signal, byHandler);
27
+ bySignal.set(key, byHandler);
32
28
  }
33
29
 
34
30
  let handlerIds = byHandler.get(handler);
@@ -52,8 +48,9 @@ const removeTrackedHandlerId = (handlerIds: number[], handlerId: number): number
52
48
  };
53
49
 
54
50
  const untrackHandlerId = (instance: object, signal: string, handlerId: number): void => {
51
+ const key = canonicalDetailedSignalName(signal);
55
52
  const bySignal = listenerTable.get(instance);
56
- const byHandler = bySignal?.get(signal);
53
+ const byHandler = bySignal?.get(key);
57
54
 
58
55
  if (byHandler === undefined) {
59
56
  return;
@@ -66,7 +63,7 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
66
63
  }
67
64
 
68
65
  if (byHandler.size === 0) {
69
- bySignal?.delete(signal);
66
+ bySignal?.delete(key);
70
67
  }
71
68
  };
72
69
 
@@ -79,8 +76,8 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
79
76
  * @param handler The callback invoked on each emission.
80
77
  * @param isAfter When true, run the handler after the default handler.
81
78
  */
82
- function onSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, isAfter?: boolean): void {
83
- const handlerId = instance.connect(signal, handler, isAfter);
79
+ function onSignal(instance: object, signal: string, handler: SignalHandler, isAfter?: boolean): void {
80
+ const handlerId = connectSignalByName(instance, signal, handler, isAfter);
84
81
  trackListener(instance, signal, handler, handlerId);
85
82
  }
86
83
 
@@ -93,18 +90,17 @@ function onSignal(instance: SignalConnectable, signal: string, handler: SignalHa
93
90
  * @param handler The callback invoked on the first emission.
94
91
  * @param isAfter When true, run the handler after the default handler.
95
92
  */
96
- function onceSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, isAfter?: boolean): void {
93
+ function onceSignal(instance: object, signal: string, handler: SignalHandler, isAfter?: boolean): void {
97
94
  let handlerId = 0;
98
95
 
99
96
  const wrapped: SignalHandler = (...args) => {
100
97
  untrackHandlerId(instance, signal, handlerId);
101
- untrackConnection(instance, handlerId);
102
- instance.disconnect(handlerId);
98
+ disconnectSignal(instance, handlerId);
103
99
 
104
100
  return handler(...args);
105
101
  };
106
102
 
107
- handlerId = instance.connect(signal, wrapped, isAfter);
103
+ handlerId = connectSignalByName(instance, signal, wrapped, isAfter);
108
104
  trackListener(instance, signal, wrapped, handlerId);
109
105
  trackListener(instance, signal, handler, handlerId);
110
106
  }
@@ -117,7 +113,7 @@ function onceSignal(instance: SignalConnectable, signal: string, handler: Signal
117
113
  * @param signal The signal name the handler was connected to.
118
114
  * @param handler The handler to disconnect.
119
115
  */
120
- function offSignal(instance: SignalConnectable, signal: string, handler: SignalHandler): void {
116
+ function offSignal(instance: object, signal: string, handler: SignalHandler): void {
121
117
  const handlerId = findListenerHandlerId(instance, signal, handler);
122
118
 
123
119
  if (handlerId === undefined) {
@@ -125,13 +121,12 @@ function offSignal(instance: SignalConnectable, signal: string, handler: SignalH
125
121
  }
126
122
 
127
123
  untrackHandlerId(instance, signal, handlerId);
128
- untrackConnection(instance, handlerId);
129
124
 
130
125
  if (!isSignalHandlerConnected(instance, handlerId)) {
131
126
  return;
132
127
  }
133
128
 
134
- instance.disconnect(handlerId);
129
+ disconnectSignal(instance, handlerId);
135
130
  }
136
131
 
137
132
  export { onSignal, onceSignal, offSignal };
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
+ };
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 { fromObjectPropertyValue, 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 getProperty<
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 getProperty(obj: object, propertyName: string, descriptor: Descriptor): unknown;
192
+ function getProperty(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 fromObjectPropertyValue(property.value);
198
+ }
199
+
200
+ if (descriptor === undefined) {
201
+ throw new TypeError("getProperty 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,15 +220,37 @@ 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 setProperty<
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 setProperty(obj: object, propertyName: string, descriptor: Descriptor, jsValue: unknown): void;
233
+ function setProperty(
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 {
200
250
  newObjectWithProperties,
201
- getObjectProperty,
251
+ getProperty,
202
252
  registerConstructProperties,
203
- setObjectProperty,
253
+ setProperty,
204
254
  type ConstructBinding,
205
255
  type ConstructBindings,
206
256
  };
package/src/properties.ts CHANGED
@@ -55,7 +55,7 @@ type AccessorBase = PropertyCheck & {
55
55
  type DeclaredAccessor = AccessorBase & {
56
56
  isInterfaceProperty?: false;
57
57
  memberName: string;
58
- hasGeneratedMember: boolean;
58
+ hasMemberAccessor: boolean;
59
59
  };
60
60
 
61
61
  type InterfaceAccessor = AccessorBase & { isInterfaceProperty: true };
@@ -100,7 +100,7 @@ const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM
100
100
  const paramSpecName = bind(LIB, "g_param_spec_get_name", [PARAM_T], stringT("borrowed"));
101
101
  const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
102
102
  const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
103
- const coercionChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
103
+ const propertyChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
104
104
  const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
105
105
 
106
106
  const classListProperties = bind(
@@ -214,8 +214,11 @@ const findSourceSpec = (source: bigint | AnyClass, name: string): ExternalObject
214
214
  * @param source The GType, wrapper class or interface declaring the property.
215
215
  * @returns Handle of the override spec.
216
216
  */
217
- const newParamSpecOverride = (name: string, source: bigint | AnyClass): ExternalObject<Handle> =>
218
- 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
+ };
219
222
 
220
223
  function describeValue(value: unknown): string {
221
224
  if (typeof value === "string") {
@@ -283,6 +286,24 @@ function assertWritable(instance: object, check: PropertyCheck, value: unknown):
283
286
  throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
284
287
  }
285
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
+
286
307
  function writeHeld(check: PropertyCheck, gValue: ExternalObject<Handle>, value: unknown): void {
287
308
  check.write ??= valueWriterFor(check.valueType);
288
309
  check.write(gValue, check.narrowValue(value));
@@ -333,7 +354,7 @@ function isReadableProperty(gtype: bigint, propertyName: string): boolean {
333
354
  }
334
355
  }
335
356
 
336
- function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null {
357
+ function lookupPropertyCheck(gtype: bigint, name: string): PropertyCheck | null {
337
358
  const klass = typeClassRef(gtype) as ExternalObject<Handle>;
338
359
 
339
360
  try {
@@ -345,10 +366,35 @@ function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null
345
366
  }
346
367
  }
347
368
 
348
- function coercionCheckFor(gtype: bigint, name: string): PropertyCheck | null {
349
- const checks = coercionChecks.getOrInsertComputed(gtype, () => new Map<string, PropertyCheck | null>());
369
+ function propertyCheckFor(gtype: bigint, name: string): PropertyCheck | null {
370
+ const checks = propertyChecks.getOrInsertComputed(gtype, () => new Map<string, PropertyCheck | null>());
371
+
372
+ return checks.getOrInsertComputed(name, () => lookupPropertyCheck(gtype, name));
373
+ }
374
+
375
+ function objectPropertyCheckFor(instance: object, name: string): PropertyCheck {
376
+ const gtype = getInstanceType(instance);
377
+ const check = typeIsA(gtype, TYPE_OBJECT) ? propertyCheckFor(gtype, name) : null;
378
+
379
+ if (check === null) {
380
+ throw new TypeError(`${instanceClassName(instance)} has no GObject property named '${name}'`);
381
+ }
350
382
 
351
- return checks.getOrInsertComputed(name, () => lookupCoercionCheck(gtype, name));
383
+ return check;
384
+ }
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) };
352
398
  }
353
399
 
354
400
  function truncateToWhole(check: PropertyCheck, value: number): number {
@@ -384,7 +430,7 @@ function coercePropertyValue(gtype: bigint, propertyName: string, value: unknown
384
430
  return value;
385
431
  }
386
432
 
387
- const check = coercionCheckFor(gtype, propertyName);
433
+ const check = propertyCheckFor(gtype, propertyName);
388
434
 
389
435
  return check === null ? value : coerceNumber(check, value);
390
436
  }
@@ -499,12 +545,35 @@ function checkedSetter(accessor: DeclaredAccessor): (this: object, value: unknow
499
545
  };
500
546
  }
501
547
 
502
- function hasOwnMember(prototype: object, alias: string): boolean {
503
- 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);
504
566
  }
505
567
 
506
568
  function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias: string): void {
507
- 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)) {
508
577
  return;
509
578
  }
510
579
 
@@ -513,13 +582,13 @@ function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias
513
582
 
514
583
  function installAccessors(klass: AnyClass, accessor: DeclaredAccessor): void {
515
584
  const prototype = (klass as { prototype: object }).prototype;
516
- accessor.hasGeneratedMember = !hasOwnMember(prototype, accessor.memberName);
585
+ accessor.hasMemberAccessor = isMemberAccessor(Object.getOwnPropertyDescriptor(prototype, accessor.memberName));
517
586
 
518
587
  const descriptor: PropertyDescriptor = {
519
588
  configurable: true,
520
589
  enumerable: true,
521
- get: accessor.hasGeneratedMember ? storedGetter(accessor) : memberGetter(accessor),
522
- set: accessor.hasGeneratedMember ? checkedSetter(accessor) : memberSetter(accessor),
590
+ get: accessor.hasMemberAccessor ? memberGetter(accessor) : storedGetter(accessor),
591
+ set: accessor.hasMemberAccessor ? memberSetter(accessor) : checkedSetter(accessor),
523
592
  };
524
593
 
525
594
  for (const alias of accessorNames(accessor.name)) {
@@ -586,7 +655,13 @@ function callMember(instance: object, member: string, args: unknown[]): unknown
586
655
  }
587
656
 
588
657
  function backingMemberFor(instance: object, accessor: DeclaredAccessor): string | undefined {
589
- 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")) {
590
665
  return undefined;
591
666
  }
592
667
 
@@ -686,7 +761,7 @@ function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): Decl
686
761
  ...checkFor(getHandle(pspec), name),
687
762
  memberName: camelCase(name),
688
763
  storage: Symbol(`gtkx:property:${name}`),
689
- hasGeneratedMember: false,
764
+ hasMemberAccessor: false,
690
765
  };
691
766
 
692
767
  installAccessors(klass, accessor);
@@ -770,8 +845,10 @@ export {
770
845
  makeGetProperty,
771
846
  makeSetProperty,
772
847
  newParamSpecOverride,
848
+ readableObjectPropertyFor,
773
849
  SET_PROPERTY_VFUNC,
774
850
  toNativeProperties,
851
+ writableObjectPropertyFor,
775
852
  type ConstructProperty,
776
853
  type PropertyDispatch,
777
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 };