Skip to content

Commit eabb12a

Browse files
feat: GArray tuple support.
Much like JS Array tuples, there are a plethora of APIs that are able to mutate the GArray tuple to violate the tuple typing. The intent is that only basic set/get functions (which are type safe for tuples) ought to be used. For now I've opted not to add support for tuples to GArray's proxy() types. Simply because the types involved are already very complicated/slow. Perhaps this will be revisited in future.
1 parent 555acc6 commit eabb12a

File tree

5 files changed

+136
-77
lines changed

5 files changed

+136
-77
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@godot-js/editor": patch
3+
---
4+
5+
**Feature:** `GArray` tuple support.
6+
7+
For example, with the type `GArray<[number, string]>` `.get(0)` will return a `number` and `.get(1)` will return a `string`.

scripts/jsb.editor/src/jsb.editor.codegen.ts

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -293,68 +293,86 @@ const TypeMutations: Record<string, TypeMutation> = {
293293
GArray: {
294294
generic_parameters: {
295295
T: {
296-
default: GodotAnyType,
296+
default: `${GodotAnyType} | ${GodotAnyType}[]`,
297+
extends: `${GodotAnyType} | ${GodotAnyType}[]`
297298
},
298299
},
299300
intro: [
300301
"/** Builder function that returns a GArray populated with elements from a JS array. */",
301-
"static create<T>(elements: [T] extends [GArray<infer E>] ? Array<E | GProxyValueWrap<E>> : Array<T | GProxyValueWrap<T>>):",
302-
" [T] extends [GArray<infer E>]",
303-
" ? [GValueWrap<E>] extends [never]",
304-
" ? never",
305-
" : GArray<GValueWrap<T>>",
306-
" : [GValueWrap<T>] extends [never]",
307-
" ? never",
308-
" : GArray<GValueWrap<T>>",
309-
"[Symbol.iterator](): IteratorObject<T>",
302+
"static create<A extends any[]>(elements: A): GValueWrap<A>",
303+
"static create<A extends GArray<any>>(",
304+
" elements: A extends GArray<infer T>",
305+
" ? [T] extends [any[]]",
306+
" ? { [I in keyof T]: GDataStructureCreateValue<T[I]> }",
307+
" : Array<GDataStructureCreateValue<T>>",
308+
" : never",
309+
"): GValueWrap<A>",
310+
"static create<E extends GAny>(elements: Array<GDataStructureCreateValue<E>>): GArray<E>",
311+
"[Symbol.iterator](): IteratorObject<GArrayElement<T>>",
310312
"/** Returns a Proxy that targets this GArray but behaves similar to a JavaScript array. */",
311-
"proxy<Write extends boolean = false>(): Write extends true ? GArrayProxy<T> : GArrayReadProxy<T>",
313+
"proxy<Write extends boolean = false>(): Write extends true ? GArrayProxy<GArrayElement<T>> : GArrayReadProxy<GArrayElement<T>>",
312314
"",
313-
`${names.get_member("set_indexed")}(index: number, value: T): void`,
314-
`${names.get_member("get_indexed")}(index: number): T`,
315+
`${names.get_member("set_indexed")}<I extends int64>(index: I, value: GArrayElement<T, I>): void`,
316+
`${names.get_member("get_indexed")}<I extends int64>(index: I): GArrayElement<T, I>`,
315317
],
316318
property_overrides: {
317-
set: mutate_parameter_type("value", "T"),
318-
push_back: mutate_parameter_type("value", "T"),
319-
push_front: mutate_parameter_type("value", "T"),
320-
append: mutate_parameter_type("value", "T"),
321-
insert: mutate_parameter_type("value", "T"),
322-
fill: mutate_parameter_type("value", "T"),
323-
erase: mutate_parameter_type("value", "T"),
324-
count: mutate_parameter_type("value", "T"),
325-
has: mutate_parameter_type("value", "T"),
326-
bsearch: mutate_parameter_type("value", "T"),
327-
bsearch_custom: chain_mutators(mutate_parameter_type("value", "T"), mutate_parameter_type("func", "Callable2<T, T, boolean>")),
328-
find: mutate_parameter_type("what", "T"),
329-
rfind: mutate_parameter_type("what", "T"),
330-
get: mutate_return_type("T"),
331-
front: mutate_return_type("T"),
332-
back: mutate_return_type("T"),
333-
pick_random: mutate_return_type("T"),
334-
pop_back: mutate_return_type("T"),
335-
pop_front: mutate_return_type("T"),
336-
pop_at: mutate_return_type("T"),
337-
min: mutate_return_type("T"),
338-
max: mutate_return_type("T"),
339-
sort_custom: mutate_parameter_type("func", "Callable2<T, T, boolean>"),
340-
all: mutate_parameter_type("method", "Callable1<T, boolean>"),
341-
any: mutate_parameter_type("method", "Callable1<T, boolean>"),
342-
filter: chain_mutators(mutate_parameter_type("method", "Callable1<T, boolean>"), mutate_return_type("GArray<T>")),
343-
map: chain_mutators(mutate_parameter_type("method", "Callable1<T, U>"), mutate_return_type("GArray<U>"), mutate_template("U")),
344-
append_array: mutate_parameter_type("array", "GArray<T>"),
345-
duplicate: mutate_return_type("GArray<T>"),
346-
slice: mutate_return_type("GArray<T>"),
319+
set: [`set<I extends int64>(index: I, value: GArrayElement<T, I>): void`],
320+
push_back: mutate_parameter_type("value", "GArrayElement<T>"),
321+
push_front: mutate_parameter_type("value", "GArrayElement<T>"),
322+
append: mutate_parameter_type("value", "GArrayElement<T>"),
323+
insert: mutate_parameter_type("value", "GArrayElement<T>"),
324+
fill: mutate_parameter_type("value", "GArrayElement<T>"),
325+
erase: mutate_parameter_type("value", "GArrayElement<T>"),
326+
count: mutate_parameter_type("value", "GArrayElement<T>"),
327+
has: mutate_parameter_type("value", "GArrayElement<T>"),
328+
bsearch: mutate_parameter_type("value", "GArrayElement<T>"),
329+
bsearch_custom: chain_mutators(mutate_parameter_type("value", "GArrayElement<T>"), mutate_parameter_type("func", "Callable<(a: GArrayElement<T>, b: GArrayElement<T>) => boolean>")),
330+
find: mutate_parameter_type("what", "GArrayElement<T>"),
331+
rfind: mutate_parameter_type("what", "GArrayElement<T>"),
332+
get: [`get<I extends int64>(index: I): GArrayElement<T, I>`],
333+
front: mutate_return_type("GArrayElement<T>"),
334+
back: mutate_return_type("GArrayElement<T>"),
335+
pick_random: mutate_return_type("GArrayElement<T>"),
336+
pop_back: mutate_return_type("GArrayElement<T>"),
337+
pop_front: mutate_return_type("GArrayElement<T>"),
338+
pop_at: mutate_return_type("GArrayElement<T>"),
339+
min: mutate_return_type("GArrayElement<T>"),
340+
max: mutate_return_type("GArrayElement<T>"),
341+
sort_custom: mutate_parameter_type("func", "Callable<(a: GArrayElement<T>, b: GArrayElement<T>) => boolean>"),
342+
all: mutate_parameter_type("method", "Callable<(value: GArrayElement<T>) => boolean>"),
343+
any: mutate_parameter_type("method", "Callable<(value: GArrayElement<T>) => boolean>"),
344+
filter: chain_mutators(mutate_parameter_type("method", "Callable<(value: GArrayElement<T>) => boolean>"), mutate_return_type("GArray<GArrayElement<T>>")),
345+
map: chain_mutators(mutate_parameter_type("method", "Callable<(value: GArrayElement<T>) => U>"), mutate_return_type("GArray<U>"), mutate_template("U")),
346+
append_array: mutate_parameter_type("array", "GArray<GArrayElement<T>>"),
347+
duplicate: mutate_return_type("this"),
348+
slice: mutate_return_type("GArray<GArrayElement<T>>"),
347349
},
348350
},
349351
GDictionary: {
352+
prelude: [
353+
"type GArrayCreateSource<T> = ReadonlyArray<T> | {",
354+
" [Symbol.iterator](): IteratorObject<GDataStructureCreateValue<T>>;",
355+
" [K: number]: GDataStructureCreateValue<T>;",
356+
"}",
357+
"type GDataStructureCreateValue<V> = V | (",
358+
" V extends GArray<infer T>",
359+
" ? [T] extends [any[]]",
360+
" ? GArrayCreateSource<{ [I in keyof T]: GDataStructureCreateValue<T[I]> }>",
361+
" : GArrayCreateSource<GDataStructureCreateValue<T>>",
362+
" : V extends GDictionary<infer T>",
363+
" ? { [K in keyof T]: GDataStructureCreateValue<T[K]> }",
364+
" : never",
365+
" )"
366+
],
350367
generic_parameters: {
351368
T: {
352369
default: "Record<any, any>",
353370
},
354371
},
355372
intro: [
356373
"/** Builder function that returns a GDictionary with properties populated from a source JS object. */",
357-
"static create<T>(properties: T extends GDictionary<infer S> ? GDictionaryProxy<S> : GDictionaryProxy<T>): T extends GDictionary<infer S> ? GValueWrap<S> : GValueWrap<T>",
374+
"static create<V extends { [key: number | string]: GWrappableValue }>(properties: V): GValueWrap<V>",
375+
"static create<V extends GDictionary<any>>(properties: V extends GDictionary<infer T> ? { [K in keyof T]: GDataStructureCreateValue<T[K]> } : never): V",
358376
"[Symbol.iterator](): IteratorObject<{ key: any, value: any }>",
359377
"/** Returns a Proxy that targets this GDictionary but behaves similar to a regular JavaScript object. Values are exposed as enumerable properties, so Object.keys(), Object.entries() etc. will work. */",
360378
"proxy<Write extends boolean = false>(): Write extends true ? GDictionaryProxy<T> : GDictionaryReadProxy<T>",

scripts/jsb.runtime/src/jsb.inject.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type * as Godot from "godot";
22
import type * as GodotJsb from "godot-jsb";
33

4-
type GArrayProxy<T> = Godot.GArrayProxy<T> & {
4+
type GArrayProxy<T extends Godot.GAny> = Godot.GArrayProxy<T> & {
55
[Godot.ProxyTarget]: Godot.GArray<T>;
66
}
77

@@ -47,7 +47,7 @@ function get_helpers(): ProxyHelpers {
4747
},
4848
godot_wrap: function (value: any) {
4949
if (Array.isArray(value)) {
50-
return GArray.create(value);
50+
return GArray.create<any>(value);
5151
}
5252

5353
const proto = Object.getPrototypeOf(value);

scripts/typings/godot.generated.d.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,24 @@ declare module "godot" {
1111
class Script extends Resource { }
1212
interface ResourceTypes { }
1313

14-
class GArray<T = any> {
15-
static create<T>(elements: [T] extends [GArray<infer E>] ? Array<E | GProxyValueWrap<E>> : Array<T | GProxyValueWrap<T>>):
16-
[T] extends [GArray<infer E>]
17-
? [GValueWrap<E>] extends [never]
18-
? never
19-
: GArray<GValueWrap<T>>
20-
: [GValueWrap<T>] extends [never]
21-
? never
22-
: GArray<GValueWrap<T>>
23-
[Symbol.iterator](): IteratorObject<T>
24-
proxy<Write extends boolean = false>(): Write extends true ? GArrayProxy<T> : GArrayReadProxy<T>;
25-
get_indexed(index: number): T
26-
get(index: int64): T
27-
set(index: int64, value: T): void
28-
size(): number
29-
push_back(value: T): void
30-
pop_back(): T
31-
has(value: T): boolean
32-
find(what: T, from: int64 = 0): int64
14+
type GArrayCreateSource<T> = ReadonlyArray<T> | {
15+
[Symbol.iterator](): IteratorObject<GDataStructureCreateValue<T>>;
16+
[K: number]: GDataStructureCreateValue<T>;
3317
}
34-
class GDictionary<T = any> {
35-
static create<T>(properties: T extends GDictionary<infer S> ? GDictionaryProxy<S> : GDictionaryProxy<T>): T extends GDictionary<infer S> ? GValueWrap<S> : GValueWrap<T>
18+
19+
type GDataStructureCreateValue<V> = V | (
20+
V extends GArray<infer T>
21+
? [T] extends [any[]]
22+
? GArrayCreateSource<{ [I in keyof T]: GDataStructureCreateValue<T[I]> }>
23+
: GArrayCreateSource<GDataStructureCreateValue<T>>
24+
: V extends GDictionary<infer T>
25+
? { [K in keyof T]: GDataStructureCreateValue<T[K]> }
26+
: never
27+
)
28+
29+
class GDictionary<T = Record<any, any>> {
30+
static create<V extends { [key: number | string]: GWrappableValue }>(properties: V): GValueWrap<V>
31+
static create<V extends GDictionary<any>>(properties: V extends GDictionary<infer T> ? { [K in keyof T]: GDataStructureCreateValue<T[K]> } : never): V
3632
proxy<Write extends boolean = false>(): Write extends true ? GDictionaryProxy<T> : GDictionaryReadProxy<T>;
3733
get<K extends keyof T>(key: K, default_: any = <any> {}): T[K]
3834
get_keyed<K extends keyof T>(index: K): T[K]
@@ -41,6 +37,27 @@ declare module "godot" {
4137
erase(key: keyof T): boolean
4238
has(key: keyof T): boolean
4339
}
40+
class GArray<T extends GAny | GAny[] = GAny | GAny[]> {
41+
static create<A extends any[]>(elements: A): GValueWrap<A>
42+
static create<A extends GArray<any>>(
43+
elements: A extends GArray<infer T>
44+
? [T] extends [any[]]
45+
? { [I in keyof T]: GDataStructureCreateValue<T[I]> }
46+
: Array<GDataStructureCreateValue<T>>
47+
: never
48+
): GValueWrap<A>
49+
static create<E extends GAny>(elements: Array<GDataStructureCreateValue<E>>): GArray<E>
50+
[Symbol.iterator](): IteratorObject<GArrayElement<T>>
51+
proxy<Write extends boolean = false>(): Write extends true ? GArrayProxy<GArrayElement<T>> : GArrayReadProxy<GArrayElement<T>>
52+
get_indexed<I extends int64>(index: I): GArrayElement<T, I>
53+
get<I extends int64>(index: I): GArrayElement<T, I>
54+
set<I extends int64>(index: I, value: GArrayElement<T, I>): void
55+
size(): number
56+
push_back(value: GArrayElement<T>): void
57+
pop_back(): GArrayElement<T>
58+
has(value: GArrayElement<T>): boolean
59+
find(what: GArrayElement<T>, from: int64 = 0): int64
60+
}
4461
type byte = number
4562
type int32 = number
4663
type uint32 = number

scripts/typings/godot.mix.d.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
///<reference path="godot.generated.d.ts" />
21
declare module "godot" {
32
const IntegerType: unique symbol;
43
const FloatType: unique symbol;
@@ -199,6 +198,10 @@ declare module "godot" {
199198
type ResolveAnimationMixerPath<Map extends AnimationMixerPathMap, Path extends string, Default = never> =
200199
ResolvePath<Map, Path, Default, Animation, "", typeof __PathMappableDummyKeys['AnimationLibrary' | 'AnimationMixer']>;
201200

201+
type GArrayElement<T extends GAny | GAny[], I extends int64 = int64> = T extends any[]
202+
? T[I]
203+
: T;
204+
202205
/**
203206
* GArray elements are exposed with a subset of JavaScript's standard Array API. Array indexes are exposed as
204207
* enumerable properties, thus if you want to perform more complex operations you can convert to a regular
@@ -245,31 +248,45 @@ declare module "godot" {
245248

246249
// Ideally this would be a class, but TS currently doesn't provide a way to type a class with mapped properties.
247250
/**
248-
* GObject entries are exposed as enumerable properties, so Object.keys(), Object.entries() etc. will work.
251+
* GObject entries are exposed as enumerable properties, so Object.keys(), GObject.entries() etc. will work.
249252
*/
250253
type GDictionaryProxy<T> = {
251254
[K in keyof T]: T[K] | GProxyValueWrap<T[K]>; // More accurate get type blocked by https://github.com/microsoft/TypeScript/issues/43826
252255
};
253256

254-
type GProxyValueWrap<V> = V extends GArray<infer E>
255-
? GArrayProxy<E>
257+
type GProxyValueWrap<V> = V extends GArray<infer T>
258+
? GArrayProxy<GArrayElement<T>>
256259
: V extends GDictionary<infer T>
257260
? GDictionaryProxy<T>
258261
: V;
259262

260-
type GProxyValueUnwrap<V> = V extends GArray<infer E>
263+
type GProxyValueUnwrap<V> = V extends GArrayProxy<infer E>
261264
? E
262-
: V extends GDictionary<infer T>
265+
: V extends GDictionaryProxy<infer T>
263266
? T
264267
: V;
265268

266-
type GWrappableValue = GAny | GWrappableValue[] | { [key: string]: GWrappableValue };
267-
type GValueWrapUnchecked<V> = V extends Array<infer E>
268-
? GArray<GValueWrapUnchecked<E>>
269+
type GWrappableValue = GAny | GWrappableValue[] | { [key: number | string]: GWrappableValue };
270+
type GValueWrapUnchecked<V> = V extends any[]
271+
? number extends V["length"]
272+
? GArray<GValueWrapUnchecked<V[number]>>
273+
: GArray<{ [I in keyof V]: GValueWrapUnchecked<V[I]> }>
269274
: V extends GAny
270275
? V
271276
: GDictionary<{ [K in keyof V]: GValueWrapUnchecked<V[K]> }>;
272-
type GValueWrap<V> = [V] extends [GWrappableValue] ? GValueWrapUnchecked<V> : never;
277+
type GValueWrap<V> = [keyof V] extends [never]
278+
? GDictionary<{}>
279+
: [V] extends [GWrappableValue]
280+
? GValueWrapUnchecked<V>
281+
: never;
282+
283+
type GValueUnwrap<V> = V extends GArray<infer T>
284+
? T extends any[]
285+
? { [I in keyof T]: GValueUnwrap<T[I]> }
286+
: Array<GValueUnwrap<T>>
287+
: V extends GDictionary<infer T>
288+
? { [K in keyof T]: GValueUnwrap<T[K]> }
289+
: V;
273290

274291
/**
275292
* Semi-workaround for https://github.com/microsoft/TypeScript/issues/43826.

0 commit comments

Comments
 (0)