tywrap 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -22
- package/SECURITY.md +38 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +2 -1
- package/dist/core/annotation-parser.d.ts.map +1 -1
- package/dist/core/annotation-parser.js +6 -3
- package/dist/core/annotation-parser.js.map +1 -1
- package/dist/core/generator.d.ts +23 -4
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +254 -170
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +3 -2
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +5 -5
- package/dist/core/mapper.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/base-bridge.d.ts +3 -7
- package/dist/runtime/base-bridge.d.ts.map +1 -1
- package/dist/runtime/base-bridge.js +4 -17
- package/dist/runtime/base-bridge.js.map +1 -1
- package/dist/runtime/bounded-context.d.ts +3 -22
- package/dist/runtime/bounded-context.d.ts.map +1 -1
- package/dist/runtime/bounded-context.js +13 -53
- package/dist/runtime/bounded-context.js.map +1 -1
- package/dist/runtime/bridge-codec.d.ts +3 -3
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +129 -62
- package/dist/runtime/bridge-codec.js.map +1 -1
- package/dist/runtime/errors.d.ts +16 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +17 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-transport.d.ts +1 -1
- package/dist/runtime/http-transport.d.ts.map +1 -1
- package/dist/runtime/http-transport.js +1 -1
- package/dist/runtime/http-transport.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +7 -36
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +2 -80
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +120 -59
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +345 -78
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
- package/dist/runtime/pyodide-transport.d.ts +1 -1
- package/dist/runtime/pyodide-transport.d.ts.map +1 -1
- package/dist/runtime/pyodide-transport.js +2 -3
- package/dist/runtime/pyodide-transport.js.map +1 -1
- package/dist/runtime/rpc-client.d.ts +7 -36
- package/dist/runtime/rpc-client.d.ts.map +1 -1
- package/dist/runtime/rpc-client.js +20 -102
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +30 -69
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +179 -236
- package/dist/runtime/subprocess-transport.js.map +1 -1
- package/dist/runtime/timed-out-request-tracker.d.ts +2 -1
- package/dist/runtime/timed-out-request-tracker.d.ts.map +1 -1
- package/dist/runtime/transport.d.ts +9 -19
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +1 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +50 -0
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +154 -0
- package/dist/runtime/validators.js.map +1 -1
- package/dist/types/index.d.ts +14 -39
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +3 -2
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +140 -13
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts +3 -16
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/codec.d.ts +17 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +500 -85
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +2 -1
- package/dist/utils/ir-cache.d.ts.map +1 -1
- package/dist/utils/runtime.d.ts +0 -29
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +16 -107
- package/dist/utils/runtime.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +7 -6
- package/runtime/frame_codec.py +7 -1
- package/runtime/python_bridge.py +85 -125
- package/runtime/tywrap_bridge_core.py +515 -187
- package/src/config/index.ts +11 -0
- package/src/core/annotation-parser.ts +7 -4
- package/src/core/generator.ts +330 -205
- package/src/core/mapper.ts +8 -8
- package/src/index.ts +7 -4
- package/src/runtime/base-bridge.ts +5 -30
- package/src/runtime/bounded-context.ts +12 -67
- package/src/runtime/bridge-codec.ts +158 -74
- package/src/runtime/errors.ts +21 -0
- package/src/runtime/http-transport.ts +6 -1
- package/src/runtime/index.ts +6 -0
- package/src/runtime/node.ts +9 -120
- package/src/runtime/pooled-transport.ts +424 -90
- package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
- package/src/runtime/pyodide-transport.ts +7 -3
- package/src/runtime/rpc-client.ts +37 -139
- package/src/runtime/subprocess-transport.ts +210 -285
- package/src/runtime/timed-out-request-tracker.ts +1 -1
- package/src/runtime/transport.ts +15 -23
- package/src/runtime/validators.ts +212 -0
- package/src/types/index.ts +21 -60
- package/src/tywrap.ts +157 -22
- package/src/utils/cache.ts +3 -3
- package/src/utils/codec.ts +749 -112
- package/src/utils/ir-cache.ts +1 -1
- package/src/utils/runtime.ts +17 -128
- package/src/version.ts +1 -1
- package/dist/core/discovery.d.ts +0 -103
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -380
- package/dist/core/discovery.js.map +0 -1
- package/dist/core/validation.d.ts +0 -102
- package/dist/core/validation.d.ts.map +0 -1
- package/dist/core/validation.js +0 -490
- package/dist/core/validation.js.map +0 -1
- package/dist/runtime/base.d.ts +0 -22
- package/dist/runtime/base.d.ts.map +0 -1
- package/dist/runtime/base.js +0 -23
- package/dist/runtime/base.js.map +0 -1
- package/dist/runtime/transport-pool.d.ts +0 -196
- package/dist/runtime/transport-pool.d.ts.map +0 -1
- package/dist/runtime/transport-pool.js +0 -418
- package/dist/runtime/transport-pool.js.map +0 -1
- package/runtime/__pycache__/_tywrap_conformance_chunking_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_member_fixtures.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w4_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w5_request_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/_tywrap_w6_pool_chunking_fixture.cpython-311.pyc +0 -0
- package/runtime/__pycache__/frame_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/safe_codec.py +0 -352
- package/src/core/discovery.ts +0 -477
- package/src/core/validation.ts +0 -729
- package/src/runtime/base.ts +0 -24
- package/src/runtime/transport-pool.ts +0 -538
package/dist/core/generator.js
CHANGED
|
@@ -6,6 +6,28 @@ import { emitArgGuards, emitCallPrelude, } from './emit-call.js';
|
|
|
6
6
|
import { TypeMapper } from './mapper.js';
|
|
7
7
|
export class CodeGenerator {
|
|
8
8
|
mapper;
|
|
9
|
+
onTypeDegrade;
|
|
10
|
+
builtinGenericNames = new Set([
|
|
11
|
+
'Array',
|
|
12
|
+
'AsyncIterator',
|
|
13
|
+
'Generator',
|
|
14
|
+
'Iterable',
|
|
15
|
+
'Iterator',
|
|
16
|
+
'Promise',
|
|
17
|
+
'Record',
|
|
18
|
+
]);
|
|
19
|
+
// Iterator-protocol objects can never cross the bridge: the Python side
|
|
20
|
+
// rejects them loudly at serialization, so a return typed as one of these
|
|
21
|
+
// could never carry a value. Returns degrade to `unknown` (and count as a
|
|
22
|
+
// degrade for --fail-on-warn). Iterable/Sequence are NOT here — a list
|
|
23
|
+
// satisfies those annotations and decodes to an array, which structurally
|
|
24
|
+
// satisfies the emitted TS type.
|
|
25
|
+
nonDecodableReturnGenerics = new Set([
|
|
26
|
+
'Generator',
|
|
27
|
+
'AsyncGenerator',
|
|
28
|
+
'Iterator',
|
|
29
|
+
'AsyncIterator',
|
|
30
|
+
]);
|
|
9
31
|
reservedTsIdentifiers = new Set([
|
|
10
32
|
'default',
|
|
11
33
|
'delete',
|
|
@@ -34,8 +56,9 @@ export class CodeGenerator {
|
|
|
34
56
|
'true',
|
|
35
57
|
'false',
|
|
36
58
|
]);
|
|
37
|
-
constructor(mapper = new TypeMapper()) {
|
|
59
|
+
constructor(mapper = new TypeMapper(), options = {}) {
|
|
38
60
|
this.mapper = mapper;
|
|
61
|
+
this.onTypeDegrade = options.onTypeDegrade;
|
|
39
62
|
}
|
|
40
63
|
/**
|
|
41
64
|
* Convert Python snake_case to TypeScript camelCase and escape reserved words
|
|
@@ -93,7 +116,7 @@ export class CodeGenerator {
|
|
|
93
116
|
getTypeParameters(typeParameters) {
|
|
94
117
|
return typeParameters ?? [];
|
|
95
118
|
}
|
|
96
|
-
buildGenericRenderContext(typeParameters, types, currentModule) {
|
|
119
|
+
buildGenericRenderContext(typeParameters, types, currentModule, localDeclaredNames = new Set()) {
|
|
97
120
|
const callableParamSpecs = new Set();
|
|
98
121
|
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
99
122
|
const emitted = [];
|
|
@@ -116,6 +139,7 @@ export class CodeGenerator {
|
|
|
116
139
|
});
|
|
117
140
|
return {
|
|
118
141
|
currentModule,
|
|
142
|
+
localDeclaredNames,
|
|
119
143
|
declaration: emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
120
144
|
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
121
145
|
emittedNames,
|
|
@@ -125,6 +149,7 @@ export class CodeGenerator {
|
|
|
125
149
|
mergeGenericRenderContexts(outer, inner) {
|
|
126
150
|
return {
|
|
127
151
|
currentModule: inner.currentModule ?? outer.currentModule,
|
|
152
|
+
localDeclaredNames: inner.localDeclaredNames,
|
|
128
153
|
declaration: inner.declaration,
|
|
129
154
|
typeArguments: inner.typeArguments,
|
|
130
155
|
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
@@ -240,7 +265,18 @@ export class CodeGenerator {
|
|
|
240
265
|
}
|
|
241
266
|
}
|
|
242
267
|
typeToTsFromPython(type, ctx, mappingContext) {
|
|
243
|
-
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext));
|
|
268
|
+
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext), ctx, mappingContext);
|
|
269
|
+
}
|
|
270
|
+
isLocalTypeIdentity(type, ctx) {
|
|
271
|
+
if (!ctx.localDeclaredNames.has(type.name)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return type.module === undefined || type.module === ctx.currentModule;
|
|
275
|
+
}
|
|
276
|
+
degradeType(type) {
|
|
277
|
+
const identity = type.module ? `${type.module}.${type.name}` : type.name;
|
|
278
|
+
this.onTypeDegrade?.(identity);
|
|
279
|
+
return 'unknown';
|
|
244
280
|
}
|
|
245
281
|
renderLooksLikeKwargsExpr(valueExpr, options) {
|
|
246
282
|
const base = `typeof ${valueExpr} === 'object' && ${valueExpr} !== null && !globalThis.Array.isArray(${valueExpr}) && (Object.getPrototypeOf(${valueExpr}) === Object.prototype || Object.getPrototypeOf(${valueExpr}) === null)`;
|
|
@@ -270,7 +306,154 @@ export class CodeGenerator {
|
|
|
270
306
|
renderLooksLikeKwargsExpr: (valueExpr, options) => this.renderLooksLikeKwargsExpr(valueExpr, options),
|
|
271
307
|
};
|
|
272
308
|
}
|
|
273
|
-
|
|
309
|
+
/**
|
|
310
|
+
* Preserve Python provenance in generated return validators instead of
|
|
311
|
+
* reconstructing a checker from the final TypeScript spelling. In particular
|
|
312
|
+
* pandas/NumPy values retain their marker contract after Arrow decoding.
|
|
313
|
+
*/
|
|
314
|
+
returnSchema(type, definitions = new Set()) {
|
|
315
|
+
// A bare `-> None` return maps to TS void and validates nothing, but a
|
|
316
|
+
// NESTED None (union member, tuple element) is a real wire value — it
|
|
317
|
+
// decodes to null and must be checked as null, or a union containing it
|
|
318
|
+
// would carry an any-member and accept everything.
|
|
319
|
+
if (type.kind === 'primitive' && type.name === 'None') {
|
|
320
|
+
return { kind: 'any' };
|
|
321
|
+
}
|
|
322
|
+
const schema = (current) => {
|
|
323
|
+
switch (current.kind) {
|
|
324
|
+
case 'primitive':
|
|
325
|
+
return current.name === 'int' || current.name === 'float'
|
|
326
|
+
? { kind: 'primitive', type: 'number' }
|
|
327
|
+
: current.name === 'str'
|
|
328
|
+
? { kind: 'primitive', type: 'string' }
|
|
329
|
+
: current.name === 'bool'
|
|
330
|
+
? { kind: 'primitive', type: 'boolean' }
|
|
331
|
+
: current.name === 'bytes'
|
|
332
|
+
? { kind: 'primitive', type: 'Uint8Array' }
|
|
333
|
+
: current.name === 'None'
|
|
334
|
+
? { kind: 'primitive', type: 'null' }
|
|
335
|
+
: { kind: 'any' };
|
|
336
|
+
case 'literal':
|
|
337
|
+
return { kind: 'literal', value: current.value };
|
|
338
|
+
case 'annotated':
|
|
339
|
+
return schema(current.base);
|
|
340
|
+
case 'final':
|
|
341
|
+
case 'classvar':
|
|
342
|
+
return schema(current.type);
|
|
343
|
+
case 'optional':
|
|
344
|
+
return {
|
|
345
|
+
kind: 'union',
|
|
346
|
+
options: [schema(current.type), { kind: 'primitive', type: 'null' }],
|
|
347
|
+
};
|
|
348
|
+
case 'union':
|
|
349
|
+
return { kind: 'union', options: current.types.map(schema) };
|
|
350
|
+
case 'collection': {
|
|
351
|
+
if (current.name === 'tuple') {
|
|
352
|
+
if (current.itemTypes[1]?.kind === 'custom' && current.itemTypes[1].name === '...') {
|
|
353
|
+
return {
|
|
354
|
+
kind: 'array',
|
|
355
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return { kind: 'tuple', elements: current.itemTypes.map(schema) };
|
|
359
|
+
}
|
|
360
|
+
if (current.name === 'dict') {
|
|
361
|
+
return {
|
|
362
|
+
kind: 'record',
|
|
363
|
+
values: schema(current.itemTypes[1] ?? { kind: 'custom', name: 'Any' }),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
kind: 'array',
|
|
368
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
case 'generic': {
|
|
372
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
373
|
+
if (leaf === 'NDArray' || leaf === 'ndarray') {
|
|
374
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
375
|
+
}
|
|
376
|
+
// Iterator/Generator are deliberately absent: those returns emit
|
|
377
|
+
// `unknown` (see nonDecodableReturnGenerics) and validate nothing.
|
|
378
|
+
if (['list', 'List', 'Sequence', 'Iterable', 'set', 'frozenset'].includes(leaf)) {
|
|
379
|
+
return {
|
|
380
|
+
kind: 'array',
|
|
381
|
+
element: schema(current.typeArgs[0] ?? { kind: 'custom', name: 'Any' }),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (['dict', 'Dict', 'Mapping', 'MutableMapping'].includes(leaf)) {
|
|
385
|
+
return {
|
|
386
|
+
kind: 'record',
|
|
387
|
+
values: schema(current.typeArgs[1] ?? { kind: 'custom', name: 'Any' }),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
391
|
+
}
|
|
392
|
+
case 'custom': {
|
|
393
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
394
|
+
const full = `${current.module ?? ''}.${leaf}`;
|
|
395
|
+
if (leaf === 'None') {
|
|
396
|
+
return { kind: 'primitive', type: 'null' };
|
|
397
|
+
}
|
|
398
|
+
if (leaf === 'Any' || leaf === 'object' || leaf === 'NoReturn' || leaf === 'Never') {
|
|
399
|
+
return { kind: 'any' };
|
|
400
|
+
}
|
|
401
|
+
if (leaf === 'DataFrame' && (!current.module || current.module.startsWith('pandas'))) {
|
|
402
|
+
return { kind: 'marker', marker: 'dataframe' };
|
|
403
|
+
}
|
|
404
|
+
if (leaf === 'Series' && (!current.module || current.module.startsWith('pandas'))) {
|
|
405
|
+
return { kind: 'marker', marker: 'series' };
|
|
406
|
+
}
|
|
407
|
+
if (leaf === 'ndarray' || leaf === 'NDArray' || full.includes('numpy')) {
|
|
408
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
409
|
+
}
|
|
410
|
+
if (['csr_matrix', 'csc_matrix', 'coo_matrix', 'spmatrix'].includes(leaf) &&
|
|
411
|
+
(!current.module || current.module.startsWith('scipy'))) {
|
|
412
|
+
return { kind: 'marker', marker: 'scipy.sparse' };
|
|
413
|
+
}
|
|
414
|
+
if (leaf === 'Tensor' && (!current.module || current.module.startsWith('torch'))) {
|
|
415
|
+
return { kind: 'marker', marker: 'torch.tensor' };
|
|
416
|
+
}
|
|
417
|
+
if (leaf === 'BaseEstimator' &&
|
|
418
|
+
(!current.module || current.module.startsWith('sklearn'))) {
|
|
419
|
+
return { kind: 'marker', marker: 'sklearn.estimator' };
|
|
420
|
+
}
|
|
421
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
422
|
+
}
|
|
423
|
+
case 'typevar':
|
|
424
|
+
case 'paramspec':
|
|
425
|
+
case 'paramspec_args':
|
|
426
|
+
case 'paramspec_kwargs':
|
|
427
|
+
case 'typevartuple':
|
|
428
|
+
case 'unpack':
|
|
429
|
+
case 'callable':
|
|
430
|
+
return { kind: 'any' };
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
return schema(type);
|
|
434
|
+
}
|
|
435
|
+
returnDefinitions(module) {
|
|
436
|
+
return new Set(module.classes
|
|
437
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
438
|
+
.map(cls => cls.name));
|
|
439
|
+
}
|
|
440
|
+
emitReturnDefinitions(module) {
|
|
441
|
+
const definitions = this.returnDefinitions(module);
|
|
442
|
+
const entries = module.classes
|
|
443
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
444
|
+
.map(cls => {
|
|
445
|
+
const fields = Object.fromEntries(cls.properties.map(property => [
|
|
446
|
+
property.name,
|
|
447
|
+
{
|
|
448
|
+
schema: this.returnSchema(property.type, definitions),
|
|
449
|
+
optional: property.optional === true,
|
|
450
|
+
},
|
|
451
|
+
]));
|
|
452
|
+
return [cls.name, { kind: 'record', fields }];
|
|
453
|
+
});
|
|
454
|
+
return `const __tywrapReturnDefinitions: Record<string, ReturnSchema> = ${JSON.stringify(Object.fromEntries(entries))};\n\n`;
|
|
455
|
+
}
|
|
456
|
+
generateFunctionWrapper(func, moduleName, annotatedJSDoc = false, localDeclaredNames = new Set(), returnDefinitions = new Set()) {
|
|
274
457
|
const jsdoc = this.generateJsDoc(func.docstring, annotatedJSDoc ? func.parameters.map(p => String(p.type)) : undefined);
|
|
275
458
|
const filteredParams = func.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
276
459
|
const keywordOnlyParams = filteredParams.filter(p => p.keywordOnly);
|
|
@@ -280,7 +463,7 @@ export class CodeGenerator {
|
|
|
280
463
|
const varArgsParam = filteredParams.find(p => p.varArgs);
|
|
281
464
|
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
282
465
|
const positionalParams = filteredParams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
283
|
-
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(func.typeParameters), [func.returnType, ...filteredParams.map(param => param.type)], moduleName);
|
|
466
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(func.typeParameters), [func.returnType, ...filteredParams.map(param => param.type)], moduleName, localDeclaredNames);
|
|
284
467
|
const typeParamDecl = genericContext.declaration;
|
|
285
468
|
const tsTypeForValue = (p) => this.typeToTsFromPython(p.type, genericContext, 'value');
|
|
286
469
|
const kwargsType = (() => {
|
|
@@ -336,6 +519,8 @@ export class CodeGenerator {
|
|
|
336
519
|
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
337
520
|
const fname = this.escapeIdentifier(func.name);
|
|
338
521
|
const moduleId = moduleName ?? '__main__';
|
|
522
|
+
const validatorName = `__validate${this.escapeIdentifier(func.name, { preserveCase: true })}Result`;
|
|
523
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(func.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${func.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
339
524
|
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs).
|
|
340
525
|
// Why: Python APIs frequently have many optional tail params. TypeScript callers expect
|
|
341
526
|
// `fn(a)`, `fn(a, b)`, ... all to typecheck. We emit a family of overloads that progressively
|
|
@@ -413,8 +598,8 @@ export class CodeGenerator {
|
|
|
413
598
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
414
599
|
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
415
600
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
416
|
-
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
417
|
-
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${hasKwArgs ? '
|
|
601
|
+
const ts = `${jsdoc}${returnValidator}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
602
|
+
${callPrelude}${guards} return getRuntimeBridge().call<${returnType}>('${moduleId}', '${func.name}', __args, ${hasKwArgs ? '__kwargs' : 'undefined'}, ${validatorName});
|
|
418
603
|
}
|
|
419
604
|
`;
|
|
420
605
|
const declarationBody = overloads.length > 0
|
|
@@ -422,7 +607,9 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
422
607
|
: `export function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}>;\n`;
|
|
423
608
|
return this.wrap(ts, `${jsdoc}${declarationBody}`, [func.name]);
|
|
424
609
|
}
|
|
425
|
-
generateClassWrapper(cls, moduleName, _annotatedJSDoc = false) {
|
|
610
|
+
generateClassWrapper(cls, moduleName, _annotatedJSDoc = false, localDeclaredNames = new Set(), returnDefinitions = new Set()) {
|
|
611
|
+
const moduleDeclaredNames = new Set(localDeclaredNames);
|
|
612
|
+
moduleDeclaredNames.add(cls.name);
|
|
426
613
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
427
614
|
const classGenericContext = this.buildGenericRenderContext(this.getTypeParameters(cls.typeParameters), [
|
|
428
615
|
...cls.properties.map(property => property.type),
|
|
@@ -431,11 +618,9 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
431
618
|
method.returnType,
|
|
432
619
|
...method.parameters.map(p => p.type),
|
|
433
620
|
]),
|
|
434
|
-
], moduleName);
|
|
621
|
+
], moduleName, moduleDeclaredNames);
|
|
435
622
|
const classTypeParamDecl = classGenericContext.declaration;
|
|
436
623
|
const cname = this.escapeIdentifier(cls.name);
|
|
437
|
-
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
438
|
-
const tsValueType = (p) => this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
439
624
|
const wrapAlias = (body) => {
|
|
440
625
|
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
441
626
|
return this.wrap(ts, ts, [cls.name]);
|
|
@@ -469,7 +654,7 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
469
654
|
.filter(m => m.name !== '__init__')
|
|
470
655
|
.map(m => {
|
|
471
656
|
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
472
|
-
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(m.typeParameters), [m.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
657
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(m.typeParameters), [m.returnType, ...fparams.map(param => param.type)], moduleName, moduleDeclaredNames);
|
|
473
658
|
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
474
659
|
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
475
660
|
const paramsDecl = fparams
|
|
@@ -497,16 +682,14 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
497
682
|
const methodBodies = [];
|
|
498
683
|
const methodDeclarations = [];
|
|
499
684
|
sortedMethods
|
|
500
|
-
.filter(method => method.name !== '__init__'
|
|
685
|
+
.filter(method => method.name !== '__init__' &&
|
|
686
|
+
(method.methodKind === 'class' || method.methodKind === 'static'))
|
|
501
687
|
.forEach(method => {
|
|
502
|
-
//
|
|
503
|
-
// the
|
|
504
|
-
|
|
505
|
-
// their output stays byte-identical.
|
|
506
|
-
const isStatic = method.methodKind === 'class' || method.methodKind === 'static';
|
|
507
|
-
const staticPrefix = isStatic ? 'static ' : '';
|
|
688
|
+
// v0.9 wrappers expose only class/static methods. They route through
|
|
689
|
+
// the ordinary module call path and never retain process-local state.
|
|
690
|
+
const staticPrefix = 'static ';
|
|
508
691
|
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
509
|
-
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(method.typeParameters), [method.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
692
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(method.typeParameters), [method.returnType, ...fparams.map(param => param.type)], moduleName, moduleDeclaredNames);
|
|
510
693
|
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
511
694
|
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
512
695
|
const methodTsValueType = (p) => this.typeToTsFromPython(p.type, methodGenericContext, 'value');
|
|
@@ -553,6 +736,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
553
736
|
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
554
737
|
const returnType = this.typeToTsFromPython(method.returnType, methodGenericContext, 'return');
|
|
555
738
|
const mname = this.escapeIdentifier(method.name);
|
|
739
|
+
const validatorName = `__validate${this.escapeIdentifier(cls.name, { preserveCase: true })}${this.escapeIdentifier(method.name, { preserveCase: true })}Result`;
|
|
740
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(method.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${cls.name}.${method.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
556
741
|
const overloads = [];
|
|
557
742
|
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
558
743
|
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
@@ -589,154 +774,33 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
589
774
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
590
775
|
const guardLines = emitArgGuards(callDescriptor);
|
|
591
776
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
592
|
-
const callExpr =
|
|
593
|
-
? `getRuntimeBridge().call('${moduleId}', '${cls.name}.${method.name}', __args${needsKwargsParam ? ', __kwargs' : ''})`
|
|
594
|
-
: `getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${needsKwargsParam ? ', __kwargs' : ''})`;
|
|
777
|
+
const callExpr = `getRuntimeBridge().call<${returnType}>('${moduleId}', '${cls.name}.${method.name}', __args, ${needsKwargsParam ? '__kwargs' : 'undefined'}, ${validatorName})`;
|
|
595
778
|
methodBodies.push(`${overloadDecl} ${staticPrefix}async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
596
|
-
${callPrelude}${guards} return ${callExpr};
|
|
779
|
+
${returnValidator}${callPrelude}${guards} return ${callExpr};
|
|
597
780
|
}`);
|
|
598
781
|
methodDeclarations.push(`${overloadDecl}${overloads.length > 0 ? '' : ` ${staticPrefix}${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`);
|
|
599
782
|
});
|
|
600
|
-
const init = cls.methods.find(m => m.name === '__init__');
|
|
601
|
-
const ctorSpec = (() => {
|
|
602
|
-
if (!init) {
|
|
603
|
-
return {
|
|
604
|
-
overloadDecl: '',
|
|
605
|
-
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
606
|
-
paramsDecl: `...args: unknown[]`,
|
|
607
|
-
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
608
|
-
hasKwargs: false,
|
|
609
|
-
guardLines: [],
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
613
|
-
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
614
|
-
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
615
|
-
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
616
|
-
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
617
|
-
const varArgsParam = fparams.find(p => p.varArgs);
|
|
618
|
-
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
619
|
-
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
620
|
-
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
621
|
-
const requiredPosCount = firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
622
|
-
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
623
|
-
const renderPositionalParam = (p, forceRequired = false) => {
|
|
624
|
-
const pname = this.escapeIdentifier(p.name);
|
|
625
|
-
const opt = !forceRequired && p.optional ? '?' : '';
|
|
626
|
-
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
627
|
-
};
|
|
628
|
-
const kwargsType = (() => {
|
|
629
|
-
if (!needsKwargsParam) {
|
|
630
|
-
return '';
|
|
631
|
-
}
|
|
632
|
-
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
633
|
-
return 'Record<string, unknown>';
|
|
634
|
-
}
|
|
635
|
-
const props = keywordOnlyParams
|
|
636
|
-
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
637
|
-
.join(' ');
|
|
638
|
-
const obj = `{ ${props} }`;
|
|
639
|
-
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
640
|
-
})();
|
|
641
|
-
const paramsDeclParts = [];
|
|
642
|
-
positionalParams.forEach(p => {
|
|
643
|
-
paramsDeclParts.push(renderPositionalParam(p));
|
|
644
|
-
});
|
|
645
|
-
if (varArgsParam) {
|
|
646
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
647
|
-
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
648
|
-
}
|
|
649
|
-
if (needsKwargsParam) {
|
|
650
|
-
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
651
|
-
}
|
|
652
|
-
const paramsDecl = paramsDeclParts.join(', ');
|
|
653
|
-
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
654
|
-
const overloads = [];
|
|
655
|
-
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
656
|
-
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
657
|
-
const requiredPosCount = firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
658
|
-
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
659
|
-
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
660
|
-
const rest = [];
|
|
661
|
-
if (varArgsParam) {
|
|
662
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
663
|
-
rest.push(needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`);
|
|
664
|
-
}
|
|
665
|
-
rest.push(`kwargs: ${kwargsType}`);
|
|
666
|
-
overloads.push(` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`);
|
|
667
|
-
if (varArgsParam && needsVarArgsArray) {
|
|
668
|
-
overloads.push(` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
673
|
-
const declaration = overloads.length > 0
|
|
674
|
-
? overloadDecl
|
|
675
|
-
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
676
|
-
const callDescriptor = {
|
|
677
|
-
positionalParams,
|
|
678
|
-
varArgsParam,
|
|
679
|
-
needsVarArgsArray,
|
|
680
|
-
hasKwArgs: needsKwargsParam,
|
|
681
|
-
hasVarKwArgs,
|
|
682
|
-
keywordOnlyNames,
|
|
683
|
-
requiredKwOnlyNames,
|
|
684
|
-
positionalOnlyNames,
|
|
685
|
-
requiredPosCount,
|
|
686
|
-
indent: ' ',
|
|
687
|
-
errorLabel: '__init__',
|
|
688
|
-
};
|
|
689
|
-
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
690
|
-
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
691
|
-
const guardLines = emitArgGuards(callDescriptor);
|
|
692
|
-
return {
|
|
693
|
-
overloadDecl,
|
|
694
|
-
declaration,
|
|
695
|
-
paramsDecl,
|
|
696
|
-
callPrelude,
|
|
697
|
-
hasKwargs: needsKwargsParam,
|
|
698
|
-
guardLines,
|
|
699
|
-
};
|
|
700
|
-
})();
|
|
701
|
-
// @property / functools.cached_property → TS getters returning Promise<T>.
|
|
702
|
-
// Reading the attribute fires the getter on the Python side; the bridge
|
|
703
|
-
// resolves it via callMethod with no args. Sorted for stable output.
|
|
704
|
-
const accessorBodies = [];
|
|
705
|
-
const accessorDeclarations = [];
|
|
706
|
-
const sortedAccessors = [...(cls.accessors ?? [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
707
|
-
sortedAccessors.forEach(accessor => {
|
|
708
|
-
const aname = this.escapeIdentifier(accessor.name);
|
|
709
|
-
const accessorType = this.typeToTsFromPython(accessor.type, classGenericContext, 'return');
|
|
710
|
-
const jsdocAcc = this.generateJsDoc(accessor.docstring);
|
|
711
|
-
accessorBodies.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}> { return getRuntimeBridge().callMethod(this.__handle, '${accessor.name}', []); }`);
|
|
712
|
-
accessorDeclarations.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}>;\n`);
|
|
713
|
-
});
|
|
714
|
-
const accessorsImpl = accessorBodies.length > 0 ? `${accessorBodies.join('\n')}\n` : '';
|
|
715
|
-
const accessorsDecl = accessorDeclarations.length > 0 ? `${accessorDeclarations.join('')}` : '';
|
|
716
783
|
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
717
784
|
const declarationMethodsSection = methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
718
|
-
|
|
719
|
-
|
|
785
|
+
// The constructor counts: a class whose only member was __init__ loses
|
|
786
|
+
// create(), so it needs the migration note as much as one with methods.
|
|
787
|
+
const omittedInstanceMembers = cls.methods.some(method => method.methodKind !== 'class' && method.methodKind !== 'static') ||
|
|
788
|
+
(cls.accessors?.length ?? 0) > 0;
|
|
789
|
+
const migrationNote = omittedInstanceMembers
|
|
790
|
+
? ' // NOTE: Instance members are not generated in v0.9; migrate this API to value-returning module functions.\n'
|
|
791
|
+
: '';
|
|
720
792
|
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
721
|
-
|
|
722
|
-
private constructor(handle: string) { this.__handle = handle; }
|
|
723
|
-
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
724
|
-
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${ctorSpec.hasKwargs ? ', __kwargs' : ''});
|
|
725
|
-
return ${newClassExpr};
|
|
726
|
-
}
|
|
727
|
-
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection}${accessorsImpl} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
793
|
+
${migrationNote}${methodsSection}
|
|
728
794
|
}
|
|
729
795
|
`;
|
|
730
796
|
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
731
|
-
|
|
732
|
-
private constructor(handle: string);
|
|
733
|
-
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection}${accessorsDecl} disposeHandle(): Promise<void>;
|
|
797
|
+
${migrationNote}${declarationMethodsSection}
|
|
734
798
|
}
|
|
735
799
|
`;
|
|
736
800
|
return this.wrap(ts, declaration, [cls.name]);
|
|
737
801
|
}
|
|
738
|
-
generateTypeAlias(alias, moduleName) {
|
|
739
|
-
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(alias.typeParameters), [alias.type], moduleName);
|
|
802
|
+
generateTypeAlias(alias, moduleName, localDeclaredNames = new Set()) {
|
|
803
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(alias.typeParameters), [alias.type], moduleName, localDeclaredNames);
|
|
740
804
|
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
741
805
|
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
742
806
|
const ts = `export type ${aliasName}${genericContext.declaration} = ${body}\n`;
|
|
@@ -761,15 +825,19 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
761
825
|
return result;
|
|
762
826
|
}
|
|
763
827
|
generateModuleDefinition(module, annotatedJSDoc = false) {
|
|
828
|
+
const localDeclaredNames = new Set([
|
|
829
|
+
...module.classes.map(cls => cls.name),
|
|
830
|
+
...(module.typeAliases ?? []).map(alias => alias.name),
|
|
831
|
+
]);
|
|
764
832
|
const functionResults = [...module.functions]
|
|
765
833
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
766
|
-
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc));
|
|
834
|
+
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc, localDeclaredNames, this.returnDefinitions(module)));
|
|
767
835
|
const classResults = [...module.classes]
|
|
768
836
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
769
|
-
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc));
|
|
837
|
+
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc, localDeclaredNames, this.returnDefinitions(module)));
|
|
770
838
|
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
771
839
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
772
|
-
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
840
|
+
.map(alias => this.generateTypeAlias(alias, module.name, localDeclaredNames));
|
|
773
841
|
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
774
842
|
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
775
843
|
const typeAliasCodes = typeAliasResults.map(result => result.typescript).join('\n');
|
|
@@ -780,7 +848,9 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
780
848
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
781
849
|
});
|
|
782
850
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
783
|
-
const bridgeDecl = needsRuntime
|
|
851
|
+
const bridgeDecl = needsRuntime
|
|
852
|
+
? `import { createReturnValidator, getRuntimeBridge, type ReturnSchema } from 'tywrap/runtime';\n\n${this.emitReturnDefinitions(module)}`
|
|
853
|
+
: '';
|
|
784
854
|
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
785
855
|
const declaration = `${`${declarationHeader}${functionResults
|
|
786
856
|
.map(result => result.declaration)
|
|
@@ -818,53 +888,67 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
818
888
|
},
|
|
819
889
|
};
|
|
820
890
|
}
|
|
821
|
-
typeToTs(type) {
|
|
891
|
+
typeToTs(type, ctx, mappingContext = 'value') {
|
|
822
892
|
switch (type.kind) {
|
|
823
893
|
case 'primitive':
|
|
824
894
|
return type.name;
|
|
825
895
|
case 'array':
|
|
826
|
-
return `${this.typeToTs(type.elementType)}[]`;
|
|
896
|
+
return `${this.typeToTs(type.elementType, ctx, mappingContext)}[]`;
|
|
827
897
|
case 'tuple': {
|
|
828
898
|
const t = type;
|
|
829
|
-
const parts = t.elementTypes.map(e => this.typeToTs(e)).join(', ');
|
|
899
|
+
const parts = t.elementTypes.map(e => this.typeToTs(e, ctx, mappingContext)).join(', ');
|
|
830
900
|
return `[${parts}]`;
|
|
831
901
|
}
|
|
832
902
|
case 'object': {
|
|
833
903
|
const t = type;
|
|
834
904
|
// If it's a simple Record<string, T> pattern, use that syntax
|
|
835
905
|
if (t.properties.length === 0 && t.indexSignature) {
|
|
836
|
-
const keyType = this.typeToTs(t.indexSignature.keyType);
|
|
837
|
-
const valueType = this.typeToTs(t.indexSignature.valueType);
|
|
906
|
+
const keyType = this.typeToTs(t.indexSignature.keyType, ctx, mappingContext);
|
|
907
|
+
const valueType = this.typeToTs(t.indexSignature.valueType, ctx, mappingContext);
|
|
838
908
|
if (keyType === 'string') {
|
|
839
909
|
return `Record<string, ${valueType}>`;
|
|
840
910
|
}
|
|
841
911
|
}
|
|
842
912
|
const props = t.properties
|
|
843
|
-
.map(p => `${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)};`)
|
|
913
|
+
.map(p => `${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)};`)
|
|
844
914
|
.join(' ');
|
|
845
915
|
const indexSig = t.indexSignature
|
|
846
|
-
? `[key: ${this.typeToTs(t.indexSignature.keyType)}]: ${this.typeToTs(t.indexSignature.valueType)};`
|
|
916
|
+
? `[key: ${this.typeToTs(t.indexSignature.keyType, ctx, mappingContext)}]: ${this.typeToTs(t.indexSignature.valueType, ctx, mappingContext)};`
|
|
847
917
|
: '';
|
|
848
918
|
return `{ ${props} ${indexSig} }`;
|
|
849
919
|
}
|
|
850
920
|
case 'union':
|
|
851
|
-
return type.types.map(t => this.typeToTs(t)).join(' | ');
|
|
921
|
+
return type.types.map(t => this.typeToTs(t, ctx, mappingContext)).join(' | ');
|
|
852
922
|
case 'function': {
|
|
853
923
|
const ft = type;
|
|
854
924
|
const params = ft.parameters
|
|
855
|
-
.map(p => `${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)}`)
|
|
925
|
+
.map(p => `${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)}`)
|
|
856
926
|
.join(', ');
|
|
857
|
-
return `(${params}) => ${this.typeToTs(ft.returnType)}`;
|
|
927
|
+
return `(${params}) => ${this.typeToTs(ft.returnType, ctx, mappingContext)}`;
|
|
858
928
|
}
|
|
859
929
|
case 'generic': {
|
|
860
930
|
const g = type;
|
|
861
|
-
|
|
931
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(g.name)) {
|
|
932
|
+
return this.degradeType(g);
|
|
933
|
+
}
|
|
934
|
+
if (mappingContext === 'return' && this.nonDecodableReturnGenerics.has(g.name)) {
|
|
935
|
+
return this.degradeType(g);
|
|
936
|
+
}
|
|
937
|
+
if (ctx && !this.builtinGenericNames.has(g.name) && !this.isLocalTypeIdentity(g, ctx)) {
|
|
938
|
+
return this.degradeType(g);
|
|
939
|
+
}
|
|
940
|
+
const args = g.typeArgs.map(a => this.typeToTs(a, ctx, mappingContext)).join(', ');
|
|
862
941
|
return `${g.name}<${args}>`;
|
|
863
942
|
}
|
|
864
943
|
case 'custom': {
|
|
865
944
|
const c = type;
|
|
866
945
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
867
|
-
return
|
|
946
|
+
return this.degradeType(c);
|
|
947
|
+
}
|
|
948
|
+
if (ctx &&
|
|
949
|
+
!this.isLocalTypeIdentity(c, ctx) &&
|
|
950
|
+
!(c.module === 'typing' && ctx.emittedNames.has(c.name))) {
|
|
951
|
+
return this.degradeType(c);
|
|
868
952
|
}
|
|
869
953
|
return c.name;
|
|
870
954
|
}
|