@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.
- package/README.md +5 -5
- package/dist/application-class.d.ts +37 -0
- package/dist/application-class.d.ts.map +1 -0
- package/dist/application-class.js +78 -0
- package/dist/application-class.js.map +1 -0
- package/dist/arg.d.ts +2 -1
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +2 -1
- package/dist/arg.js.map +1 -1
- package/dist/bind.d.ts +11 -0
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +11 -0
- package/dist/bind.js.map +1 -1
- package/dist/callback.d.ts +4 -3
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +124 -29
- package/dist/callback.js.map +1 -1
- package/dist/closure.d.ts +26 -0
- package/dist/closure.d.ts.map +1 -0
- package/dist/closure.js +99 -0
- package/dist/closure.js.map +1 -0
- package/dist/default-application.d.ts +4 -0
- package/dist/default-application.d.ts.map +1 -0
- package/dist/default-application.js +17 -0
- package/dist/default-application.js.map +1 -0
- package/dist/descriptors.d.ts +98 -4
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +72 -6
- package/dist/descriptors.js.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/exit-hook.js +13 -0
- package/dist/exit-hook.js.map +1 -1
- package/dist/fn.d.ts +24 -2
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +67 -32
- package/dist/fn.js.map +1 -1
- package/dist/folded-lengths.d.ts +16 -0
- package/dist/folded-lengths.d.ts.map +1 -0
- package/dist/folded-lengths.js +38 -0
- package/dist/folded-lengths.js.map +1 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/library.d.ts +3 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +3 -1
- package/dist/library.js.map +1 -1
- package/dist/lifecycle.d.ts +58 -11
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +95 -22
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +19 -5
- package/dist/listeners.js.map +1 -1
- package/dist/mixin.d.ts +3 -2
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +13 -1
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +4 -3
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +40 -7
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +95 -26
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts +11 -0
- package/dist/param-spec.d.ts.map +1 -0
- package/dist/param-spec.js +147 -0
- package/dist/param-spec.js.map +1 -0
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +3 -2
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +39 -7
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +310 -43
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +147 -12
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +129 -83
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +80 -21
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +86 -33
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +11 -4
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +85 -25
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +2 -0
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js +1 -0
- package/dist/t.js.map +1 -1
- package/dist/tuple.d.ts +6 -3
- package/dist/tuple.d.ts.map +1 -1
- package/dist/tuple.js +9 -7
- package/dist/tuple.js.map +1 -1
- package/dist/type.d.ts +2 -1
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +2 -0
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +10 -1
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +99 -24
- package/dist/value.js.map +1 -1
- package/dist/vfunc-call.d.ts +37 -0
- package/dist/vfunc-call.d.ts.map +1 -0
- package/dist/vfunc-call.js +172 -0
- package/dist/vfunc-call.js.map +1 -0
- package/dist/vfunc-seeds.d.ts +12 -0
- package/dist/vfunc-seeds.d.ts.map +1 -0
- package/dist/vfunc-seeds.js +19 -0
- package/dist/vfunc-seeds.js.map +1 -0
- package/dist/vfunc.d.ts +8 -0
- package/dist/vfunc.d.ts.map +1 -0
- package/dist/vfunc.js +29 -0
- package/dist/vfunc.js.map +1 -0
- package/package.json +14 -4
- package/src/application-class.ts +126 -0
- package/src/arg.ts +2 -1
- package/src/bind.ts +11 -0
- package/src/callback.ts +178 -34
- package/src/closure.ts +136 -0
- package/src/default-application.ts +21 -0
- package/src/descriptors.ts +150 -34
- package/src/error.ts +1 -0
- package/src/exit-hook.ts +18 -0
- package/src/fn.ts +107 -36
- package/src/folded-lengths.ts +64 -0
- package/src/index.ts +21 -6
- package/src/internal.ts +2 -0
- package/src/library.ts +3 -1
- package/src/lifecycle.ts +138 -25
- package/src/listeners.ts +25 -6
- package/src/mixin.ts +15 -2
- package/src/native-value.ts +5 -3
- package/src/object.ts +134 -35
- package/src/param-spec.ts +234 -0
- package/src/promisify.ts +4 -2
- package/src/properties.ts +473 -46
- package/src/register-class.ts +386 -114
- package/src/registry.ts +169 -49
- package/src/signal.ts +116 -42
- package/src/t.ts +3 -0
- package/src/tuple.ts +18 -12
- package/src/type.ts +3 -1
- package/src/value.ts +143 -23
- package/src/vfunc-call.ts +247 -0
- package/src/vfunc-seeds.ts +36 -0
- package/src/vfunc.ts +38 -0
package/src/register-class.ts
CHANGED
|
@@ -1,59 +1,251 @@
|
|
|
1
1
|
import type { Descriptor } from "@gtkx/native";
|
|
2
2
|
import {
|
|
3
3
|
registerClass as nativeRegisterClass,
|
|
4
|
+
type RegisterClassInterface as NativeRegisterClassInterface,
|
|
4
5
|
type RegisterClassOptions as NativeRegisterClassOptions,
|
|
5
6
|
type RegisterClassVfunc as NativeRegisterClassVfunc,
|
|
6
7
|
} from "@gtkx/native";
|
|
7
8
|
import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
|
|
8
9
|
import { wrapCallback } from "./callback.js";
|
|
10
|
+
import { insertMixinLayer } from "./mixin.js";
|
|
9
11
|
import {
|
|
10
|
-
|
|
12
|
+
buildPropertyDispatch,
|
|
13
|
+
GET_PROPERTY_VFUNC,
|
|
11
14
|
makeGetProperty,
|
|
12
15
|
makeSetProperty,
|
|
13
|
-
type
|
|
16
|
+
type PropertyDispatch,
|
|
14
17
|
type PropertySpec,
|
|
18
|
+
SET_PROPERTY_VFUNC,
|
|
15
19
|
toNativeProperties,
|
|
16
20
|
} from "./properties.js";
|
|
17
21
|
import {
|
|
18
22
|
getClassType,
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
getInterfaceMixin,
|
|
24
|
+
markDerivedClass,
|
|
21
25
|
registerClassType,
|
|
22
26
|
type VfuncDescriptor,
|
|
23
27
|
} from "./registry.js";
|
|
24
|
-
import { TYPE_INVALID, typeInterfaces } from "./type.js";
|
|
28
|
+
import { TYPE_INTERFACE, TYPE_INVALID, typeFundamental, typeInterfaces, typeIsA } from "./type.js";
|
|
29
|
+
import { findClassVfuncDescriptor, findInterfaceVfuncDescriptor } from "./vfunc.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A generated interface value, such as `Gio.ListModel`, in the form {@link registerClass} takes it.
|
|
33
|
+
* `__impl__` exists only in the type system: it carries the interface's `Impl` type, of which a class
|
|
34
|
+
* has to match every member it declares itself, which rejects a class value and a member whose
|
|
35
|
+
* signature does not fit the slot it fills. Leaving a slot to the interface is not rejected,
|
|
36
|
+
* because `registerClass` does not reject it either: every member of an `Impl` type is optional,
|
|
37
|
+
* and the `Partial` holds that open whatever the type declares. The `object` beside it keeps
|
|
38
|
+
* TypeScript from also demanding one member in common. An interface that introspection describes
|
|
39
|
+
* no vtable for carries `unknown`, since it has no slot a class could fill.
|
|
40
|
+
*/
|
|
41
|
+
type Interface<TImpl> = AnyClass & {
|
|
42
|
+
/** Type-level slot holding the interface's `Impl` type; no value ever carries it. */
|
|
43
|
+
__impl__: (impl: Partial<TImpl> & object) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* One key of `RegisterClassOptions.properties` with every underscore turned into the dash a canonical
|
|
48
|
+
* GObject property name separates its words with.
|
|
49
|
+
*/
|
|
50
|
+
type Dashed<TName extends string> = TName extends `${infer THead}_${infer TTail}`
|
|
51
|
+
? Dashed<`${THead}-${TTail}`>
|
|
52
|
+
: TName;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One dashed key in the camelCase spelling the accessors {@link registerClass} installs carry, which is
|
|
56
|
+
* the spelling the hooks that address a property by name take.
|
|
57
|
+
*/
|
|
58
|
+
type Camelized<TName extends string> = TName extends `${infer THead}-${infer TTail}`
|
|
59
|
+
? `${THead}${Capitalize<Camelized<TTail>>}`
|
|
60
|
+
: TName;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The names a registered class carries in its property map: every key of
|
|
64
|
+
* `RegisterClassOptions.properties` in camelCase, whichever of the three spellings it was written in.
|
|
65
|
+
* A `properties` object given a type of its own rather than left to inference has `string` for its key
|
|
66
|
+
* type and names nothing, because a name only known as `string` addresses no member in particular.
|
|
67
|
+
*/
|
|
68
|
+
type InstalledNames<TProperties> = string extends keyof TProperties
|
|
69
|
+
? never
|
|
70
|
+
: Camelized<Dashed<keyof TProperties & string>>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* An instance of a registered class: everything the class itself declares, plus the property map the
|
|
74
|
+
* hooks that address a property by name, such as `useProperty` from `@gtkx/react`, read the installed
|
|
75
|
+
* names off. Each name is typed with the value type the class declares for the member of that name,
|
|
76
|
+
* so a property the class does not `declare` contributes nothing.
|
|
77
|
+
*/
|
|
78
|
+
type RegisteredInstance<TInstance, TProperties> = TInstance & {
|
|
79
|
+
/** Type-level map from installed property name to value type; no value ever carries it. */
|
|
80
|
+
__properties__: Pick<TInstance, InstalledNames<TProperties> & keyof TInstance>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The construct signature and prototype a registered class carries, both giving
|
|
85
|
+
* {@link RegisteredInstance}. The signature stays abstract for as long as the class itself is, so
|
|
86
|
+
* registering an abstract base leaves it as impossible to construct as it was.
|
|
87
|
+
*/
|
|
88
|
+
type RegisteredConstructor<TClass, TArgs extends unknown[], TInstance> = {
|
|
89
|
+
/** Object the class's instances inherit from. */
|
|
90
|
+
prototype: TInstance;
|
|
91
|
+
} & (TClass extends new (...args: never) => unknown
|
|
92
|
+
? new (...args: TArgs) => TInstance
|
|
93
|
+
: abstract new (...args: TArgs) => TInstance);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The class {@link registerClass} hands back: the same class, with the same statics, whose instances
|
|
97
|
+
* carry the properties `RegisterClassOptions.properties` installed. Binding the call to a name, rather
|
|
98
|
+
* than discarding it, is what carries those names into the type system.
|
|
99
|
+
*/
|
|
100
|
+
type RegisteredClass<TClass extends AnyClass, TProperties> =
|
|
101
|
+
TClass extends abstract new (...args: infer TArgs) => infer TInstance
|
|
102
|
+
? Omit<TClass, "prototype"> & RegisteredConstructor<TClass, TArgs, RegisteredInstance<TInstance, TProperties>>
|
|
103
|
+
: never;
|
|
25
104
|
|
|
26
105
|
/** What {@link registerClass} adds to the new GType beyond the vtable slots it discovers on the class. */
|
|
27
|
-
type RegisterClassOptions = {
|
|
106
|
+
type RegisterClassOptions<TInstance extends object, TProperties extends Record<string, PropertySpec>> = {
|
|
28
107
|
/** Name to register the new GType under, defaulting to the class's own name. */
|
|
29
108
|
typeName?: string;
|
|
30
109
|
/**
|
|
31
|
-
*
|
|
32
|
-
* `
|
|
33
|
-
* prototype
|
|
110
|
+
* Interfaces the new type implements on top of the ones it inherits, given as the interface values
|
|
111
|
+
* themselves, such as `Gio.ListModel`. Their vtable slots are filled from the `vfunc`-prefixed methods on
|
|
112
|
+
* the class's prototype chain, each of which has to match the interface's `Impl` type, such as
|
|
113
|
+
* `Gio.ListModelImpl`.
|
|
114
|
+
*/
|
|
115
|
+
implements?: Interface<TInstance>[];
|
|
116
|
+
/**
|
|
117
|
+
* Properties to install on the new type, keyed by the name JavaScript addresses each one by and
|
|
118
|
+
* valued with the `GObject.ParamSpec` describing it. A key is read in camelCase however it is
|
|
119
|
+
* written, so `dewPoint`, `dew_point` and `dew-point` all name the same member, and the ParamSpec
|
|
120
|
+
* has to carry the canonical spelling of that name, `dew-point`, or registration throws: the
|
|
121
|
+
* ParamSpec's name is the one GObject emits `notify` with, and a name the key does not spell
|
|
122
|
+
* reaches nothing that listens for it. Every property gains prototype accessors, one for the key
|
|
123
|
+
* as written, one for it with dashes turned into underscores and one for it in camelCase, each
|
|
124
|
+
* unless the class already defines that name. They serve the value from storage of their own on
|
|
125
|
+
* the instance, which is also what the type's `get_property` and `set_property` slots read and
|
|
126
|
+
* write, so a value set from JavaScript, from `g_object_set_property` and at construction all
|
|
127
|
+
* land in the same place.
|
|
128
|
+
*
|
|
129
|
+
* A write the ParamSpec would refuse throws rather than reaching GObject, which reports such a
|
|
130
|
+
* write as a GLib critical and drops it: a `TypeError` for a read-only or construct-only
|
|
131
|
+
* property and for a value of a type the property cannot hold, and a `RangeError` for a value
|
|
132
|
+
* the ParamSpec rejects. The same two checks run over a value handed to the constructor, where
|
|
133
|
+
* a construct-only property is the one that is writable. An accepted write emits one `notify`,
|
|
134
|
+
* which a `freeze_notify` batch collects; a write of the value the property already holds is
|
|
135
|
+
* dropped and emits none.
|
|
136
|
+
*
|
|
137
|
+
* `null` and `undefined` both mean NULL, and mean it only where the ParamSpec's own type holds
|
|
138
|
+
* NULL, which is a string, string-array, boxed, object, interface, param, variant or pointer
|
|
139
|
+
* property. Such a property holds the `null` either spelling wrote, so the member, the type's
|
|
140
|
+
* `get_property` slot and `g_object_get_property` serve the same thing, and writing the other
|
|
141
|
+
* spelling over it emits no `notify`. Every other property, so every integer, floating-point,
|
|
142
|
+
* boolean, enum, flags and GType one, refuses both with the same `TypeError` it refuses a
|
|
143
|
+
* string with, and keeps the value it already holds. It refuses them for what it holds rather
|
|
144
|
+
* than for its range, whatever that range is: the type is checked before the range, so a
|
|
145
|
+
* `gint` whose range excludes 0 answers a nullish with that same `TypeError` and never with
|
|
146
|
+
* the `RangeError` that names the value GObject would put in its place. The one place
|
|
147
|
+
* `undefined` means something else is the constructor, which reads it as the property not
|
|
148
|
+
* being given at all and leaves it at the ParamSpec's default, so a property is never handed a
|
|
149
|
+
* value it cannot serve back.
|
|
150
|
+
*
|
|
151
|
+
* A floating-point property takes every JavaScript number, `NaN` and both infinities
|
|
152
|
+
* included, and its ParamSpec alone rules on which of them the range admits: a `gdouble`
|
|
153
|
+
* bounded by `-Infinity` and `Infinity` holds either infinity, and a magnitude a bounded
|
|
154
|
+
* one excludes, like any `NaN`, comes back as the `RangeError` that names what GObject
|
|
155
|
+
* would put in its place rather than as the `TypeError` a type the property cannot hold
|
|
156
|
+
* earns. A `gfloat` property holds what GObject narrows the double to, so it serves `0.1`
|
|
157
|
+
* back as `0.10000000149011612` and a finite magnitude no `gfloat` reaches as an infinity,
|
|
158
|
+
* which the range then rules on in turn. That narrowing belongs to the property alone: the
|
|
159
|
+
* same magnitude written to a `gfloat` through a generated binding, a signal argument or a
|
|
160
|
+
* closure return is refused outright rather than narrowed.
|
|
161
|
+
*
|
|
162
|
+
* A generated property of a wrapped type answers a nullish differently, and the two halves of
|
|
163
|
+
* the API disagree here: that property marshals what it is written through its descriptor
|
|
164
|
+
* rather than through the checks above, so `new Gtk.Label({ widthRequest: null })` and a later
|
|
165
|
+
* write of `null` to that member both land 0, where a `gint` installed here refuses both.
|
|
166
|
+
*
|
|
167
|
+
* A class that defines the camelCase member itself owns the property: its own accessor decides
|
|
168
|
+
* what a write means, the other two spellings forward to it, and the type's property slots read
|
|
169
|
+
* and write it rather than the generated storage.
|
|
34
170
|
*/
|
|
35
|
-
properties?:
|
|
171
|
+
properties?: TProperties;
|
|
36
172
|
};
|
|
37
173
|
|
|
174
|
+
/** {@link RegisterClassOptions} with the widest instance and property types {@link registerClass} accepts. */
|
|
175
|
+
type AnyRegisterClassOptions = RegisterClassOptions<object, Record<string, PropertySpec>>;
|
|
38
176
|
type VfuncFn = NativeRegisterClassVfunc["fn"];
|
|
39
|
-
type DiscoveredVfunc
|
|
40
|
-
type
|
|
41
|
-
type
|
|
42
|
-
type InterfaceVfuncBinding = { gtype: bigint; vtableSize: number; vfuncs: DiscoveredInterfaceVfunc[] };
|
|
177
|
+
type DiscoveredVfunc = VfuncDescriptor & { methodName: string; fn: VfuncFn };
|
|
178
|
+
type MethodTable = Map<string, VfuncFn>;
|
|
179
|
+
type InstanceMembers = { methods: MethodTable; names: Set<string>; inheritedNames: Set<string> };
|
|
43
180
|
|
|
44
|
-
|
|
181
|
+
type InterfaceVfuncBinding = {
|
|
182
|
+
gtype: bigint;
|
|
183
|
+
vfuncs: DiscoveredVfunc[];
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
type PropertyVfuncSource = {
|
|
187
|
+
klass: AnyClass;
|
|
188
|
+
methods: MethodTable;
|
|
189
|
+
dispatch: PropertyDispatch;
|
|
190
|
+
adoptedTypes: bigint[];
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
type PropertyVfuncSpec = {
|
|
194
|
+
methodName: string;
|
|
195
|
+
isValueOut: boolean;
|
|
196
|
+
makeDispatch: (dispatch: PropertyDispatch) => VfuncFn;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
type ArgPatch = { isCallerAllocated: true } | { isCallScoped: true };
|
|
200
|
+
|
|
201
|
+
const INSTANCE_ARG_INDEX = 0;
|
|
45
202
|
const VALUE_ARG_INDEX = 2;
|
|
203
|
+
const TEARDOWN_VFUNC_NAMES: Set<string> = new Set(["dispose", "finalize"]);
|
|
204
|
+
|
|
205
|
+
const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
|
|
206
|
+
{ methodName: GET_PROPERTY_VFUNC, isValueOut: true, makeDispatch: makeGetProperty },
|
|
207
|
+
{ methodName: SET_PROPERTY_VFUNC, isValueOut: false, makeDispatch: makeSetProperty },
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec) => spec.methodName));
|
|
46
211
|
|
|
47
212
|
/**
|
|
48
|
-
* Registers a subclass of a wrapper class as a new GType, wiring up any class and
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
213
|
+
* Registers a subclass of a wrapper class as a new GType, wiring up any class and interface
|
|
214
|
+
* virtual functions it overrides, both for the interfaces it inherits and for the ones
|
|
215
|
+
* `RegisterClassOptions.implements` names.
|
|
216
|
+
*
|
|
217
|
+
* Throws when the class does not extend a registered wrapper class, when it has no derivable type
|
|
218
|
+
* name, when an entry in `RegisterClassOptions.implements` is not a registered interface, when a
|
|
219
|
+
* listed interface has a prerequisite that neither the parent type nor another listed interface meets,
|
|
220
|
+
* and when an entry in `RegisterClassOptions.properties` names its `GObject.ParamSpec` something other
|
|
221
|
+
* than the canonical spelling of the key it sits under.
|
|
222
|
+
*
|
|
223
|
+
* A slot is filled from the `vfunc`-prefixed methods on the class's prototype chain, up to but not
|
|
224
|
+
* including the registered ancestor the class extends, so a method an intermediate base class
|
|
225
|
+
* declares fills a slot the same way one the class itself declares does. A slot nothing on that
|
|
226
|
+
* chain fills is left untouched.
|
|
227
|
+
*
|
|
228
|
+
* Declare every slot as a method: a class field holding a function, such as `vfuncGetNItems = () => 1`,
|
|
229
|
+
* is assigned to each instance after registration and never reaches the vtable.
|
|
230
|
+
*
|
|
231
|
+
* An override of `vfuncConstructed` runs from inside the base constructor, before JavaScript
|
|
232
|
+
* installs the subclass's field initializers and runs its constructor body, so a field still
|
|
233
|
+
* reads `undefined` there and reading a `#private` field throws. Declare state the override
|
|
234
|
+
* touches without an initializer, and assign private state from the constructor body after
|
|
235
|
+
* `super()`. An instance a native caller creates, through `GObject.newv` or `Gtk.Builder`,
|
|
236
|
+
* never runs the subclass constructor at all, so its declared fields stay uninitialized for
|
|
237
|
+
* the object's whole life.
|
|
52
238
|
*
|
|
53
239
|
* @param klass The subclass to register.
|
|
54
|
-
* @
|
|
240
|
+
* @param options What the new GType gains beyond the vtable slots the class overrides.
|
|
241
|
+
* @returns The same class, now registered, with every name in `options.properties` in its property map.
|
|
55
242
|
*/
|
|
56
|
-
function registerClass<
|
|
243
|
+
function registerClass<
|
|
244
|
+
T extends AnyClass,
|
|
245
|
+
TProperties extends Record<string, PropertySpec> = Record<never, PropertySpec>,
|
|
246
|
+
>(klass: T, options?: RegisterClassOptions<T["prototype"], TProperties>): RegisteredClass<T, TProperties>;
|
|
247
|
+
|
|
248
|
+
function registerClass(klass: AnyClass, options: AnyRegisterClassOptions = {}): AnyClass {
|
|
57
249
|
const parentType = resolveParentType(klass);
|
|
58
250
|
|
|
59
251
|
if (parentType === TYPE_INVALID) {
|
|
@@ -66,18 +258,56 @@ function registerClass<T extends AnyClass>(klass: T, options: RegisterClassOptio
|
|
|
66
258
|
throw new Error("registerClass: cannot derive a GType name (anonymous class with no typeName option)");
|
|
67
259
|
}
|
|
68
260
|
|
|
261
|
+
const declaredTypes = resolveInterfaceTypes(klass, options.implements ?? []);
|
|
262
|
+
const adoptedTypes = declaredTypes.filter((gtype) => !typeIsA(parentType, gtype));
|
|
69
263
|
const properties = options.properties ?? {};
|
|
70
|
-
const
|
|
71
|
-
const
|
|
264
|
+
const { methods, inheritedNames } = collectInstanceMembers(klass);
|
|
265
|
+
const dispatch = buildPropertyDispatch({ klass, properties, adoptedTypes });
|
|
266
|
+
|
|
267
|
+
const classVfuncs = [
|
|
268
|
+
...discoverClassVfuncs(klass, methods),
|
|
269
|
+
...propertyVfuncs({ klass, methods, dispatch, adoptedTypes }),
|
|
270
|
+
];
|
|
271
|
+
|
|
72
272
|
const claimedMethodNames = new Set(classVfuncs.map((vfunc) => vfunc.methodName));
|
|
73
|
-
const interfaceBindings =
|
|
273
|
+
const interfaceBindings = discoverInterfaceBindings(methods, parentType, declaredTypes, claimedMethodNames);
|
|
74
274
|
const nativeOptions = toNativeOptions(classVfuncs, interfaceBindings, properties);
|
|
75
275
|
const newType: bigint = nativeRegisterClass(name, parentType, nativeOptions);
|
|
76
276
|
registerClassType(klass, newType);
|
|
277
|
+
markDerivedClass(klass);
|
|
278
|
+
applyInterfaceMixins(klass, adoptedTypes, inheritedNames);
|
|
77
279
|
|
|
78
280
|
return klass;
|
|
79
281
|
}
|
|
80
282
|
|
|
283
|
+
function resolveInterfaceType(klass: AnyClass, entry: AnyClass): bigint {
|
|
284
|
+
const gtype = getClassType(entry);
|
|
285
|
+
|
|
286
|
+
if (typeFundamental(gtype) !== TYPE_INTERFACE) {
|
|
287
|
+
const label = typeof entry === "function" ? entry.name : String(entry);
|
|
288
|
+
|
|
289
|
+
throw new TypeError(
|
|
290
|
+
`registerClass: ${klass.name} lists '${label}' in implements, which is not a registered interface`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return gtype;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function resolveInterfaceTypes(klass: AnyClass, entries: AnyClass[]): bigint[] {
|
|
298
|
+
return [...new Set(entries.map((entry) => resolveInterfaceType(klass, entry)))];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function applyInterfaceMixins(klass: AnyClass, adoptedTypes: bigint[], inheritedNames: Set<string>): void {
|
|
302
|
+
for (const gtype of adoptedTypes) {
|
|
303
|
+
const mixin = getInterfaceMixin(gtype);
|
|
304
|
+
|
|
305
|
+
if (mixin !== undefined) {
|
|
306
|
+
insertMixinLayer(klass, mixin, inheritedNames);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
81
311
|
function resolveParentType(klass: AnyClass): bigint {
|
|
82
312
|
return (
|
|
83
313
|
walkClassChain(getParentClass(klass), (cls) => {
|
|
@@ -88,28 +318,57 @@ function resolveParentType(klass: AnyClass): bigint {
|
|
|
88
318
|
);
|
|
89
319
|
}
|
|
90
320
|
|
|
91
|
-
function
|
|
321
|
+
function addOwnMember(members: InstanceMembers, proto: object, name: string, isInherited: boolean): void {
|
|
322
|
+
if (name === "constructor") {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (isInherited) {
|
|
327
|
+
members.inheritedNames.add(name);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (members.names.has(name)) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
members.names.add(name);
|
|
335
|
+
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
|
|
336
|
+
|
|
337
|
+
if (typeof descriptor?.value === "function") {
|
|
338
|
+
members.methods.set(name, descriptor.value as VfuncFn);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function addOwnMembers(members: InstanceMembers, klass: AnyClass, isInherited: boolean): void {
|
|
92
343
|
const proto = (klass as { prototype?: object }).prototype;
|
|
93
344
|
|
|
94
|
-
if (
|
|
95
|
-
return
|
|
345
|
+
if (proto === undefined) {
|
|
346
|
+
return;
|
|
96
347
|
}
|
|
97
348
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
349
|
+
for (const name of Object.getOwnPropertyNames(proto)) {
|
|
350
|
+
addOwnMember(members, proto, name, isInherited);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function collectInstanceMembers(klass: AnyClass): InstanceMembers {
|
|
355
|
+
const members: InstanceMembers = { methods: new Map(), names: new Set(), inheritedNames: new Set() };
|
|
356
|
+
let cls: AnyClass | null = klass;
|
|
102
357
|
|
|
103
|
-
|
|
104
|
-
|
|
358
|
+
while (cls !== null && getClassType(cls) === TYPE_INVALID) {
|
|
359
|
+
addOwnMembers(members, cls, cls !== klass);
|
|
360
|
+
cls = getParentClass(cls);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return members;
|
|
105
364
|
}
|
|
106
365
|
|
|
107
|
-
function buildDiscoveredVfunc
|
|
108
|
-
|
|
366
|
+
function buildDiscoveredVfunc(
|
|
367
|
+
fn: VfuncFn,
|
|
109
368
|
methodName: string,
|
|
110
|
-
resolveDescriptor: (methodName: string) => VfuncDescriptor
|
|
369
|
+
resolveDescriptor: (methodName: string) => VfuncDescriptor | undefined,
|
|
111
370
|
skip: Set<string> | undefined,
|
|
112
|
-
): DiscoveredVfunc
|
|
371
|
+
): DiscoveredVfunc | undefined {
|
|
113
372
|
if (skip?.has(methodName)) {
|
|
114
373
|
return undefined;
|
|
115
374
|
}
|
|
@@ -120,29 +379,25 @@ function buildDiscoveredVfunc<K extends "class" | "interface">(
|
|
|
120
379
|
return undefined;
|
|
121
380
|
}
|
|
122
381
|
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
if (!fn) {
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
382
|
+
const argDescriptors = slotArgDescriptors(descriptor);
|
|
128
383
|
|
|
129
384
|
return {
|
|
130
385
|
...descriptor,
|
|
131
386
|
methodName,
|
|
132
|
-
|
|
387
|
+
argDescriptors,
|
|
388
|
+
fn: wrapVfunc(fn, argDescriptors, descriptor),
|
|
133
389
|
};
|
|
134
390
|
}
|
|
135
391
|
|
|
136
|
-
function collectDiscoveredVfuncs
|
|
137
|
-
|
|
138
|
-
resolveDescriptor: (methodName: string) => VfuncDescriptor
|
|
392
|
+
function collectDiscoveredVfuncs(
|
|
393
|
+
methods: MethodTable,
|
|
394
|
+
resolveDescriptor: (methodName: string) => VfuncDescriptor | undefined,
|
|
139
395
|
skip?: Set<string>,
|
|
140
|
-
): DiscoveredVfunc
|
|
141
|
-
const
|
|
142
|
-
const result: DiscoveredVfunc<K>[] = [];
|
|
396
|
+
): DiscoveredVfunc[] {
|
|
397
|
+
const result: DiscoveredVfunc[] = [];
|
|
143
398
|
|
|
144
|
-
for (const methodName of
|
|
145
|
-
const discovered = buildDiscoveredVfunc(
|
|
399
|
+
for (const [methodName, fn] of methods) {
|
|
400
|
+
const discovered = buildDiscoveredVfunc(fn, methodName, resolveDescriptor, skip);
|
|
146
401
|
|
|
147
402
|
if (discovered) {
|
|
148
403
|
result.push(discovered);
|
|
@@ -152,42 +407,41 @@ function collectDiscoveredVfuncs<K extends "class" | "interface">(
|
|
|
152
407
|
return result;
|
|
153
408
|
}
|
|
154
409
|
|
|
155
|
-
function discoverClassVfuncs(klass: AnyClass):
|
|
156
|
-
return collectDiscoveredVfuncs(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`registerClass: overriding the GObject construct-time vtable slot '${methodName}' is not ` +
|
|
162
|
-
"supported; run construct-time initialization in the subclass constructor, after " +
|
|
163
|
-
"super(...), instead",
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return descriptor ?? undefined;
|
|
168
|
-
});
|
|
410
|
+
function discoverClassVfuncs(klass: AnyClass, methods: MethodTable): DiscoveredVfunc[] {
|
|
411
|
+
return collectDiscoveredVfuncs(
|
|
412
|
+
methods,
|
|
413
|
+
(methodName) => findClassVfuncDescriptor(klass, methodName) ?? undefined,
|
|
414
|
+
PROPERTY_VFUNC_NAMES,
|
|
415
|
+
);
|
|
169
416
|
}
|
|
170
417
|
|
|
171
418
|
function wrapVfunc(
|
|
172
419
|
fn: VfuncFn,
|
|
173
420
|
argDescriptors: NativeRegisterClassVfunc["argDescriptors"],
|
|
174
|
-
|
|
421
|
+
descriptor: VfuncDescriptor,
|
|
175
422
|
): VfuncFn {
|
|
176
|
-
return wrapCallback(
|
|
423
|
+
return wrapCallback(
|
|
424
|
+
fn as (...args: unknown[]) => unknown,
|
|
425
|
+
{ argDescriptors, returnDescriptor: descriptor.returnDescriptor },
|
|
426
|
+
"vfunc",
|
|
427
|
+
);
|
|
177
428
|
}
|
|
178
429
|
|
|
179
|
-
function
|
|
180
|
-
|
|
430
|
+
function discoverInterfaceBindings(
|
|
431
|
+
methods: MethodTable,
|
|
181
432
|
parentGtype: bigint,
|
|
433
|
+
declaredTypes: bigint[],
|
|
182
434
|
claimedMethodNames: Set<string>,
|
|
183
435
|
): InterfaceVfuncBinding[] {
|
|
436
|
+
const declared: Set<bigint> = new Set(declaredTypes);
|
|
437
|
+
const gtypes: Set<bigint> = new Set([...typeInterfaces(parentGtype), ...declaredTypes]);
|
|
184
438
|
const bindings: InterfaceVfuncBinding[] = [];
|
|
185
439
|
|
|
186
|
-
for (const
|
|
187
|
-
const
|
|
440
|
+
for (const gtype of gtypes) {
|
|
441
|
+
const vfuncs = discoverInterfaceVfuncs(methods, gtype, claimedMethodNames);
|
|
188
442
|
|
|
189
|
-
if (
|
|
190
|
-
bindings.push({ gtype
|
|
443
|
+
if (vfuncs.length > 0 || declared.has(gtype)) {
|
|
444
|
+
bindings.push({ gtype, vfuncs });
|
|
191
445
|
}
|
|
192
446
|
}
|
|
193
447
|
|
|
@@ -195,51 +449,58 @@ function discoverInheritedInterfaceVfuncs(
|
|
|
195
449
|
}
|
|
196
450
|
|
|
197
451
|
function discoverInterfaceVfuncs(
|
|
198
|
-
|
|
452
|
+
methods: MethodTable,
|
|
199
453
|
interfaceGtype: bigint,
|
|
200
454
|
claimedMethodNames: Set<string>,
|
|
201
|
-
):
|
|
202
|
-
const vfuncRegistry = getInterfaceVfuncRegistry(interfaceGtype);
|
|
203
|
-
|
|
204
|
-
if (!vfuncRegistry) {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
|
|
455
|
+
): DiscoveredVfunc[] {
|
|
208
456
|
return collectDiscoveredVfuncs(
|
|
209
|
-
|
|
210
|
-
(methodName) =>
|
|
211
|
-
const entry = vfuncRegistry[methodName];
|
|
212
|
-
|
|
213
|
-
return entry?.kind === "interface" ? entry : undefined;
|
|
214
|
-
},
|
|
457
|
+
methods,
|
|
458
|
+
(methodName) => findInterfaceVfuncDescriptor(interfaceGtype, methodName),
|
|
215
459
|
claimedMethodNames,
|
|
216
460
|
);
|
|
217
461
|
}
|
|
218
462
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
463
|
+
const hasDispatchedProperties = (dispatch: PropertyDispatch, adoptedTypes: bigint[]): boolean =>
|
|
464
|
+
dispatch.accessors.length > 0 || adoptedTypes.length > 0;
|
|
465
|
+
|
|
466
|
+
function propertyVfuncFor(
|
|
467
|
+
source: PropertyVfuncSource,
|
|
468
|
+
spec: PropertyVfuncSpec,
|
|
469
|
+
): DiscoveredVfunc | undefined {
|
|
470
|
+
const { klass, methods, dispatch, adoptedTypes } = source;
|
|
471
|
+
|
|
472
|
+
const dispatched = hasDispatchedProperties(dispatch, adoptedTypes)
|
|
473
|
+
? spec.makeDispatch(dispatch)
|
|
474
|
+
: undefined;
|
|
475
|
+
|
|
476
|
+
const fn = methods.get(spec.methodName) ?? dispatched;
|
|
223
477
|
|
|
224
|
-
|
|
225
|
-
|
|
478
|
+
return fn === undefined ? undefined : buildPropertyVfunc(klass, spec.methodName, fn, spec.isValueOut);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function propertyVfuncs(source: PropertyVfuncSource): DiscoveredVfunc[] {
|
|
482
|
+
return PROPERTY_VFUNC_SPECS.map((spec) => propertyVfuncFor(source, spec)).filter(
|
|
483
|
+
(vfunc) => vfunc !== undefined,
|
|
226
484
|
);
|
|
227
485
|
}
|
|
228
486
|
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
487
|
+
function markArg(argDescriptors: Descriptor[], index: number, patch: ArgPatch): Descriptor[] {
|
|
488
|
+
return argDescriptors.map((arg, at) => (at === index ? { ...arg, ...patch } : arg));
|
|
489
|
+
}
|
|
233
490
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
491
|
+
function isTeardownSlot(descriptor: VfuncDescriptor): boolean {
|
|
492
|
+
return (
|
|
493
|
+
TEARDOWN_VFUNC_NAMES.has(descriptor.vfuncName) &&
|
|
494
|
+
descriptor.argDescriptors[INSTANCE_ARG_INDEX]?.kind === "object"
|
|
495
|
+
);
|
|
238
496
|
}
|
|
239
497
|
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
498
|
+
function slotArgDescriptors(descriptor: VfuncDescriptor): Descriptor[] {
|
|
499
|
+
if (!isTeardownSlot(descriptor)) {
|
|
500
|
+
return descriptor.argDescriptors;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return markArg(descriptor.argDescriptors, INSTANCE_ARG_INDEX, { isCallScoped: true });
|
|
243
504
|
}
|
|
244
505
|
|
|
245
506
|
function buildPropertyVfunc(
|
|
@@ -247,7 +508,7 @@ function buildPropertyVfunc(
|
|
|
247
508
|
methodName: string,
|
|
248
509
|
fn: VfuncFn,
|
|
249
510
|
isValueOut: boolean,
|
|
250
|
-
):
|
|
511
|
+
): DiscoveredVfunc {
|
|
251
512
|
const descriptor = findClassVfuncDescriptor(klass, methodName);
|
|
252
513
|
|
|
253
514
|
if (!descriptor) {
|
|
@@ -255,19 +516,34 @@ function buildPropertyVfunc(
|
|
|
255
516
|
}
|
|
256
517
|
|
|
257
518
|
const argDescriptors = isValueOut
|
|
258
|
-
?
|
|
519
|
+
? markArg(descriptor.argDescriptors, VALUE_ARG_INDEX, { isCallerAllocated: true })
|
|
259
520
|
: descriptor.argDescriptors;
|
|
260
521
|
|
|
261
522
|
return {
|
|
262
523
|
...descriptor,
|
|
263
524
|
methodName,
|
|
264
525
|
argDescriptors,
|
|
265
|
-
fn: wrapVfunc(fn, argDescriptors, descriptor
|
|
526
|
+
fn: wrapVfunc(fn, argDescriptors, descriptor),
|
|
266
527
|
};
|
|
267
528
|
}
|
|
268
529
|
|
|
530
|
+
function toNativeInterface(binding: InterfaceVfuncBinding): NativeRegisterClassInterface {
|
|
531
|
+
const nativeInterface: NativeRegisterClassInterface = {
|
|
532
|
+
type: binding.gtype,
|
|
533
|
+
vfuncs: [...binding.vfuncs],
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const vtableSize = binding.vfuncs[0]?.vtableSize;
|
|
537
|
+
|
|
538
|
+
if (vtableSize !== undefined) {
|
|
539
|
+
nativeInterface.vtableSize = vtableSize;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return nativeInterface;
|
|
543
|
+
}
|
|
544
|
+
|
|
269
545
|
function toNativeOptions(
|
|
270
|
-
classVfuncs:
|
|
546
|
+
classVfuncs: DiscoveredVfunc[],
|
|
271
547
|
interfaceBindings: InterfaceVfuncBinding[],
|
|
272
548
|
properties: Record<string, PropertySpec>,
|
|
273
549
|
): NativeRegisterClassOptions | undefined {
|
|
@@ -290,14 +566,10 @@ function toNativeOptions(
|
|
|
290
566
|
}
|
|
291
567
|
|
|
292
568
|
if (hasInterfaces) {
|
|
293
|
-
options.interfaces = interfaceBindings.map((binding) => (
|
|
294
|
-
type: binding.gtype,
|
|
295
|
-
vtableSize: binding.vtableSize,
|
|
296
|
-
vfuncs: [...binding.vfuncs],
|
|
297
|
-
}));
|
|
569
|
+
options.interfaces = interfaceBindings.map((binding) => toNativeInterface(binding));
|
|
298
570
|
}
|
|
299
571
|
|
|
300
572
|
return options;
|
|
301
573
|
}
|
|
302
574
|
|
|
303
|
-
export { registerClass };
|
|
575
|
+
export { type Interface, registerClass };
|