tywrap 0.7.0 → 0.9.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 +36 -11
- package/SECURITY.md +39 -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/emit-call.d.ts.map +1 -1
- package/dist/core/emit-call.js +1 -1
- package/dist/core/emit-call.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 +243 -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/dev.d.ts.map +1 -1
- package/dist/dev.js +1 -3
- package/dist/dev.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 +1 -1
- package/dist/runtime/bridge-codec.d.ts.map +1 -1
- package/dist/runtime/bridge-codec.js +78 -55
- 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/frame-codec.d.ts +111 -0
- package/dist/runtime/frame-codec.d.ts.map +1 -0
- package/dist/runtime/frame-codec.js +352 -0
- package/dist/runtime/frame-codec.js.map +1 -0
- 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 +2 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/node.d.ts +7 -23
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +5 -78
- 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 +37 -72
- package/dist/runtime/rpc-client.js.map +1 -1
- package/dist/runtime/subprocess-transport.d.ts +134 -8
- package/dist/runtime/subprocess-transport.d.ts.map +1 -1
- package/dist/runtime/subprocess-transport.js +490 -65
- 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 +90 -18
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js +21 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +49 -0
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +152 -0
- package/dist/runtime/validators.js.map +1 -1
- package/dist/types/index.d.ts +14 -15
- 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 -21
- 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 +1 -0
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +165 -17
- 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.js +1 -1
- package/package.json +7 -6
- package/runtime/frame_codec.py +430 -0
- package/runtime/python_bridge.py +213 -54
- package/runtime/tywrap_bridge_core.py +217 -147
- package/src/config/index.ts +11 -0
- package/src/core/annotation-parser.ts +9 -5
- package/src/core/emit-call.ts +1 -7
- package/src/core/generator.ts +315 -205
- package/src/core/mapper.ts +8 -8
- package/src/dev.ts +1 -3
- package/src/index.ts +7 -3
- package/src/runtime/base-bridge.ts +5 -30
- package/src/runtime/bounded-context.ts +12 -67
- package/src/runtime/bridge-codec.ts +94 -65
- package/src/runtime/errors.ts +21 -0
- package/src/runtime/frame-codec.ts +469 -0
- package/src/runtime/http-transport.ts +6 -1
- package/src/runtime/index.ts +7 -6
- package/src/runtime/node.ts +17 -104
- 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 +58 -93
- package/src/runtime/subprocess-transport.ts +585 -80
- package/src/runtime/timed-out-request-tracker.ts +1 -1
- package/src/runtime/transport.ts +112 -22
- package/src/runtime/validators.ts +204 -0
- package/src/types/index.ts +21 -35
- package/src/tywrap.ts +157 -30
- package/src/utils/cache.ts +3 -3
- package/src/utils/codec.ts +205 -16
- 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_member_fixtures.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/src/core/generator.ts
CHANGED
|
@@ -29,14 +29,44 @@ interface GenericRenderParam {
|
|
|
29
29
|
|
|
30
30
|
interface GenericRenderContext {
|
|
31
31
|
currentModule?: string;
|
|
32
|
+
localDeclaredNames: Set<string>;
|
|
32
33
|
declaration: string;
|
|
33
34
|
typeArguments: string;
|
|
34
35
|
emittedNames: Set<string>;
|
|
35
36
|
emittedParamSpecs: Set<string>;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
type ReturnDefinitionNames = ReadonlySet<string>;
|
|
40
|
+
|
|
41
|
+
export interface CodeGeneratorOptions {
|
|
42
|
+
/** Reports a generated annotation that cannot be represented by emitted declarations. */
|
|
43
|
+
onTypeDegrade?: (typeName: string) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
export class CodeGenerator {
|
|
39
47
|
private readonly mapper: TypeMapper;
|
|
48
|
+
private readonly onTypeDegrade?: (typeName: string) => void;
|
|
49
|
+
private readonly builtinGenericNames = new Set([
|
|
50
|
+
'Array',
|
|
51
|
+
'AsyncIterator',
|
|
52
|
+
'Generator',
|
|
53
|
+
'Iterable',
|
|
54
|
+
'Iterator',
|
|
55
|
+
'Promise',
|
|
56
|
+
'Record',
|
|
57
|
+
]);
|
|
58
|
+
// Iterator-protocol objects can never cross the bridge: the Python side
|
|
59
|
+
// rejects them loudly at serialization, so a return typed as one of these
|
|
60
|
+
// could never carry a value. Returns degrade to `unknown` (and count as a
|
|
61
|
+
// degrade for --fail-on-warn). Iterable/Sequence are NOT here — a list
|
|
62
|
+
// satisfies those annotations and decodes to an array, which structurally
|
|
63
|
+
// satisfies the emitted TS type.
|
|
64
|
+
private readonly nonDecodableReturnGenerics = new Set([
|
|
65
|
+
'Generator',
|
|
66
|
+
'AsyncGenerator',
|
|
67
|
+
'Iterator',
|
|
68
|
+
'AsyncIterator',
|
|
69
|
+
]);
|
|
40
70
|
private readonly reservedTsIdentifiers = new Set([
|
|
41
71
|
'default',
|
|
42
72
|
'delete',
|
|
@@ -66,8 +96,9 @@ export class CodeGenerator {
|
|
|
66
96
|
'false',
|
|
67
97
|
]);
|
|
68
98
|
|
|
69
|
-
constructor(mapper: TypeMapper = new TypeMapper()) {
|
|
99
|
+
constructor(mapper: TypeMapper = new TypeMapper(), options: CodeGeneratorOptions = {}) {
|
|
70
100
|
this.mapper = mapper;
|
|
101
|
+
this.onTypeDegrade = options.onTypeDegrade;
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
/**
|
|
@@ -140,7 +171,8 @@ export class CodeGenerator {
|
|
|
140
171
|
private buildGenericRenderContext(
|
|
141
172
|
typeParameters: readonly PythonGenericParameter[],
|
|
142
173
|
types: readonly PythonType[],
|
|
143
|
-
currentModule?: string
|
|
174
|
+
currentModule?: string,
|
|
175
|
+
localDeclaredNames: Set<string> = new Set()
|
|
144
176
|
): GenericRenderContext {
|
|
145
177
|
const callableParamSpecs = new Set<string>();
|
|
146
178
|
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
@@ -170,6 +202,7 @@ export class CodeGenerator {
|
|
|
170
202
|
|
|
171
203
|
return {
|
|
172
204
|
currentModule,
|
|
205
|
+
localDeclaredNames,
|
|
173
206
|
declaration:
|
|
174
207
|
emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
175
208
|
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
@@ -184,6 +217,7 @@ export class CodeGenerator {
|
|
|
184
217
|
): GenericRenderContext {
|
|
185
218
|
return {
|
|
186
219
|
currentModule: inner.currentModule ?? outer.currentModule,
|
|
220
|
+
localDeclaredNames: inner.localDeclaredNames,
|
|
187
221
|
declaration: inner.declaration,
|
|
188
222
|
typeArguments: inner.typeArguments,
|
|
189
223
|
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
@@ -308,7 +342,27 @@ export class CodeGenerator {
|
|
|
308
342
|
ctx: GenericRenderContext,
|
|
309
343
|
mappingContext: 'value' | 'return'
|
|
310
344
|
): string {
|
|
311
|
-
return this.typeToTs(
|
|
345
|
+
return this.typeToTs(
|
|
346
|
+
this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext),
|
|
347
|
+
ctx,
|
|
348
|
+
mappingContext
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private isLocalTypeIdentity(
|
|
353
|
+
type: { name: string; module?: string },
|
|
354
|
+
ctx: GenericRenderContext
|
|
355
|
+
): boolean {
|
|
356
|
+
if (!ctx.localDeclaredNames.has(type.name)) {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
return type.module === undefined || type.module === ctx.currentModule;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private degradeType(type: { name: string; module?: string }): string {
|
|
363
|
+
const identity = type.module ? `${type.module}.${type.name}` : type.name;
|
|
364
|
+
this.onTypeDegrade?.(identity);
|
|
365
|
+
return 'unknown';
|
|
312
366
|
}
|
|
313
367
|
|
|
314
368
|
private renderLooksLikeKwargsExpr(
|
|
@@ -357,10 +411,155 @@ export class CodeGenerator {
|
|
|
357
411
|
};
|
|
358
412
|
}
|
|
359
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Preserve Python provenance in generated return validators instead of
|
|
416
|
+
* reconstructing a checker from the final TypeScript spelling. In particular
|
|
417
|
+
* pandas/NumPy values retain their marker contract after Arrow decoding.
|
|
418
|
+
*/
|
|
419
|
+
private returnSchema(type: PythonType, definitions: ReturnDefinitionNames = new Set()): unknown {
|
|
420
|
+
// A bare `-> None` return maps to TS void and validates nothing, but a
|
|
421
|
+
// NESTED None (union member, tuple element) is a real wire value — it
|
|
422
|
+
// decodes to null and must be checked as null, or a union containing it
|
|
423
|
+
// would carry an any-member and accept everything.
|
|
424
|
+
if (type.kind === 'primitive' && type.name === 'None') {
|
|
425
|
+
return { kind: 'any' };
|
|
426
|
+
}
|
|
427
|
+
const schema = (current: PythonType): unknown => {
|
|
428
|
+
switch (current.kind) {
|
|
429
|
+
case 'primitive':
|
|
430
|
+
return current.name === 'int' || current.name === 'float'
|
|
431
|
+
? { kind: 'primitive', type: 'number' }
|
|
432
|
+
: current.name === 'str'
|
|
433
|
+
? { kind: 'primitive', type: 'string' }
|
|
434
|
+
: current.name === 'bool'
|
|
435
|
+
? { kind: 'primitive', type: 'boolean' }
|
|
436
|
+
: current.name === 'bytes'
|
|
437
|
+
? { kind: 'primitive', type: 'Uint8Array' }
|
|
438
|
+
: current.name === 'None'
|
|
439
|
+
? { kind: 'primitive', type: 'null' }
|
|
440
|
+
: { kind: 'any' };
|
|
441
|
+
case 'literal':
|
|
442
|
+
return { kind: 'literal', value: current.value };
|
|
443
|
+
case 'annotated':
|
|
444
|
+
return schema(current.base);
|
|
445
|
+
case 'final':
|
|
446
|
+
case 'classvar':
|
|
447
|
+
return schema(current.type);
|
|
448
|
+
case 'optional':
|
|
449
|
+
return {
|
|
450
|
+
kind: 'union',
|
|
451
|
+
options: [schema(current.type), { kind: 'primitive', type: 'null' }],
|
|
452
|
+
};
|
|
453
|
+
case 'union':
|
|
454
|
+
return { kind: 'union', options: current.types.map(schema) };
|
|
455
|
+
case 'collection': {
|
|
456
|
+
if (current.name === 'tuple') {
|
|
457
|
+
if (current.itemTypes[1]?.kind === 'custom' && current.itemTypes[1].name === '...') {
|
|
458
|
+
return {
|
|
459
|
+
kind: 'array',
|
|
460
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return { kind: 'tuple', elements: current.itemTypes.map(schema) };
|
|
464
|
+
}
|
|
465
|
+
if (current.name === 'dict') {
|
|
466
|
+
return {
|
|
467
|
+
kind: 'record',
|
|
468
|
+
values: schema(current.itemTypes[1] ?? { kind: 'custom', name: 'Any' }),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
return {
|
|
472
|
+
kind: 'array',
|
|
473
|
+
element: schema(current.itemTypes[0] ?? { kind: 'custom', name: 'Any' }),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
case 'generic': {
|
|
477
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
478
|
+
if (leaf === 'NDArray' || leaf === 'ndarray') {
|
|
479
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
480
|
+
}
|
|
481
|
+
// Iterator/Generator are deliberately absent: those returns emit
|
|
482
|
+
// `unknown` (see nonDecodableReturnGenerics) and validate nothing.
|
|
483
|
+
if (['list', 'List', 'Sequence', 'Iterable', 'set', 'frozenset'].includes(leaf)) {
|
|
484
|
+
return {
|
|
485
|
+
kind: 'array',
|
|
486
|
+
element: schema(current.typeArgs[0] ?? { kind: 'custom', name: 'Any' }),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
if (['dict', 'Dict', 'Mapping', 'MutableMapping'].includes(leaf)) {
|
|
490
|
+
return {
|
|
491
|
+
kind: 'record',
|
|
492
|
+
values: schema(current.typeArgs[1] ?? { kind: 'custom', name: 'Any' }),
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
496
|
+
}
|
|
497
|
+
case 'custom': {
|
|
498
|
+
const leaf = current.name.split('.').at(-1) ?? current.name;
|
|
499
|
+
const full = `${current.module ?? ''}.${leaf}`;
|
|
500
|
+
if (leaf === 'None') {
|
|
501
|
+
return { kind: 'primitive', type: 'null' };
|
|
502
|
+
}
|
|
503
|
+
if (leaf === 'Any' || leaf === 'object' || leaf === 'NoReturn' || leaf === 'Never') {
|
|
504
|
+
return { kind: 'any' };
|
|
505
|
+
}
|
|
506
|
+
if (leaf === 'DataFrame' && (!current.module || current.module.startsWith('pandas'))) {
|
|
507
|
+
return { kind: 'marker', marker: 'dataframe' };
|
|
508
|
+
}
|
|
509
|
+
if (leaf === 'Series' && (!current.module || current.module.startsWith('pandas'))) {
|
|
510
|
+
return { kind: 'marker', marker: 'series' };
|
|
511
|
+
}
|
|
512
|
+
if (leaf === 'ndarray' || leaf === 'NDArray' || full.includes('numpy')) {
|
|
513
|
+
return { kind: 'marker', marker: 'ndarray' };
|
|
514
|
+
}
|
|
515
|
+
return definitions.has(leaf) ? { kind: 'ref', name: leaf } : { kind: 'any' };
|
|
516
|
+
}
|
|
517
|
+
case 'typevar':
|
|
518
|
+
case 'paramspec':
|
|
519
|
+
case 'paramspec_args':
|
|
520
|
+
case 'paramspec_kwargs':
|
|
521
|
+
case 'typevartuple':
|
|
522
|
+
case 'unpack':
|
|
523
|
+
case 'callable':
|
|
524
|
+
return { kind: 'any' };
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
return schema(type);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private returnDefinitions(module: PythonModule): ReturnDefinitionNames {
|
|
531
|
+
return new Set(
|
|
532
|
+
module.classes
|
|
533
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
534
|
+
.map(cls => cls.name)
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
private emitReturnDefinitions(module: PythonModule): string {
|
|
539
|
+
const definitions = this.returnDefinitions(module);
|
|
540
|
+
const entries = module.classes
|
|
541
|
+
.filter(cls => cls.kind === 'typed_dict' || cls.decorators.includes('__typed_dict__'))
|
|
542
|
+
.map(cls => {
|
|
543
|
+
const fields = Object.fromEntries(
|
|
544
|
+
cls.properties.map(property => [
|
|
545
|
+
property.name,
|
|
546
|
+
{
|
|
547
|
+
schema: this.returnSchema(property.type, definitions),
|
|
548
|
+
optional: property.optional === true,
|
|
549
|
+
},
|
|
550
|
+
])
|
|
551
|
+
);
|
|
552
|
+
return [cls.name, { kind: 'record', fields }];
|
|
553
|
+
});
|
|
554
|
+
return `const __tywrapReturnDefinitions: Record<string, ReturnSchema> = ${JSON.stringify(Object.fromEntries(entries))};\n\n`;
|
|
555
|
+
}
|
|
556
|
+
|
|
360
557
|
generateFunctionWrapper(
|
|
361
558
|
func: PythonFunction,
|
|
362
559
|
moduleName?: string,
|
|
363
|
-
annotatedJSDoc = false
|
|
560
|
+
annotatedJSDoc = false,
|
|
561
|
+
localDeclaredNames: Set<string> = new Set(),
|
|
562
|
+
returnDefinitions: ReturnDefinitionNames = new Set()
|
|
364
563
|
): GeneratedCode {
|
|
365
564
|
const jsdoc = this.generateJsDoc(
|
|
366
565
|
func.docstring,
|
|
@@ -379,7 +578,8 @@ export class CodeGenerator {
|
|
|
379
578
|
const genericContext = this.buildGenericRenderContext(
|
|
380
579
|
this.getTypeParameters(func.typeParameters),
|
|
381
580
|
[func.returnType, ...filteredParams.map(param => param.type)],
|
|
382
|
-
moduleName
|
|
581
|
+
moduleName,
|
|
582
|
+
localDeclaredNames
|
|
383
583
|
);
|
|
384
584
|
const typeParamDecl = genericContext.declaration;
|
|
385
585
|
|
|
@@ -447,6 +647,8 @@ export class CodeGenerator {
|
|
|
447
647
|
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
448
648
|
const fname = this.escapeIdentifier(func.name);
|
|
449
649
|
const moduleId = moduleName ?? '__main__';
|
|
650
|
+
const validatorName = `__validate${this.escapeIdentifier(func.name, { preserveCase: true })}Result`;
|
|
651
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(func.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${func.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
450
652
|
|
|
451
653
|
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs).
|
|
452
654
|
// Why: Python APIs frequently have many optional tail params. TypeScript callers expect
|
|
@@ -535,10 +737,8 @@ export class CodeGenerator {
|
|
|
535
737
|
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
536
738
|
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
537
739
|
|
|
538
|
-
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
539
|
-
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${
|
|
540
|
-
hasKwArgs ? ', __kwargs' : ''
|
|
541
|
-
});
|
|
740
|
+
const ts = `${jsdoc}${returnValidator}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
741
|
+
${callPrelude}${guards} return getRuntimeBridge().call<${returnType}>('${moduleId}', '${func.name}', __args, ${hasKwArgs ? '__kwargs' : 'undefined'}, ${validatorName});
|
|
542
742
|
}
|
|
543
743
|
`;
|
|
544
744
|
|
|
@@ -553,8 +753,12 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
553
753
|
generateClassWrapper(
|
|
554
754
|
cls: PythonClass,
|
|
555
755
|
moduleName?: string,
|
|
556
|
-
_annotatedJSDoc = false
|
|
756
|
+
_annotatedJSDoc = false,
|
|
757
|
+
localDeclaredNames: Set<string> = new Set(),
|
|
758
|
+
returnDefinitions: ReturnDefinitionNames = new Set()
|
|
557
759
|
): GeneratedCode {
|
|
760
|
+
const moduleDeclaredNames = new Set(localDeclaredNames);
|
|
761
|
+
moduleDeclaredNames.add(cls.name);
|
|
558
762
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
559
763
|
const classGenericContext = this.buildGenericRenderContext(
|
|
560
764
|
this.getTypeParameters(cls.typeParameters),
|
|
@@ -566,14 +770,11 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
566
770
|
...method.parameters.map(p => p.type),
|
|
567
771
|
]),
|
|
568
772
|
],
|
|
569
|
-
moduleName
|
|
773
|
+
moduleName,
|
|
774
|
+
moduleDeclaredNames
|
|
570
775
|
);
|
|
571
776
|
const classTypeParamDecl = classGenericContext.declaration;
|
|
572
777
|
const cname = this.escapeIdentifier(cls.name);
|
|
573
|
-
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
574
|
-
const tsValueType = (p: (typeof cls.methods)[number]['parameters'][number]): string =>
|
|
575
|
-
this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
576
|
-
|
|
577
778
|
const wrapAlias = (body: string): GeneratedCode => {
|
|
578
779
|
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
579
780
|
return this.wrap(ts, ts, [cls.name]);
|
|
@@ -621,7 +822,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
621
822
|
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
622
823
|
this.getTypeParameters(m.typeParameters),
|
|
623
824
|
[m.returnType, ...fparams.map(param => param.type)],
|
|
624
|
-
moduleName
|
|
825
|
+
moduleName,
|
|
826
|
+
moduleDeclaredNames
|
|
625
827
|
);
|
|
626
828
|
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
627
829
|
classGenericContext,
|
|
@@ -659,19 +861,21 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
659
861
|
const methodDeclarations: string[] = [];
|
|
660
862
|
|
|
661
863
|
sortedMethods
|
|
662
|
-
.filter(
|
|
864
|
+
.filter(
|
|
865
|
+
method =>
|
|
866
|
+
method.name !== '__init__' &&
|
|
867
|
+
(method.methodKind === 'class' || method.methodKind === 'static')
|
|
868
|
+
)
|
|
663
869
|
.forEach(method => {
|
|
664
|
-
//
|
|
665
|
-
// the
|
|
666
|
-
|
|
667
|
-
// their output stays byte-identical.
|
|
668
|
-
const isStatic = method.methodKind === 'class' || method.methodKind === 'static';
|
|
669
|
-
const staticPrefix = isStatic ? 'static ' : '';
|
|
870
|
+
// v0.9 wrappers expose only class/static methods. They route through
|
|
871
|
+
// the ordinary module call path and never retain process-local state.
|
|
872
|
+
const staticPrefix = 'static ';
|
|
670
873
|
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
671
874
|
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
672
875
|
this.getTypeParameters(method.typeParameters),
|
|
673
876
|
[method.returnType, ...fparams.map(param => param.type)],
|
|
674
|
-
moduleName
|
|
877
|
+
moduleName,
|
|
878
|
+
moduleDeclaredNames
|
|
675
879
|
);
|
|
676
880
|
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
677
881
|
classGenericContext,
|
|
@@ -737,6 +941,8 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
737
941
|
'return'
|
|
738
942
|
);
|
|
739
943
|
const mname = this.escapeIdentifier(method.name);
|
|
944
|
+
const validatorName = `__validate${this.escapeIdentifier(cls.name, { preserveCase: true })}${this.escapeIdentifier(method.name, { preserveCase: true })}Result`;
|
|
945
|
+
const returnValidator = `const ${validatorName} = createReturnValidator(${JSON.stringify(this.returnSchema(method.returnType, returnDefinitions))}, ${JSON.stringify(`${moduleId}.${cls.name}.${method.name}`)}, __tywrapReturnDefinitions);\n\n`;
|
|
740
946
|
|
|
741
947
|
const overloads: string[] = [];
|
|
742
948
|
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
@@ -784,193 +990,49 @@ ${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.n
|
|
|
784
990
|
const guardLines = emitArgGuards(callDescriptor);
|
|
785
991
|
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
786
992
|
|
|
787
|
-
const callExpr =
|
|
788
|
-
? `getRuntimeBridge().call('${moduleId}', '${cls.name}.${method.name}', __args${
|
|
789
|
-
needsKwargsParam ? ', __kwargs' : ''
|
|
790
|
-
})`
|
|
791
|
-
: `getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${
|
|
792
|
-
needsKwargsParam ? ', __kwargs' : ''
|
|
793
|
-
})`;
|
|
993
|
+
const callExpr = `getRuntimeBridge().call<${returnType}>('${moduleId}', '${cls.name}.${method.name}', __args, ${needsKwargsParam ? '__kwargs' : 'undefined'}, ${validatorName})`;
|
|
794
994
|
methodBodies.push(`${overloadDecl} ${staticPrefix}async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
795
|
-
${callPrelude}${guards} return ${callExpr};
|
|
995
|
+
${returnValidator}${callPrelude}${guards} return ${callExpr};
|
|
796
996
|
}`);
|
|
797
997
|
methodDeclarations.push(
|
|
798
998
|
`${overloadDecl}${overloads.length > 0 ? '' : ` ${staticPrefix}${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`
|
|
799
999
|
);
|
|
800
1000
|
});
|
|
801
1001
|
|
|
802
|
-
const init = cls.methods.find(m => m.name === '__init__');
|
|
803
|
-
const ctorSpec = (() => {
|
|
804
|
-
if (!init) {
|
|
805
|
-
return {
|
|
806
|
-
overloadDecl: '',
|
|
807
|
-
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
808
|
-
paramsDecl: `...args: unknown[]`,
|
|
809
|
-
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
810
|
-
hasKwargs: false,
|
|
811
|
-
guardLines: [] as string[],
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
816
|
-
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
817
|
-
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
818
|
-
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
819
|
-
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
820
|
-
const varArgsParam = fparams.find(p => p.varArgs);
|
|
821
|
-
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
822
|
-
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
823
|
-
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
824
|
-
const requiredPosCount =
|
|
825
|
-
firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
826
|
-
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
827
|
-
|
|
828
|
-
const renderPositionalParam = (
|
|
829
|
-
p: (typeof positionalParams)[number],
|
|
830
|
-
forceRequired = false
|
|
831
|
-
): string => {
|
|
832
|
-
const pname = this.escapeIdentifier(p.name);
|
|
833
|
-
const opt = !forceRequired && p.optional ? '?' : '';
|
|
834
|
-
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
const kwargsType = (() => {
|
|
838
|
-
if (!needsKwargsParam) {
|
|
839
|
-
return '';
|
|
840
|
-
}
|
|
841
|
-
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
842
|
-
return 'Record<string, unknown>';
|
|
843
|
-
}
|
|
844
|
-
const props = keywordOnlyParams
|
|
845
|
-
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
846
|
-
.join(' ');
|
|
847
|
-
const obj = `{ ${props} }`;
|
|
848
|
-
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
849
|
-
})();
|
|
850
|
-
|
|
851
|
-
const paramsDeclParts: string[] = [];
|
|
852
|
-
positionalParams.forEach(p => {
|
|
853
|
-
paramsDeclParts.push(renderPositionalParam(p));
|
|
854
|
-
});
|
|
855
|
-
if (varArgsParam) {
|
|
856
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
857
|
-
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
858
|
-
}
|
|
859
|
-
if (needsKwargsParam) {
|
|
860
|
-
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
861
|
-
}
|
|
862
|
-
const paramsDecl = paramsDeclParts.join(', ');
|
|
863
|
-
|
|
864
|
-
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
865
|
-
const overloads: string[] = [];
|
|
866
|
-
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
867
|
-
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
868
|
-
const requiredPosCount =
|
|
869
|
-
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
870
|
-
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
871
|
-
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
872
|
-
const rest: string[] = [];
|
|
873
|
-
if (varArgsParam) {
|
|
874
|
-
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
875
|
-
rest.push(
|
|
876
|
-
needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`
|
|
877
|
-
);
|
|
878
|
-
}
|
|
879
|
-
rest.push(`kwargs: ${kwargsType}`);
|
|
880
|
-
overloads.push(
|
|
881
|
-
` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`
|
|
882
|
-
);
|
|
883
|
-
if (varArgsParam && needsVarArgsArray) {
|
|
884
|
-
overloads.push(
|
|
885
|
-
` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
891
|
-
const declaration =
|
|
892
|
-
overloads.length > 0
|
|
893
|
-
? overloadDecl
|
|
894
|
-
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
895
|
-
|
|
896
|
-
const callDescriptor: CallDescriptor = {
|
|
897
|
-
positionalParams,
|
|
898
|
-
varArgsParam,
|
|
899
|
-
needsVarArgsArray,
|
|
900
|
-
hasKwArgs: needsKwargsParam,
|
|
901
|
-
hasVarKwArgs,
|
|
902
|
-
keywordOnlyNames,
|
|
903
|
-
requiredKwOnlyNames,
|
|
904
|
-
positionalOnlyNames,
|
|
905
|
-
requiredPosCount,
|
|
906
|
-
indent: ' ',
|
|
907
|
-
errorLabel: '__init__',
|
|
908
|
-
};
|
|
909
|
-
const callPreludeLines = emitCallPrelude(callDescriptor, this.callEmitHelpers());
|
|
910
|
-
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
911
|
-
const guardLines = emitArgGuards(callDescriptor);
|
|
912
|
-
|
|
913
|
-
return {
|
|
914
|
-
overloadDecl,
|
|
915
|
-
declaration,
|
|
916
|
-
paramsDecl,
|
|
917
|
-
callPrelude,
|
|
918
|
-
hasKwargs: needsKwargsParam,
|
|
919
|
-
guardLines,
|
|
920
|
-
};
|
|
921
|
-
})();
|
|
922
|
-
|
|
923
|
-
// @property / functools.cached_property → TS getters returning Promise<T>.
|
|
924
|
-
// Reading the attribute fires the getter on the Python side; the bridge
|
|
925
|
-
// resolves it via callMethod with no args. Sorted for stable output.
|
|
926
|
-
const accessorBodies: string[] = [];
|
|
927
|
-
const accessorDeclarations: string[] = [];
|
|
928
|
-
const sortedAccessors = [...(cls.accessors ?? [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
929
|
-
sortedAccessors.forEach(accessor => {
|
|
930
|
-
const aname = this.escapeIdentifier(accessor.name);
|
|
931
|
-
const accessorType = this.typeToTsFromPython(accessor.type, classGenericContext, 'return');
|
|
932
|
-
const jsdocAcc = this.generateJsDoc(accessor.docstring);
|
|
933
|
-
accessorBodies.push(
|
|
934
|
-
`${jsdocAcc} get ${aname}(): Promise<${accessorType}> { return getRuntimeBridge().callMethod(this.__handle, '${accessor.name}', []); }`
|
|
935
|
-
);
|
|
936
|
-
accessorDeclarations.push(`${jsdocAcc} get ${aname}(): Promise<${accessorType}>;\n`);
|
|
937
|
-
});
|
|
938
|
-
const accessorsImpl = accessorBodies.length > 0 ? `${accessorBodies.join('\n')}\n` : '';
|
|
939
|
-
const accessorsDecl = accessorDeclarations.length > 0 ? `${accessorDeclarations.join('')}` : '';
|
|
940
|
-
|
|
941
1002
|
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
942
1003
|
const declarationMethodsSection =
|
|
943
1004
|
methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
944
|
-
|
|
945
|
-
|
|
1005
|
+
// The constructor counts: a class whose only member was __init__ loses
|
|
1006
|
+
// create(), so it needs the migration note as much as one with methods.
|
|
1007
|
+
const omittedInstanceMembers =
|
|
1008
|
+
cls.methods.some(method => method.methodKind !== 'class' && method.methodKind !== 'static') ||
|
|
1009
|
+
(cls.accessors?.length ?? 0) > 0;
|
|
1010
|
+
const migrationNote = omittedInstanceMembers
|
|
1011
|
+
? ' // NOTE: Instance members are not generated in v0.9; migrate this API to value-returning module functions.\n'
|
|
1012
|
+
: '';
|
|
946
1013
|
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
947
|
-
|
|
948
|
-
private constructor(handle: string) { this.__handle = handle; }
|
|
949
|
-
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
950
|
-
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${
|
|
951
|
-
ctorSpec.hasKwargs ? ', __kwargs' : ''
|
|
952
|
-
});
|
|
953
|
-
return ${newClassExpr};
|
|
954
|
-
}
|
|
955
|
-
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection}${accessorsImpl} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
1014
|
+
${migrationNote}${methodsSection}
|
|
956
1015
|
}
|
|
957
1016
|
`;
|
|
958
1017
|
|
|
959
1018
|
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
960
|
-
|
|
961
|
-
private constructor(handle: string);
|
|
962
|
-
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection}${accessorsDecl} disposeHandle(): Promise<void>;
|
|
1019
|
+
${migrationNote}${declarationMethodsSection}
|
|
963
1020
|
}
|
|
964
1021
|
`;
|
|
965
1022
|
|
|
966
1023
|
return this.wrap(ts, declaration, [cls.name]);
|
|
967
1024
|
}
|
|
968
1025
|
|
|
969
|
-
generateTypeAlias(
|
|
1026
|
+
generateTypeAlias(
|
|
1027
|
+
alias: PythonTypeAlias,
|
|
1028
|
+
moduleName?: string,
|
|
1029
|
+
localDeclaredNames: Set<string> = new Set()
|
|
1030
|
+
): GeneratedCode {
|
|
970
1031
|
const genericContext = this.buildGenericRenderContext(
|
|
971
1032
|
this.getTypeParameters(alias.typeParameters),
|
|
972
1033
|
[alias.type],
|
|
973
|
-
moduleName
|
|
1034
|
+
moduleName,
|
|
1035
|
+
localDeclaredNames
|
|
974
1036
|
);
|
|
975
1037
|
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
976
1038
|
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
@@ -1004,15 +1066,35 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1004
1066
|
}
|
|
1005
1067
|
|
|
1006
1068
|
generateModuleDefinition(module: PythonModule, annotatedJSDoc = false): GeneratedCode {
|
|
1069
|
+
const localDeclaredNames = new Set([
|
|
1070
|
+
...module.classes.map(cls => cls.name),
|
|
1071
|
+
...(module.typeAliases ?? []).map(alias => alias.name),
|
|
1072
|
+
]);
|
|
1007
1073
|
const functionResults = [...module.functions]
|
|
1008
1074
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1009
|
-
.map(f =>
|
|
1075
|
+
.map(f =>
|
|
1076
|
+
this.generateFunctionWrapper(
|
|
1077
|
+
f,
|
|
1078
|
+
module.name,
|
|
1079
|
+
annotatedJSDoc,
|
|
1080
|
+
localDeclaredNames,
|
|
1081
|
+
this.returnDefinitions(module)
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1010
1084
|
const classResults = [...module.classes]
|
|
1011
1085
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1012
|
-
.map(c =>
|
|
1086
|
+
.map(c =>
|
|
1087
|
+
this.generateClassWrapper(
|
|
1088
|
+
c,
|
|
1089
|
+
module.name,
|
|
1090
|
+
annotatedJSDoc,
|
|
1091
|
+
localDeclaredNames,
|
|
1092
|
+
this.returnDefinitions(module)
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1013
1095
|
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
1014
1096
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1015
|
-
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
1097
|
+
.map(alias => this.generateTypeAlias(alias, module.name, localDeclaredNames));
|
|
1016
1098
|
|
|
1017
1099
|
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
1018
1100
|
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
@@ -1025,7 +1107,9 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1025
1107
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
1026
1108
|
});
|
|
1027
1109
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
1028
|
-
const bridgeDecl = needsRuntime
|
|
1110
|
+
const bridgeDecl = needsRuntime
|
|
1111
|
+
? `import { createReturnValidator, getRuntimeBridge, type ReturnSchema } from 'tywrap/runtime';\n\n${this.emitReturnDefinitions(module)}`
|
|
1112
|
+
: '';
|
|
1029
1113
|
|
|
1030
1114
|
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
1031
1115
|
const declaration = `${`${declarationHeader}${functionResults
|
|
@@ -1067,23 +1151,27 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1067
1151
|
};
|
|
1068
1152
|
}
|
|
1069
1153
|
|
|
1070
|
-
private typeToTs(
|
|
1154
|
+
private typeToTs(
|
|
1155
|
+
type: TypescriptType,
|
|
1156
|
+
ctx?: GenericRenderContext,
|
|
1157
|
+
mappingContext: 'value' | 'return' = 'value'
|
|
1158
|
+
): string {
|
|
1071
1159
|
switch (type.kind) {
|
|
1072
1160
|
case 'primitive':
|
|
1073
1161
|
return type.name;
|
|
1074
1162
|
case 'array':
|
|
1075
|
-
return `${this.typeToTs(type.elementType)}[]`;
|
|
1163
|
+
return `${this.typeToTs(type.elementType, ctx, mappingContext)}[]`;
|
|
1076
1164
|
case 'tuple': {
|
|
1077
1165
|
const t = type as { kind: 'tuple'; elementTypes: TypescriptType[] };
|
|
1078
|
-
const parts = t.elementTypes.map(e => this.typeToTs(e)).join(', ');
|
|
1166
|
+
const parts = t.elementTypes.map(e => this.typeToTs(e, ctx, mappingContext)).join(', ');
|
|
1079
1167
|
return `[${parts}]`;
|
|
1080
1168
|
}
|
|
1081
1169
|
case 'object': {
|
|
1082
1170
|
const t = type;
|
|
1083
1171
|
// If it's a simple Record<string, T> pattern, use that syntax
|
|
1084
1172
|
if (t.properties.length === 0 && t.indexSignature) {
|
|
1085
|
-
const keyType = this.typeToTs(t.indexSignature.keyType);
|
|
1086
|
-
const valueType = this.typeToTs(t.indexSignature.valueType);
|
|
1173
|
+
const keyType = this.typeToTs(t.indexSignature.keyType, ctx, mappingContext);
|
|
1174
|
+
const valueType = this.typeToTs(t.indexSignature.valueType, ctx, mappingContext);
|
|
1087
1175
|
if (keyType === 'string') {
|
|
1088
1176
|
return `Record<string, ${valueType}>`;
|
|
1089
1177
|
}
|
|
@@ -1092,34 +1180,56 @@ ${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string):
|
|
|
1092
1180
|
const props = t.properties
|
|
1093
1181
|
.map(
|
|
1094
1182
|
p =>
|
|
1095
|
-
`${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type)};`
|
|
1183
|
+
`${p.readonly ? 'readonly ' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)};`
|
|
1096
1184
|
)
|
|
1097
1185
|
.join(' ');
|
|
1098
1186
|
const indexSig = t.indexSignature
|
|
1099
|
-
? `[key: ${this.typeToTs(t.indexSignature.keyType)}]: ${this.typeToTs(t.indexSignature.valueType)};`
|
|
1187
|
+
? `[key: ${this.typeToTs(t.indexSignature.keyType, ctx, mappingContext)}]: ${this.typeToTs(t.indexSignature.valueType, ctx, mappingContext)};`
|
|
1100
1188
|
: '';
|
|
1101
1189
|
return `{ ${props} ${indexSig} }`;
|
|
1102
1190
|
}
|
|
1103
1191
|
case 'union':
|
|
1104
|
-
return type.types.map(t => this.typeToTs(t)).join(' | ');
|
|
1192
|
+
return type.types.map(t => this.typeToTs(t, ctx, mappingContext)).join(' | ');
|
|
1105
1193
|
case 'function': {
|
|
1106
1194
|
const ft = type;
|
|
1107
1195
|
const params = ft.parameters
|
|
1108
1196
|
.map(
|
|
1109
|
-
p =>
|
|
1197
|
+
p =>
|
|
1198
|
+
`${p.rest ? '...' : ''}${p.name}${p.optional ? '?' : ''}: ${this.typeToTs(p.type, ctx, mappingContext)}`
|
|
1110
1199
|
)
|
|
1111
1200
|
.join(', ');
|
|
1112
|
-
return `(${params}) => ${this.typeToTs(ft.returnType)}`;
|
|
1201
|
+
return `(${params}) => ${this.typeToTs(ft.returnType, ctx, mappingContext)}`;
|
|
1113
1202
|
}
|
|
1114
1203
|
case 'generic': {
|
|
1115
|
-
const g = type as {
|
|
1116
|
-
|
|
1204
|
+
const g = type as {
|
|
1205
|
+
kind: 'generic';
|
|
1206
|
+
name: string;
|
|
1207
|
+
module?: string;
|
|
1208
|
+
typeArgs: TypescriptType[];
|
|
1209
|
+
};
|
|
1210
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(g.name)) {
|
|
1211
|
+
return this.degradeType(g);
|
|
1212
|
+
}
|
|
1213
|
+
if (mappingContext === 'return' && this.nonDecodableReturnGenerics.has(g.name)) {
|
|
1214
|
+
return this.degradeType(g);
|
|
1215
|
+
}
|
|
1216
|
+
if (ctx && !this.builtinGenericNames.has(g.name) && !this.isLocalTypeIdentity(g, ctx)) {
|
|
1217
|
+
return this.degradeType(g);
|
|
1218
|
+
}
|
|
1219
|
+
const args = g.typeArgs.map(a => this.typeToTs(a, ctx, mappingContext)).join(', ');
|
|
1117
1220
|
return `${g.name}<${args}>`;
|
|
1118
1221
|
}
|
|
1119
1222
|
case 'custom': {
|
|
1120
|
-
const c = type as { kind: 'custom'; name: string };
|
|
1223
|
+
const c = type as { kind: 'custom'; name: string; module?: string };
|
|
1121
1224
|
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
1122
|
-
return
|
|
1225
|
+
return this.degradeType(c);
|
|
1226
|
+
}
|
|
1227
|
+
if (
|
|
1228
|
+
ctx &&
|
|
1229
|
+
!this.isLocalTypeIdentity(c, ctx) &&
|
|
1230
|
+
!(c.module === 'typing' && ctx.emittedNames.has(c.name))
|
|
1231
|
+
) {
|
|
1232
|
+
return this.degradeType(c);
|
|
1123
1233
|
}
|
|
1124
1234
|
return c.name;
|
|
1125
1235
|
}
|