@gtkx/codegen 1.2.0 → 1.2.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 (34) hide show
  1. package/dist/docs/symbol-page.js +1 -1
  2. package/dist/docs/symbol-page.js.map +1 -1
  3. package/dist/gir/class.d.ts +7 -2
  4. package/dist/gir/class.d.ts.map +1 -1
  5. package/dist/gir/class.js +4 -1
  6. package/dist/gir/class.js.map +1 -1
  7. package/dist/store/gi/interface.js +2 -2
  8. package/dist/store/gi/interface.js.map +1 -1
  9. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  10. package/dist/store/gi/record-constructor.js +4 -2
  11. package/dist/store/gi/record-constructor.js.map +1 -1
  12. package/dist/store/gi/record-field-accessor.d.ts +3 -2
  13. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  14. package/dist/store/gi/record-field-accessor.js +156 -18
  15. package/dist/store/gi/record-field-accessor.js.map +1 -1
  16. package/dist/store/gi/record-layout.d.ts +5 -2
  17. package/dist/store/gi/record-layout.d.ts.map +1 -1
  18. package/dist/store/gi/record-layout.js +2 -1
  19. package/dist/store/gi/record-layout.js.map +1 -1
  20. package/dist/store/gi/signal.d.ts.map +1 -1
  21. package/dist/store/gi/signal.js +14 -14
  22. package/dist/store/gi/signal.js.map +1 -1
  23. package/dist/store/resolve-store.d.ts.map +1 -1
  24. package/dist/store/resolve-store.js +1 -0
  25. package/dist/store/resolve-store.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/docs/symbol-page.ts +1 -1
  28. package/src/gir/class.ts +12 -3
  29. package/src/store/gi/interface.ts +2 -2
  30. package/src/store/gi/record-constructor.ts +4 -2
  31. package/src/store/gi/record-field-accessor.ts +241 -20
  32. package/src/store/gi/record-layout.ts +11 -1
  33. package/src/store/gi/signal.ts +20 -23
  34. package/src/store/resolve-store.ts +1 -0
@@ -1,6 +1,7 @@
1
1
  import { toCamelIdentifier } from "@gtkx/utils";
2
2
  import type { GirAnnotations } from "../../gir/annotations.js";
3
3
  import type { GirField } from "../../gir/field.js";
4
+ import type { PrimitiveCategory } from "../../gir/primitives.js";
4
5
  import type { GirRecord } from "../../gir/record.js";
5
6
  import type { FieldSlot } from "../../gir/size.js";
6
7
  import type { TypeId } from "../../gir/type-id.js";
@@ -9,9 +10,16 @@ import type { ModuleContext } from "../../writer/context.js";
9
10
  import { isInlineCallbackRef, renderDescriptor } from "../../analysis/descriptor-render.js";
10
11
  import { tStruct } from "../../analysis/descriptor.js";
11
12
  import { renderTsType } from "../../analysis/ts-type.js";
13
+ import { isByteSequence, isUnboundedArray } from "../../analysis/type-shape.js";
12
14
  import { indent, renderBlock } from "../../writer/emit.js";
13
15
  import { getDoc } from "./doc-spec.js";
14
- import { bitMask, computeRecordFieldSlots, mergeBitfield, type RecordFieldSlot } from "./record-layout.js";
16
+ import {
17
+ bitMask,
18
+ computeRecordFieldSlots,
19
+ inlineArrayStride,
20
+ mergeBitfield,
21
+ type RecordFieldSlot,
22
+ } from "./record-layout.js";
15
23
  import { wrapReturnValue } from "./return-wrap.js";
16
24
  import { isValueMarshalable } from "./value-marshalable.js";
17
25
 
@@ -27,6 +35,10 @@ type AdmittedField = {
27
35
  jsName: string;
28
36
  };
29
37
 
