@defold-typescript/types 0.50.1 → 0.52.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/api-availability.json +4 -58
- package/api-signatures.json +1128 -1552
- package/api-targets.json +15 -5
- package/examples/translations.json +19 -71
- package/fixtures/messages_doc.json +20 -39
- package/generated/b2d_world.d.ts +10 -10
- package/generated/buffer.d.ts +9 -9
- package/generated/builtin-messages.d.ts +16 -14
- package/generated/bullet3d_collision_object.d.ts +1 -1
- package/generated/bullet3d_constraint.d.ts +109 -7
- package/generated/bullet3d_shape.d.ts +2 -2
- package/generated/collectionfactory.d.ts +1 -1
- package/generated/collectionproxy.d.ts +2 -2
- package/generated/factory.d.ts +4 -2
- package/generated/go.d.ts +114 -220
- package/generated/graphics.d.ts +5 -0
- package/generated/gui.d.ts +148 -371
- package/generated/http.d.ts +4 -2
- package/generated/image.d.ts +56 -4
- package/generated/kinds/gui-script.d.ts +3 -0
- package/generated/kinds/render-script.d.ts +15 -15
- package/generated/kinds/script.d.ts +1 -0
- package/generated/liveupdate.d.ts +5 -1
- package/generated/material.d.ts +3 -3
- package/generated/model.d.ts +4 -5
- package/generated/particlefx.d.ts +20 -1
- package/generated/physics.d.ts +48 -4
- package/generated/render-script-modules/b2d.d.ts +711 -0
- package/generated/render-script-modules/bullet3d.d.ts +73 -0
- package/generated/render-script-modules/collectionfactory.d.ts +25 -0
- package/generated/render-script-modules/collectionproxy.d.ts +39 -0
- package/generated/render-script-modules/factory.d.ts +25 -0
- package/generated/render-script-modules/go.d.ts +436 -0
- package/generated/render-script-modules/label.d.ts +105 -0
- package/generated/render-script-modules/model.d.ts +89 -0
- package/generated/render-script-modules/particlefx.d.ts +59 -0
- package/generated/render-script-modules/physics.d.ts +333 -0
- package/generated/render-script-modules/resource.d.ts +1233 -0
- package/generated/render-script-modules/sound.d.ts +227 -0
- package/generated/render-script-modules/sprite.d.ts +68 -0
- package/generated/render-script-modules/tilemap.d.ts +43 -0
- package/generated/render.d.ts +175 -21
- package/generated/resource.d.ts +31 -10
- package/generated/sprite.d.ts +49 -1
- package/generated/sys.d.ts +78 -10
- package/generated/tilemap.d.ts +4 -4
- package/generated/versions/defold-1.12.4/buffer.d.ts +8 -8
- package/generated/versions/defold-1.12.4/collectionfactory.d.ts +1 -1
- package/generated/versions/defold-1.12.4/collectionproxy.d.ts +1 -1
- package/generated/versions/defold-1.12.4/factory.d.ts +4 -2
- package/generated/versions/defold-1.12.4/go.d.ts +115 -343
- package/generated/versions/defold-1.12.4/graphics.d.ts +1 -0
- package/generated/versions/defold-1.12.4/gui.d.ts +151 -513
- package/generated/versions/defold-1.12.4/http.d.ts +4 -2
- package/generated/versions/defold-1.12.4/image.d.ts +83 -3
- package/generated/versions/defold-1.12.4/index.d.ts +2 -0
- package/generated/versions/defold-1.12.4/liveupdate.d.ts +4 -0
- package/generated/versions/defold-1.12.4/model.d.ts +4 -5
- package/generated/versions/defold-1.12.4/particlefx.d.ts +22 -1
- package/generated/versions/defold-1.12.4/physics.d.ts +47 -3
- package/generated/versions/defold-1.12.4/render.d.ts +119 -15
- package/generated/versions/defold-1.12.4/resource.d.ts +43 -10
- package/generated/versions/defold-1.12.4/sprite.d.ts +58 -1
- package/generated/versions/defold-1.12.4/sys.d.ts +101 -6
- package/generated/versions/defold-1.12.4/tilemap.d.ts +4 -4
- package/generated/versions/defold-1.12.4/vmath.d.ts +0 -24
- package/generated/versions/defold-1.13.0/buffer.d.ts +8 -8
- package/generated/versions/defold-1.13.0/collectionfactory.d.ts +1 -1
- package/generated/versions/defold-1.13.0/collectionproxy.d.ts +1 -1
- package/generated/versions/defold-1.13.0/factory.d.ts +4 -2
- package/generated/versions/defold-1.13.0/go.d.ts +115 -370
- package/generated/versions/defold-1.13.0/graphics.d.ts +1 -0
- package/generated/versions/defold-1.13.0/gui.d.ts +151 -513
- package/generated/versions/defold-1.13.0/http.d.ts +4 -2
- package/generated/versions/defold-1.13.0/image.d.ts +83 -3
- package/generated/versions/defold-1.13.0/index.d.ts +2 -0
- package/generated/versions/defold-1.13.0/liveupdate.d.ts +4 -0
- package/generated/versions/defold-1.13.0/material.d.ts +3 -1
- package/generated/versions/defold-1.13.0/model.d.ts +4 -5
- package/generated/versions/defold-1.13.0/particlefx.d.ts +22 -1
- package/generated/versions/defold-1.13.0/physics.d.ts +47 -3
- package/generated/versions/defold-1.13.0/render.d.ts +182 -16
- package/generated/versions/defold-1.13.0/resource.d.ts +43 -10
- package/generated/versions/defold-1.13.0/sprite.d.ts +58 -1
- package/generated/versions/defold-1.13.0/sys.d.ts +101 -6
- package/generated/versions/defold-1.13.0/tilemap.d.ts +4 -4
- package/generated/versions/defold-1.13.0/vmath.d.ts +0 -24
- package/generated/versions/defold-1.13.1/buffer.d.ts +8 -8
- package/generated/versions/defold-1.13.1/collectionfactory.d.ts +1 -1
- package/generated/versions/defold-1.13.1/collectionproxy.d.ts +1 -1
- package/generated/versions/defold-1.13.1/factory.d.ts +4 -2
- package/generated/versions/defold-1.13.1/go.d.ts +115 -370
- package/generated/versions/defold-1.13.1/graphics.d.ts +1 -0
- package/generated/versions/defold-1.13.1/gui.d.ts +151 -529
- package/generated/versions/defold-1.13.1/http.d.ts +4 -2
- package/generated/versions/defold-1.13.1/image.d.ts +83 -3
- package/generated/versions/defold-1.13.1/index.d.ts +3 -0
- package/generated/versions/defold-1.13.1/liveupdate.d.ts +4 -0
- package/generated/versions/defold-1.13.1/material.d.ts +3 -1
- package/generated/versions/defold-1.13.1/model.d.ts +4 -5
- package/generated/versions/defold-1.13.1/particlefx.d.ts +22 -1
- package/generated/versions/defold-1.13.1/physics.d.ts +47 -3
- package/generated/versions/defold-1.13.1/render.d.ts +182 -16
- package/generated/versions/defold-1.13.1/resource.d.ts +43 -10
- package/generated/versions/defold-1.13.1/sprite.d.ts +58 -1
- package/generated/versions/defold-1.13.1/sys.d.ts +101 -6
- package/generated/versions/defold-1.13.1/tilemap.d.ts +4 -4
- package/generated/versions/defold-1.13.1/vmath.d.ts +0 -24
- package/generated/versions/defold-1.13.2/b2d_world.d.ts +10 -10
- package/generated/versions/defold-1.13.2/buffer.d.ts +9 -9
- package/generated/versions/defold-1.13.2/bullet3d_collision_object.d.ts +1 -1
- package/generated/versions/defold-1.13.2/bullet3d_constraint.d.ts +109 -7
- package/generated/versions/defold-1.13.2/bullet3d_shape.d.ts +2 -2
- package/generated/versions/defold-1.13.2/collectionfactory.d.ts +1 -1
- package/generated/versions/defold-1.13.2/collectionproxy.d.ts +2 -2
- package/generated/versions/defold-1.13.2/factory.d.ts +4 -2
- package/generated/versions/defold-1.13.2/go.d.ts +114 -220
- package/generated/versions/defold-1.13.2/graphics.d.ts +1 -0
- package/generated/versions/defold-1.13.2/gui.d.ts +148 -371
- package/generated/versions/defold-1.13.2/http.d.ts +4 -2
- package/generated/versions/defold-1.13.2/image.d.ts +56 -4
- package/generated/versions/defold-1.13.2/index.d.ts +3 -0
- package/generated/versions/defold-1.13.2/liveupdate.d.ts +5 -1
- package/generated/versions/defold-1.13.2/material.d.ts +3 -3
- package/generated/versions/defold-1.13.2/model.d.ts +4 -5
- package/generated/versions/defold-1.13.2/particlefx.d.ts +20 -1
- package/generated/versions/defold-1.13.2/physics.d.ts +48 -4
- package/generated/versions/defold-1.13.2/render.d.ts +164 -20
- package/generated/versions/defold-1.13.2/resource.d.ts +31 -10
- package/generated/versions/defold-1.13.2/sprite.d.ts +49 -1
- package/generated/versions/defold-1.13.2/sys.d.ts +78 -10
- package/generated/versions/defold-1.13.2/tilemap.d.ts +4 -4
- package/generated/versions/defold-1.13.2/vmath.d.ts +0 -24
- package/generated/versions/defold-1.13.2/window.d.ts +1 -1
- package/generated/vmath.d.ts +0 -24
- package/generated/window.d.ts +1 -1
- package/identity-vocabulary.json +98 -7
- package/index.d.ts +3 -0
- package/package.json +3 -1
- package/scripts/generate-api-availability.ts +6 -22
- package/scripts/generate-api-signatures.ts +87 -21
- package/scripts/materialize-version.ts +53 -6
- package/scripts/regen.ts +256 -44
- package/scripts/signature-store-fs.ts +1 -0
- package/scripts/target-version.ts +26 -0
- package/src/api-doc.ts +26 -0
- package/src/component-properties.d.ts +226 -6
- package/src/core-types.ts +82 -20
- package/src/doc-corrections.ts +53 -5
- package/src/emit-dts.ts +1300 -269
- package/src/emit-messages.ts +289 -0
- package/src/engine-globals.d.ts +8 -2
- package/src/go-overloads.d.ts +80 -15
- package/src/gui-overloads.d.ts +489 -0
- package/src/gui-texture-removal.d.ts +41 -0
- package/src/index.ts +2 -0
- package/src/lifecycle.ts +46 -37
- package/src/luals-type-expr.ts +6 -1
- package/src/msg-overloads.d.ts +25 -13
- package/src/physics-event-guard.d.ts +39 -0
- package/src/render-overloads.d.ts +2 -2
- package/src/return-correction-provenance.ts +36 -6
- package/src/signature-store.ts +50 -0
- package/src/timers.d.ts +2 -1
- package/src/vmath-overloads.d.ts +26 -0
- package/src/window-event-guard.d.ts +5 -3
package/src/emit-dts.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import messagesDoc from "../fixtures/messages_doc.json" with { type: "json" };
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ApiSymbolIdentity,
|
|
4
|
+
type Box2dBackend,
|
|
5
|
+
normalizedFunctionSignature,
|
|
6
|
+
} from "./api-availability";
|
|
3
7
|
import type {
|
|
4
8
|
ApiConstant,
|
|
5
9
|
ApiFunction,
|
|
@@ -21,6 +25,7 @@ import {
|
|
|
21
25
|
splitExampleSources,
|
|
22
26
|
summaryFor,
|
|
23
27
|
} from "./doc-comment";
|
|
28
|
+
import { applyMessageOverlays, parseMessagesDoc } from "./emit-messages";
|
|
24
29
|
import type { TranslationStore } from "./example-store";
|
|
25
30
|
import { lookupExampleSegments, translateProseFences } from "./example-store";
|
|
26
31
|
import {
|
|
@@ -30,7 +35,13 @@ import {
|
|
|
30
35
|
indexBaseNoteResolver,
|
|
31
36
|
OVERRIDE_RETURN_SLOT,
|
|
32
37
|
} from "./index-slot-classifications";
|
|
33
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
isLualsFunctionToken,
|
|
40
|
+
type LualsPosition,
|
|
41
|
+
mapLualsExpression,
|
|
42
|
+
needsArrayParens,
|
|
43
|
+
splitTopLevel,
|
|
44
|
+
} from "./luals-type-expr";
|
|
34
45
|
import { classifyUrlParameter, type UrlParameterTable } from "./url-parameters";
|
|
35
46
|
|
|
36
47
|
export { summaryFor };
|
|
@@ -75,6 +86,21 @@ export interface EmitOptions {
|
|
|
75
86
|
// `UNDOCUMENTED_CONSTANTS` back-fill. Absent for a surface with no vendored
|
|
76
87
|
// bindings (a ref-doc target), where every entry the ref-doc omits is emitted.
|
|
77
88
|
registeredConstants?: ReadonlySet<string>;
|
|
89
|
+
// The `RETURN_TYPE_CORRECTIONS` and `STRUCT_MEMBER_ADDITIONS` keys whose binding
|
|
90
|
+
// pin holds in the target's vendored engine source. A binding-pinned entry
|
|
91
|
+
// applies only when its key is here, so a surface with no vendored bindings
|
|
92
|
+
// (absent) applies none of them.
|
|
93
|
+
bindingEvidence?: ReadonlySet<string>;
|
|
94
|
+
// The 1-based parameter slots, keyed by function FQN, that the target's engine
|
|
95
|
+
// bindings let a call omit but raise on when passed nil. Each optional one
|
|
96
|
+
// splits its function into a call without the slot and a call requiring it,
|
|
97
|
+
// so a possibly-undefined value is a compile error. Absent for a surface with
|
|
98
|
+
// no vendored bindings (a ref-doc target), which keeps every `?`.
|
|
99
|
+
nilRaisingSlots?: ReadonlyMap<string, readonly number[]>;
|
|
100
|
+
// The functions a target's skip rules removed from the module, read only to
|
|
101
|
+
// type an `OVERLOAD_SLOT_ALIASES` slot and to resolve a
|
|
102
|
+
// `CONSTANT_SLOT_RESOLUTIONS` borrow from their docs.
|
|
103
|
+
withheldFunctions?: readonly ApiFunction[];
|
|
78
104
|
}
|
|
79
105
|
|
|
80
106
|
export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -165,14 +191,12 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
|
|
|
165
191
|
// runtime-owned passthrough: the table is filled by user code at runtime, never
|
|
166
192
|
// by Defold's API contract. factory.create/collectionfactory.create `properties`
|
|
167
193
|
// are spawn-time overrides keyed by the spawned object's own script-property
|
|
168
|
-
// names
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
// mapping; each per-instance property table stays that faithful `Record`.
|
|
194
|
+
// names. No static shape exists, so `Record<string | number, unknown>` is
|
|
195
|
+
// faithful, not a loss. collectionfactory.create curates the instance-id layer
|
|
196
|
+
// of `properties` as a mapping; each per-instance property table stays that
|
|
197
|
+
// faithful `Record`.
|
|
173
198
|
"factory.create",
|
|
174
199
|
"collectionfactory.create",
|
|
175
|
-
"on_message",
|
|
176
200
|
// engine-formatted blob: the engine produces the value at runtime and no
|
|
177
201
|
// field list is documented, so the whole-slot `Record` is faithful, not a
|
|
178
202
|
// loss. crash.get_backtrace is "table containing the backtrace",
|
|
@@ -245,6 +269,11 @@ export const OVERLOAD_COVERED_SKIPS = new Set([
|
|
|
245
269
|
"go.get",
|
|
246
270
|
"go.set",
|
|
247
271
|
"go.property",
|
|
272
|
+
// gui-overloads.d.ts closes the node property names and types each value per
|
|
273
|
+
// name, with a hash form for material constants.
|
|
274
|
+
"gui.animate",
|
|
275
|
+
"gui.get",
|
|
276
|
+
"gui.set",
|
|
248
277
|
// msg-overloads.d.ts supplies the typed msg.post shape.
|
|
249
278
|
"msg.post",
|
|
250
279
|
// msg-overloads.d.ts supplies the typed msg.url shape (0 / 1-string / 3-arg
|
|
@@ -263,6 +292,7 @@ export const OVERLOAD_COVERED_SKIPS = new Set([
|
|
|
263
292
|
"vmath.slerp",
|
|
264
293
|
"vmath.mul_per_elem",
|
|
265
294
|
"vmath.normalize",
|
|
295
|
+
"vmath.dot",
|
|
266
296
|
// sys-overloads.d.ts supplies the disabling form and the form that takes the
|
|
267
297
|
// cooldown the engine requires when enabling.
|
|
268
298
|
"sys.set_engine_throttle",
|
|
@@ -320,13 +350,10 @@ export const MAPPING_TABLE_SLOTS: ReadonlyMap<string, { key: string; value: stri
|
|
|
320
350
|
]);
|
|
321
351
|
|
|
322
352
|
// FQN-keyed return-type overrides for functions whose doc `returnvalues` are
|
|
323
|
-
// empty yet the engine returns a value.
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
// -> unknown" rule: `gui.set` also has empty returnvalues and `void` is correct
|
|
327
|
-
// there.
|
|
353
|
+
// empty yet the engine returns a value. Per-FQN, not a blanket "empty
|
|
354
|
+
// returnvalues -> unknown" rule: most functions with empty returnvalues return
|
|
355
|
+
// nothing, and `void` is correct there.
|
|
328
356
|
export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
329
|
-
["gui.get", "unknown"],
|
|
330
357
|
// Upstream documents the returned transaction step in prose only — its
|
|
331
358
|
// `returnvalues` is empty while every sibling `editor.tx.*` builder declares
|
|
332
359
|
// one, and `editor.transact()` takes exactly that.
|
|
@@ -362,8 +389,22 @@ export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
362
389
|
// destructured into separate arguments; a boxed `LuaMultiReturn` is rejected
|
|
363
390
|
// because TSTL passes it as one table, which is always truthy. `reason` quotes
|
|
364
391
|
// the slot prose the provenance test searches for, as for the `nil` entries.
|
|
365
|
-
|
|
366
|
-
|
|
392
|
+
//
|
|
393
|
+
// An entry with a `binding` rests on the engine source instead of the prose:
|
|
394
|
+
// it applies only on a target whose vendored binding shows the pin (the
|
|
395
|
+
// `EmitOptions.bindingEvidence` keys), and `reason` names that binding in words.
|
|
396
|
+
// A nil push proves nil-ability and nothing about the base type, so such an
|
|
397
|
+
// entry usually carries `nullable` rather than `ts`.
|
|
398
|
+
export interface BindingPin {
|
|
399
|
+
// Relative to the target's vendored `engine/` tree.
|
|
400
|
+
readonly file: string;
|
|
401
|
+
readonly cFunction: string;
|
|
402
|
+
// Token snippets the function body must contain and must lack; at least one.
|
|
403
|
+
readonly contains?: string;
|
|
404
|
+
readonly lacks?: string;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
interface ReturnCorrectionBase {
|
|
367
408
|
readonly upstream: readonly string[];
|
|
368
409
|
// A later release's spelling of the same defect; see `correction-pins.ts`.
|
|
369
410
|
readonly retypedUpstream?: readonly (readonly string[])[];
|
|
@@ -371,10 +412,33 @@ export interface ReturnTypeCorrection {
|
|
|
371
412
|
// The named value of a multi-return the correction rewrites; `ts` is then
|
|
372
413
|
// that value's type. Absent, the correction rewrites a single return.
|
|
373
414
|
readonly slot?: string;
|
|
415
|
+
readonly binding?: BindingPin;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// `ts` replaces the emitted type. `nullable` keeps the type each target maps
|
|
419
|
+
// from its own tokens and only adds or drops the `nil`, so a target whose
|
|
420
|
+
// spelling differs keeps its own base.
|
|
421
|
+
export type ReturnTypeCorrection = ReturnCorrectionBase &
|
|
422
|
+
(
|
|
423
|
+
| { readonly ts: string; readonly nullable?: undefined }
|
|
424
|
+
| { readonly nullable: boolean; readonly ts?: undefined }
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
// The early nil return both tilemap readers take for an unknown layer, and
|
|
428
|
+
// `TileMap_Get` for a cell out of range too.
|
|
429
|
+
function tilemapEarlyNil(cFunction: string): BindingPin {
|
|
430
|
+
return {
|
|
431
|
+
file: "gamesys/src/gamesys/scripts/script_tilemap.cpp",
|
|
432
|
+
cFunction,
|
|
433
|
+
contains: "lua_pushnil(L); assert(top + 1 == lua_gettop(L)); return 1;",
|
|
434
|
+
};
|
|
374
435
|
}
|
|
375
436
|
|
|
376
437
|
// Keyed by FQN, mirroring PROPERTY_TYPE_CORRECTIONS.
|
|
377
|
-
export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection> = new Map
|
|
438
|
+
export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection> = new Map<
|
|
439
|
+
string,
|
|
440
|
+
ReturnTypeCorrection
|
|
441
|
+
>([
|
|
378
442
|
[
|
|
379
443
|
"b2d.get_body",
|
|
380
444
|
{
|
|
@@ -408,8 +472,8 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
|
|
|
408
472
|
slot: "code",
|
|
409
473
|
},
|
|
410
474
|
],
|
|
411
|
-
// 1.13.2
|
|
412
|
-
//
|
|
475
|
+
// 1.13.2 retypes the return to `render_predicate`, whose TYPEDEF aliases
|
|
476
|
+
// `userdata`; its type mapping emits the same brand.
|
|
413
477
|
[
|
|
414
478
|
"render.predicate",
|
|
415
479
|
{
|
|
@@ -439,6 +503,161 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
|
|
|
439
503
|
reason: "the customized try function.",
|
|
440
504
|
},
|
|
441
505
|
],
|
|
506
|
+
[
|
|
507
|
+
"factory.create",
|
|
508
|
+
{
|
|
509
|
+
nullable: true,
|
|
510
|
+
upstream: ["hash"],
|
|
511
|
+
reason: "script_factory.cpp:FactoryComp_Create pushes nil when the spawn fails",
|
|
512
|
+
binding: {
|
|
513
|
+
file: "gamesys/src/gamesys/scripts/script_factory.cpp",
|
|
514
|
+
cFunction: "FactoryComp_Create",
|
|
515
|
+
contains: "dmScript::PushHash(L, id); } else { lua_pushnil(L); }",
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
// 1.13.2 retypes each tilemap return to a precise spelling of the same value
|
|
520
|
+
// and still omits the nil.
|
|
521
|
+
...(
|
|
522
|
+
[
|
|
523
|
+
["tilemap.get_tile", ["number"], ["integer"]],
|
|
524
|
+
[
|
|
525
|
+
"tilemap.get_tile_info",
|
|
526
|
+
["table"],
|
|
527
|
+
["{ index:integer, h_flip:boolean, v_flip:boolean, rotate_90:boolean }"],
|
|
528
|
+
],
|
|
529
|
+
] as const
|
|
530
|
+
).map(
|
|
531
|
+
([key, upstream, retyped]) =>
|
|
532
|
+
[
|
|
533
|
+
key,
|
|
534
|
+
{
|
|
535
|
+
nullable: true,
|
|
536
|
+
upstream,
|
|
537
|
+
retypedUpstream: [retyped],
|
|
538
|
+
reason:
|
|
539
|
+
"script_tilemap.cpp:TileMap_Get pushes nil for an unknown layer or a cell out of range",
|
|
540
|
+
binding: tilemapEarlyNil("TileMap_Get"),
|
|
541
|
+
},
|
|
542
|
+
] as const,
|
|
543
|
+
),
|
|
544
|
+
[
|
|
545
|
+
"tilemap.get_tiles",
|
|
546
|
+
{
|
|
547
|
+
nullable: true,
|
|
548
|
+
upstream: ["table"],
|
|
549
|
+
retypedUpstream: [["table<integer, table<integer, integer>>"]],
|
|
550
|
+
reason: "script_tilemap.cpp:TileMap_GetTiles pushes nil for an unknown layer",
|
|
551
|
+
binding: tilemapEarlyNil("TileMap_GetTiles"),
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
[
|
|
555
|
+
"bullet3d.shape.get_shape",
|
|
556
|
+
{
|
|
557
|
+
ts: "bullet3d.shape.definition | { type: typeof bullet3d.shape.SHAPE_TYPE_MESH }",
|
|
558
|
+
upstream: ["bullet3d.shape.definition"],
|
|
559
|
+
reason:
|
|
560
|
+
"script_bullet3d_shape.cpp:Shape_GetShape sets only `type` for a triangle mesh, which no shape definition arm matches",
|
|
561
|
+
binding: {
|
|
562
|
+
file: "gamesys/src/gamesys/scripts/bullet3d/script_bullet3d_shape.cpp",
|
|
563
|
+
cFunction: "Shape_GetShape",
|
|
564
|
+
contains: "case TRIANGLE_MESH_SHAPE_PROXYTYPE: case GIMPACT_SHAPE_PROXYTYPE: break;",
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
[
|
|
569
|
+
"collectionproxy.set_collection",
|
|
570
|
+
{
|
|
571
|
+
nullable: true,
|
|
572
|
+
upstream: ["number"],
|
|
573
|
+
retypedUpstream: [["collectionproxy.RESULT"]],
|
|
574
|
+
reason:
|
|
575
|
+
"script_collectionproxy.cpp:CollectionProxy_SetCollection pushes true, nil when the change succeeds",
|
|
576
|
+
slot: "code",
|
|
577
|
+
binding: {
|
|
578
|
+
file: "gamesys/src/gamesys/scripts/script_collectionproxy.cpp",
|
|
579
|
+
cFunction: "CollectionProxy_SetCollection",
|
|
580
|
+
contains: "case SET_COLLECTION_PATH_RESULT_OK: lua_pushboolean(L, 1); lua_pushnil(L);",
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
],
|
|
584
|
+
[
|
|
585
|
+
"image.get_astc_header",
|
|
586
|
+
{
|
|
587
|
+
nullable: false,
|
|
588
|
+
upstream: ["table", "nil"],
|
|
589
|
+
retypedUpstream: [["image.astc_header", "nil"]],
|
|
590
|
+
reason: "script_image.cpp:Image_GetAstcHeader raises on invalid data and never pushes nil",
|
|
591
|
+
binding: {
|
|
592
|
+
file: "gamesys/src/gamesys/scripts/script_image.cpp",
|
|
593
|
+
cFunction: "Image_GetAstcHeader",
|
|
594
|
+
contains: 'return luaL_error(L, "Data is not a valid .astc file");',
|
|
595
|
+
lacks: "lua_pushnil(L);",
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
// `PushRayCastResponse` has two definitions; `Physics_RayCast` calls the one
|
|
600
|
+
// that takes the world, which sets every payload field but `request_id`.
|
|
601
|
+
[
|
|
602
|
+
"physics.raycast",
|
|
603
|
+
{
|
|
604
|
+
ts: 'Omit<BuiltinMessages["ray_cast_response"], "request_id">[] | Omit<BuiltinMessages["ray_cast_response"], "request_id"> | undefined',
|
|
605
|
+
upstream: ["table", "nil"],
|
|
606
|
+
retypedUpstream: [
|
|
607
|
+
["message.physics.ray_cast_response[]", "message.physics.ray_cast_response", "nil"],
|
|
608
|
+
],
|
|
609
|
+
reason:
|
|
610
|
+
"script_physics.cpp:Physics_RayCast pushes each hit through PushRayCastResponse(L, world, hit), which sets no request_id",
|
|
611
|
+
binding: {
|
|
612
|
+
file: "gamesys/src/gamesys/scripts/script_physics.cpp",
|
|
613
|
+
cFunction: "Physics_RayCast",
|
|
614
|
+
contains: "PushRayCastResponse(L, world, hits[i]);",
|
|
615
|
+
lacks: '"request_id"',
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
...(
|
|
620
|
+
[
|
|
621
|
+
[
|
|
622
|
+
"collectionfactory.create",
|
|
623
|
+
"Hash",
|
|
624
|
+
["table<hash, hash>"],
|
|
625
|
+
"gamesys/src/gamesys/scripts/script_collection_factory.cpp",
|
|
626
|
+
"CollectionFactoryComp_Create",
|
|
627
|
+
],
|
|
628
|
+
[
|
|
629
|
+
"gui.get_tree",
|
|
630
|
+
'Opaque<"node">',
|
|
631
|
+
["table<hash, node>"],
|
|
632
|
+
"gui/src/gui_script.cpp",
|
|
633
|
+
"LuaGetTree",
|
|
634
|
+
],
|
|
635
|
+
[
|
|
636
|
+
"gui.clone_tree",
|
|
637
|
+
'Opaque<"node">',
|
|
638
|
+
["table<hash, node>"],
|
|
639
|
+
"gui/src/gui_script.cpp",
|
|
640
|
+
"LuaCloneTree",
|
|
641
|
+
],
|
|
642
|
+
] as const
|
|
643
|
+
).map(
|
|
644
|
+
([key, value, retyped, file, cFunction]) =>
|
|
645
|
+
[
|
|
646
|
+
key,
|
|
647
|
+
{
|
|
648
|
+
ts: `LuaTable<Hash, ${value}> & { get: LuaTableGetMethod<string, ${value}> }`,
|
|
649
|
+
upstream: ["table"],
|
|
650
|
+
retypedUpstream: [retyped],
|
|
651
|
+
reason: `${file.slice(file.lastIndexOf("/") + 1)}:${cFunction} sets HashTableIndex as the id table's __index, which hashes a string key`,
|
|
652
|
+
binding: {
|
|
653
|
+
file,
|
|
654
|
+
cFunction,
|
|
655
|
+
contains:
|
|
656
|
+
'lua_pushcfunction(L, HashTableIndex); lua_setfield(L, -2, "__index"); lua_setmetatable(L, -2);',
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
] as const,
|
|
660
|
+
),
|
|
442
661
|
]);
|
|
443
662
|
|
|
444
663
|
// A parameter whose declared `types` disagree with what the same element's
|
|
@@ -565,6 +784,35 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
565
784
|
},
|
|
566
785
|
] as const,
|
|
567
786
|
),
|
|
787
|
+
...(
|
|
788
|
+
[
|
|
789
|
+
["clone_tree", "LuaCloneTree"],
|
|
790
|
+
["get_tree", "LuaGetTree"],
|
|
791
|
+
] as const
|
|
792
|
+
).map(
|
|
793
|
+
([name, cFunction]) =>
|
|
794
|
+
[
|
|
795
|
+
`gui.${name}:param:node`,
|
|
796
|
+
{
|
|
797
|
+
adds: "undefined",
|
|
798
|
+
upstream: ["node"],
|
|
799
|
+
reason: `gui_script.cpp:${cFunction} pushes its result table before testing argument 1 with lua_isnil, so nil selects the whole scene and an absent argument reaches LuaCheckNodeInternal and raises`,
|
|
800
|
+
},
|
|
801
|
+
] as const,
|
|
802
|
+
),
|
|
803
|
+
...(["get_slider_motor", "set_slider_motor"] as const).map(
|
|
804
|
+
(name) =>
|
|
805
|
+
[
|
|
806
|
+
`bullet3d.constraint.${name}:param:motor`,
|
|
807
|
+
{
|
|
808
|
+
adds: '"linear" | "angular"',
|
|
809
|
+
removes: ["string"],
|
|
810
|
+
upstream: ["string"],
|
|
811
|
+
reason:
|
|
812
|
+
"script_bullet3d_constraint.cpp:CheckSliderMotorAxis raises \"motor must be 'linear' or 'angular'\" on any other string",
|
|
813
|
+
},
|
|
814
|
+
] as const,
|
|
815
|
+
),
|
|
568
816
|
[
|
|
569
817
|
"sound.get_group_name:param:group",
|
|
570
818
|
{
|
|
@@ -643,9 +891,10 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
643
891
|
"render.disable_texture:param:binding",
|
|
644
892
|
{
|
|
645
893
|
adds: "number",
|
|
894
|
+
removes: ['Opaque<"texture">'],
|
|
646
895
|
upstream: ["texture", "string", "hash"],
|
|
647
896
|
reason:
|
|
648
|
-
|
|
897
|
+
"render_script.cpp:RenderScript_DisableTexture reads a number as a texture unit with lua_isnumber and anything else with CheckHashOrString; a texture handle is a number, so it is read as a unit",
|
|
649
898
|
},
|
|
650
899
|
],
|
|
651
900
|
[
|
|
@@ -663,11 +912,42 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
663
912
|
adds: 'Opaque<"render_predicate">',
|
|
664
913
|
removes: ["number"],
|
|
665
914
|
upstream: ["number"],
|
|
666
|
-
retypedUpstream: [["render_predicate"]],
|
|
667
915
|
reason:
|
|
668
|
-
"render_script.cpp:RenderScript_Draw reads the predicate with RenderScriptPredicate_Check, the userdata render.predicate returns; 1.13.2
|
|
916
|
+
"render_script.cpp:RenderScript_Draw reads the predicate with RenderScriptPredicate_Check, the userdata render.predicate returns; 1.13.2 types the slot `render_predicate`, whose type mapping is that brand",
|
|
669
917
|
},
|
|
670
918
|
],
|
|
919
|
+
...(["enable_state", "disable_state"] as const).map(
|
|
920
|
+
(name) =>
|
|
921
|
+
[
|
|
922
|
+
`render.${name}:param:state`,
|
|
923
|
+
{
|
|
924
|
+
adds: "graphics.State",
|
|
925
|
+
removes: ["graphics.STATE"],
|
|
926
|
+
upstream: ["graphics.STATE"],
|
|
927
|
+
reason: `render_script.cpp:RenderScript_${name === "enable_state" ? "Enable" : "Disable"}State raises "Invalid state" on any state but the six it lists, so graphics.STATE_SCISSOR_TEST and graphics.STATE_ALPHA_TEST_SUPPORTED are rejected`,
|
|
928
|
+
},
|
|
929
|
+
] as const,
|
|
930
|
+
),
|
|
931
|
+
...(
|
|
932
|
+
[
|
|
933
|
+
"source_factor_color",
|
|
934
|
+
"destination_factor_color",
|
|
935
|
+
"source_factor_alpha",
|
|
936
|
+
"destination_factor_alpha",
|
|
937
|
+
] as const
|
|
938
|
+
).map(
|
|
939
|
+
(slot) =>
|
|
940
|
+
[
|
|
941
|
+
`render.set_blend_func_separate:param:${slot}`,
|
|
942
|
+
{
|
|
943
|
+
adds: "graphics.BlendFactor",
|
|
944
|
+
removes: ["number"],
|
|
945
|
+
upstream: ["number"],
|
|
946
|
+
reason:
|
|
947
|
+
'render_script.cpp:RenderScript_SetBlendFuncSeparate raises "Invalid blend factor" on any value CheckBlendFactor does not list',
|
|
948
|
+
},
|
|
949
|
+
] as const,
|
|
950
|
+
),
|
|
671
951
|
[
|
|
672
952
|
"go.delete:param:id",
|
|
673
953
|
{
|
|
@@ -686,15 +966,6 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
686
966
|
reason: 'sys.load_buffer example `buffer.get_bytes(my_buffer, "data")`',
|
|
687
967
|
},
|
|
688
968
|
],
|
|
689
|
-
[
|
|
690
|
-
"gui.set:param:value",
|
|
691
|
-
{
|
|
692
|
-
adds: "Matrix4 | Hash",
|
|
693
|
-
upstream: ["number", "vector4", "vector3", "quaternion"],
|
|
694
|
-
reason:
|
|
695
|
-
'example `gui.set(node, "light_matrix", vmath.matrix4())` ("matrix4 is also supported"); example `gui.set(msg.url(), "textures", atlas_id, {key = "runtime_texture"})` passes the hash `resource.create_atlas` returns',
|
|
696
|
-
},
|
|
697
|
-
],
|
|
698
969
|
[
|
|
699
970
|
"gui.set_rotation:param:rotation",
|
|
700
971
|
{
|
|
@@ -903,6 +1174,10 @@ export const OPTIONAL_SLOT_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
903
1174
|
"render.set_render_target:param:render_target",
|
|
904
1175
|
"render_script.cpp:RenderScript_SetRenderTarget sets the default target when lua_gettop(L) == 0",
|
|
905
1176
|
],
|
|
1177
|
+
[
|
|
1178
|
+
"buffer.get_bytes:param:stream_name",
|
|
1179
|
+
"script_buffer.cpp:GetBytes reads only argument 1 with CheckBufferUnpack and returns every byte of the buffer",
|
|
1180
|
+
],
|
|
906
1181
|
["socket.newtry:param:finalizer", "socket.protect's example `local try = socket.newtry()`"],
|
|
907
1182
|
...(
|
|
908
1183
|
[
|
|
@@ -1045,13 +1320,6 @@ export const REQUIRED_SLOT_CORRECTIONS: ReadonlyMap<string, RequiredSlotCorrecti
|
|
|
1045
1320
|
evidence: "gui_script.cpp:LuaSetParent reads a node from argument 2 unless lua_isnil(L, 2)",
|
|
1046
1321
|
},
|
|
1047
1322
|
],
|
|
1048
|
-
[
|
|
1049
|
-
"gui.set:param:value",
|
|
1050
|
-
{
|
|
1051
|
-
nil: true,
|
|
1052
|
-
evidence: "gui_script.cpp:LuaSet converts argument 3 with LuaToVar unless lua_isnil(L, 3)",
|
|
1053
|
-
},
|
|
1054
|
-
],
|
|
1055
1323
|
[
|
|
1056
1324
|
"window.set_listener:param:callback",
|
|
1057
1325
|
{
|
|
@@ -1141,6 +1409,335 @@ function firstSlotAlternative(
|
|
|
1141
1409
|
return original === null ? null : { ...prepared, original };
|
|
1142
1410
|
}
|
|
1143
1411
|
|
|
1412
|
+
// The calls a function splits into at its optional nil-raising slots
|
|
1413
|
+
// (`EmitOptions.nilRaisingSlots`): one ending before each such slot, then one
|
|
1414
|
+
// taking them all, every such slot before an arm's end declared required. A
|
|
1415
|
+
// required slot, or one a rest parameter folds in, splits nothing.
|
|
1416
|
+
function nilRaisingArms(
|
|
1417
|
+
prepared: PreparedFunction,
|
|
1418
|
+
nilRaisingSlots: ReadonlyMap<string, readonly number[]>,
|
|
1419
|
+
): PreparedFunction[] {
|
|
1420
|
+
const { name, parameters } = prepared.original;
|
|
1421
|
+
const firstVararg = parameters.findIndex(isVarargParameter);
|
|
1422
|
+
const positional = firstVararg === -1 ? parameters.length : firstVararg;
|
|
1423
|
+
const split = [...new Set(nilRaisingSlots.get(name) ?? [])]
|
|
1424
|
+
.map((slot) => slot - 1)
|
|
1425
|
+
.filter((index) => {
|
|
1426
|
+
const p = parameters[index];
|
|
1427
|
+
return index < positional && p !== undefined && isDocOptional(p, name);
|
|
1428
|
+
})
|
|
1429
|
+
.sort((a, b) => a - b);
|
|
1430
|
+
if (split.length === 0) return [prepared];
|
|
1431
|
+
const shorter = split.map((end, k) => ({
|
|
1432
|
+
...prepared,
|
|
1433
|
+
original: { ...prepared.original, parameters: parameters.slice(0, end) },
|
|
1434
|
+
required: new Set(split.slice(0, k)),
|
|
1435
|
+
}));
|
|
1436
|
+
return [...shorter, { ...prepared, required: new Set(split) }];
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Table fields a binding reads as required only when `argument` is non-nil,
|
|
1440
|
+
// keyed by FQN. A curated function splits into a call whose `table` slot requires
|
|
1441
|
+
// `fields`, with `argument` as declared, then a call whose `argument` is nil, so
|
|
1442
|
+
// a possibly-nil argument must carry them.
|
|
1443
|
+
export interface PresenceRequiredFields {
|
|
1444
|
+
readonly argument: string;
|
|
1445
|
+
readonly table: string;
|
|
1446
|
+
readonly fields: readonly string[];
|
|
1447
|
+
readonly reason: string;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const FRAME_B_FIELDS = ["frame_b_position", "frame_b_rotation"] as const;
|
|
1451
|
+
|
|
1452
|
+
export const PRESENCE_REQUIRED_FIELDS: ReadonlyMap<string, PresenceRequiredFields> = new Map<
|
|
1453
|
+
string,
|
|
1454
|
+
PresenceRequiredFields
|
|
1455
|
+
>([
|
|
1456
|
+
[
|
|
1457
|
+
"bullet3d.constraint.create_point_to_point",
|
|
1458
|
+
{
|
|
1459
|
+
argument: "body_b",
|
|
1460
|
+
table: "params",
|
|
1461
|
+
fields: ["pivot_b"],
|
|
1462
|
+
reason:
|
|
1463
|
+
"script_bullet3d_constraint.cpp:Constraint_CreatePointToPoint reads pivot_b as required when input.m_BodyB != 0",
|
|
1464
|
+
},
|
|
1465
|
+
],
|
|
1466
|
+
...(
|
|
1467
|
+
[
|
|
1468
|
+
["create_hinge", "Constraint_CreateHinge"],
|
|
1469
|
+
["create_cone_twist", "Constraint_CreateConeTwist"],
|
|
1470
|
+
["create_generic_6dof", "Constraint_CreateGeneric6Dof"],
|
|
1471
|
+
["create_slider", "Constraint_CreateSlider"],
|
|
1472
|
+
] as const
|
|
1473
|
+
).map(
|
|
1474
|
+
([name, cFunction]) =>
|
|
1475
|
+
[
|
|
1476
|
+
`bullet3d.constraint.${name}`,
|
|
1477
|
+
{
|
|
1478
|
+
argument: "body_b",
|
|
1479
|
+
table: "params",
|
|
1480
|
+
fields: FRAME_B_FIELDS,
|
|
1481
|
+
reason: `script_bullet3d_constraint.cpp:${cFunction} reads frame B as required under if (input.m_BodyB)`,
|
|
1482
|
+
},
|
|
1483
|
+
] as const,
|
|
1484
|
+
),
|
|
1485
|
+
]);
|
|
1486
|
+
|
|
1487
|
+
// The calls PRESENCE_REQUIRED_FIELDS splits `prepared` into: the declared one
|
|
1488
|
+
// with its fields required, then the one whose argument is nil.
|
|
1489
|
+
function presenceArms(
|
|
1490
|
+
prepared: PreparedFunction,
|
|
1491
|
+
structs: readonly ApiStruct[],
|
|
1492
|
+
): PreparedFunction[] {
|
|
1493
|
+
const { name, parameters } = prepared.original;
|
|
1494
|
+
const entry = PRESENCE_REQUIRED_FIELDS.get(name);
|
|
1495
|
+
if (entry === undefined) return [prepared];
|
|
1496
|
+
const argument = parameters.findIndex((p) => p.name === entry.argument);
|
|
1497
|
+
const table = parameters.findIndex((p) => p.name === entry.table);
|
|
1498
|
+
const struct = structs.find((s) => parameters[table]?.types.includes(s.name));
|
|
1499
|
+
if (argument === -1 || struct === undefined) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
`presence-required fields ${name}: no ${entry.argument} parameter or ${entry.table} table`,
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
const missing = entry.fields.filter((field) => !struct.members.some((m) => m.name === field));
|
|
1505
|
+
if (missing.length > 0) {
|
|
1506
|
+
throw new Error(`presence-required fields ${name}: ${struct.name} lacks ${missing.join(", ")}`);
|
|
1507
|
+
}
|
|
1508
|
+
const picked = entry.fields.map((field) => `"${field}"`).join(" | ");
|
|
1509
|
+
const requireFields = (type: string): string => {
|
|
1510
|
+
const operand = /^[\w$.]+$/.test(type) ? type : `(${type})`;
|
|
1511
|
+
return `${operand} & Required<Pick<${operand}, ${picked}>>`;
|
|
1512
|
+
};
|
|
1513
|
+
const strict: PreparedFunction = { ...prepared, retype: new Map([[table, requireFields]]) };
|
|
1514
|
+
// Declared required, the nil argument emits as `undefined` alone rather than
|
|
1515
|
+
// gaining the `| undefined` of an interior optional slot.
|
|
1516
|
+
const absent: PreparedFunction = {
|
|
1517
|
+
...prepared,
|
|
1518
|
+
required: new Set([...(prepared.required ?? []), argument]),
|
|
1519
|
+
retype: new Map([[argument, () => "undefined"]]),
|
|
1520
|
+
};
|
|
1521
|
+
return [strict, absent];
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// A rule an engine binding enforces on one parameter that no ref-doc token
|
|
1525
|
+
// states, keyed `<fqn>:param:<name>`. Each rewrites the slot's mapped type, so
|
|
1526
|
+
// one entry holds on every target whatever token spelling produced that type.
|
|
1527
|
+
export type EngineSlotRule =
|
|
1528
|
+
// `E[]` becomes `[E, ...E[]]`: the binding raises on an empty list.
|
|
1529
|
+
| { readonly kind: "non-empty"; readonly reason: string }
|
|
1530
|
+
// The constants the binding raises on leave the slot's type.
|
|
1531
|
+
| { readonly kind: "excludes"; readonly constants: readonly string[]; readonly reason: string }
|
|
1532
|
+
// With `flag` true the table must carry `fields`, which the binding then reads
|
|
1533
|
+
// without a default.
|
|
1534
|
+
| {
|
|
1535
|
+
readonly kind: "flag-requires";
|
|
1536
|
+
readonly flag: string;
|
|
1537
|
+
readonly fields: readonly string[];
|
|
1538
|
+
readonly reason: string;
|
|
1539
|
+
}
|
|
1540
|
+
// In each call that requires `slot`, this slot is required too and its
|
|
1541
|
+
// top-level union loses `drops`.
|
|
1542
|
+
| {
|
|
1543
|
+
readonly kind: "alongside";
|
|
1544
|
+
readonly slot: string;
|
|
1545
|
+
readonly drops: readonly string[];
|
|
1546
|
+
readonly reason: string;
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
export const ENGINE_SLOT_RULES: ReadonlyMap<string, EngineSlotRule> = new Map<
|
|
1550
|
+
string,
|
|
1551
|
+
EngineSlotRule
|
|
1552
|
+
>([
|
|
1553
|
+
[
|
|
1554
|
+
"buffer.create:param:declaration",
|
|
1555
|
+
{
|
|
1556
|
+
kind: "non-empty",
|
|
1557
|
+
reason:
|
|
1558
|
+
'script_buffer.cpp:Create raises "You must specify at least one stream declaration" when the list holds fewer than one entry',
|
|
1559
|
+
},
|
|
1560
|
+
],
|
|
1561
|
+
[
|
|
1562
|
+
"buffer.set_metadata:param:values",
|
|
1563
|
+
{
|
|
1564
|
+
kind: "non-empty",
|
|
1565
|
+
reason:
|
|
1566
|
+
'script_buffer.cpp:SetMetadata raises "invalid metadata" when lua_objlen of values is 0',
|
|
1567
|
+
},
|
|
1568
|
+
],
|
|
1569
|
+
[
|
|
1570
|
+
"buffer.set_metadata:param:value_type",
|
|
1571
|
+
{
|
|
1572
|
+
kind: "excludes",
|
|
1573
|
+
constants: ["buffer.VALUE_TYPE_INT64", "buffer.VALUE_TYPE_UINT64"],
|
|
1574
|
+
reason:
|
|
1575
|
+
'script_buffer.cpp:SetMetadata raises "64 bit integer metadata are not supported." on VALUE_TYPE_UINT64 and VALUE_TYPE_INT64',
|
|
1576
|
+
},
|
|
1577
|
+
],
|
|
1578
|
+
[
|
|
1579
|
+
"resource.create_sound_data:param:options",
|
|
1580
|
+
{
|
|
1581
|
+
kind: "flag-requires",
|
|
1582
|
+
flag: "partial",
|
|
1583
|
+
fields: ["filesize"],
|
|
1584
|
+
reason:
|
|
1585
|
+
"script_resource.cpp:CreateSoundData reads filesize with the no-default CheckFieldValue<int> when partial is true, and CheckValue<int> raises on a non-number",
|
|
1586
|
+
},
|
|
1587
|
+
],
|
|
1588
|
+
[
|
|
1589
|
+
"go.delete:param:id",
|
|
1590
|
+
{
|
|
1591
|
+
kind: "alongside",
|
|
1592
|
+
slot: "recursive",
|
|
1593
|
+
drops: ["boolean"],
|
|
1594
|
+
reason:
|
|
1595
|
+
"gameobject_script.cpp:Script_Delete raises on a boolean id with a second argument, and on a nil id once recursive is popped",
|
|
1596
|
+
},
|
|
1597
|
+
],
|
|
1598
|
+
]);
|
|
1599
|
+
|
|
1600
|
+
// `prepared` with `rewrite` applied after any rewrite it already makes at `index`.
|
|
1601
|
+
function withRetype(
|
|
1602
|
+
prepared: PreparedFunction,
|
|
1603
|
+
index: number,
|
|
1604
|
+
rewrite: (type: string) => string,
|
|
1605
|
+
): PreparedFunction {
|
|
1606
|
+
const retype = new Map(prepared.retype);
|
|
1607
|
+
const prior = retype.get(index);
|
|
1608
|
+
retype.set(index, prior === undefined ? rewrite : (type) => rewrite(prior(type)));
|
|
1609
|
+
return { ...prepared, retype };
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// `E[]` as `[E, ...E[]]`; the element keeps its own parentheses.
|
|
1613
|
+
function nonEmptyList(type: string, key: string): string {
|
|
1614
|
+
const element = type.endsWith("[]") ? type.slice(0, -2) : "";
|
|
1615
|
+
const single =
|
|
1616
|
+
splitTopLevel(type, "|").length === 1 &&
|
|
1617
|
+
splitTopLevel(type, "&").length === 1 &&
|
|
1618
|
+
!isFunctionTypeText(type);
|
|
1619
|
+
if (element === "" || !single) {
|
|
1620
|
+
throw new Error(`engine slot rule ${key}: ${type} is not one array`);
|
|
1621
|
+
}
|
|
1622
|
+
return `[${element}, ...${element}[]]`;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function withoutMembers(type: string, drops: readonly string[], key: string): string {
|
|
1626
|
+
const members = splitTopLevel(type, "|").map((member) => member.trim());
|
|
1627
|
+
const missing = drops.filter((drop) => !members.includes(drop));
|
|
1628
|
+
if (missing.length > 0) {
|
|
1629
|
+
throw new Error(`engine slot rule ${key}: ${type} has no ${missing.join(", ")} to drop`);
|
|
1630
|
+
}
|
|
1631
|
+
return members.filter((member) => !drops.includes(member)).join(" | ");
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
// The field names a table slot declares: its STRUCT's members, else the fields
|
|
1635
|
+
// parsed from its own doc.
|
|
1636
|
+
function slotFieldNames(parameter: ApiParameter, structs: readonly ApiStruct[]): string[] {
|
|
1637
|
+
const struct = structs.find((s) => parameter.types.includes(s.name));
|
|
1638
|
+
if (struct !== undefined) return struct.members.map((member) => member.name);
|
|
1639
|
+
const fields = parameter.fields ?? parseTableFields(parameter.doc) ?? [];
|
|
1640
|
+
return fields.map((field) => field.name);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function engineSlotRuleArms(
|
|
1644
|
+
prepared: PreparedFunction,
|
|
1645
|
+
arms: readonly PreparedFunction[],
|
|
1646
|
+
key: string,
|
|
1647
|
+
rule: EngineSlotRule,
|
|
1648
|
+
index: number,
|
|
1649
|
+
structs: readonly ApiStruct[],
|
|
1650
|
+
bindingsRead: boolean,
|
|
1651
|
+
): PreparedFunction[] {
|
|
1652
|
+
const { name, parameters } = prepared.original;
|
|
1653
|
+
const takes = (arm: PreparedFunction): boolean => index < arm.original.parameters.length;
|
|
1654
|
+
const rewriteEach = (rewrite: (type: string) => string): PreparedFunction[] =>
|
|
1655
|
+
arms.map((arm) => (takes(arm) ? withRetype(arm, index, rewrite) : arm));
|
|
1656
|
+
switch (rule.kind) {
|
|
1657
|
+
case "non-empty":
|
|
1658
|
+
return rewriteEach((type) => nonEmptyList(type, key));
|
|
1659
|
+
case "excludes": {
|
|
1660
|
+
const excluded = rule.constants.map((constant) => `typeof ${constant}`).join(" | ");
|
|
1661
|
+
return rewriteEach((type) => `Exclude<${type}, ${excluded}>`);
|
|
1662
|
+
}
|
|
1663
|
+
case "flag-requires": {
|
|
1664
|
+
const declared = slotFieldNames(parameters[index] as ApiParameter, structs);
|
|
1665
|
+
const missing = [rule.flag, ...rule.fields].filter((field) => !declared.includes(field));
|
|
1666
|
+
if (missing.length > 0) {
|
|
1667
|
+
throw new Error(`engine slot rule ${key}: the table declares no ${missing.join(", ")}`);
|
|
1668
|
+
}
|
|
1669
|
+
const picked = rule.fields.map((field) => `"${field}"`).join(" | ");
|
|
1670
|
+
return rewriteEach((type) => {
|
|
1671
|
+
const operand =
|
|
1672
|
+
splitTopLevel(type, "|").length > 1 || isFunctionTypeText(type) ? `(${type})` : type;
|
|
1673
|
+
return `${operand} & ({ ${rule.flag}?: false } | ({ ${rule.flag}: true } & Required<Pick<${type}, ${picked}>>))`;
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
case "alongside": {
|
|
1677
|
+
const partner = parameters.findIndex((p) => p.name === rule.slot);
|
|
1678
|
+
const partnerParameter = parameters[partner];
|
|
1679
|
+
const requires = (arm: PreparedFunction): boolean =>
|
|
1680
|
+
partnerParameter !== undefined &&
|
|
1681
|
+
partner < arm.original.parameters.length &&
|
|
1682
|
+
(arm.required?.has(partner) === true || !isDocOptional(partnerParameter, name));
|
|
1683
|
+
if (!arms.some(requires)) {
|
|
1684
|
+
// Without the target's nil-raising slots no call was split out to
|
|
1685
|
+
// require the partner, so there is no binding to have stopped marking it.
|
|
1686
|
+
if (!bindingsRead) return [...arms];
|
|
1687
|
+
throw new Error(
|
|
1688
|
+
partnerParameter === undefined
|
|
1689
|
+
? `engine slot rule ${key}: ${name} has no ${rule.slot} parameter`
|
|
1690
|
+
: `engine slot rule ${key}: no call of ${name} requires ${rule.slot}`,
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
return arms.map((arm) =>
|
|
1694
|
+
requires(arm)
|
|
1695
|
+
? {
|
|
1696
|
+
...withRetype(arm, index, (type) => withoutMembers(type, rule.drops, key)),
|
|
1697
|
+
required: new Set([...(arm.required ?? []), index]),
|
|
1698
|
+
}
|
|
1699
|
+
: arm,
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
// `arms` with every ENGINE_SLOT_RULES entry keyed to `prepared` applied.
|
|
1706
|
+
// `bindingsRead` says the emit was given the target's nil-raising slots.
|
|
1707
|
+
function applyEngineSlotRules(
|
|
1708
|
+
prepared: PreparedFunction,
|
|
1709
|
+
arms: readonly PreparedFunction[],
|
|
1710
|
+
structs: readonly ApiStruct[],
|
|
1711
|
+
bindingsRead: boolean,
|
|
1712
|
+
): PreparedFunction[] {
|
|
1713
|
+
const { name, parameters } = prepared.original;
|
|
1714
|
+
const prefix = `${name}:param:`;
|
|
1715
|
+
let ruled = [...arms];
|
|
1716
|
+
for (const [key, rule] of ENGINE_SLOT_RULES) {
|
|
1717
|
+
if (!key.startsWith(prefix)) continue;
|
|
1718
|
+
const slot = key.slice(prefix.length);
|
|
1719
|
+
const index = parameters.findIndex((p) => p.name === slot);
|
|
1720
|
+
if (index === -1) {
|
|
1721
|
+
throw new Error(`engine slot rule ${key}: ${name} has no ${slot} parameter`);
|
|
1722
|
+
}
|
|
1723
|
+
ruled = engineSlotRuleArms(prepared, ruled, key, rule, index, structs, bindingsRead);
|
|
1724
|
+
}
|
|
1725
|
+
return ruled;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
// Every call a function declares, in order: its nil-raising arms, each split
|
|
1729
|
+
// at its presence-required fields, then narrowed by its engine slot rules.
|
|
1730
|
+
function callArms(
|
|
1731
|
+
prepared: PreparedFunction,
|
|
1732
|
+
nilRaisingSlots: ReadonlyMap<string, readonly number[]> | undefined,
|
|
1733
|
+
structs: readonly ApiStruct[],
|
|
1734
|
+
): PreparedFunction[] {
|
|
1735
|
+
const arms = nilRaisingArms(prepared, nilRaisingSlots ?? new Map()).flatMap((arm) =>
|
|
1736
|
+
presenceArms(arm, structs),
|
|
1737
|
+
);
|
|
1738
|
+
return applyEngineSlotRules(prepared, arms, structs, nilRaisingSlots !== undefined);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1144
1741
|
export type ConstantSlotResolution =
|
|
1145
1742
|
| { readonly borrow: string }
|
|
1146
1743
|
| { readonly family: string; readonly evidence: string }
|
|
@@ -1155,8 +1752,6 @@ export const CONSTANT_SLOT_RESOLUTIONS: ReadonlyMap<string, ConstantSlotResoluti
|
|
|
1155
1752
|
string,
|
|
1156
1753
|
ConstantSlotResolution
|
|
1157
1754
|
>([
|
|
1158
|
-
["gui.get:param:property", { borrow: "gui.animate:param:property" }],
|
|
1159
|
-
["gui.set:param:property", { borrow: "gui.animate:param:property" }],
|
|
1160
1755
|
["gui.cancel_animations:param:property", { borrow: "gui.animate:param:property" }],
|
|
1161
1756
|
[
|
|
1162
1757
|
"buffer.set_metadata:param:value_type",
|
|
@@ -1285,6 +1880,29 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
|
|
|
1285
1880
|
"go.PLAYBACK_LOOP_PINGPONG",
|
|
1286
1881
|
],
|
|
1287
1882
|
},
|
|
1883
|
+
// The factors render_script.cpp:CheckBlendFactor accepts, in its order. Every
|
|
1884
|
+
// target defines the constants; only the newer ones name the union.
|
|
1885
|
+
{
|
|
1886
|
+
home: "graphics",
|
|
1887
|
+
name: "BlendFactor",
|
|
1888
|
+
members: [
|
|
1889
|
+
"graphics.BLEND_FACTOR_ZERO",
|
|
1890
|
+
"graphics.BLEND_FACTOR_ONE",
|
|
1891
|
+
"graphics.BLEND_FACTOR_SRC_COLOR",
|
|
1892
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_SRC_COLOR",
|
|
1893
|
+
"graphics.BLEND_FACTOR_DST_COLOR",
|
|
1894
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_DST_COLOR",
|
|
1895
|
+
"graphics.BLEND_FACTOR_SRC_ALPHA",
|
|
1896
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_SRC_ALPHA",
|
|
1897
|
+
"graphics.BLEND_FACTOR_DST_ALPHA",
|
|
1898
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_DST_ALPHA",
|
|
1899
|
+
"graphics.BLEND_FACTOR_SRC_ALPHA_SATURATE",
|
|
1900
|
+
"graphics.BLEND_FACTOR_CONSTANT_COLOR",
|
|
1901
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR",
|
|
1902
|
+
"graphics.BLEND_FACTOR_CONSTANT_ALPHA",
|
|
1903
|
+
"graphics.BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA",
|
|
1904
|
+
],
|
|
1905
|
+
},
|
|
1288
1906
|
{
|
|
1289
1907
|
home: "graphics",
|
|
1290
1908
|
name: "BufferType",
|
|
@@ -1498,6 +2116,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
|
|
|
1498
2116
|
"graphics.BUFFER_TYPE_STENCIL_BIT",
|
|
1499
2117
|
],
|
|
1500
2118
|
},
|
|
2119
|
+
// The render-target creator raises on any other texture type.
|
|
2120
|
+
{
|
|
2121
|
+
home: "render",
|
|
2122
|
+
name: "RenderTargetTextureType",
|
|
2123
|
+
members: ["graphics.TEXTURE_TYPE_2D", "graphics.TEXTURE_TYPE_CUBE_MAP"],
|
|
2124
|
+
},
|
|
1501
2125
|
{
|
|
1502
2126
|
home: "sys",
|
|
1503
2127
|
name: "NetworkConnectivity",
|
|
@@ -1553,6 +2177,13 @@ function unionAliasDeclaration(
|
|
|
1553
2177
|
return `type ${name} = ${members.map(arm).join(" | ")};`;
|
|
1554
2178
|
}
|
|
1555
2179
|
|
|
2180
|
+
// A slot of a function the authored-overload lane declares, published under
|
|
2181
|
+
// one alias on every target so the version-independent overload file can
|
|
2182
|
+
// name the type each target's own reference gives that slot.
|
|
2183
|
+
export const OVERLOAD_SLOT_ALIASES: ReadonlyMap<string, string> = new Map([
|
|
2184
|
+
["render.render_target:param:parameters", "RenderTargetParams"],
|
|
2185
|
+
]);
|
|
2186
|
+
|
|
1556
2187
|
const DOC_CONSTANT_TOKEN =
|
|
1557
2188
|
/(?<![A-Za-z0-9_.])([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*\.[A-Z][A-Z0-9_]*)(\*)?/g;
|
|
1558
2189
|
|
|
@@ -1602,9 +2233,14 @@ function constantSlotTokens(
|
|
|
1602
2233
|
return documentedConstantTokens(entry.family, universe);
|
|
1603
2234
|
}
|
|
1604
2235
|
|
|
1605
|
-
function
|
|
2236
|
+
// A borrow reads the ref-doc, so a slot of a function the skip rules withheld
|
|
2237
|
+
// (`gui.animate`, which gui-overloads.d.ts declares) still lends its doc.
|
|
2238
|
+
function slotDocLookup(
|
|
2239
|
+
module: ApiModule,
|
|
2240
|
+
withheldFunctions: readonly ApiFunction[] = [],
|
|
2241
|
+
): SlotDocLookup {
|
|
1606
2242
|
const docs = new Map<string, string>();
|
|
1607
|
-
for (const fn of module.functions) {
|
|
2243
|
+
for (const fn of [...module.functions, ...withheldFunctions]) {
|
|
1608
2244
|
for (const p of fn.parameters) {
|
|
1609
2245
|
const key = tableSlotKey(fn.name, "param", p.name);
|
|
1610
2246
|
if (!docs.has(key)) docs.set(key, p.doc);
|
|
@@ -1624,8 +2260,9 @@ export function resolveConstantSlotTokens(
|
|
|
1624
2260
|
module: ApiModule,
|
|
1625
2261
|
slotKey: string,
|
|
1626
2262
|
universe: ReadonlySet<string>,
|
|
2263
|
+
withheldFunctions: readonly ApiFunction[] = [],
|
|
1627
2264
|
): string[] {
|
|
1628
|
-
const lookupDoc = slotDocLookup(module);
|
|
2265
|
+
const lookupDoc = slotDocLookup(module, withheldFunctions);
|
|
1629
2266
|
return constantSlotTokens(lookupDoc(slotKey) ?? "", slotKey, lookupDoc, universe);
|
|
1630
2267
|
}
|
|
1631
2268
|
|
|
@@ -1639,21 +2276,23 @@ type ConstantSlotTokens = (
|
|
|
1639
2276
|
function constantSlotTokenResolver(
|
|
1640
2277
|
module: ApiModule,
|
|
1641
2278
|
universe: ReadonlySet<string>,
|
|
2279
|
+
withheldFunctions: readonly ApiFunction[] = [],
|
|
1642
2280
|
): ConstantSlotTokens {
|
|
1643
|
-
const lookupDoc = slotDocLookup(module);
|
|
2281
|
+
const lookupDoc = slotDocLookup(module, withheldFunctions);
|
|
1644
2282
|
return (elementName, slotKind, slotName, doc) =>
|
|
1645
2283
|
constantSlotTokens(doc, tableSlotKey(elementName, slotKind, slotName), lookupDoc, universe);
|
|
1646
2284
|
}
|
|
1647
2285
|
|
|
1648
|
-
// FQN-keyed allowlist of the
|
|
1649
|
-
//
|
|
1650
|
-
//
|
|
1651
|
-
//
|
|
1652
|
-
//
|
|
1653
|
-
// `
|
|
1654
|
-
//
|
|
1655
|
-
//
|
|
1656
|
-
//
|
|
2286
|
+
// FQN-keyed allowlist of the checks that genuinely narrow their argument, mapped
|
|
2287
|
+
// to the `DEFOLD_TYPE_MAP` token whose type they prove: the `types.is_*` checks,
|
|
2288
|
+
// and `bullet3d.collision_object.is_rigid_body`, whose binding is a
|
|
2289
|
+
// `btRigidBody::upcast` test. Emitting these as user-defined type guards
|
|
2290
|
+
// (`var_ is Vector3`) is the only way to narrow an engine userdata value, which
|
|
2291
|
+
// `typeof` cannot. An allowlist, not a blanket `is_*` match: `b2d.body.is_active`,
|
|
2292
|
+
// `gui.is_enabled`, `sound.is_music_playing`, `go.exists`, `sys.exists` are
|
|
2293
|
+
// runtime-state / existence checks on already-typed arguments and must stay
|
|
2294
|
+
// `boolean`. An unlisted future `types.is_*` also stays a visible `boolean` until
|
|
2295
|
+
// curated here, matching the ARBITRARY_TABLE_SLOTS deliberate-opt-in precedent.
|
|
1657
2296
|
export const TYPE_PREDICATES: ReadonlyMap<string, string> = new Map([
|
|
1658
2297
|
["types.is_hash", "hash"],
|
|
1659
2298
|
["types.is_matrix4", "matrix4"],
|
|
@@ -1662,13 +2301,47 @@ export const TYPE_PREDICATES: ReadonlyMap<string, string> = new Map([
|
|
|
1662
2301
|
["types.is_vector", "vector"],
|
|
1663
2302
|
["types.is_vector3", "vector3"],
|
|
1664
2303
|
["types.is_vector4", "vector4"],
|
|
2304
|
+
["bullet3d.collision_object.is_rigid_body", "btRigidBody"],
|
|
2305
|
+
]);
|
|
2306
|
+
|
|
2307
|
+
// The merged ref-doc gives each Box2D backend's form of these queries one
|
|
2308
|
+
// parameter list, so the first (v2) arm always wins and a v3 hit is unreachable.
|
|
2309
|
+
// A type parameter tells them apart: v2/script_box2d_world_v2.cpp:PushCastHit
|
|
2310
|
+
// sets `fixture` and `shape` to one PushFixtureInfo table, while
|
|
2311
|
+
// v3/script_box2d_world_v3.cpp:PushCastHit sets `shape` alone from
|
|
2312
|
+
// PushShapeInfoForShape. The v2 arm is declared first, so a call with no type
|
|
2313
|
+
// argument keeps the v2 hits; its `= "v2"` default only states that. Keyed by
|
|
2314
|
+
// `box2dBackendArmKey`.
|
|
2315
|
+
export const BOX2D_BACKEND_ARMS: ReadonlyMap<string, Box2dBackend> = new Map<string, Box2dBackend>([
|
|
2316
|
+
["b2d.world.cast_ray:b2d.fixture_cast_hit[]", "v2"],
|
|
2317
|
+
["b2d.world.cast_ray:b2d.shape_cast_hit[]", "v3"],
|
|
2318
|
+
["b2d.world.cast_ray_closest:b2d.fixture_cast_hit", "v2"],
|
|
2319
|
+
["b2d.world.cast_ray_closest:b2d.shape_cast_hit", "v3"],
|
|
2320
|
+
["b2d.world.cast_shape:b2d.fixture_cast_hit[]", "v2"],
|
|
2321
|
+
["b2d.world.cast_shape:b2d.shape_cast_hit[]", "v3"],
|
|
2322
|
+
["b2d.world.overlap_aabb:b2d.fixture_info[]", "v2"],
|
|
2323
|
+
["b2d.world.overlap_aabb:b2d.shape_info[]", "v3"],
|
|
2324
|
+
["b2d.world.overlap_shape:b2d.fixture_info[]", "v2"],
|
|
2325
|
+
["b2d.world.overlap_shape:b2d.shape_info[]", "v3"],
|
|
1665
2326
|
]);
|
|
1666
2327
|
|
|
2328
|
+
// `<fqn>:<first non-nil type of the first return>`: the two arms of each
|
|
2329
|
+
// `BOX2D_BACKEND_ARMS` query share every parameter and differ only there.
|
|
2330
|
+
export function box2dBackendArmKey(fn: ApiFunction): string {
|
|
2331
|
+
const first = fn.returnValues[0]?.types.find((type) => type !== "nil") ?? "";
|
|
2332
|
+
return `${fn.name}:${first}`;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
function box2dBackendTypeParameters(fn: ApiFunction): string {
|
|
2336
|
+
const backend = BOX2D_BACKEND_ARMS.get(box2dBackendArmKey(fn));
|
|
2337
|
+
if (backend === undefined) return "";
|
|
2338
|
+
return backend === "v2" ? '<Backend extends "v2" = "v2">' : '<Backend extends "v3">';
|
|
2339
|
+
}
|
|
2340
|
+
|
|
1667
2341
|
// Element names whose `table` slot is a prose-only `array/list/table of <T>` shape
|
|
1668
2342
|
// the field-list parser cannot read, but whose element type a human curated from
|
|
1669
2343
|
// the doc. The value is a single element token (`T[]`) or a token list when the
|
|
1670
|
-
// element is itself a union
|
|
1671
|
-
// `string | hash | url`, emitted `(string | Hash | Url)[]`. Emitted as a plain
|
|
2344
|
+
// element is itself a union, emitted `(A | B)[]`. Emitted as a plain
|
|
1672
2345
|
// array, never `LuaMap`: an array element carries no illegal-index-key problem so
|
|
1673
2346
|
// no TSTL wiring is needed. The audit consults this map too, so the gate and the
|
|
1674
2347
|
// emitted surface stay coupled; a slot not curated here still surfaces under
|
|
@@ -1683,7 +2356,6 @@ export const HOMOGENEOUS_ARRAY_SLOTS: ReadonlyMap<string, string | readonly stri
|
|
|
1683
2356
|
["vmath.vector", "number"],
|
|
1684
2357
|
["sound.get_groups", "hash"],
|
|
1685
2358
|
["iap.list", "string"],
|
|
1686
|
-
["go.delete", ["string", "hash", "url"]],
|
|
1687
2359
|
// push.register's `notifications` is a prose-only array of push.NOTIFICATION_*
|
|
1688
2360
|
// bitmask constants (the ref example sums NOTIFICATION_BADGE/SOUND/ALERT). The
|
|
1689
2361
|
// vendored push_doc.json fixture declares no NOTIFICATION_* constant elements, so
|
|
@@ -1706,7 +2378,11 @@ export type NestedMapping = { key: string; value: string };
|
|
|
1706
2378
|
// for `collectionfactory.create`'s properties.
|
|
1707
2379
|
export type TableSlotCuration = (
|
|
1708
2380
|
| { kind: "mapping"; key: string; value: string | readonly TableField[] | NestedMapping }
|
|
1709
|
-
|
|
2381
|
+
// `pairs` also admits any table whose values are the element, for a binding
|
|
2382
|
+
// that walks the table with lua_next and reads only its values. The extra arm
|
|
2383
|
+
// is `LuaPairsIterable`, which is covariant in its value; `LuaTable` is
|
|
2384
|
+
// invariant through `set`, so it would reject a `LuaTable<Hash, Hash>`.
|
|
2385
|
+
| { kind: "array"; element: string | readonly string[]; pairs?: true }
|
|
1710
2386
|
| { kind: "object"; fields: readonly TableField[] }
|
|
1711
2387
|
| { kind: "array-object"; fields: readonly TableField[] }
|
|
1712
2388
|
// A `<key> -> <args table>` slot: the doc's `<dl>` describes the *value* shape,
|
|
@@ -1883,25 +2559,6 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1883
2559
|
string,
|
|
1884
2560
|
TableSlotCuration
|
|
1885
2561
|
>([
|
|
1886
|
-
// gui_script.cpp:LuaGet and LuaSet read the `key` and `keys` options. On a
|
|
1887
|
-
// prose `table` slot TABLE_SLOT_FIELD_ADDITIONS supplies them; 1.13.2 types the
|
|
1888
|
-
// slots as an inline record and a struct that omit them.
|
|
1889
|
-
...(
|
|
1890
|
-
[
|
|
1891
|
-
["gui.get:param:options", "{ index?:integer }"],
|
|
1892
|
-
["gui.set:param:options", "gui.set_options"],
|
|
1893
|
-
] as const
|
|
1894
|
-
).map(
|
|
1895
|
-
([key, retyped]) =>
|
|
1896
|
-
[
|
|
1897
|
-
key,
|
|
1898
|
-
{
|
|
1899
|
-
kind: "verbatim",
|
|
1900
|
-
ts: "{ index?: number; key?: string | Hash; keys?: (Hash | string)[] }",
|
|
1901
|
-
retypedUpstream: [retyped],
|
|
1902
|
-
},
|
|
1903
|
-
] as const,
|
|
1904
|
-
),
|
|
1905
2562
|
// buffer.create's `declaration` is "a table where each entry (table) describes
|
|
1906
2563
|
// a stream", and upstream's example passes a list of stream records whose
|
|
1907
2564
|
// `type` is a buffer.VALUE_TYPE_* constant.
|
|
@@ -1929,6 +2586,19 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1929
2586
|
},
|
|
1930
2587
|
],
|
|
1931
2588
|
["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
|
|
2589
|
+
// go.delete's "table of id's": gameobject_script.cpp:DeleteGOTable walks any
|
|
2590
|
+
// table with lua_next and resolves each value with ResolveURL, so the id table
|
|
2591
|
+
// collectionfactory.create returns deletes as it is. 1.13.2 retypes the slot
|
|
2592
|
+
// to an id array, which still rejects that table.
|
|
2593
|
+
[
|
|
2594
|
+
"go.delete:param:id",
|
|
2595
|
+
{
|
|
2596
|
+
kind: "array",
|
|
2597
|
+
element: ["string", "hash", "url"],
|
|
2598
|
+
pairs: true,
|
|
2599
|
+
retypedUpstream: ["(string|hash|url)[]"],
|
|
2600
|
+
},
|
|
2601
|
+
],
|
|
1932
2602
|
// font.get_info's `info` return is a `<dl>` with `path: hash` and a nested
|
|
1933
2603
|
// `fonts` field whose `<dd>` is itself a `<dl>` (`path: string`, `path_hash:
|
|
1934
2604
|
// hash`). parseTableFields flattens the inner `<dl>` to the top level, which
|
|
@@ -2186,62 +2856,6 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2186
2856
|
value: "number | vector4",
|
|
2187
2857
|
},
|
|
2188
2858
|
],
|
|
2189
|
-
// go.on_input and gui.on_input share the same well-known InputAction shape
|
|
2190
|
-
// (the on_input function description lists the well-known fields: value,
|
|
2191
|
-
// pressed, released, repeated, x/y/screen_x/screen_y/dx/dy/screen_dx/screen_dy,
|
|
2192
|
-
// plus gamepad/touch/text multi-touch fields). The doc markup carries no
|
|
2193
|
-
// field list of its own, so the shape is curated verbatim from the prose.
|
|
2194
|
-
// Param-side optional fields: each input kind (mouse/key/text/gamepad) sets a
|
|
2195
|
-
// different subset, so the call site always sees an `?` on every field.
|
|
2196
|
-
// `touch` stays `table` (the nested multi-touch table has no machine-readable
|
|
2197
|
-
// field list; mirrors the render.set_render_target.transient recursive case).
|
|
2198
|
-
// The element is the bare `on_input` script hook (no namespace prefix), the
|
|
2199
|
-
// same form the `on_message` arbitrary-table entry uses. The doc carries a
|
|
2200
|
-
// well-formed touch input table, so `touch` is curated as a nested
|
|
2201
|
-
// array-of-touch-records (the field has its own `<table>` listing id,
|
|
2202
|
-
// pressed, released, tap_count, x, y, dx, dy, acc_x/y/z).
|
|
2203
|
-
[
|
|
2204
|
-
"on_input:param:action",
|
|
2205
|
-
{
|
|
2206
|
-
kind: "object",
|
|
2207
|
-
fields: [
|
|
2208
|
-
{ name: "value", types: ["number"], optional: true },
|
|
2209
|
-
{ name: "pressed", types: ["boolean"], optional: true },
|
|
2210
|
-
{ name: "released", types: ["boolean"], optional: true },
|
|
2211
|
-
{ name: "repeated", types: ["boolean"], optional: true },
|
|
2212
|
-
{ name: "x", types: ["number"], optional: true },
|
|
2213
|
-
{ name: "y", types: ["number"], optional: true },
|
|
2214
|
-
{ name: "screen_x", types: ["number"], optional: true },
|
|
2215
|
-
{ name: "screen_y", types: ["number"], optional: true },
|
|
2216
|
-
{ name: "dx", types: ["number"], optional: true },
|
|
2217
|
-
{ name: "dy", types: ["number"], optional: true },
|
|
2218
|
-
{ name: "screen_dx", types: ["number"], optional: true },
|
|
2219
|
-
{ name: "screen_dy", types: ["number"], optional: true },
|
|
2220
|
-
{ name: "gamepad", types: ["number"], optional: true },
|
|
2221
|
-
{ name: "gamepad_axis", types: ["vector3"], optional: true },
|
|
2222
|
-
{
|
|
2223
|
-
name: "touch",
|
|
2224
|
-
types: ["table"],
|
|
2225
|
-
optional: true,
|
|
2226
|
-
isList: true,
|
|
2227
|
-
fields: [
|
|
2228
|
-
{ name: "id", types: ["number"] },
|
|
2229
|
-
{ name: "pressed", types: ["boolean"] },
|
|
2230
|
-
{ name: "released", types: ["boolean"] },
|
|
2231
|
-
{ name: "tap_count", types: ["number"] },
|
|
2232
|
-
{ name: "x", types: ["number"] },
|
|
2233
|
-
{ name: "y", types: ["number"] },
|
|
2234
|
-
{ name: "dx", types: ["number"] },
|
|
2235
|
-
{ name: "dy", types: ["number"] },
|
|
2236
|
-
{ name: "acc_x", types: ["number"] },
|
|
2237
|
-
{ name: "acc_y", types: ["number"] },
|
|
2238
|
-
{ name: "acc_z", types: ["number"] },
|
|
2239
|
-
],
|
|
2240
|
-
},
|
|
2241
|
-
{ name: "text", types: ["string"], optional: true },
|
|
2242
|
-
],
|
|
2243
|
-
},
|
|
2244
|
-
],
|
|
2245
2859
|
// physics.create_joint and physics.set_joint_properties both declare
|
|
2246
2860
|
// `properties` as a joint-type-specific table; the only universal field is
|
|
2247
2861
|
// `collide_connected` (parseTableFields already recovers it from the
|
|
@@ -2267,10 +2881,13 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2267
2881
|
// buildTableDocResolver resolves only FUNCTION elements so the cross-ref
|
|
2268
2882
|
// cannot fire (the message payload lives in messages_doc.json, not the
|
|
2269
2883
|
// FUNCTION slot pool). The shape is curated directly from the message
|
|
2270
|
-
// catalog
|
|
2884
|
+
// catalog: the ray_cast_response payload fields but `request_id`, which
|
|
2885
|
+
// `Physics_RayCast` never sets. The doc declares a
|
|
2271
2886
|
// `nil` branch ("If missed it returns <code>nil</code>"), so the existing
|
|
2272
2887
|
// nil-aware emit already projects the curated array onto the
|
|
2273
2888
|
// `array-object | nil` return type — no curation change needed there.
|
|
2889
|
+
// Where its binding pin holds, the `physics.raycast` RETURN_TYPE_CORRECTIONS
|
|
2890
|
+
// entry replaces the whole return.
|
|
2274
2891
|
[
|
|
2275
2892
|
"physics.raycast:return:result",
|
|
2276
2893
|
{
|
|
@@ -2281,7 +2898,6 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2281
2898
|
{ name: "normal", types: ["vector3"] },
|
|
2282
2899
|
{ name: "id", types: ["hash"] },
|
|
2283
2900
|
{ name: "group", types: ["hash"] },
|
|
2284
|
-
{ name: "request_id", types: ["number"] },
|
|
2285
2901
|
],
|
|
2286
2902
|
},
|
|
2287
2903
|
],
|
|
@@ -2704,41 +3320,105 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2704
3320
|
]);
|
|
2705
3321
|
|
|
2706
3322
|
// Slot-keyed (`element:param:name`, mirroring TABLE_SLOT_CURATIONS) replacements
|
|
2707
|
-
// for a callback parameter's
|
|
2708
|
-
//
|
|
2709
|
-
//
|
|
2710
|
-
//
|
|
2711
|
-
// `
|
|
2712
|
-
//
|
|
2713
|
-
//
|
|
2714
|
-
// (receive, narrow) split.
|
|
3323
|
+
// for a callback parameter's signature. Each entry carries two forms, chosen by
|
|
3324
|
+
// the token the reference gives the slot. `untyped` answers a bare
|
|
3325
|
+
// `function(a, b, c)` token, where the generic `recoverCallbackSignature`
|
|
3326
|
+
// `unknown`-everywhere form leaves real engine type information on the table.
|
|
3327
|
+
// `typed` answers a `fun(a:T, ...)` token, where the reference names a type for
|
|
3328
|
+
// each parameter; `null` there emits the reference's own signature. Each form
|
|
3329
|
+
// is the full emitted function type. Honest only.
|
|
2715
3330
|
//
|
|
2716
|
-
//
|
|
3331
|
+
// `window.set_listener` discriminates `event` by the union of the
|
|
3332
|
+
// `WINDOW_EVENT_*` constants the surface's own `window` namespace declares, on
|
|
3333
|
+
// both forms: the union is derived from the surface, so each version accepts
|
|
3334
|
+
// exactly the constants it declares. Its untyped `data` is a bare record (only
|
|
3335
|
+
// resize carries fields) and its typed `data` is the reference's
|
|
3336
|
+
// `window.event_data`; `isWindowEvent` narrows either to the event's payload,
|
|
3337
|
+
// mirroring the `msg.post` (send, typed) / `isMessage` (receive, narrow) split.
|
|
3338
|
+
//
|
|
3339
|
+
// The other untyped forms type what the slot's own prose lists and nothing more.
|
|
2717
3340
|
// `http.request`'s response fields are the `<ul>` under "The response data.
|
|
2718
3341
|
// Contains the fields:"; each is conditional ("if not saved on disc", "only if
|
|
2719
3342
|
// option report_progress is true") except `status`, and `headers` is the same
|
|
2720
|
-
// string map the request sends.
|
|
2721
|
-
//
|
|
3343
|
+
// string map the request sends. The prose omits two fields the engine sets:
|
|
3344
|
+
// `url`, which every final response carries (script_http_util.h), and
|
|
3345
|
+
// `bytes_sent`, which a report_progress event carries beside `bytes_received`,
|
|
3346
|
+
// `bytes_total` and `url` (http_ddf.proto `HttpRequestProgress`). That event has
|
|
3347
|
+
// no `status`; typing it as its own shape needs a per-target authored overload,
|
|
3348
|
+
// so `status` stays required and the slot's `note` says how to tell the two
|
|
3349
|
+
// apart. Its typed form is the same signature: the typed reference names
|
|
3350
|
+
// `http.response`, which the runtime surface withholds. Both forms add the
|
|
3351
|
+
// `sender` no reference lists: comp_script.cpp delivers the response as a
|
|
3352
|
+
// message, with `PushURL(L, message->m_Sender)` then `PCall(L, 4, 0)`.
|
|
3353
|
+
// `model.play_anim`'s completion callback receives self, the message id and the
|
|
3354
|
+
// message only: script_model.cpp calls it with `PCall(L, 3, 0)`, so both forms
|
|
3355
|
+
// drop the `sender` every reference lists and type the message as the typed
|
|
3356
|
+
// reference's `model_animation_done`.
|
|
3357
|
+
// `sprite.play_flipbook`'s message is the "Information about the completion"
|
|
3358
|
+
// list. Its typed form is the same signature: the typed reference names the
|
|
3359
|
+
// built-in `animation_done` message, which has this shape, and the inline form
|
|
3360
|
+
// keeps the fields visible in the signature. `collectionproxy.load` documents
|
|
2722
3361
|
// only that the callback receives `proxy_loading`, `proxy_ready` or
|
|
2723
|
-
// `proxy_error` messages and lists no fields
|
|
2724
|
-
// `window.set_listener`'s `data` is
|
|
2725
|
-
|
|
3362
|
+
// `proxy_error` messages and lists no fields on the untyped reference, so its
|
|
3363
|
+
// untyped message is the bare record `window.set_listener`'s untyped `data` is;
|
|
3364
|
+
// the typed reference names `collectionproxy.load_data`, so its typed form
|
|
3365
|
+
// defers to the reference.
|
|
3366
|
+
export interface CallbackSignatureCuration {
|
|
3367
|
+
/** Emitted for a bare `function(...)` token. */
|
|
3368
|
+
readonly untyped: string;
|
|
3369
|
+
/** Emitted for a typed `fun(...)` token; `null` emits the reference's own signature. */
|
|
3370
|
+
readonly typed: string | null;
|
|
3371
|
+
/** Appended to the parameter's doc, for what the signature cannot express. */
|
|
3372
|
+
readonly note?: string;
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
const onBothForms = (signature: string, note?: string): CallbackSignatureCuration => ({
|
|
3376
|
+
untyped: signature,
|
|
3377
|
+
typed: signature,
|
|
3378
|
+
...(note === undefined ? {} : { note }),
|
|
3379
|
+
});
|
|
3380
|
+
|
|
3381
|
+
export const CALLBACK_SIGNATURE_CURATIONS: ReadonlyMap<string, CallbackSignatureCuration> = new Map<
|
|
3382
|
+
string,
|
|
3383
|
+
CallbackSignatureCuration
|
|
3384
|
+
>([
|
|
2726
3385
|
[
|
|
2727
3386
|
"window.set_listener:param:callback",
|
|
2728
|
-
|
|
2729
|
-
|
|
3387
|
+
{
|
|
3388
|
+
untyped:
|
|
3389
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: emitted verbatim as a TS template literal type
|
|
3390
|
+
"(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: Record<string | number, unknown>) => void",
|
|
3391
|
+
typed:
|
|
3392
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: emitted verbatim as a TS template literal type
|
|
3393
|
+
"(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: window.event_data) => void",
|
|
3394
|
+
},
|
|
2730
3395
|
],
|
|
2731
3396
|
[
|
|
2732
3397
|
"http.request:param:callback",
|
|
2733
|
-
|
|
3398
|
+
onBothForms(
|
|
3399
|
+
"(self: unknown, id: Hash, response: { status: number; url: string; response?: string; headers?: LuaMap<string, string>; path?: string; error?: string; bytes_sent?: number; bytes_received?: number; bytes_total?: number; range_start?: number; range_end?: number; document_size?: number }, sender: Url) => void",
|
|
3400
|
+
"With `report_progress` set, the callback also receives progress events carrying `bytes_sent`, `bytes_received`, `bytes_total` and `url` but no `status`; `response.bytes_received !== undefined` marks a progress event, since a final response never carries it.",
|
|
3401
|
+
),
|
|
3402
|
+
],
|
|
3403
|
+
[
|
|
3404
|
+
"model.play_anim:param:complete_function",
|
|
3405
|
+
onBothForms(
|
|
3406
|
+
'(self: unknown, message_id: Hash, message: BuiltinMessages["model_animation_done"]) => void',
|
|
3407
|
+
),
|
|
2734
3408
|
],
|
|
2735
3409
|
[
|
|
2736
3410
|
"sprite.play_flipbook:param:complete_function",
|
|
2737
|
-
|
|
3411
|
+
onBothForms(
|
|
3412
|
+
"(self: unknown, message_id: Hash, message: { current_tile: number; id: Hash }, sender: Url) => void",
|
|
3413
|
+
),
|
|
2738
3414
|
],
|
|
2739
3415
|
[
|
|
2740
3416
|
"collectionproxy.load:param:callback",
|
|
2741
|
-
|
|
3417
|
+
{
|
|
3418
|
+
untyped:
|
|
3419
|
+
"(self: unknown, message_id: Hash, message: Record<string | number, unknown>, sender: Url) => void",
|
|
3420
|
+
typed: null,
|
|
3421
|
+
},
|
|
2742
3422
|
],
|
|
2743
3423
|
]);
|
|
2744
3424
|
|
|
@@ -2801,11 +3481,11 @@ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
2801
3481
|
["resource.set_atlas:param:table:animations[].playback", "go.Playback"],
|
|
2802
3482
|
["resource.create_atlas:param:table:animations[].playback", "go.Playback"],
|
|
2803
3483
|
["resource.get_atlas:return:data:animations[].playback", "go.Playback"],
|
|
2804
|
-
["gui.set:param:options:key", "string | Hash"],
|
|
2805
3484
|
["render.set_render_target:param:options:transient", "graphics.BufferType[]"],
|
|
2806
3485
|
["resource.create_sound_data:param:options:data", 'string | Opaque<"buffer">'],
|
|
2807
|
-
// The
|
|
2808
|
-
//
|
|
3486
|
+
// The option bags 1.13.2 types as a STRUCT, whose members still carry the
|
|
3487
|
+
// narrower upstream types: `gui.set`'s (its slot is declared in
|
|
3488
|
+
// gui-overloads.d.ts) and `resource.create_sound_data`'s.
|
|
2809
3489
|
["gui.set_options:member:key", "string | Hash"],
|
|
2810
3490
|
["resource.sound_data_options:member:data", 'string | Opaque<"buffer">'],
|
|
2811
3491
|
// Bitmasks typed as their flag enum: the create_texture example passes
|
|
@@ -2820,6 +3500,11 @@ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
2820
3500
|
] as const
|
|
2821
3501
|
).map((struct) => [`${struct}:member:flags`, "graphics.TEXTURE_USAGE_FLAG | number"] as const),
|
|
2822
3502
|
["render.render_target_buffer_params:member:flags", "render.RENDER_TARGET_FLAG | number"],
|
|
3503
|
+
// gamesys_script.cpp:PushSampler, behind both get_samplers, pushes the raw
|
|
3504
|
+
// texture type; NormalizeBindableTextureType never runs there. A separate
|
|
3505
|
+
// sampler reports TEXTURE_TYPE_SAMPLER (6) and a separate texture binding one
|
|
3506
|
+
// of the four TEXTURE_TYPE_TEXTURE_* (7-10), none of which has a Lua constant.
|
|
3507
|
+
["material.sampler_info:member:type", "graphics.TEXTURE_TYPE | 6 | 7 | 8 | 9 | 10"],
|
|
2823
3508
|
]);
|
|
2824
3509
|
|
|
2825
3510
|
// A field the engine accepts on an option bag whose `<dl>` upstream otherwise
|
|
@@ -2839,13 +3524,6 @@ export interface TableFieldAddition {
|
|
|
2839
3524
|
readonly evidence: readonly string[];
|
|
2840
3525
|
}
|
|
2841
3526
|
|
|
2842
|
-
const PROPERTY_OPTION_KEYS: TableField = {
|
|
2843
|
-
name: "keys",
|
|
2844
|
-
types: ["table"],
|
|
2845
|
-
optional: true,
|
|
2846
|
-
tsType: "(Hash | string)[]",
|
|
2847
|
-
};
|
|
2848
|
-
|
|
2849
3527
|
export const TABLE_SLOT_FIELD_ADDITIONS: ReadonlyMap<string, TableFieldAddition> = new Map<
|
|
2850
3528
|
string,
|
|
2851
3529
|
TableFieldAddition
|
|
@@ -2860,24 +3538,6 @@ export const TABLE_SLOT_FIELD_ADDITIONS: ReadonlyMap<string, TableFieldAddition>
|
|
|
2860
3538
|
],
|
|
2861
3539
|
},
|
|
2862
3540
|
],
|
|
2863
|
-
[
|
|
2864
|
-
"gui.get:param:options",
|
|
2865
|
-
{
|
|
2866
|
-
fields: [{ name: "key", types: ["string", "hash"], optional: true }, PROPERTY_OPTION_KEYS],
|
|
2867
|
-
evidence: [
|
|
2868
|
-
"gui_script.cpp:LuaGet reads its options through dmGameObject::LuaToPropertyOptions, which reads index, key and keys",
|
|
2869
|
-
],
|
|
2870
|
-
},
|
|
2871
|
-
],
|
|
2872
|
-
[
|
|
2873
|
-
"gui.set:param:options",
|
|
2874
|
-
{
|
|
2875
|
-
fields: [PROPERTY_OPTION_KEYS],
|
|
2876
|
-
evidence: [
|
|
2877
|
-
"gui_script.cpp:LuaSet reads its options through dmGameObject::LuaToPropertyOptions, which reads index, key and keys",
|
|
2878
|
-
],
|
|
2879
|
-
},
|
|
2880
|
-
],
|
|
2881
3541
|
]);
|
|
2882
3542
|
|
|
2883
3543
|
// A return-slot field upstream documents as platform-gated or nil-valued while
|
|
@@ -2899,32 +3559,13 @@ export interface ReturnFieldOptionalityCorrection {
|
|
|
2899
3559
|
readonly evidence: string;
|
|
2900
3560
|
}
|
|
2901
3561
|
|
|
2902
|
-
//
|
|
2903
|
-
//
|
|
2904
|
-
//
|
|
3562
|
+
// A platform gate alone is not evidence: `sys.get_sys_info` gates four fields
|
|
3563
|
+
// on a platform, yet `Sys_GetSysInfo` pushes each from a zero-filled char array,
|
|
3564
|
+
// so off-platform they are empty strings, never nil.
|
|
2905
3565
|
export const RETURN_FIELD_OPTIONALITY_CORRECTIONS: ReadonlyMap<
|
|
2906
3566
|
string,
|
|
2907
3567
|
ReturnFieldOptionalityCorrection
|
|
2908
3568
|
> = new Map([
|
|
2909
|
-
[
|
|
2910
|
-
"sys.get_sys_info:return:sys_info:device_model",
|
|
2911
|
-
{ upstream: ["string"], evidence: "[ios][android] Only available on iOS and Android." },
|
|
2912
|
-
],
|
|
2913
|
-
[
|
|
2914
|
-
"sys.get_sys_info:return:sys_info:manufacturer",
|
|
2915
|
-
{ upstream: ["string"], evidence: "[ios][android] Only available on iOS and Android." },
|
|
2916
|
-
],
|
|
2917
|
-
[
|
|
2918
|
-
"sys.get_sys_info:return:sys_info:device_ident",
|
|
2919
|
-
{
|
|
2920
|
-
upstream: ["string"],
|
|
2921
|
-
evidence: '[ios] "identifierForVendor" on iOS. [android] "android_id" on Android.',
|
|
2922
|
-
},
|
|
2923
|
-
],
|
|
2924
|
-
[
|
|
2925
|
-
"sys.get_sys_info:return:sys_info:user_agent",
|
|
2926
|
-
{ upstream: ["string"], evidence: "[html5] The HTTP user agent" },
|
|
2927
|
-
],
|
|
2928
3569
|
[
|
|
2929
3570
|
"sys.get_ifaddrs:return:ifaddrs:address",
|
|
2930
3571
|
{ upstream: ["string"], evidence: "might be nil if not available." },
|
|
@@ -2975,8 +3616,10 @@ export function applyFieldOptionalityCorrections(
|
|
|
2975
3616
|
// `<element>:param:<slot>:<field>`, the TABLE_FIELD_TYPE_OVERRIDES shape, with
|
|
2976
3617
|
// `<field>.<member>` or `<field>[].<member>` one level down. A `STRUCT` member
|
|
2977
3618
|
// is keyed `<struct>:member:<field>`, for a target whose slot names that struct
|
|
2978
|
-
// (`field-correction-coverage.ts` pairs the two)
|
|
2979
|
-
//
|
|
3619
|
+
// (`field-correction-coverage.ts` pairs the two), or for a returned struct whose
|
|
3620
|
+
// binding always sets a member upstream marks optional. The value is the
|
|
3621
|
+
// evidence: the C++ binding, `<file>:<cFunction>`, and how it reads or sets the
|
|
3622
|
+
// field.
|
|
2980
3623
|
const CHECK_SHAPE_DEF = "CheckShapeDef reads the shape table with luaL_checkinteger(type)";
|
|
2981
3624
|
const V2_FILTER_DATA =
|
|
2982
3625
|
"v2/script_box2d_fixture_v2.cpp:CheckFilterData reads each with luaL_checkinteger";
|
|
@@ -3000,6 +3643,49 @@ export const OPTIONAL_MEMBER_CORRECTIONS: ReadonlyMap<string, string> = new Map(
|
|
|
3000
3643
|
],
|
|
3001
3644
|
]);
|
|
3002
3645
|
|
|
3646
|
+
// A `STRUCT` member the engine binding sets while the target's reference omits
|
|
3647
|
+
// it, keyed `<struct>:member:<field>`. It applies only where its binding pin
|
|
3648
|
+
// holds (the `EmitOptions.bindingEvidence` keys), and a struct that already
|
|
3649
|
+
// declares the member throws, so no entry outlives upstream's fix.
|
|
3650
|
+
export interface StructMemberAddition {
|
|
3651
|
+
readonly member: Omit<ApiStructMember, "name">;
|
|
3652
|
+
readonly binding: BindingPin;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
export const STRUCT_MEMBER_ADDITIONS: ReadonlyMap<string, StructMemberAddition> = new Map([
|
|
3656
|
+
[
|
|
3657
|
+
memberCorrectionKey("graphics.adapter_limits", "max_storage_buffers_per_stage"),
|
|
3658
|
+
{
|
|
3659
|
+
member: {
|
|
3660
|
+
type: "integer",
|
|
3661
|
+
isOptional: false,
|
|
3662
|
+
doc: "Maximum number of storage buffers per shader stage.",
|
|
3663
|
+
},
|
|
3664
|
+
binding: {
|
|
3665
|
+
file: "script/src/script_graphics.cpp",
|
|
3666
|
+
cFunction: "Graphics_GetAdapterInfo",
|
|
3667
|
+
contains: 'lua_setfield(L, -2, "max_storage_buffers_per_stage");',
|
|
3668
|
+
},
|
|
3669
|
+
},
|
|
3670
|
+
],
|
|
3671
|
+
[
|
|
3672
|
+
memberCorrectionKey("sys.interface_info", "family"),
|
|
3673
|
+
{
|
|
3674
|
+
member: {
|
|
3675
|
+
type: '"ipv4"|"ipv6"',
|
|
3676
|
+
isOptional: true,
|
|
3677
|
+
doc: "Address family, absent for an interface that is neither IPv4 nor IPv6.",
|
|
3678
|
+
},
|
|
3679
|
+
binding: {
|
|
3680
|
+
file: "script/src/script_sys.cpp",
|
|
3681
|
+
cFunction: "Sys_GetIfaddrs",
|
|
3682
|
+
contains:
|
|
3683
|
+
'lua_pushliteral(L, "ipv6"); } else { lua_pushnil(L); } lua_setfield(L, -2, "family");',
|
|
3684
|
+
},
|
|
3685
|
+
},
|
|
3686
|
+
],
|
|
3687
|
+
]);
|
|
3688
|
+
|
|
3003
3689
|
export const REQUIRED_FIELD_CORRECTIONS: ReadonlyMap<string, string> = new Map<string, string>([
|
|
3004
3690
|
...(["mass", "center", "inertia"] as const).map(
|
|
3005
3691
|
(field) =>
|
|
@@ -3093,6 +3779,13 @@ export const REQUIRED_FIELD_CORRECTIONS: ReadonlyMap<string, string> = new Map<s
|
|
|
3093
3779
|
"resource.sound_data_options:member:data",
|
|
3094
3780
|
"script_resource.cpp:CreateSoundData reads the data with CheckBufferOrString",
|
|
3095
3781
|
],
|
|
3782
|
+
...(["device_model", "manufacturer", "device_ident", "user_agent"] as const).map(
|
|
3783
|
+
(field) =>
|
|
3784
|
+
[
|
|
3785
|
+
`sys.sys_info:member:${field}`,
|
|
3786
|
+
'script_sys.cpp:Sys_GetSysInfo pushes it with lua_pushstring from a zero-filled char array (user_agent falls back to "")',
|
|
3787
|
+
] as const,
|
|
3788
|
+
),
|
|
3096
3789
|
]);
|
|
3097
3790
|
|
|
3098
3791
|
// Mark the fields REQUIRED_FIELD_CORRECTIONS names for this slot required,
|
|
@@ -3594,6 +4287,58 @@ function groupFlattenedTableFields(fields: readonly TableField[]): TableField[]
|
|
|
3594
4287
|
return grouped;
|
|
3595
4288
|
}
|
|
3596
4289
|
|
|
4290
|
+
// A TYPEDEF whose aliased type the engine binding contradicts, replaced verbatim.
|
|
4291
|
+
// Member overrides reach only a STRUCT, and these alias inline LuaLS tables.
|
|
4292
|
+
// Each applies only while the typedef still aliases exactly its `upstream` tokens,
|
|
4293
|
+
// in order, so a changed upstream typedef is emitted as written rather than
|
|
4294
|
+
// shadowed; `typedef-correction-provenance.test.ts` pins every entry against the
|
|
4295
|
+
// retained ref-docs. Keyed by the typedef's ref-doc name.
|
|
4296
|
+
export interface TypedefAliasCorrection {
|
|
4297
|
+
readonly upstream: readonly string[];
|
|
4298
|
+
readonly ts: string;
|
|
4299
|
+
readonly reason: string;
|
|
4300
|
+
}
|
|
4301
|
+
|
|
4302
|
+
// One arm per shape a script can build, in upstream's order, each keyed by its
|
|
4303
|
+
// own shape-type constant.
|
|
4304
|
+
const BULLET3D_SHAPE_ARMS = [
|
|
4305
|
+
["SPHERE", "diameter: number"],
|
|
4306
|
+
["BOX", "dimensions: Vector3"],
|
|
4307
|
+
["CAPSULE", "diameter: number; height: number"],
|
|
4308
|
+
["HULL", "vertices: Vector3[]"],
|
|
4309
|
+
] as const;
|
|
4310
|
+
|
|
4311
|
+
export const TYPEDEF_ALIAS_CORRECTIONS: ReadonlyMap<string, TypedefAliasCorrection> = new Map([
|
|
4312
|
+
[
|
|
4313
|
+
"render.render_target_params",
|
|
4314
|
+
{
|
|
4315
|
+
upstream: [
|
|
4316
|
+
"{ type?:graphics.TEXTURE_TYPE, sample_count?:integer, [graphics.BUFFER_TYPE]:render.render_target_buffer_params }",
|
|
4317
|
+
],
|
|
4318
|
+
ts: "{ type?: render.RenderTargetTextureType; sample_count?: number } & { [key: number]: render.render_target_buffer_params }",
|
|
4319
|
+
reason:
|
|
4320
|
+
'render_script.cpp:RenderScript_RenderTarget raises "Invalid render target texture type" on any `type` but graphics.TEXTURE_TYPE_2D and graphics.TEXTURE_TYPE_CUBE_MAP',
|
|
4321
|
+
},
|
|
4322
|
+
],
|
|
4323
|
+
[
|
|
4324
|
+
"bullet3d.shape.definition",
|
|
4325
|
+
{
|
|
4326
|
+
upstream: [
|
|
4327
|
+
"{ type:bullet3d.shape.SHAPE_TYPE, diameter:number, position?:vector3, rotation?:quaternion, target_rotation?:quaternion }",
|
|
4328
|
+
"{ type:bullet3d.shape.SHAPE_TYPE, dimensions:vector3, position?:vector3, rotation?:quaternion, target_rotation?:quaternion }",
|
|
4329
|
+
"{ type:bullet3d.shape.SHAPE_TYPE, diameter:number, height:number, position?:vector3, rotation?:quaternion, target_rotation?:quaternion }",
|
|
4330
|
+
"{ type:bullet3d.shape.SHAPE_TYPE, vertices:vector3[], position?:vector3, rotation?:quaternion, target_rotation?:quaternion }",
|
|
4331
|
+
],
|
|
4332
|
+
ts: BULLET3D_SHAPE_ARMS.map(
|
|
4333
|
+
([kind, fields]) =>
|
|
4334
|
+
`{ type: typeof bullet3d.shape.SHAPE_TYPE_${kind}; ${fields}; position?: Vector3; rotation?: Quaternion; target_rotation?: Quaternion }`,
|
|
4335
|
+
).join(" | "),
|
|
4336
|
+
reason:
|
|
4337
|
+
'script_bullet3d_shape.cpp:CheckBullet3DShapeDef reads only the fields of the table\'s own `type` and raises "Unsupported shape type" on any other, a mesh included',
|
|
4338
|
+
},
|
|
4339
|
+
],
|
|
4340
|
+
]);
|
|
4341
|
+
|
|
3597
4342
|
// A handle the type map widens (`constant_buffer` takes named constants) keeps
|
|
3598
4343
|
// that shape, so annotating with the alias loses nothing the handle-returning
|
|
3599
4344
|
// function yields.
|
|
@@ -3610,8 +4355,19 @@ function typedefAlias(
|
|
|
3610
4355
|
): string {
|
|
3611
4356
|
const brand = `Opaque<"${typedef.name}">`;
|
|
3612
4357
|
const aliasOf = typedef.aliasOf ?? [];
|
|
4358
|
+
const correction = TYPEDEF_ALIAS_CORRECTIONS.get(typedef.name);
|
|
4359
|
+
if (
|
|
4360
|
+
correction !== undefined &&
|
|
4361
|
+
aliasOf.length === correction.upstream.length &&
|
|
4362
|
+
aliasOf.every((token, i) => token === correction.upstream[i])
|
|
4363
|
+
) {
|
|
4364
|
+
return `type ${local} = ${correction.ts};`;
|
|
4365
|
+
}
|
|
3613
4366
|
if (aliasOf.length > 0 && !isHandleAlias(aliasOf)) {
|
|
3614
|
-
|
|
4367
|
+
const armType: DefoldTypeMapper = DISCRIMINATED_MESSAGE_UNIONS.has(typedef.name)
|
|
4368
|
+
? (token, position) => discriminatedMessageArm(token) ?? mapType(token, position)
|
|
4369
|
+
: mapType;
|
|
4370
|
+
return `type ${local} = ${unionFromTokens(aliasOf, armType)};`;
|
|
3615
4371
|
}
|
|
3616
4372
|
const mapped = rawMapType(typedef.name);
|
|
3617
4373
|
if (mapped.startsWith(brand)) return `type ${local} = ${mapped};`;
|
|
@@ -3642,6 +4398,7 @@ function collectTypeDeclarations(
|
|
|
3642
4398
|
skip: ReadonlySet<string>,
|
|
3643
4399
|
rawMapType: (token: string) => string,
|
|
3644
4400
|
mapType: DefoldTypeMapper,
|
|
4401
|
+
bindingEvidence: ReadonlySet<string>,
|
|
3645
4402
|
): TypeDeclaration[] {
|
|
3646
4403
|
const prefix = `${module.namespace}.`;
|
|
3647
4404
|
const out: TypeDeclaration[] = [];
|
|
@@ -3665,7 +4422,7 @@ function collectTypeDeclarations(
|
|
|
3665
4422
|
for (const struct of module.structs ?? []) {
|
|
3666
4423
|
const local = localOf(struct.name);
|
|
3667
4424
|
if (local === null || skip.has(local)) continue;
|
|
3668
|
-
out.push(structDeclaration(struct, local, mapType, module.structs ?? []));
|
|
4425
|
+
out.push(structDeclaration(struct, local, mapType, module.structs ?? [], bindingEvidence));
|
|
3669
4426
|
}
|
|
3670
4427
|
return out;
|
|
3671
4428
|
}
|
|
@@ -3718,10 +4475,22 @@ function structDeclaration(
|
|
|
3718
4475
|
path: string,
|
|
3719
4476
|
mapType: DefoldTypeMapper,
|
|
3720
4477
|
structs: readonly ApiStruct[],
|
|
4478
|
+
bindingEvidence: ReadonlySet<string>,
|
|
3721
4479
|
): TypeDeclaration {
|
|
3722
4480
|
const name = path.slice(path.lastIndexOf(".") + 1);
|
|
3723
4481
|
const memberPrefix = memberCorrectionKey(struct.name, "");
|
|
3724
4482
|
const declared = new Set(struct.members.map((member) => member.name));
|
|
4483
|
+
const redundant = [...STRUCT_MEMBER_ADDITIONS.keys()].filter(
|
|
4484
|
+
(key) => key.startsWith(memberPrefix) && declared.has(key.slice(memberPrefix.length)),
|
|
4485
|
+
);
|
|
4486
|
+
if (redundant.length > 0) {
|
|
4487
|
+
throw new Error(
|
|
4488
|
+
`struct member addition names a declared member: ${redundant.sort().join(", ")}`,
|
|
4489
|
+
);
|
|
4490
|
+
}
|
|
4491
|
+
const added = [...STRUCT_MEMBER_ADDITIONS]
|
|
4492
|
+
.filter(([key]) => key.startsWith(memberPrefix) && bindingEvidence.has(key))
|
|
4493
|
+
.map(([key, addition]) => ({ ...addition.member, name: key.slice(memberPrefix.length) }));
|
|
3725
4494
|
const stale = [
|
|
3726
4495
|
...REQUIRED_FIELD_CORRECTIONS.keys(),
|
|
3727
4496
|
...LIST_FIELD_CORRECTIONS.keys(),
|
|
@@ -3731,7 +4500,7 @@ function structDeclaration(
|
|
|
3731
4500
|
if (stale.length > 0) {
|
|
3732
4501
|
throw new Error(`member-keyed field correction names no member: ${stale.sort().join(", ")}`);
|
|
3733
4502
|
}
|
|
3734
|
-
const members = struct.members.map((member) => {
|
|
4503
|
+
const members = [...struct.members, ...added].map((member) => {
|
|
3735
4504
|
const key = memberCorrectionKey(struct.name, member.name);
|
|
3736
4505
|
const list = LIST_FIELD_CORRECTIONS.get(key);
|
|
3737
4506
|
const retyped = TABLE_FIELD_TYPE_OVERRIDES.get(key);
|
|
@@ -3935,11 +4704,14 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
3935
4704
|
const knownConstantFqns = options?.knownConstantFqns;
|
|
3936
4705
|
const translations = options?.translations ?? {};
|
|
3937
4706
|
const urlParameters = options?.urlParameters ?? [];
|
|
4707
|
+
const bindingEvidence = options?.bindingEvidence ?? new Set<string>();
|
|
4708
|
+
const nilRaisingSlots = options?.nilRaisingSlots;
|
|
3938
4709
|
const baseNotes = indexBaseNoteResolver(options?.indexBaseSource ?? "engine");
|
|
3939
4710
|
const { rawMapType, mapType } = moduleTypeMappers(module, constantsByFqn, options);
|
|
3940
4711
|
const constantTokens = constantSlotTokenResolver(
|
|
3941
4712
|
module,
|
|
3942
4713
|
new Set([...constantsByFqn.keys(), ...(knownConstantFqns ?? [])]),
|
|
4714
|
+
options?.withheldFunctions,
|
|
3943
4715
|
);
|
|
3944
4716
|
|
|
3945
4717
|
const constants = module.constants
|
|
@@ -3991,7 +4763,13 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
3991
4763
|
// A typedef that also has colon methods is emitted as a method-bearing
|
|
3992
4764
|
// interface below, not an opaque brand alias.
|
|
3993
4765
|
const typeDeclarations = placeTypeDeclarations(
|
|
3994
|
-
collectTypeDeclarations(
|
|
4766
|
+
collectTypeDeclarations(
|
|
4767
|
+
module,
|
|
4768
|
+
new Set(handleGroups.keys()),
|
|
4769
|
+
rawMapType,
|
|
4770
|
+
mapType,
|
|
4771
|
+
bindingEvidence,
|
|
4772
|
+
),
|
|
3995
4773
|
nestedRoot,
|
|
3996
4774
|
);
|
|
3997
4775
|
|
|
@@ -4013,6 +4791,16 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4013
4791
|
for (const aliasDecl of constantUnionAliasDeclarations(module.namespace)) {
|
|
4014
4792
|
lines.push(`${INDENT}${decl}${aliasDecl}`);
|
|
4015
4793
|
}
|
|
4794
|
+
for (const aliasDecl of overloadSlotAliasDeclarations(
|
|
4795
|
+
[...module.functions, ...(options?.withheldFunctions ?? [])],
|
|
4796
|
+
prefix,
|
|
4797
|
+
mapType,
|
|
4798
|
+
constantTokens,
|
|
4799
|
+
urlParameters,
|
|
4800
|
+
bindingEvidence,
|
|
4801
|
+
)) {
|
|
4802
|
+
lines.push(`${INDENT}${decl}${aliasDecl}`);
|
|
4803
|
+
}
|
|
4016
4804
|
for (const alias of enumAliases.topLevel) {
|
|
4017
4805
|
lines.push(
|
|
4018
4806
|
`${INDENT}${decl}${unionAliasDeclaration(alias.name, alias.members, alias.nilable)}`,
|
|
@@ -4027,7 +4815,7 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4027
4815
|
lines.push(docLine);
|
|
4028
4816
|
}
|
|
4029
4817
|
lines.push(
|
|
4030
|
-
`${handleIndent}${emitMethod(fn, mapType, resolver, constantTokens, urlParameters)}`,
|
|
4818
|
+
`${handleIndent}${emitMethod(fn, mapType, resolver, constantTokens, urlParameters, bindingEvidence)}`,
|
|
4031
4819
|
);
|
|
4032
4820
|
}
|
|
4033
4821
|
lines.push(`${INDENT}}`);
|
|
@@ -4056,10 +4844,20 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4056
4844
|
for (const fn of functions) {
|
|
4057
4845
|
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
4058
4846
|
const emitName = aliasName(fn.name, aliases);
|
|
4059
|
-
for (const
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4847
|
+
for (const arm of callArms(fn, nilRaisingSlots, module.structs ?? [])) {
|
|
4848
|
+
for (const docLine of functionDocLines(fn.original, translations, baseNotes))
|
|
4849
|
+
lines.push(docLine);
|
|
4850
|
+
const line = emitFunction(
|
|
4851
|
+
arm,
|
|
4852
|
+
emitName,
|
|
4853
|
+
mapType,
|
|
4854
|
+
resolver,
|
|
4855
|
+
constantTokens,
|
|
4856
|
+
urlParameters,
|
|
4857
|
+
bindingEvidence,
|
|
4858
|
+
);
|
|
4859
|
+
lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
|
|
4860
|
+
}
|
|
4063
4861
|
const alternative = firstSlotAlternative(fn, module.namespace);
|
|
4064
4862
|
if (alternative !== null) {
|
|
4065
4863
|
// The overload documents its own parameters, so its hover names the base
|
|
@@ -4074,6 +4872,7 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4074
4872
|
resolver,
|
|
4075
4873
|
constantTokens,
|
|
4076
4874
|
urlParameters,
|
|
4875
|
+
bindingEvidence,
|
|
4077
4876
|
);
|
|
4078
4877
|
lines.push(`${INDENT}${reserved ? "" : decl}${overload}`);
|
|
4079
4878
|
}
|
|
@@ -4148,7 +4947,7 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4148
4947
|
lines.push(docLine);
|
|
4149
4948
|
}
|
|
4150
4949
|
lines.push(
|
|
4151
|
-
`${bodyIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver, constantTokens, urlParameters)}`,
|
|
4950
|
+
`${bodyIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver, constantTokens, urlParameters, bindingEvidence)}`,
|
|
4152
4951
|
);
|
|
4153
4952
|
}
|
|
4154
4953
|
aliasReservedSegments(group.children.keys(), segmentAliases);
|
|
@@ -4179,6 +4978,55 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
|
|
|
4179
4978
|
return `${lines.join("\n")}\n`;
|
|
4180
4979
|
}
|
|
4181
4980
|
|
|
4981
|
+
// One `type <alias> = <slot type>;` per `OVERLOAD_SLOT_ALIASES` slot these
|
|
4982
|
+
// functions carry, in alias order. The type is the one `emitFunction` reports
|
|
4983
|
+
// for the slot, so it reads as the function's own signature would. The
|
|
4984
|
+
// resolver spans these functions alone, leaving the module's own untouched.
|
|
4985
|
+
function overloadSlotAliasDeclarations(
|
|
4986
|
+
functions: readonly ApiFunction[],
|
|
4987
|
+
prefix: string,
|
|
4988
|
+
mapType: DefoldTypeMapper,
|
|
4989
|
+
constantTokens: ConstantSlotTokens,
|
|
4990
|
+
urlParameters: UrlParameterTable,
|
|
4991
|
+
bindingEvidence: ReadonlySet<string>,
|
|
4992
|
+
): string[] {
|
|
4993
|
+
const resolver = buildTableDocResolver(
|
|
4994
|
+
functions.map((fn) => ({ name: fn.name, slots: [...fn.parameters, ...fn.returnValues] })),
|
|
4995
|
+
);
|
|
4996
|
+
const types = new Map<string, string>();
|
|
4997
|
+
for (const fn of functions) {
|
|
4998
|
+
for (const [position, p] of fn.parameters.entries()) {
|
|
4999
|
+
const alias = OVERLOAD_SLOT_ALIASES.get(tableSlotKey(fn.name, "param", p.name));
|
|
5000
|
+
if (alias === undefined) continue;
|
|
5001
|
+
const prepared = prepareFunction(fn, prefix);
|
|
5002
|
+
if (prepared === null) continue;
|
|
5003
|
+
const slots: Record<string, SlotType> = {};
|
|
5004
|
+
emitFunction(
|
|
5005
|
+
prepared,
|
|
5006
|
+
prepared.name,
|
|
5007
|
+
mapType,
|
|
5008
|
+
resolver,
|
|
5009
|
+
constantTokens,
|
|
5010
|
+
urlParameters,
|
|
5011
|
+
bindingEvidence,
|
|
5012
|
+
slots,
|
|
5013
|
+
);
|
|
5014
|
+
const slot = slots[slotKey("param", position, p.name)];
|
|
5015
|
+
if (slot === undefined) {
|
|
5016
|
+
throw new Error(`${fn.name} reports no type for its aliased slot "${p.name}"`);
|
|
5017
|
+
}
|
|
5018
|
+
const seen = types.get(alias);
|
|
5019
|
+
if (seen !== undefined && seen !== slot.type) {
|
|
5020
|
+
throw new Error(`${alias} is typed both ${seen} and ${slot.type}`);
|
|
5021
|
+
}
|
|
5022
|
+
types.set(alias, slot.type);
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
return [...types]
|
|
5026
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
5027
|
+
.map(([alias, type]) => `type ${alias} = ${type};`);
|
|
5028
|
+
}
|
|
5029
|
+
|
|
4182
5030
|
/**
|
|
4183
5031
|
* Every documented slot's rendered TS type and optionality, keyed
|
|
4184
5032
|
* `param|return:<position>:<raw ref-doc name>`. The position is part of the key
|
|
@@ -4232,10 +5080,13 @@ export function emitSymbolSignatures(
|
|
|
4232
5080
|
const constantsByFqn = new Map(module.constants.map((c) => [c.name, c]));
|
|
4233
5081
|
const knownConstantFqns = options?.knownConstantFqns;
|
|
4234
5082
|
const urlParameters = options?.urlParameters ?? [];
|
|
5083
|
+
const bindingEvidence = options?.bindingEvidence ?? new Set<string>();
|
|
5084
|
+
const nilRaisingSlots = options?.nilRaisingSlots;
|
|
4235
5085
|
const { rawMapType, mapType } = moduleTypeMappers(module, constantsByFqn, options);
|
|
4236
5086
|
const constantTokens = constantSlotTokenResolver(
|
|
4237
5087
|
module,
|
|
4238
5088
|
new Set([...constantsByFqn.keys(), ...(knownConstantFqns ?? [])]),
|
|
5089
|
+
options?.withheldFunctions,
|
|
4239
5090
|
);
|
|
4240
5091
|
const resolver = buildTableDocResolver(
|
|
4241
5092
|
module.functions.map((fn) => ({
|
|
@@ -4262,27 +5113,30 @@ export function emitSymbolSignatures(
|
|
|
4262
5113
|
const prepared = prepareFunction(fn, prefix);
|
|
4263
5114
|
if (prepared === null) continue;
|
|
4264
5115
|
const slotTypes: Record<string, SlotType> = {};
|
|
5116
|
+
const [first = prepared, ...later] = callArms(prepared, nilRaisingSlots, module.structs ?? []);
|
|
4265
5117
|
const primary = emitFunction(
|
|
4266
|
-
|
|
5118
|
+
first,
|
|
4267
5119
|
emitName(prepared.name),
|
|
4268
5120
|
mapType,
|
|
4269
5121
|
resolver,
|
|
4270
5122
|
constantTokens,
|
|
4271
5123
|
urlParameters,
|
|
5124
|
+
bindingEvidence,
|
|
4272
5125
|
slotTypes,
|
|
4273
5126
|
);
|
|
4274
5127
|
const alternative = firstSlotAlternative(prepared, module.namespace);
|
|
4275
|
-
const
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
5128
|
+
const arms = [...later, ...(alternative === null ? [] : [alternative])].map((arm) =>
|
|
5129
|
+
emitFunction(
|
|
5130
|
+
arm,
|
|
5131
|
+
emitName(prepared.name),
|
|
5132
|
+
mapType,
|
|
5133
|
+
resolver,
|
|
5134
|
+
constantTokens,
|
|
5135
|
+
urlParameters,
|
|
5136
|
+
bindingEvidence,
|
|
5137
|
+
),
|
|
5138
|
+
);
|
|
5139
|
+
const tsSignature = [primary, ...arms].join("\n");
|
|
4286
5140
|
out.push({ identity: fnIdentity(fn), tsSignature, slotTypes });
|
|
4287
5141
|
}
|
|
4288
5142
|
|
|
@@ -4323,6 +5177,7 @@ export function emitSymbolSignatures(
|
|
|
4323
5177
|
resolver,
|
|
4324
5178
|
constantTokens,
|
|
4325
5179
|
urlParameters,
|
|
5180
|
+
bindingEvidence,
|
|
4326
5181
|
slotTypes,
|
|
4327
5182
|
),
|
|
4328
5183
|
slotTypes,
|
|
@@ -4341,6 +5196,7 @@ export function emitSymbolSignatures(
|
|
|
4341
5196
|
resolver,
|
|
4342
5197
|
constantTokens,
|
|
4343
5198
|
urlParameters,
|
|
5199
|
+
bindingEvidence,
|
|
4344
5200
|
slotTypes,
|
|
4345
5201
|
),
|
|
4346
5202
|
slotTypes,
|
|
@@ -4389,6 +5245,7 @@ export function emitSymbolSignatures(
|
|
|
4389
5245
|
new Set(handleGroups.keys()),
|
|
4390
5246
|
rawMapType,
|
|
4391
5247
|
mapType,
|
|
5248
|
+
bindingEvidence,
|
|
4392
5249
|
)) {
|
|
4393
5250
|
out.push({
|
|
4394
5251
|
identity: {
|
|
@@ -4436,6 +5293,12 @@ interface PreparedConstant {
|
|
|
4436
5293
|
interface PreparedFunction {
|
|
4437
5294
|
name: string;
|
|
4438
5295
|
original: ApiFunction;
|
|
5296
|
+
// Parameter positions declared required whatever their doc says: the
|
|
5297
|
+
// nil-raising slots an arm of `nilRaisingArms` takes.
|
|
5298
|
+
required?: ReadonlySet<number>;
|
|
5299
|
+
// A rewrite of the mapped type at a parameter position: the fields an arm of
|
|
5300
|
+
// `presenceArms` requires.
|
|
5301
|
+
retype?: ReadonlyMap<number, (type: string) => string>;
|
|
4439
5302
|
}
|
|
4440
5303
|
|
|
4441
5304
|
interface PreparedVariable {
|
|
@@ -4719,10 +5582,13 @@ export const NIL_CONSTANTS: ReadonlyMap<string, string> = new Map([
|
|
|
4719
5582
|
// `undocumented-constant-provenance.test.ts` reds an entry no committed target
|
|
4720
5583
|
// still needs. `sameValueAs` names the documented constant the engine registers
|
|
4721
5584
|
// with the same value; where the surface declares it, the back-fill takes its
|
|
4722
|
-
// type, so a value compares equal to either spelling.
|
|
5585
|
+
// type, so a value compares equal to either spelling. `memberOf` names the
|
|
5586
|
+
// ref-doc `ENUM` the engine returns the constant through; the back-fill joins it
|
|
5587
|
+
// only on a surface whose ref-doc declares that enum, so an older surface
|
|
5588
|
+
// without one keeps a plain constant.
|
|
4723
5589
|
export const UNDOCUMENTED_CONSTANTS: ReadonlyMap<
|
|
4724
5590
|
string,
|
|
4725
|
-
{ readonly doc: string; readonly sameValueAs?: string }
|
|
5591
|
+
{ readonly doc: string; readonly sameValueAs?: string; readonly memberOf?: string }
|
|
4726
5592
|
> = new Map([
|
|
4727
5593
|
[
|
|
4728
5594
|
"render.CONTEXT_EVENT_CONTEXT_LOST",
|
|
@@ -4743,6 +5609,13 @@ export const UNDOCUMENTED_CONSTANTS: ReadonlyMap<
|
|
|
4743
5609
|
sameValueAs: "window.WINDOW_EVENT_ICONIFIED",
|
|
4744
5610
|
},
|
|
4745
5611
|
],
|
|
5612
|
+
[
|
|
5613
|
+
"liveupdate.LIVEUPDATE_NOT_INITIALIZED",
|
|
5614
|
+
{
|
|
5615
|
+
doc: "Live update is not initialized. `liveupdate.add_mount` and `liveupdate.remove_mount` return it while live update is disabled.",
|
|
5616
|
+
memberOf: "liveupdate.LIVEUPDATE",
|
|
5617
|
+
},
|
|
5618
|
+
],
|
|
4746
5619
|
]);
|
|
4747
5620
|
|
|
4748
5621
|
function withUndocumentedConstants(
|
|
@@ -4752,7 +5625,8 @@ function withUndocumentedConstants(
|
|
|
4752
5625
|
const prefix = `${module.namespace}.`;
|
|
4753
5626
|
const documented = new Set(module.constants.map((c) => c.name));
|
|
4754
5627
|
const backfill: ApiConstant[] = [];
|
|
4755
|
-
|
|
5628
|
+
const joined = new Map<string, string[]>();
|
|
5629
|
+
for (const [fqn, { doc, sameValueAs, memberOf }] of UNDOCUMENTED_CONSTANTS) {
|
|
4756
5630
|
if (!fqn.startsWith(prefix) || fqn.slice(prefix.length).includes(".")) continue;
|
|
4757
5631
|
if (documented.has(fqn)) continue;
|
|
4758
5632
|
if (registered !== undefined && !registered.has(fqn)) continue;
|
|
@@ -4764,10 +5638,18 @@ function withUndocumentedConstants(
|
|
|
4764
5638
|
description: "",
|
|
4765
5639
|
...(aliasOf === undefined ? {} : { aliasOf }),
|
|
4766
5640
|
});
|
|
5641
|
+
if (memberOf !== undefined) joined.set(memberOf, [...(joined.get(memberOf) ?? []), fqn]);
|
|
4767
5642
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
5643
|
+
if (backfill.length === 0) return module;
|
|
5644
|
+
const enums = module.enums?.map((e) => {
|
|
5645
|
+
const members = joined.get(e.name);
|
|
5646
|
+
return members === undefined ? e : { ...e, members: [...e.members, ...members] };
|
|
5647
|
+
});
|
|
5648
|
+
return {
|
|
5649
|
+
...module,
|
|
5650
|
+
constants: [...module.constants, ...backfill],
|
|
5651
|
+
...(enums === undefined ? {} : { enums }),
|
|
5652
|
+
};
|
|
4771
5653
|
}
|
|
4772
5654
|
|
|
4773
5655
|
// `constant` is the module's own element for `fqn`, absent for a constant another
|
|
@@ -4846,14 +5728,30 @@ function memberSignature(
|
|
|
4846
5728
|
resolver: TableDocResolver,
|
|
4847
5729
|
constantTokens: ConstantSlotTokens,
|
|
4848
5730
|
urlParameters: UrlParameterTable,
|
|
5731
|
+
bindingEvidence: ReadonlySet<string>,
|
|
4849
5732
|
slotSink?: Record<string, SlotType>,
|
|
4850
5733
|
): string {
|
|
4851
5734
|
const original = prepared.original.parameters;
|
|
4852
5735
|
const elementName = prepared.original.name;
|
|
4853
|
-
const
|
|
5736
|
+
const required = prepared.required ?? new Set<number>();
|
|
5737
|
+
const cutoff = Math.max(
|
|
5738
|
+
trailingOptionalCutoff(original, elementName),
|
|
5739
|
+
...[...required].map((index) => index + 1),
|
|
5740
|
+
);
|
|
4854
5741
|
const varargIndex = original.findIndex(isVarargParameter);
|
|
4855
5742
|
const positional = (varargIndex === -1 ? original : original.slice(0, varargIndex)).map((p, i) =>
|
|
4856
|
-
emitParameter(
|
|
5743
|
+
emitParameter(
|
|
5744
|
+
p,
|
|
5745
|
+
i,
|
|
5746
|
+
i >= cutoff,
|
|
5747
|
+
mapType,
|
|
5748
|
+
resolver,
|
|
5749
|
+
constantTokens,
|
|
5750
|
+
elementName,
|
|
5751
|
+
urlParameters,
|
|
5752
|
+
required.has(i),
|
|
5753
|
+
prepared.retype?.get(i),
|
|
5754
|
+
),
|
|
4857
5755
|
);
|
|
4858
5756
|
const rest =
|
|
4859
5757
|
varargIndex === -1
|
|
@@ -4874,6 +5772,7 @@ function memberSignature(
|
|
|
4874
5772
|
resolver,
|
|
4875
5773
|
constantTokens,
|
|
4876
5774
|
elementName,
|
|
5775
|
+
bindingEvidence,
|
|
4877
5776
|
);
|
|
4878
5777
|
const predicateToken = TYPE_PREDICATES.get(elementName);
|
|
4879
5778
|
const soleParam = original[0];
|
|
@@ -4901,7 +5800,7 @@ function memberSignature(
|
|
|
4901
5800
|
if (isPredicate && soleParam !== undefined && predicateToken !== undefined) {
|
|
4902
5801
|
return `${name}(${params}): ${safeParamName(soleParam.name, 0)} is ${mapType(predicateToken)};`;
|
|
4903
5802
|
}
|
|
4904
|
-
return `${name}(${params}): ${ret.type};${ret.trailing}`;
|
|
5803
|
+
return `${name}${box2dBackendTypeParameters(prepared.original)}(${params}): ${ret.type};${ret.trailing}`;
|
|
4905
5804
|
}
|
|
4906
5805
|
|
|
4907
5806
|
function emitFunction(
|
|
@@ -4911,9 +5810,10 @@ function emitFunction(
|
|
|
4911
5810
|
resolver: TableDocResolver,
|
|
4912
5811
|
constantTokens: ConstantSlotTokens,
|
|
4913
5812
|
urlParameters: UrlParameterTable,
|
|
5813
|
+
bindingEvidence: ReadonlySet<string>,
|
|
4914
5814
|
slotSink?: Record<string, SlotType>,
|
|
4915
5815
|
): string {
|
|
4916
|
-
return `function ${memberSignature(prepared, name, mapType, resolver, constantTokens, urlParameters, slotSink)}`;
|
|
5816
|
+
return `function ${memberSignature(prepared, name, mapType, resolver, constantTokens, urlParameters, bindingEvidence, slotSink)}`;
|
|
4917
5817
|
}
|
|
4918
5818
|
|
|
4919
5819
|
// A colon-method member of a handle interface: identical signature machinery to a
|
|
@@ -4925,6 +5825,7 @@ function emitMethod(
|
|
|
4925
5825
|
resolver: TableDocResolver,
|
|
4926
5826
|
constantTokens: ConstantSlotTokens,
|
|
4927
5827
|
urlParameters: UrlParameterTable,
|
|
5828
|
+
bindingEvidence: ReadonlySet<string>,
|
|
4928
5829
|
slotSink?: Record<string, SlotType>,
|
|
4929
5830
|
): string {
|
|
4930
5831
|
return memberSignature(
|
|
@@ -4934,6 +5835,7 @@ function emitMethod(
|
|
|
4934
5835
|
resolver,
|
|
4935
5836
|
constantTokens,
|
|
4936
5837
|
urlParameters,
|
|
5838
|
+
bindingEvidence,
|
|
4937
5839
|
slotSink,
|
|
4938
5840
|
);
|
|
4939
5841
|
}
|
|
@@ -4956,14 +5858,20 @@ function functionDocLines(
|
|
|
4956
5858
|
baseNotes: IndexBaseNoteResolver,
|
|
4957
5859
|
indent: string = INDENT,
|
|
4958
5860
|
): string[] {
|
|
4959
|
-
const params = fn.parameters.map((p, index) =>
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
5861
|
+
const params = fn.parameters.map((p, index) => {
|
|
5862
|
+
const curationNote = CALLBACK_SIGNATURE_CURATIONS.get(
|
|
5863
|
+
tableSlotKey(fn.name, "param", p.name),
|
|
5864
|
+
)?.note;
|
|
5865
|
+
return {
|
|
5866
|
+
name: emittedParamName(p, index),
|
|
5867
|
+
doc: appendNotes(translateProseFences(htmlToDocText(p.doc), fn.name, translations), [
|
|
5868
|
+
...baseNotes(fn.name, "param", p.name),
|
|
5869
|
+
...(curationNote === undefined ? [] : [curationNote]),
|
|
5870
|
+
]),
|
|
5871
|
+
};
|
|
5872
|
+
});
|
|
4966
5873
|
const onlyReturn = fn.returnValues.length === 1 ? fn.returnValues[0] : undefined;
|
|
5874
|
+
const sparseNote = SPARSE_ARRAY_RETURNS.get(fn.name)?.note;
|
|
4967
5875
|
const notedReturns =
|
|
4968
5876
|
multiReturnDoc(fn, translations, baseNotes) ?? overrideReturnDoc(fn, baseNotes);
|
|
4969
5877
|
const lua = htmlToCodeText(fn.examples ?? "");
|
|
@@ -4996,7 +5904,10 @@ function functionDocLines(
|
|
|
4996
5904
|
? {
|
|
4997
5905
|
returns: appendNotes(
|
|
4998
5906
|
translateProseFences(htmlToDocText(onlyReturn.doc), fn.name, translations),
|
|
4999
|
-
|
|
5907
|
+
[
|
|
5908
|
+
...baseNotes(fn.name, "return", onlyReturn.name),
|
|
5909
|
+
...(sparseNote === undefined ? [] : [sparseNote]),
|
|
5910
|
+
],
|
|
5000
5911
|
),
|
|
5001
5912
|
}
|
|
5002
5913
|
: notedReturns !== undefined
|
|
@@ -5258,27 +6169,43 @@ function emitParameter(
|
|
|
5258
6169
|
constantTokens: ConstantSlotTokens,
|
|
5259
6170
|
elementName: string,
|
|
5260
6171
|
urlParameters: UrlParameterTable,
|
|
6172
|
+
required = false,
|
|
6173
|
+
retype: (type: string) => string = (type) => type,
|
|
5261
6174
|
): { text: string; type: string; optional: boolean } {
|
|
5262
6175
|
const name = safeParamName(p.name, index);
|
|
5263
|
-
const ts =
|
|
6176
|
+
const ts = retype(
|
|
6177
|
+
parameterType(p, mapType, resolver, constantTokens, elementName, urlParameters),
|
|
6178
|
+
);
|
|
5264
6179
|
// An interior doc-optional param (a required param follows, so the trailing-`?`
|
|
5265
6180
|
// projection cannot mark it) keeps its optionality as `| undefined` — TSTL
|
|
5266
6181
|
// lowers `undefined` to `nil`, the faithful call. Trailing optionals keep the
|
|
5267
6182
|
// `?` form; required params are untouched.
|
|
5268
6183
|
const nilRequired = requiredSlotCorrection(p, elementName)?.nil === true;
|
|
5269
|
-
const interiorOptional = !optional && (isDocOptional(p, elementName) || nilRequired);
|
|
6184
|
+
const interiorOptional = !optional && !required && (isDocOptional(p, elementName) || nilRequired);
|
|
5270
6185
|
// `(a) => void | undefined` reads as a function returning `void | undefined`.
|
|
5271
6186
|
const operand = interiorOptional && isFunctionTypeText(ts) ? `(${ts})` : ts;
|
|
5272
6187
|
const type = `${operand}${interiorOptional ? " | undefined" : ""}`;
|
|
5273
6188
|
return { text: `${name}${optional ? "?" : ""}: ${type}`, type, optional };
|
|
5274
6189
|
}
|
|
5275
6190
|
|
|
6191
|
+
// A `nullable` correction rewrites only the `nil` in the declared tokens and
|
|
6192
|
+
// leaves the rest to the ordinary mapping and its top-level `nil` projection.
|
|
6193
|
+
function correctedReturnTokens(
|
|
6194
|
+
types: readonly string[],
|
|
6195
|
+
correction: ReturnTypeCorrection | undefined,
|
|
6196
|
+
): readonly string[] {
|
|
6197
|
+
if (correction?.nullable === undefined) return types;
|
|
6198
|
+
const base = types.filter((token) => token !== "nil");
|
|
6199
|
+
return correction.nullable ? [...base, "nil"] : base;
|
|
6200
|
+
}
|
|
6201
|
+
|
|
5276
6202
|
function emitReturn(
|
|
5277
6203
|
returnValues: ApiParameter[],
|
|
5278
6204
|
mapType: DefoldTypeMapper,
|
|
5279
6205
|
resolver: TableDocResolver,
|
|
5280
6206
|
constantTokens: ConstantSlotTokens,
|
|
5281
6207
|
elementName: string,
|
|
6208
|
+
bindingEvidence: ReadonlySet<string>,
|
|
5282
6209
|
): { type: string; trailing: string; slots: EmittedSlot[] } {
|
|
5283
6210
|
// An authored whole-return override is not a per-slot render, so it reports no
|
|
5284
6211
|
// slots and leaves the consumer on its token-derived fallback.
|
|
@@ -5287,23 +6214,30 @@ function emitReturn(
|
|
|
5287
6214
|
// Kept beside the override lookup so the two stay visibly distinct: this one
|
|
5288
6215
|
// contradicts a declared token rather than filling an empty `returnvalues`,
|
|
5289
6216
|
// and it is a per-slot render, so it reports its slot.
|
|
5290
|
-
const
|
|
6217
|
+
const listed = RETURN_TYPE_CORRECTIONS.get(elementName);
|
|
6218
|
+
const correction =
|
|
6219
|
+
listed?.binding === undefined || bindingEvidence.has(elementName) ? listed : undefined;
|
|
5291
6220
|
if (returnValues.length === 0) return { type: "void", trailing: "", slots: [] };
|
|
5292
6221
|
if (returnValues.length > 1) {
|
|
5293
6222
|
// Defold multi-returns are positional and always present; each slot maps
|
|
5294
6223
|
// straight through (unknown when the doc lists no type) into a tuple that
|
|
5295
6224
|
// typescript-to-lua erases to `local a, b = fn()`.
|
|
5296
|
-
const emitted = returnValues.map((rv, index) =>
|
|
5297
|
-
|
|
5298
|
-
name: rv.name,
|
|
5299
|
-
ts:
|
|
6225
|
+
const emitted = returnValues.map((rv, index) => {
|
|
6226
|
+
const applied =
|
|
5300
6227
|
correction?.slot !== undefined &&
|
|
5301
6228
|
correction.slot === rv.name &&
|
|
5302
6229
|
pinMatches(rv.types, correctionPins(correction))
|
|
5303
|
-
? correction
|
|
5304
|
-
:
|
|
6230
|
+
? correction
|
|
6231
|
+
: undefined;
|
|
6232
|
+
const types = correctedReturnTokens(rv.types, applied);
|
|
6233
|
+
return {
|
|
6234
|
+
position: index,
|
|
6235
|
+
name: rv.name,
|
|
6236
|
+
ts:
|
|
6237
|
+
applied?.ts ??
|
|
6238
|
+
(types.length > 0
|
|
5305
6239
|
? mapSlotUnion(
|
|
5306
|
-
|
|
6240
|
+
types,
|
|
5307
6241
|
rv.doc,
|
|
5308
6242
|
mapType,
|
|
5309
6243
|
false,
|
|
@@ -5313,8 +6247,9 @@ function emitReturn(
|
|
|
5313
6247
|
"return",
|
|
5314
6248
|
rv.name,
|
|
5315
6249
|
)
|
|
5316
|
-
: "unknown",
|
|
5317
|
-
|
|
6250
|
+
: "unknown"),
|
|
6251
|
+
};
|
|
6252
|
+
});
|
|
5318
6253
|
return {
|
|
5319
6254
|
type: `LuaMultiReturn<[${emitted.map((slot) => slot.ts).join(", ")}]>`,
|
|
5320
6255
|
trailing: "",
|
|
@@ -5323,21 +6258,24 @@ function emitReturn(
|
|
|
5323
6258
|
}
|
|
5324
6259
|
const first = returnValues[0];
|
|
5325
6260
|
if (!first) return { type: "void", trailing: "", slots: [] };
|
|
5326
|
-
|
|
6261
|
+
const applied =
|
|
5327
6262
|
correction !== undefined &&
|
|
5328
6263
|
correction.slot === undefined &&
|
|
5329
6264
|
pinMatches(first.types, correctionPins(correction))
|
|
5330
|
-
|
|
6265
|
+
? correction
|
|
6266
|
+
: undefined;
|
|
6267
|
+
if (applied?.ts !== undefined) {
|
|
5331
6268
|
return {
|
|
5332
|
-
type:
|
|
6269
|
+
type: applied.ts,
|
|
5333
6270
|
trailing: "",
|
|
5334
|
-
slots: [{ position: 0, name: first.name, ts:
|
|
6271
|
+
slots: [{ position: 0, name: first.name, ts: applied.ts }],
|
|
5335
6272
|
};
|
|
5336
6273
|
}
|
|
6274
|
+
const types = correctedReturnTokens(first.types, applied);
|
|
5337
6275
|
const ts =
|
|
5338
|
-
|
|
6276
|
+
types.length > 0
|
|
5339
6277
|
? mapSlotUnion(
|
|
5340
|
-
|
|
6278
|
+
types,
|
|
5341
6279
|
first.doc,
|
|
5342
6280
|
mapType,
|
|
5343
6281
|
false,
|
|
@@ -5361,6 +6299,26 @@ function emitReturn(
|
|
|
5361
6299
|
// only".
|
|
5362
6300
|
const READ_ONLY_MARK = /^\s*<span class="mark">READ ONLY<\/span>/;
|
|
5363
6301
|
|
|
6302
|
+
// A property the engine reads but refuses to write while upstream omits the
|
|
6303
|
+
// READ ONLY mark. `evidence` names the component source that shows the missing
|
|
6304
|
+
// write branch. The emit throws once upstream adds the mark, so the entry is
|
|
6305
|
+
// deleted rather than outliving the defect it corrects.
|
|
6306
|
+
export interface ReadOnlyPropertyCorrection {
|
|
6307
|
+
readonly evidence: string;
|
|
6308
|
+
}
|
|
6309
|
+
|
|
6310
|
+
// Keyed `<namespace>.<property>`, mirroring PROPERTY_TYPE_CORRECTIONS.
|
|
6311
|
+
export const READ_ONLY_PROPERTY_CORRECTIONS: ReadonlyMap<string, ReadOnlyPropertyCorrection> =
|
|
6312
|
+
new Map([
|
|
6313
|
+
[
|
|
6314
|
+
"model.animation",
|
|
6315
|
+
{
|
|
6316
|
+
evidence:
|
|
6317
|
+
"comp_model.cpp:CompModelSetProperty has no animation branch, so the write falls through to the material-constant lookup and fails",
|
|
6318
|
+
},
|
|
6319
|
+
],
|
|
6320
|
+
]);
|
|
6321
|
+
|
|
5364
6322
|
function emitPropertyMembers(
|
|
5365
6323
|
p: ApiProperty,
|
|
5366
6324
|
mapType: DefoldTypeMapper,
|
|
@@ -5381,10 +6339,59 @@ function emitPropertyMembers(
|
|
|
5381
6339
|
: p.types.length > 0
|
|
5382
6340
|
? unionFromTokens(p.types, mapType)
|
|
5383
6341
|
: "unknown";
|
|
5384
|
-
const
|
|
6342
|
+
const marked = READ_ONLY_MARK.test(p.description);
|
|
6343
|
+
const readOnlyCorrected = READ_ONLY_PROPERTY_CORRECTIONS.has(`${namespace}.${p.name}`);
|
|
6344
|
+
if (marked && readOnlyCorrected) {
|
|
6345
|
+
throw new Error(
|
|
6346
|
+
`read-only property correction ${namespace}.${p.name}: upstream marks the property READ ONLY`,
|
|
6347
|
+
);
|
|
6348
|
+
}
|
|
6349
|
+
const modifier = marked || readOnlyCorrected ? "readonly " : "";
|
|
5385
6350
|
return keys.map((key) => `${modifier}${key}: ${ts};`);
|
|
5386
6351
|
}
|
|
5387
6352
|
|
|
6353
|
+
// A function whose returned table the engine binding keys by a sparse integer,
|
|
6354
|
+
// so the sequence its reference declares is a map with holes: `ipairs` stops at
|
|
6355
|
+
// the first hole, and `#t` may return any border. Keyed by function name, since the return slot is named
|
|
6356
|
+
// `table` in 1.13.0/1.13.1 and `textures` from 1.13.2. The return's `T[]` member
|
|
6357
|
+
// emits as `LuaMap<number, T>`, which `for...of` lowers to `pairs`, and `note`
|
|
6358
|
+
// joins its `@returns`.
|
|
6359
|
+
export const SPARSE_ARRAY_RETURNS: ReadonlyMap<string, { note: string; reason: string }> = new Map([
|
|
6360
|
+
[
|
|
6361
|
+
"material.get_textures",
|
|
6362
|
+
{
|
|
6363
|
+
note: "Keyed by sampler slot from 1; a slot with no texture has no entry, so iterate with `for (const [slot, info] of textures)`.",
|
|
6364
|
+
reason:
|
|
6365
|
+
"script_material.cpp:Material_GetTextures pushes key i+1 only for a slot that has a texture, unlike Compute_GetTextures, which packs its keys",
|
|
6366
|
+
},
|
|
6367
|
+
],
|
|
6368
|
+
]);
|
|
6369
|
+
|
|
6370
|
+
function sparseArrayMap(ts: string, elementName: string): string {
|
|
6371
|
+
const members = unionMembers(ts);
|
|
6372
|
+
const arrays = members.filter((member) => member.endsWith("[]"));
|
|
6373
|
+
if (arrays.length === 0) {
|
|
6374
|
+
throw new Error(`SPARSE_ARRAY_RETURNS ${elementName}: the return emits no array (${ts})`);
|
|
6375
|
+
}
|
|
6376
|
+
return members
|
|
6377
|
+
.map((member) => {
|
|
6378
|
+
if (!member.endsWith("[]")) return member;
|
|
6379
|
+
const element = member.slice(0, -2);
|
|
6380
|
+
return `LuaMap<number, ${wrappedInParens(element) ? element.slice(1, -1) : element}>`;
|
|
6381
|
+
})
|
|
6382
|
+
.join(" | ");
|
|
6383
|
+
}
|
|
6384
|
+
|
|
6385
|
+
function wrappedInParens(text: string): boolean {
|
|
6386
|
+
if (!text.startsWith("(")) return false;
|
|
6387
|
+
let depth = 0;
|
|
6388
|
+
for (let i = 0; i < text.length; i++) {
|
|
6389
|
+
if (text[i] === "(") depth++;
|
|
6390
|
+
else if (text[i] === ")" && --depth === 0) return i === text.length - 1;
|
|
6391
|
+
}
|
|
6392
|
+
return false;
|
|
6393
|
+
}
|
|
6394
|
+
|
|
5388
6395
|
// Like `unionFromTokens`, but a `table` token whose slot doc carries a parseable
|
|
5389
6396
|
// `<dl>` field list emits an inline object type instead of the opaque `Record`
|
|
5390
6397
|
// fallback. Other tokens in the union map unchanged. `optionalFields` marks the
|
|
@@ -5465,6 +6472,10 @@ function mapSlotUnion(
|
|
|
5465
6472
|
}
|
|
5466
6473
|
} else if (element !== undefined) {
|
|
5467
6474
|
ts = arrayTypeFromTokens(element, mapType);
|
|
6475
|
+
if (curation?.kind === "array" && curation.pairs === true) {
|
|
6476
|
+
const tokens = typeof element === "string" ? [element] : element;
|
|
6477
|
+
ts = `${ts} | LuaPairsIterable<AnyNotNil, ${unionFromTokens(tokens, mapType)}>`;
|
|
6478
|
+
}
|
|
5468
6479
|
} else if (curation?.kind === "verbatim") {
|
|
5469
6480
|
ts = curation.ts;
|
|
5470
6481
|
} else if (curation?.kind === "object" || curation?.kind === "array-object") {
|
|
@@ -5521,19 +6532,27 @@ function mapSlotUnion(
|
|
|
5521
6532
|
// return slots.
|
|
5522
6533
|
ts = "undefined";
|
|
5523
6534
|
} else {
|
|
5524
|
-
const
|
|
6535
|
+
const curation =
|
|
5525
6536
|
slotKind !== undefined && slotName !== undefined
|
|
5526
6537
|
? CALLBACK_SIGNATURE_CURATIONS.get(tableSlotKey(elementName, slotKind, slotName))
|
|
5527
6538
|
: undefined;
|
|
5528
|
-
|
|
6539
|
+
if (curation === undefined) {
|
|
6540
|
+
ts = mapType(token);
|
|
6541
|
+
} else if (isLualsFunctionToken(token)) {
|
|
6542
|
+
ts = curation.typed ?? mapType(token);
|
|
6543
|
+
} else {
|
|
6544
|
+
ts = curation.untyped;
|
|
6545
|
+
}
|
|
5529
6546
|
}
|
|
5530
6547
|
push(ts);
|
|
5531
6548
|
}
|
|
5532
6549
|
// A genuine `unknown` member (an `any` slot, including `any | nil`) absorbs
|
|
5533
6550
|
// every other union member, so collapse to exactly `unknown` rather than emit
|
|
5534
6551
|
// a redundant `T | unknown` or `unknown | undefined`.
|
|
5535
|
-
|
|
5536
|
-
return
|
|
6552
|
+
const joined = mapped.includes("unknown") ? "unknown" : joinUnionArms(mapped);
|
|
6553
|
+
return slotKind === "return" && SPARSE_ARRAY_RETURNS.has(elementName)
|
|
6554
|
+
? sparseArrayMap(joined, elementName)
|
|
6555
|
+
: joined;
|
|
5537
6556
|
}
|
|
5538
6557
|
|
|
5539
6558
|
function tableSlotKey(elementName: string, slotKind: "param" | "return", slotName: string): string {
|
|
@@ -5690,7 +6709,7 @@ function joinUnionArms(mapped: readonly string[]): string {
|
|
|
5690
6709
|
}
|
|
5691
6710
|
|
|
5692
6711
|
const BUILTIN_MESSAGE_NAMES: ReadonlySet<string> = new Set(
|
|
5693
|
-
messagesDoc.
|
|
6712
|
+
applyMessageOverlays(parseMessagesDoc(messagesDoc)).entries.map((entry) => entry.name),
|
|
5694
6713
|
);
|
|
5695
6714
|
const MESSAGE_REFERENCE = /^message\.[a-z_]+\.([a-z_]+)$/;
|
|
5696
6715
|
|
|
@@ -5706,6 +6725,18 @@ function resolveDefoldLeaf(leaf: string): string | undefined {
|
|
|
5706
6725
|
return undefined;
|
|
5707
6726
|
}
|
|
5708
6727
|
|
|
6728
|
+
// The union typedefs whose message arms each carry the engine's message-name
|
|
6729
|
+
// hash in `type`: script_physics.cpp sets it on every `physics.set_event_listener`
|
|
6730
|
+
// event. The same message referenced anywhere else (`physics.raycast`) has none.
|
|
6731
|
+
const DISCRIMINATED_MESSAGE_UNIONS: ReadonlySet<string> = new Set(["physics.event"]);
|
|
6732
|
+
|
|
6733
|
+
function discriminatedMessageArm(token: string): string | undefined {
|
|
6734
|
+
const name = MESSAGE_REFERENCE.exec(token)?.[1];
|
|
6735
|
+
const payload = resolveDefoldLeaf(token);
|
|
6736
|
+
if (name === undefined || payload === undefined) return undefined;
|
|
6737
|
+
return `${payload} & { type: Hash<"${name}"> }`;
|
|
6738
|
+
}
|
|
6739
|
+
|
|
5709
6740
|
// Whole-token lookup and callback recovery run first, so the grammar sees only
|
|
5710
6741
|
// what they miss; every pre-1.13.2 token resolves before it is reached.
|
|
5711
6742
|
function wholeTokenMapping(token: string): string | undefined {
|