38
+ type AccessibleField = AdmittedField & {
39
+ isSettable: boolean;
40
+ };
41
+
30
42
  type RecordFieldEntry = {
31
43
  jsName: string;
32
44
  tsType: string;
@@ -94,6 +106,22 @@ type AccessorOptions = {
94
106
  fieldType: TypeId;
95
107
  };
96
108
 
109
+ type InlineArrayResolution = {
110
+ element: TypeId;
111
+ count: number;
112
+ stride: number;
113
+ isTypedByteArray: boolean;
114
+ };
115
+
116
+ type InlineArrayAccessorOptions = {
117
+ context: ModuleContext;
118
+ jsName: string;
119
+ tsType: string;
120
+ descriptor: string;
121
+ offset: number;
122
+ resolution: InlineArrayResolution;
123
+ };
124
+
97
125
  const isMarshalableField = (context: ModuleContext, field: GirField): boolean => {
98
126
  if (field.cType?.endsWith("*") === true) {
99
127
  return true;
@@ -167,12 +195,52 @@ const admitField = (
167
195
  return { field, jsName };
168
196
  };
169
197
 
198
+ const isNullTerminatedArrayField = (context: ModuleContext, ref: TypeId): boolean => {
199
+ const type = context.library.typeFor(ref);
200
+
201
+ return type?.kind === "carray" && isUnboundedArray(type) && type.elementCType?.endsWith("*") === true;
202
+ };
203
+
204
+ const isAccessibleFieldType = (context: ModuleContext, ref: TypeId): boolean =>
205
+ isAccessorEligibleType(context, ref) || isNullTerminatedArrayField(context, ref);
206
+
207
+ const isStorableFieldType = (context: ModuleContext, ref: TypeId): boolean => {
208
+ const kind = context.library.typeFor(ref)?.kind;
209
+
210
+ return kind !== "carray" && kind !== "list" && kind !== "hashtable";
211
+ };
212
+
213
+ const hasArrayAccessor = (context: ModuleContext, target: StructArrayTarget): boolean =>
214
+ resolveInlineArray(context, target) !== undefined || resolveStructArray(context, target) !== undefined;
215
+
216
+ const admitAccessibleField = (
217
+ context: ModuleContext,
218
+ slot: RecordFieldSlot,
219
+ claimedNames: Set<string>,
220
+ siblingFields: GirField[],
221
+ ): AccessibleField | undefined => {
222
+ const admitted = admitField(context, slot, claimedNames);
223
+
224
+ if (admitted === undefined) {
225
+ return undefined;
226
+ }
227
+
228
+ const { field, jsName } = admitted;
229
+ const target: StructArrayTarget = { field, jsName, slot: slot.slot, siblingFields };
230
+ const hasArray = hasArrayAccessor(context, target);
231
+ const hasAccessor = hasArray || isAccessibleFieldType(context, field.type);
232
+ const isSettable = field.writable && (hasArray || isStorableFieldType(context, field.type));
233
+
234
+ return hasAccessor ? { ...admitted, isSettable } : undefined;
235
+ };
236
+
170
237
  const resolveRecordFieldEntry = (
171
238
  context: ModuleContext,
172
239
  slot: RecordFieldSlot,
173
240
  claimedNames: Set<string>,
241
+ siblingFields: GirField[],
174
242
  ): RecordFieldEntry | undefined => {
175
- const admitted = admitField(context, slot, claimedNames);
243
+ const admitted = admitAccessibleField(context, slot, claimedNames, siblingFields);
176
244
 
177
245
  if (admitted === undefined) {
178
246
  return undefined;
@@ -181,7 +249,7 @@ const resolveRecordFieldEntry = (
181
249
  return {
182
250
  jsName: admitted.jsName,
183
251
  tsType: renderTsType(context, admitted.field.type, false),
184
- isWritable: admitted.field.writable,
252
+ isWritable: admitted.isSettable,
185
253
  doc: admitted.field.doc,
186
254
  annotations: admitted.field.annotations,
187
255
  };
@@ -193,7 +261,7 @@ const renderRecordFieldAccessor = (
193
261
  claimedNames: Set<string>,
194
262
  siblingFields: GirField[],
195
263
  ): string | undefined => {
196
- const admitted = admitField(context, slot, claimedNames);
264
+ const admitted = admitAccessibleField(context, slot, claimedNames, siblingFields);
197
265
 
198
266
  if (admitted === undefined) {
199
267
  return undefined;
@@ -201,16 +269,11 @@ const renderRecordFieldAccessor = (
201
269
 
202
270
  const { field, jsName } = admitted;
203
271
  const doc = getDoc(field);
204
- const structArray = renderStructArrayAccessor(context, { field, jsName, slot: slot.slot, siblingFields });
205
-
206
- if (structArray !== undefined) {
207
- return `${doc}${structArray}`;
208
- }
209
-
210
- if (!isAccessorEligibleType(context, field.type)) {
211
- const tsType = renderTsType(context, field.type, false);
272
+ const target: StructArrayTarget = { field, jsName, slot: slot.slot, siblingFields };
273
+ const arrayAccessor = renderInlineArrayAccessor(context, target) ?? renderStructArrayAccessor(context, target);
212
274
 
213
- return `${doc}declare ${jsName}: ${tsType};`;
275
+ if (arrayAccessor !== undefined) {
276
+ return `${doc}${arrayAccessor}`;
214
277
  }
215
278
 
216
279
  const descriptor = context.hoistDescriptor(
@@ -230,7 +293,7 @@ const renderRecordFieldAccessor = (
230
293
 
231
294
  const blocks: string[] = [getterBlock(accessorOptions)];
232
295
 
233
- if (field.writable) {
296
+ if (admitted.isSettable) {
234
297
  blocks.push(setterBlock(accessorOptions));
235
298
  }
236
299
 
@@ -259,7 +322,7 @@ const isAccessorEligibleType = (context: ModuleContext, ref: TypeId): boolean =>
259
322
  return context.library.nameFor(ref) !== undefined;
260
323
  }
261
324
  case "carray": {
262
- return type.fixedSize !== undefined;
325
+ return type.fixedSize !== undefined && type.arrayCType?.endsWith("*") === true;
263
326
  }
264
327
  case "list":
265
328
  case "hashtable":
@@ -302,15 +365,96 @@ const resolveInlineStructFields = (
302
365
  return type.value.fields;
303
366
  };
304
367
 
368
+ const isInlineScalarCategory = (category: PrimitiveCategory, isBehindPointer: boolean): boolean => {
369
+ if (category === "pointer") {
370
+ return true;
371
+ }
372
+
373
+ return !isBehindPointer && category !== "void" && category !== "unichar" && category !== "string";
374
+ };
375
+
376
+ const isInlineArrayElement = (context: ModuleContext, ref: TypeId | undefined, cType: string | undefined): boolean => {
377
+ const type = ref === undefined ? undefined : context.library.typeFor(ref);
378
+
379
+ if (type === undefined) {
380
+ return false;
381
+ }
382
+
383
+ const isBehindPointer = cType?.endsWith("*") === true;
384
+
385
+ switch (type.kind) {
386
+ case "primitive": {
387
+ return isInlineScalarCategory(type.category, isBehindPointer);
388
+ }
389
+ case "enum": {
390
+ return !isBehindPointer;
391
+ }
392
+ case "record": {
393
+ return (
394
+ resolveInlineStructFields(context, ref, cType) !== undefined &&
395
+ isValueMarshalable(context, type.namespace.name, type.value)
396
+ );
397
+ }
398
+ case "alias": {
399
+ return isInlineArrayElement(context, type.value.target, cType ?? type.value.targetCType);
400
+ }
401
+ case "callback":
402
+ case "carray":
403
+ case "class":
404
+ case "hashtable":
405
+ case "interface":
406
+ case "list":
407
+ case "varargs": {
408
+ return false;
409
+ }
410
+ }
411
+ };
412
+
413
+ const inlineArrayType = (
414
+ context: ModuleContext,
415
+ target: StructArrayTarget,
416
+ ): Extract<GirType, { kind: "carray" }> | undefined => {
417
+ const { field, slot } = target;
418
+
419
+ if (field.type === undefined || slot.bitWidth !== undefined) {
420
+ return undefined;
421
+ }
422
+
423
+ const type = context.library.typeFor(field.type);
424
+
425
+ if (type?.kind !== "carray" || type.fixedSize === undefined) {
426
+ return undefined;
427
+ }
428
+
429
+ return type.arrayCType?.endsWith("*") === true ? undefined : type;
430
+ };
431
+
432
+ const resolveInlineArray = (context: ModuleContext, target: StructArrayTarget): InlineArrayResolution | undefined => {
433
+ const type = inlineArrayType(context, target);
434
+
435
+ if (type?.fixedSize === undefined || !isInlineArrayElement(context, type.element, type.elementCType)) {
436
+ return undefined;
437
+ }
438
+
439
+ const stride = inlineArrayStride(context, type);
440
+
441
+ if (stride === 0) {
442
+ return undefined;
443
+ }
444
+
445
+ return {
446
+ element: type.element,
447
+ count: type.fixedSize,
448
+ stride,
449
+ isTypedByteArray: context.library.isByteArrayTyped && isByteSequence(context.library, type),
450
+ };
451
+ };
452
+
305
453
  const arrayLengthExpression = (
306
454
  arrayRef: Extract<GirType, { kind: "carray" }>,
307
455
  siblingFields: GirField[],
308
456
  ): string | undefined => {
309
- if (arrayRef.fixedSize !== undefined) {
310
- return String(arrayRef.fixedSize);
311
- }
312
-
313
- if (arrayRef.lengthParameterIndex === undefined) {
457
+ if (arrayRef.fixedSize !== undefined || arrayRef.lengthParameterIndex === undefined) {
314
458
  return undefined;
315
459
  }
316
460
 
@@ -570,6 +714,82 @@ const resolveStructArray = (context: ModuleContext, target: StructArrayTarget):
570
714
  };
571
715
  };
572
716
 
717
+ const inlineArrayElementOffset = (options: InlineArrayAccessorOptions): string =>
718
+ `${String(options.offset)} + __index * ${String(options.resolution.stride)}`;
719
+
720
+ const inlineArrayGetterBlock = (options: InlineArrayAccessorOptions): string => {
721
+ const { context, jsName, tsType, descriptor, resolution } = options;
722
+ const container = resolution.isTypedByteArray ? `new ${tsType}(${String(resolution.count)})` : "[]";
723
+
724
+ const element = wrapReturnValue(context, {
725
+ ref: resolution.element,
726
+ isNullable: false,
727
+ valueExpression: `read(getHandle(this), ${descriptor}, ${inlineArrayElementOffset(options)})`,
728
+ });
729
+
730
+ const body = [
731
+ `const __result: ${tsType} = ${container};`,
732
+ `for (let __index = 0; __index < ${String(resolution.count)}; __index++) {`,
733
+ indent(`__result[__index] = ${element};`, 1),
734
+ "}",
735
+ "return __result;",
736
+ ].join("\n");
737
+
738
+ return renderBlock(`get ${jsName}(): ${tsType}`, body);
739
+ };
740
+
741
+ const inlineArraySetterBlock = (options: InlineArrayAccessorOptions): string => {
742
+ const { jsName, tsType, descriptor, resolution } = options;
743
+
744
+ const write =
745
+ `write(getHandle(this), ${descriptor}, ${inlineArrayElementOffset(options)}, ` +
746
+ `toNative(${descriptor}, __element));`;
747
+
748
+ const body = [
749
+ "for (const [__index, __element] of __value.entries()) {",
750
+ indent(`if (__index >= ${String(resolution.count)}) {\n break;\n}\n\n${write}`, 1),
751
+ "}",
752
+ ].join("\n");
753
+
754
+ return renderBlock(`set ${jsName}(__value: ${tsType})`, body);
755
+ };
756
+
757
+ const renderInlineArrayAccessor = (context: ModuleContext, target: StructArrayTarget): string | undefined => {
758
+ const resolution = resolveInlineArray(context, target);
759
+
760
+ if (resolution === undefined) {
761
+ return undefined;
762
+ }
763
+
764
+ const { field, jsName, slot } = target;
765
+ context.addRuntimeImport("read");
766
+ context.addRuntimeImport("getHandle");
767
+ context.addRuntimeImport("t");
768
+
769
+ const options: InlineArrayAccessorOptions = {
770
+ context,
771
+ jsName,
772
+ tsType: renderTsType(context, field.type, false),
773
+ descriptor: context.hoistDescriptor(renderDescriptor(context, resolution.element, "none", { isInline: true })),
774
+ offset: slot.byteOffset,
775
+ resolution,
776
+ };
777
+
778
+ const blocks: string[] = [];
779
+
780
+ if (field.readable) {
781
+ blocks.push(inlineArrayGetterBlock(options));
782
+ }
783
+
784
+ if (field.writable) {
785
+ context.addRuntimeImport("write");
786
+ context.addRuntimeImport("toNative");
787
+ blocks.push(inlineArraySetterBlock(options));
788
+ }
789
+
790
+ return blocks.length === 0 ? undefined : blocks.join("\n\n");
791
+ };
792
+
573
793
  const renderStructArrayAccessor = (context: ModuleContext, target: StructArrayTarget): string | undefined => {
574
794
  const resolution = resolveStructArray(context, target);
575
795
 
@@ -654,6 +874,7 @@ const setterBlock = (options: AccessorOptions): string =>
654
874
  export {
655
875
  isEmittableField,
656
876
  isInlineField,
877
+ isStorableFieldType,
657
878
  emitFieldWrite,
658
879
  resolveRecordFieldEntry,
659
880
  renderRecordFieldAccessor,
@@ -56,6 +56,9 @@ const recordInlineSize = (context: ModuleContext, record: ResolvedRecordValue):
56
56
  return size > 0 ? size : undefined;
57
57
  };
58
58
 
59
+ const inlineArrayStride = (context: ModuleContext, array: Extract<GirType, { kind: "carray" }>): number =>
60
+ layoutOfType(context, array.element, array.elementCType, new Set()).size;
61
+
59
62
  const bitMask = (width: number): number => {
60
63
  if (width >= 32) {
61
64
  return 0xFF_FF_FF_FF;
@@ -224,4 +227,11 @@ const resolveAliasLayout = (
224
227
  return layoutOfType(context, ref, resolved.value.targetCType, visited);
225
228
  };
226
229
 
227
- export { computeRecordFieldSlots, recordInlineSize, bitMask, mergeBitfield, type RecordFieldSlot };
230
+ export {
231
+ computeRecordFieldSlots,
232
+ inlineArrayStride,
233
+ recordInlineSize,
234
+ bitMask,
235
+ mergeBitfield,
236
+ type RecordFieldSlot,
237
+ };
@@ -1,5 +1,5 @@
1
1
  import { camelCase, sourceStringLiteral } from "@gtkx/utils";
2
- import type { GirClass } from "../../gir/class.js";
2
+ import type { GirClass, GirSignal } from "../../gir/class.js";
3
3
  import type { GirCallable, GirParameter, GirReturnValue } from "../../gir/parameter.js";
4
4
  import type { GirProperty } from "../../gir/property.js";
5
5
  import type { ModuleContext } from "../../writer/context.js";
@@ -139,16 +139,20 @@ const renderSignalMap = (spec: SignalMapSpec): string => {
139
139
  const { context, klass, className, isParentlessObjectSubclass, suffix, renderEntry } = spec;
140
140
  const extendsRefs = signalMapParentRefs(context, klass, isParentlessObjectSubclass, suffix);
141
141
  const extendsClause = extendsRefs.length === 0 ? "" : ` extends ${extendsRefs.join(", ")}`;
142
+ const signals = collectClassSignals(context, klass);
142
143
 
143
- const signalEntries = collectClassSignals(context, klass).map(
144
- (signal) => `${signalDoc(signal)}${sourceStringLiteral(signal.name)}: ${renderEntry(context, signal)};`,
145
- );
144
+ const signalEntries = signals.flatMap((signal) => {
145
+ const value = renderEntry(context, signal);
146
+ const entry = `${signalDoc(signal)}${sourceStringLiteral(signal.name)}: ${value};`;
146
147
 
147
- const entries = [
148
- ...signalEntries,
149
- ...renderNotifyDetailEntries(context, klass, suffix),
150
- ...renderCustomNotifyEntry(context, klass, suffix),
151
- ];
148
+ if (!signal.isDetailed) {
149
+ return [entry];
150
+ }
151
+
152
+ return [entry, `[detail: \`${signal.name}::\${string}\`]: ${value};`];
153
+ });
154
+
155
+ const entries = [...signalEntries, ...renderNotifyDetailEntries(context, klass, suffix)];
152
156
 
153
157
  return renderBracedOrEmpty(`export interface ${className}${suffix}${extendsClause}`, entries.join("\n"));
154
158
  };
@@ -156,14 +160,6 @@ const renderSignalMap = (spec: SignalMapSpec): string => {
156
160
  const signalDoc = (signal: GirCallable): string =>
157
161
  renderJsDoc(signal.doc, undefined, handlerSpec(signal, signal.parameters));
158
162
 
159
- const renderCustomNotifyEntry = (context: ModuleContext, klass: GirClass, suffix: string): string[] => {
160
- if (context.namespace.name !== "GObject" || klass.name !== "Object") {
161
- return [];
162
- }
163
-
164
- return [`[detail: \`notify::\${string}\`]: ${gobjectObjectMapRef(context, suffix)}["notify"];`];
165
- };
166
-
167
163
  const renderNotifyDetailEntries = (context: ModuleContext, klass: GirClass, suffix: string): string[] => {
168
164
  const notifyValue = `${gobjectObjectMapRef(context, suffix)}["notify"]`;
169
165
 
@@ -219,10 +215,11 @@ const renderSignalConnectInterface = (className: string, isRootObject: boolean):
219
215
  `__signals__?: ${map};`,
220
216
  `connect<K extends keyof ${map}>(signal: K, handler: ${map}[K], isAfter?: boolean): number;`,
221
217
  `emit<K extends keyof ${emitMap}>(sigName: K, ...args: ${emitMap}[K]["args"]): ${emitMap}[K]["result"];`,
222
- "emit(sigName: string, ...args: unknown[]): unknown;",
223
218
  ];
224
219
 
225
- if (!isRootObject) {
220
+ if (isRootObject) {
221
+ lines.push("emit(sigName: string, ...args: unknown[]): unknown;");
222
+ } else {
226
223
  const chainable = (methods: string[], trailing: string): void => {
227
224
  for (const method of methods) {
228
225
  lines.push(
@@ -382,7 +379,7 @@ const renderCallback = (context: ModuleContext, signal: GirCallable): string =>
382
379
  const forEachInterfaceSignal = (
383
380
  context: ModuleContext,
384
381
  klass: GirClass,
385
- consider: (signal: GirCallable) => void,
382
+ consider: (signal: GirSignal) => void,
386
383
  ): void => {
387
384
  for (const iface of resolveInterfaces(context.library, context.namespace.name, klass.implements)) {
388
385
  for (const signal of iface.klass.signals) {
@@ -391,12 +388,12 @@ const forEachInterfaceSignal = (
391
388
  }
392
389
  };
393
390
 
394
- const collectClassSignals = (context: ModuleContext, klass: GirClass): GirCallable[] => {
391
+ const collectClassSignals = (context: ModuleContext, klass: GirClass): GirSignal[] => {
395
392
  const inheritedNames = collectInheritedSignalNames(context, klass);
396
393
  const seen: Set<string> = new Set();
397
- const result: GirCallable[] = [];
394
+ const result: GirSignal[] = [];
398
395
 
399
- const consider = (signal: GirCallable): void => {
396
+ const consider = (signal: GirSignal): void => {
400
397
  const name = camelCase(signal.name);
401
398
 
402
399
  if (!signal.introspectable || inheritedNames.has(name) || seen.has(name)) {
@@ -28,6 +28,7 @@ const STORE_CONSUMERS: string[] = [
28
28
  "@gtkx/components",
29
29
  "@gtkx/css",
30
30
  "@gtkx/react",
31
+ "@gtkx/runtime",
31
32
  "@gtkx/testing",
32
33
  ];
33
34