tywrap 0.2.1 → 0.4.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 +100 -32
- package/dist/cli.js +70 -5
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts +11 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +62 -20
- package/dist/config/index.js.map +1 -1
- package/dist/core/analyzer.d.ts +1 -0
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +46 -12
- package/dist/core/analyzer.js.map +1 -1
- package/dist/core/annotation-parser.d.ts +8 -0
- package/dist/core/annotation-parser.d.ts.map +1 -0
- package/dist/core/annotation-parser.js +409 -0
- package/dist/core/annotation-parser.js.map +1 -0
- package/dist/core/discovery.d.ts +1 -0
- package/dist/core/discovery.d.ts.map +1 -1
- package/dist/core/discovery.js +9 -9
- package/dist/core/discovery.js.map +1 -1
- package/dist/core/generator.d.ts +9 -1
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +738 -104
- package/dist/core/generator.js.map +1 -1
- package/dist/core/mapper.d.ts +9 -7
- package/dist/core/mapper.d.ts.map +1 -1
- package/dist/core/mapper.js +129 -44
- package/dist/core/mapper.js.map +1 -1
- package/dist/dev.d.ts +57 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +743 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/runtime/bridge-core.d.ts +1 -0
- package/dist/runtime/bridge-core.d.ts.map +1 -1
- package/dist/runtime/bridge-core.js +52 -17
- package/dist/runtime/bridge-core.js.map +1 -1
- package/dist/runtime/bridge-protocol.d.ts +17 -0
- package/dist/runtime/bridge-protocol.d.ts.map +1 -1
- package/dist/runtime/bridge-protocol.js +97 -0
- package/dist/runtime/bridge-protocol.js.map +1 -1
- package/dist/runtime/errors.d.ts +10 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +9 -0
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/http-io.d.ts.map +1 -1
- package/dist/runtime/http-io.js.map +1 -1
- package/dist/runtime/http.d.ts +1 -0
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +6 -0
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +206 -32
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +6 -5
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +6 -5
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +4 -0
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +2 -0
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/process-io.d.ts +5 -0
- package/dist/runtime/process-io.d.ts.map +1 -1
- package/dist/runtime/process-io.js +64 -30
- package/dist/runtime/process-io.js.map +1 -1
- package/dist/runtime/pyodide-io.d.ts +4 -5
- package/dist/runtime/pyodide-io.d.ts.map +1 -1
- package/dist/runtime/pyodide-io.js +99 -37
- package/dist/runtime/pyodide-io.js.map +1 -1
- package/dist/runtime/safe-codec.d.ts +25 -3
- package/dist/runtime/safe-codec.d.ts.map +1 -1
- package/dist/runtime/safe-codec.js +231 -57
- package/dist/runtime/safe-codec.js.map +1 -1
- package/dist/runtime/transport.d.ts +6 -1
- package/dist/runtime/transport.d.ts.map +1 -1
- package/dist/runtime/transport.js.map +1 -1
- package/dist/runtime/validators.d.ts +0 -11
- package/dist/runtime/validators.d.ts.map +1 -1
- package/dist/runtime/validators.js +13 -3
- package/dist/runtime/validators.js.map +1 -1
- package/dist/runtime/worker-pool.d.ts +17 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +97 -22
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +51 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +6 -0
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +237 -331
- package/dist/tywrap.js.map +1 -1
- package/dist/utils/cache.d.ts.map +1 -1
- package/dist/utils/cache.js +53 -5
- package/dist/utils/cache.js.map +1 -1
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +10 -16
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/ir-cache.d.ts +28 -0
- package/dist/utils/ir-cache.d.ts.map +1 -0
- package/dist/utils/ir-cache.js +29 -0
- package/dist/utils/ir-cache.js.map +1 -0
- package/dist/utils/parallel-processor.d.ts +1 -1
- package/dist/utils/parallel-processor.d.ts.map +1 -1
- package/dist/utils/parallel-processor.js +111 -40
- package/dist/utils/parallel-processor.js.map +1 -1
- package/dist/utils/runtime.d.ts +5 -1
- package/dist/utils/runtime.d.ts.map +1 -1
- package/dist/utils/runtime.js +112 -18
- package/dist/utils/runtime.js.map +1 -1
- package/package.json +47 -21
- package/runtime/python_bridge.py +78 -12
- package/src/cli.ts +85 -5
- package/src/config/index.ts +91 -32
- package/src/core/analyzer.ts +51 -15
- package/src/core/annotation-parser.ts +500 -0
- package/src/core/discovery.ts +26 -12
- package/src/core/generator.ts +956 -116
- package/src/core/mapper.ts +158 -51
- package/src/dev.ts +983 -0
- package/src/index.ts +35 -6
- package/src/runtime/bridge-core.ts +58 -18
- package/src/runtime/bridge-protocol.ts +164 -0
- package/src/runtime/errors.ts +14 -0
- package/src/runtime/http-io.ts +2 -1
- package/src/runtime/http.ts +7 -0
- package/src/runtime/node.ts +312 -46
- package/src/runtime/optimized-node.ts +9 -5
- package/src/runtime/pooled-transport.ts +12 -1
- package/src/runtime/process-io.ts +83 -55
- package/src/runtime/pyodide-io.ts +110 -44
- package/src/runtime/safe-codec.ts +290 -82
- package/src/runtime/transport.ts +6 -1
- package/src/runtime/validators.ts +14 -3
- package/src/runtime/worker-pool.ts +120 -24
- package/src/types/global.d.ts +11 -1
- package/src/types/index.ts +63 -8
- package/src/tywrap.ts +296 -361
- package/src/utils/cache.ts +59 -5
- package/src/utils/codec.ts +11 -15
- package/src/utils/ir-cache.ts +51 -0
- package/src/utils/parallel-processor.ts +119 -43
- package/src/utils/runtime.ts +129 -18
package/src/core/generator.ts
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type {
|
|
6
|
+
PythonGenericParameter,
|
|
6
7
|
PythonFunction,
|
|
7
8
|
PythonClass,
|
|
8
9
|
PythonModule,
|
|
10
|
+
PythonType,
|
|
11
|
+
PythonTypeAlias,
|
|
9
12
|
GeneratedCode,
|
|
10
13
|
TypescriptType,
|
|
11
14
|
} from '../types/index.js';
|
|
@@ -13,6 +16,19 @@ import { globalCache } from '../utils/cache.js';
|
|
|
13
16
|
|
|
14
17
|
import { TypeMapper } from './mapper.js';
|
|
15
18
|
|
|
19
|
+
interface GenericRenderParam {
|
|
20
|
+
name: string;
|
|
21
|
+
declaration: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface GenericRenderContext {
|
|
25
|
+
currentModule?: string;
|
|
26
|
+
declaration: string;
|
|
27
|
+
typeArguments: string;
|
|
28
|
+
emittedNames: Set<string>;
|
|
29
|
+
emittedParamSpecs: Set<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
export class CodeGenerator {
|
|
17
33
|
private readonly mapper: TypeMapper;
|
|
18
34
|
private readonly reservedTsIdentifiers = new Set([
|
|
@@ -109,6 +125,217 @@ export class CodeGenerator {
|
|
|
109
125
|
});
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
private getTypeParameters(
|
|
129
|
+
typeParameters?: readonly PythonGenericParameter[]
|
|
130
|
+
): readonly PythonGenericParameter[] {
|
|
131
|
+
return typeParameters ?? [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private buildGenericRenderContext(
|
|
135
|
+
typeParameters: readonly PythonGenericParameter[],
|
|
136
|
+
types: readonly PythonType[],
|
|
137
|
+
currentModule?: string
|
|
138
|
+
): GenericRenderContext {
|
|
139
|
+
const callableParamSpecs = new Set<string>();
|
|
140
|
+
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
141
|
+
|
|
142
|
+
const emitted: GenericRenderParam[] = [];
|
|
143
|
+
const emittedNames = new Set<string>();
|
|
144
|
+
const emittedParamSpecs = new Set<string>();
|
|
145
|
+
|
|
146
|
+
typeParameters.forEach(param => {
|
|
147
|
+
if (
|
|
148
|
+
param.kind === 'typevar' &&
|
|
149
|
+
!param.bound &&
|
|
150
|
+
!(param.constraints && param.constraints.length > 0) &&
|
|
151
|
+
(!param.variance || param.variance === 'invariant')
|
|
152
|
+
) {
|
|
153
|
+
emitted.push({ name: param.name, declaration: param.name });
|
|
154
|
+
emittedNames.add(param.name);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (param.kind === 'paramspec' && callableParamSpecs.has(param.name)) {
|
|
159
|
+
emitted.push({ name: param.name, declaration: `${param.name} extends unknown[]` });
|
|
160
|
+
emittedNames.add(param.name);
|
|
161
|
+
emittedParamSpecs.add(param.name);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
currentModule,
|
|
167
|
+
declaration:
|
|
168
|
+
emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
169
|
+
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
170
|
+
emittedNames,
|
|
171
|
+
emittedParamSpecs,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private mergeGenericRenderContexts(
|
|
176
|
+
outer: GenericRenderContext,
|
|
177
|
+
inner: GenericRenderContext
|
|
178
|
+
): GenericRenderContext {
|
|
179
|
+
return {
|
|
180
|
+
currentModule: inner.currentModule ?? outer.currentModule,
|
|
181
|
+
declaration: inner.declaration,
|
|
182
|
+
typeArguments: inner.typeArguments,
|
|
183
|
+
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
184
|
+
emittedParamSpecs: new Set([...outer.emittedParamSpecs, ...inner.emittedParamSpecs]),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private collectCallableParamSpecs(type: PythonType, out: Set<string>): void {
|
|
189
|
+
switch (type.kind) {
|
|
190
|
+
case 'collection':
|
|
191
|
+
type.itemTypes.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
192
|
+
break;
|
|
193
|
+
case 'paramspec':
|
|
194
|
+
out.add(type.name);
|
|
195
|
+
break;
|
|
196
|
+
case 'union':
|
|
197
|
+
type.types.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
198
|
+
break;
|
|
199
|
+
case 'optional':
|
|
200
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
201
|
+
break;
|
|
202
|
+
case 'generic':
|
|
203
|
+
type.typeArgs.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
204
|
+
break;
|
|
205
|
+
case 'callable':
|
|
206
|
+
if (type.parameterSpec?.kind === 'paramspec') {
|
|
207
|
+
out.add(type.parameterSpec.name);
|
|
208
|
+
}
|
|
209
|
+
type.parameters.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
210
|
+
this.collectCallableParamSpecs(type.returnType, out);
|
|
211
|
+
break;
|
|
212
|
+
case 'annotated':
|
|
213
|
+
this.collectCallableParamSpecs(type.base, out);
|
|
214
|
+
break;
|
|
215
|
+
case 'final':
|
|
216
|
+
case 'classvar':
|
|
217
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
218
|
+
break;
|
|
219
|
+
case 'unpack':
|
|
220
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
221
|
+
break;
|
|
222
|
+
default:
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private sanitizeType(type: PythonType, ctx: GenericRenderContext): PythonType {
|
|
228
|
+
const unknownType = (): PythonType => ({ kind: 'custom', name: 'Any', module: 'typing' });
|
|
229
|
+
|
|
230
|
+
switch (type.kind) {
|
|
231
|
+
case 'primitive':
|
|
232
|
+
case 'literal':
|
|
233
|
+
return type;
|
|
234
|
+
case 'custom':
|
|
235
|
+
return type;
|
|
236
|
+
case 'collection':
|
|
237
|
+
return {
|
|
238
|
+
...type,
|
|
239
|
+
itemTypes: type.itemTypes.map(item => this.sanitizeType(item, ctx)),
|
|
240
|
+
};
|
|
241
|
+
case 'union':
|
|
242
|
+
return {
|
|
243
|
+
...type,
|
|
244
|
+
types: type.types.map(item => this.sanitizeType(item, ctx)),
|
|
245
|
+
};
|
|
246
|
+
case 'optional':
|
|
247
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
248
|
+
case 'generic':
|
|
249
|
+
return {
|
|
250
|
+
...type,
|
|
251
|
+
module: type.module === ctx.currentModule ? undefined : type.module,
|
|
252
|
+
typeArgs: type.typeArgs.map(item => this.sanitizeType(item, ctx)),
|
|
253
|
+
};
|
|
254
|
+
case 'callable':
|
|
255
|
+
if (type.parameterSpec && !ctx.emittedParamSpecs.has(type.parameterSpec.name)) {
|
|
256
|
+
return {
|
|
257
|
+
...type,
|
|
258
|
+
parameters: [{ kind: 'custom', name: '...' }],
|
|
259
|
+
parameterSpec: undefined,
|
|
260
|
+
returnType: this.sanitizeType(type.returnType, ctx),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
...type,
|
|
265
|
+
parameters: type.parameters.map(item => this.sanitizeType(item, ctx)),
|
|
266
|
+
parameterSpec:
|
|
267
|
+
type.parameterSpec && ctx.emittedParamSpecs.has(type.parameterSpec.name)
|
|
268
|
+
? type.parameterSpec
|
|
269
|
+
: undefined,
|
|
270
|
+
returnType: this.sanitizeType(type.returnType, ctx),
|
|
271
|
+
};
|
|
272
|
+
case 'annotated':
|
|
273
|
+
return { ...type, base: this.sanitizeType(type.base, ctx) };
|
|
274
|
+
case 'typevar':
|
|
275
|
+
case 'paramspec':
|
|
276
|
+
return ctx.emittedNames.has(type.name) ? type : unknownType();
|
|
277
|
+
case 'paramspec_args':
|
|
278
|
+
return {
|
|
279
|
+
kind: 'collection',
|
|
280
|
+
name: 'list',
|
|
281
|
+
itemTypes: [unknownType()],
|
|
282
|
+
};
|
|
283
|
+
case 'paramspec_kwargs':
|
|
284
|
+
return {
|
|
285
|
+
kind: 'collection',
|
|
286
|
+
name: 'dict',
|
|
287
|
+
itemTypes: [{ kind: 'primitive', name: 'str' }, unknownType()],
|
|
288
|
+
};
|
|
289
|
+
case 'typevartuple':
|
|
290
|
+
return unknownType();
|
|
291
|
+
case 'unpack':
|
|
292
|
+
return unknownType();
|
|
293
|
+
case 'final':
|
|
294
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
295
|
+
case 'classvar':
|
|
296
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private typeToTsFromPython(
|
|
301
|
+
type: PythonType,
|
|
302
|
+
ctx: GenericRenderContext,
|
|
303
|
+
mappingContext: 'value' | 'return'
|
|
304
|
+
): string {
|
|
305
|
+
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private renderLooksLikeKwargsExpr(
|
|
309
|
+
valueExpr: string,
|
|
310
|
+
options: {
|
|
311
|
+
keywordOnlyNames: string[];
|
|
312
|
+
requiredKwOnlyNames: string[];
|
|
313
|
+
hasVarKwArgs: boolean;
|
|
314
|
+
}
|
|
315
|
+
): string {
|
|
316
|
+
const base = `typeof ${valueExpr} === 'object' && ${valueExpr} !== null && !globalThis.Array.isArray(${valueExpr}) && (Object.getPrototypeOf(${valueExpr}) === Object.prototype || Object.getPrototypeOf(${valueExpr}) === null)`;
|
|
317
|
+
|
|
318
|
+
const keyCheck = (() => {
|
|
319
|
+
if (options.requiredKwOnlyNames.length > 0) {
|
|
320
|
+
return options.requiredKwOnlyNames
|
|
321
|
+
.map(k => `Object.prototype.hasOwnProperty.call(${valueExpr}, ${JSON.stringify(k)})`)
|
|
322
|
+
.join(' && ');
|
|
323
|
+
}
|
|
324
|
+
if (options.hasVarKwArgs) {
|
|
325
|
+
// With **kwargs, any plain object could be kwargs.
|
|
326
|
+
return 'true';
|
|
327
|
+
}
|
|
328
|
+
if (options.keywordOnlyNames.length > 0) {
|
|
329
|
+
return options.keywordOnlyNames
|
|
330
|
+
.map(k => `Object.prototype.hasOwnProperty.call(${valueExpr}, ${JSON.stringify(k)})`)
|
|
331
|
+
.join(' || ');
|
|
332
|
+
}
|
|
333
|
+
return 'false';
|
|
334
|
+
})();
|
|
335
|
+
|
|
336
|
+
return `(${base} && (${keyCheck}))`;
|
|
337
|
+
}
|
|
338
|
+
|
|
112
339
|
generateFunctionWrapper(
|
|
113
340
|
func: PythonFunction,
|
|
114
341
|
moduleName?: string,
|
|
@@ -119,25 +346,84 @@ export class CodeGenerator {
|
|
|
119
346
|
annotatedJSDoc ? func.parameters.map(p => String(p.type)) : undefined
|
|
120
347
|
);
|
|
121
348
|
const filteredParams = func.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
122
|
-
const
|
|
349
|
+
const keywordOnlyParams = filteredParams.filter(p => p.keywordOnly);
|
|
350
|
+
const positionalOnlyNames = filteredParams.filter(p => p.positionalOnly).map(p => p.name);
|
|
351
|
+
const hasVarKwArgs = filteredParams.some(p => p.kwArgs);
|
|
352
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
353
|
+
|
|
354
|
+
const varArgsParam = filteredParams.find(p => p.varArgs);
|
|
355
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
356
|
+
|
|
357
|
+
const positionalParams = filteredParams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
358
|
+
const genericContext = this.buildGenericRenderContext(
|
|
359
|
+
this.getTypeParameters(func.typeParameters),
|
|
360
|
+
[func.returnType, ...filteredParams.map(param => param.type)],
|
|
361
|
+
moduleName
|
|
362
|
+
);
|
|
363
|
+
const typeParamDecl = genericContext.declaration;
|
|
364
|
+
|
|
365
|
+
const tsTypeForValue = (p: (typeof filteredParams)[number]): string =>
|
|
366
|
+
this.typeToTsFromPython(p.type, genericContext, 'value');
|
|
367
|
+
|
|
368
|
+
const kwargsType = (() => {
|
|
369
|
+
if (!needsKwargsParam) {
|
|
370
|
+
return '';
|
|
371
|
+
}
|
|
372
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
373
|
+
return 'Record<string, unknown>';
|
|
374
|
+
}
|
|
375
|
+
const props = keywordOnlyParams
|
|
376
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsTypeForValue(p)};`)
|
|
377
|
+
.join(' ');
|
|
378
|
+
const obj = `{ ${props} }`;
|
|
379
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
380
|
+
})();
|
|
381
|
+
|
|
382
|
+
const renderPositionalParam = (
|
|
383
|
+
p: (typeof positionalParams)[number],
|
|
384
|
+
forceRequired = false
|
|
385
|
+
): string => {
|
|
123
386
|
const pname = this.escapeIdentifier(p.name);
|
|
124
|
-
|
|
387
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
388
|
+
return `${pname}${opt}: ${tsTypeForValue(p)}`;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const renderVarArgsParam = (forceRequired = false): string | null => {
|
|
392
|
+
if (!varArgsParam) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
const pname = this.escapeIdentifier(varArgsParam.name);
|
|
396
|
+
if (!needsVarArgsArray) {
|
|
125
397
|
return `...${pname}: unknown[]`;
|
|
126
398
|
}
|
|
127
|
-
|
|
128
|
-
|
|
399
|
+
const opt = forceRequired ? '' : '?';
|
|
400
|
+
return `${pname}${opt}: unknown[]`;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const renderKwargsParam = (forceRequired = false): string | null => {
|
|
404
|
+
if (!needsKwargsParam) {
|
|
405
|
+
return null;
|
|
129
406
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)}`;
|
|
407
|
+
const opt = forceRequired ? '' : '?';
|
|
408
|
+
return `kwargs${opt}: ${kwargsType}`;
|
|
133
409
|
};
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
.
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
410
|
+
|
|
411
|
+
const implParams: string[] = [];
|
|
412
|
+
for (const p of positionalParams) {
|
|
413
|
+
implParams.push(renderPositionalParam(p));
|
|
414
|
+
}
|
|
415
|
+
const varArgsDecl = renderVarArgsParam(false);
|
|
416
|
+
if (varArgsDecl) {
|
|
417
|
+
implParams.push(varArgsDecl);
|
|
418
|
+
}
|
|
419
|
+
const kwargsDecl = needsKwargsParam ? `kwargs?: ${kwargsType}` : null;
|
|
420
|
+
if (kwargsDecl) {
|
|
421
|
+
implParams.push(kwargsDecl);
|
|
422
|
+
}
|
|
423
|
+
const paramDecl = implParams.join(', ');
|
|
424
|
+
|
|
425
|
+
const hasKwArgs = needsKwargsParam;
|
|
426
|
+
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
141
427
|
const fname = this.escapeIdentifier(func.name);
|
|
142
428
|
const moduleId = moduleName ?? '__main__';
|
|
143
429
|
|
|
@@ -146,25 +432,171 @@ export class CodeGenerator {
|
|
|
146
432
|
// `fn(a)`, `fn(a, b)`, ... all to typecheck. We emit a family of overloads that progressively
|
|
147
433
|
// "drop" optional tail args, but also include the full positional signature (<= length) so a
|
|
148
434
|
// call that supplies all args still matches an overload.
|
|
149
|
-
const
|
|
150
|
-
const
|
|
435
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
436
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
437
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
151
438
|
const overloads: string[] = [];
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
439
|
+
if (requiredKwOnlyNames.length > 0) {
|
|
440
|
+
// Required keyword-only params must be represented with a required `kwargs` parameter.
|
|
441
|
+
// Avoid "required after optional" by emitting overloads where all preceding parameters are required.
|
|
442
|
+
const requiredPosCount =
|
|
443
|
+
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
444
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
445
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
446
|
+
const rest: string[] = [];
|
|
447
|
+
const v = (() => {
|
|
448
|
+
if (!varArgsParam) {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
const pname = this.escapeIdentifier(varArgsParam.name);
|
|
452
|
+
if (!needsVarArgsArray) {
|
|
453
|
+
return `...${pname}: unknown[]`;
|
|
454
|
+
}
|
|
455
|
+
// In overloads where `kwargs` is required, keep the `args` surrogate parameter required,
|
|
456
|
+
// but allow `undefined` as a placeholder so callers can omit varargs while still passing kwargs.
|
|
457
|
+
return `${pname}: unknown[] | undefined`;
|
|
458
|
+
})();
|
|
459
|
+
if (v) {
|
|
460
|
+
rest.push(v);
|
|
461
|
+
}
|
|
462
|
+
const k = renderKwargsParam(true);
|
|
463
|
+
if (k) {
|
|
464
|
+
rest.push(k);
|
|
465
|
+
}
|
|
466
|
+
overloads.push(
|
|
467
|
+
`export function ${fname}${typeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`
|
|
468
|
+
);
|
|
469
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
470
|
+
// Also allow callers to omit the varargs surrogate parameter entirely (i.e. `fn(kwargs)`).
|
|
471
|
+
overloads.push(
|
|
472
|
+
`export function ${fname}${typeParamDecl}(${[...head, renderKwargsParam(true)].join(', ')}): Promise<${returnType}>;`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} else if (firstOptionalIndex >= 0 && !varArgsParam && !needsKwargsParam) {
|
|
477
|
+
for (let i = firstOptionalIndex; i <= positionalParams.length; i++) {
|
|
478
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p));
|
|
479
|
+
const rest: string[] = [];
|
|
480
|
+
const v = renderVarArgsParam(false);
|
|
481
|
+
if (v) {
|
|
482
|
+
rest.push(v);
|
|
483
|
+
}
|
|
484
|
+
const k = renderKwargsParam(false);
|
|
485
|
+
if (k) {
|
|
486
|
+
rest.push(k);
|
|
487
|
+
}
|
|
488
|
+
overloads.push(
|
|
489
|
+
`export function ${fname}${typeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`
|
|
490
|
+
);
|
|
158
491
|
}
|
|
159
492
|
}
|
|
160
493
|
|
|
161
494
|
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
162
|
-
const
|
|
163
|
-
|
|
495
|
+
const guardLines: string[] = [];
|
|
496
|
+
if (hasKwArgs && positionalOnlyNames.length > 0) {
|
|
497
|
+
guardLines.push(
|
|
498
|
+
` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`
|
|
499
|
+
);
|
|
500
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
501
|
+
guardLines.push(` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
502
|
+
guardLines.push(
|
|
503
|
+
` throw new Error(\`${func.name} does not accept positional-only argument "\${key}" as a keyword argument\`);`
|
|
504
|
+
);
|
|
505
|
+
guardLines.push(` }`);
|
|
506
|
+
guardLines.push(` }`);
|
|
507
|
+
}
|
|
508
|
+
if (hasKwArgs && requiredKwOnlyNames.length > 0) {
|
|
509
|
+
guardLines.push(
|
|
510
|
+
` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`
|
|
511
|
+
);
|
|
512
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
513
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
514
|
+
guardLines.push(
|
|
515
|
+
` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`
|
|
516
|
+
);
|
|
517
|
+
guardLines.push(` __missing.push(key);`);
|
|
518
|
+
guardLines.push(` }`);
|
|
519
|
+
guardLines.push(` }`);
|
|
520
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
521
|
+
guardLines.push(
|
|
522
|
+
` throw new Error(\`Missing required keyword-only arguments for ${func.name}: \${__missing.join(', ')}\`);`
|
|
523
|
+
);
|
|
524
|
+
guardLines.push(` }`);
|
|
525
|
+
}
|
|
526
|
+
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
527
|
+
|
|
528
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
529
|
+
const requiredPosCount =
|
|
530
|
+
firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
531
|
+
const callPreludeLines: string[] = [];
|
|
532
|
+
if (hasKwArgs) {
|
|
533
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
534
|
+
}
|
|
535
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
536
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
537
|
+
if (requiredPosCount < positionalParams.length) {
|
|
538
|
+
callPreludeLines.push(
|
|
539
|
+
` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`
|
|
540
|
+
);
|
|
541
|
+
callPreludeLines.push(` __args.pop();`);
|
|
542
|
+
callPreludeLines.push(` }`);
|
|
543
|
+
}
|
|
544
|
+
if (hasKwArgs && requiredPosCount < positionalParams.length) {
|
|
545
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
546
|
+
keywordOnlyNames,
|
|
547
|
+
requiredKwOnlyNames,
|
|
548
|
+
hasVarKwArgs,
|
|
549
|
+
});
|
|
550
|
+
callPreludeLines.push(
|
|
551
|
+
` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`
|
|
552
|
+
);
|
|
553
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
554
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
555
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
556
|
+
callPreludeLines.push(` __args.pop();`);
|
|
557
|
+
callPreludeLines.push(` }`);
|
|
558
|
+
callPreludeLines.push(` }`);
|
|
559
|
+
}
|
|
560
|
+
if (varArgsParam) {
|
|
561
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
562
|
+
if (needsVarArgsArray) {
|
|
563
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
564
|
+
keywordOnlyNames,
|
|
565
|
+
requiredKwOnlyNames,
|
|
566
|
+
hasVarKwArgs,
|
|
567
|
+
});
|
|
568
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
569
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
570
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
571
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
572
|
+
callPreludeLines.push(` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`);
|
|
573
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
574
|
+
callPreludeLines.push(` } else {`);
|
|
575
|
+
callPreludeLines.push(
|
|
576
|
+
` throw new Error(\`${func.name} expected ${varArgsParam.name} to be an array\`);`
|
|
577
|
+
);
|
|
578
|
+
callPreludeLines.push(` }`);
|
|
579
|
+
callPreludeLines.push(` }`);
|
|
580
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
581
|
+
} else {
|
|
582
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
586
|
+
|
|
587
|
+
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
588
|
+
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${
|
|
589
|
+
hasKwArgs ? ', __kwargs' : ''
|
|
590
|
+
});
|
|
164
591
|
}
|
|
165
592
|
`;
|
|
166
593
|
|
|
167
|
-
|
|
594
|
+
const declarationBody =
|
|
595
|
+
overloads.length > 0
|
|
596
|
+
? overloadDecl
|
|
597
|
+
: `export function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}>;\n`;
|
|
598
|
+
|
|
599
|
+
return this.wrap(ts, `${jsdoc}${declarationBody}`, [func.name]);
|
|
168
600
|
}
|
|
169
601
|
|
|
170
602
|
generateClassWrapper(
|
|
@@ -173,140 +605,534 @@ export class CodeGenerator {
|
|
|
173
605
|
_annotatedJSDoc = false
|
|
174
606
|
): GeneratedCode {
|
|
175
607
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
176
|
-
|
|
608
|
+
const classGenericContext = this.buildGenericRenderContext(
|
|
609
|
+
this.getTypeParameters(cls.typeParameters),
|
|
610
|
+
[
|
|
611
|
+
...cls.properties.map(property => property.type),
|
|
612
|
+
...cls.methods.flatMap(method => [
|
|
613
|
+
method.returnType,
|
|
614
|
+
...method.parameters.map(p => p.type),
|
|
615
|
+
]),
|
|
616
|
+
],
|
|
617
|
+
moduleName
|
|
618
|
+
);
|
|
619
|
+
const classTypeParamDecl = classGenericContext.declaration;
|
|
620
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
621
|
+
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
622
|
+
const tsValueType = (p: (typeof cls.methods)[number]['parameters'][number]): string =>
|
|
623
|
+
this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
624
|
+
|
|
625
|
+
const wrapAlias = (body: string): GeneratedCode => {
|
|
626
|
+
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
627
|
+
return this.wrap(ts, ts, [cls.name]);
|
|
628
|
+
};
|
|
629
|
+
|
|
177
630
|
if (cls.decorators.includes('__typed_dict__') || cls.kind === 'typed_dict') {
|
|
178
631
|
const props = cls.properties
|
|
179
632
|
.map(p => {
|
|
180
633
|
const pname = this.escapeIdentifier(p.name);
|
|
181
634
|
const opt = (p as unknown as { optional?: boolean }).optional === true ? '?' : '';
|
|
182
|
-
const t = this.
|
|
635
|
+
const t = this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
183
636
|
return `${pname}${opt}: ${t};`;
|
|
184
637
|
})
|
|
185
638
|
.join(' ');
|
|
186
|
-
|
|
187
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
188
|
-
return this.wrap(ts, [cls.name]);
|
|
639
|
+
return wrapAlias(`{ ${props} }`);
|
|
189
640
|
}
|
|
190
641
|
|
|
191
642
|
if (cls.kind === 'namedtuple') {
|
|
192
|
-
// NamedTuple -> readonly tuple type alias `[T1, T2, ...]`
|
|
193
643
|
const elements = cls.properties.map(p =>
|
|
194
|
-
this.
|
|
644
|
+
this.typeToTsFromPython(p.type, classGenericContext, 'value')
|
|
195
645
|
);
|
|
196
|
-
|
|
197
|
-
const ts = `${jsdoc}export type ${cname} = readonly [${elements.join(', ')}]\n`;
|
|
198
|
-
return this.wrap(ts, [cls.name]);
|
|
646
|
+
return wrapAlias(`readonly [${elements.join(', ')}]`);
|
|
199
647
|
}
|
|
200
648
|
|
|
201
649
|
if (cls.kind === 'protocol') {
|
|
202
|
-
// Protocol -> structural interface-like type alias for attributes and callables (subset)
|
|
203
650
|
const props = cls.properties
|
|
204
651
|
.map(
|
|
205
652
|
p =>
|
|
206
|
-
`${this.escapeIdentifier(p.name)}: ${this.
|
|
653
|
+
`${this.escapeIdentifier(p.name)}: ${this.typeToTsFromPython(p.type, classGenericContext, 'value')};`
|
|
207
654
|
)
|
|
208
655
|
.join(' ');
|
|
209
656
|
const methods = cls.methods
|
|
657
|
+
.filter(m => m.name !== '__init__')
|
|
210
658
|
.map(m => {
|
|
211
659
|
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
660
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
661
|
+
this.getTypeParameters(m.typeParameters),
|
|
662
|
+
[m.returnType, ...fparams.map(param => param.type)],
|
|
663
|
+
moduleName
|
|
664
|
+
);
|
|
665
|
+
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
666
|
+
classGenericContext,
|
|
667
|
+
methodOwnGenericContext
|
|
668
|
+
);
|
|
669
|
+
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
212
670
|
const paramsDecl = fparams
|
|
213
671
|
.map(
|
|
214
672
|
p =>
|
|
215
|
-
`${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.
|
|
673
|
+
`${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.typeToTsFromPython(p.type, methodGenericContext, 'value')}`
|
|
216
674
|
)
|
|
217
675
|
.join(', ');
|
|
218
|
-
const returnType = this.
|
|
219
|
-
return `${this.escapeIdentifier(m.name)}: (${paramsDecl}) => ${returnType};`;
|
|
676
|
+
const returnType = this.typeToTsFromPython(m.returnType, methodGenericContext, 'return');
|
|
677
|
+
return `${this.escapeIdentifier(m.name)}: ${methodTypeParamDecl}(${paramsDecl}) => ${returnType};`;
|
|
220
678
|
})
|
|
221
679
|
.join(' ');
|
|
222
|
-
|
|
223
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} ${methods} }\n`;
|
|
224
|
-
return this.wrap(ts, [cls.name]);
|
|
680
|
+
return wrapAlias(`{ ${props} ${methods} }`);
|
|
225
681
|
}
|
|
226
682
|
|
|
227
683
|
if (cls.kind === 'dataclass' || cls.kind === 'pydantic') {
|
|
228
|
-
// Data containers -> object type alias
|
|
229
684
|
const props = cls.properties
|
|
230
685
|
.map(p => {
|
|
231
686
|
const pname = this.escapeIdentifier(p.name);
|
|
232
687
|
const opt = (p as unknown as { optional?: boolean }).optional === true ? '?' : '';
|
|
233
|
-
const t = this.
|
|
688
|
+
const t = this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
234
689
|
return `${pname}${opt}: ${t};`;
|
|
235
690
|
})
|
|
236
691
|
.join(' ');
|
|
237
|
-
|
|
238
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
239
|
-
return this.wrap(ts, [cls.name]);
|
|
692
|
+
return wrapAlias(`{ ${props} }`);
|
|
240
693
|
}
|
|
694
|
+
|
|
241
695
|
const sortedMethods = [...cls.methods].sort((a, b) => a.name.localeCompare(b.name));
|
|
242
|
-
const methodBodies =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
.
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
696
|
+
const methodBodies: string[] = [];
|
|
697
|
+
const methodDeclarations: string[] = [];
|
|
698
|
+
|
|
699
|
+
sortedMethods
|
|
700
|
+
.filter(method => method.name !== '__init__')
|
|
701
|
+
.forEach(method => {
|
|
702
|
+
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
703
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(
|
|
704
|
+
this.getTypeParameters(method.typeParameters),
|
|
705
|
+
[method.returnType, ...fparams.map(param => param.type)],
|
|
706
|
+
moduleName
|
|
707
|
+
);
|
|
708
|
+
const methodGenericContext = this.mergeGenericRenderContexts(
|
|
709
|
+
classGenericContext,
|
|
710
|
+
methodOwnGenericContext
|
|
711
|
+
);
|
|
712
|
+
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
713
|
+
const methodTsValueType = (p: (typeof fparams)[number]): string =>
|
|
714
|
+
this.typeToTsFromPython(p.type, methodGenericContext, 'value');
|
|
715
|
+
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
716
|
+
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
717
|
+
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
718
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
719
|
+
const varArgsParam = fparams.find(p => p.varArgs);
|
|
720
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
721
|
+
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
722
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
723
|
+
const requiredPosCount =
|
|
724
|
+
firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
725
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
726
|
+
|
|
727
|
+
const renderPositionalParam = (
|
|
728
|
+
p: (typeof positionalParams)[number],
|
|
729
|
+
forceRequired = false
|
|
730
|
+
): string => {
|
|
731
|
+
const pname = this.escapeIdentifier(p.name);
|
|
732
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
733
|
+
return `${pname}${opt}: ${methodTsValueType(p)}`;
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const kwargsType = (() => {
|
|
737
|
+
if (!needsKwargsParam) {
|
|
738
|
+
return '';
|
|
739
|
+
}
|
|
740
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
741
|
+
return 'Record<string, unknown>';
|
|
742
|
+
}
|
|
743
|
+
const props = keywordOnlyParams
|
|
744
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${methodTsValueType(p)};`)
|
|
745
|
+
.join(' ');
|
|
746
|
+
const obj = `{ ${props} }`;
|
|
747
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
748
|
+
})();
|
|
749
|
+
|
|
750
|
+
const paramsDeclParts: string[] = [];
|
|
751
|
+
positionalParams.forEach(p => {
|
|
752
|
+
paramsDeclParts.push(renderPositionalParam(p));
|
|
753
|
+
});
|
|
754
|
+
if (varArgsParam) {
|
|
755
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
756
|
+
paramsDeclParts.push(
|
|
757
|
+
needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
if (needsKwargsParam) {
|
|
761
|
+
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
762
|
+
}
|
|
763
|
+
const paramsDecl = paramsDeclParts.join(', ');
|
|
764
|
+
|
|
765
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
766
|
+
const returnType = this.typeToTsFromPython(
|
|
767
|
+
method.returnType,
|
|
768
|
+
methodGenericContext,
|
|
769
|
+
'return'
|
|
770
|
+
);
|
|
771
|
+
const mname = this.escapeIdentifier(method.name);
|
|
772
|
+
|
|
773
|
+
const overloads: string[] = [];
|
|
774
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
775
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
776
|
+
const requiredPosCount =
|
|
777
|
+
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
778
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
779
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
780
|
+
const rest: string[] = [];
|
|
781
|
+
if (varArgsParam) {
|
|
782
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
783
|
+
rest.push(
|
|
784
|
+
needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
rest.push(`kwargs: ${kwargsType}`);
|
|
788
|
+
overloads.push(
|
|
789
|
+
` ${mname}${methodTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`
|
|
790
|
+
);
|
|
791
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
792
|
+
overloads.push(
|
|
793
|
+
` ${mname}${methodTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${returnType}>;`
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
799
|
+
|
|
800
|
+
const callPreludeLines: string[] = [];
|
|
801
|
+
if (needsKwargsParam) {
|
|
802
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
803
|
+
}
|
|
804
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
805
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
806
|
+
if (requiredPosCount < positionalParams.length) {
|
|
807
|
+
callPreludeLines.push(
|
|
808
|
+
` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`
|
|
809
|
+
);
|
|
810
|
+
callPreludeLines.push(` __args.pop();`);
|
|
811
|
+
callPreludeLines.push(` }`);
|
|
812
|
+
}
|
|
813
|
+
if (needsKwargsParam && requiredPosCount < positionalParams.length) {
|
|
814
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
815
|
+
keywordOnlyNames,
|
|
816
|
+
requiredKwOnlyNames,
|
|
817
|
+
hasVarKwArgs,
|
|
818
|
+
});
|
|
819
|
+
callPreludeLines.push(
|
|
820
|
+
` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`
|
|
821
|
+
);
|
|
822
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
823
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
824
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
825
|
+
callPreludeLines.push(` __args.pop();`);
|
|
826
|
+
callPreludeLines.push(` }`);
|
|
827
|
+
callPreludeLines.push(` }`);
|
|
828
|
+
}
|
|
829
|
+
if (varArgsParam) {
|
|
830
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
831
|
+
if (needsVarArgsArray) {
|
|
832
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
833
|
+
keywordOnlyNames,
|
|
834
|
+
requiredKwOnlyNames,
|
|
835
|
+
hasVarKwArgs,
|
|
836
|
+
});
|
|
837
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
838
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
839
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
840
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
841
|
+
callPreludeLines.push(
|
|
842
|
+
` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`
|
|
843
|
+
);
|
|
844
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
845
|
+
callPreludeLines.push(` } else {`);
|
|
846
|
+
callPreludeLines.push(
|
|
847
|
+
` throw new Error(\`${method.name} expected ${varArgsParam.name} to be an array\`);`
|
|
848
|
+
);
|
|
849
|
+
callPreludeLines.push(` }`);
|
|
850
|
+
callPreludeLines.push(` }`);
|
|
851
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
852
|
+
} else {
|
|
853
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
857
|
+
|
|
858
|
+
const guardLines: string[] = [];
|
|
859
|
+
if (needsKwargsParam && positionalOnlyNames.length > 0) {
|
|
860
|
+
guardLines.push(
|
|
861
|
+
` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`
|
|
862
|
+
);
|
|
863
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
864
|
+
guardLines.push(
|
|
865
|
+
` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`
|
|
866
|
+
);
|
|
867
|
+
guardLines.push(
|
|
868
|
+
` throw new Error(\`${method.name} does not accept positional-only argument "\${key}" as a keyword argument\`);`
|
|
869
|
+
);
|
|
870
|
+
guardLines.push(` }`);
|
|
871
|
+
guardLines.push(` }`);
|
|
872
|
+
}
|
|
873
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
874
|
+
guardLines.push(
|
|
875
|
+
` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`
|
|
876
|
+
);
|
|
877
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
878
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
879
|
+
guardLines.push(
|
|
880
|
+
` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`
|
|
881
|
+
);
|
|
882
|
+
guardLines.push(` __missing.push(key);`);
|
|
883
|
+
guardLines.push(` }`);
|
|
884
|
+
guardLines.push(` }`);
|
|
885
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
886
|
+
guardLines.push(
|
|
887
|
+
` throw new Error(\`Missing required keyword-only arguments for ${method.name}: \${__missing.join(', ')}\`);`
|
|
888
|
+
);
|
|
889
|
+
guardLines.push(` }`);
|
|
890
|
+
}
|
|
891
|
+
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
892
|
+
|
|
893
|
+
methodBodies.push(`${overloadDecl} async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
894
|
+
${callPrelude}${guards} return getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${
|
|
895
|
+
needsKwargsParam ? ', __kwargs' : ''
|
|
896
|
+
});
|
|
897
|
+
}`);
|
|
898
|
+
methodDeclarations.push(
|
|
899
|
+
`${overloadDecl}${overloads.length > 0 ? '' : ` ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`
|
|
900
|
+
);
|
|
901
|
+
});
|
|
266
902
|
|
|
267
|
-
// Constructor typing from __init__
|
|
268
903
|
const init = cls.methods.find(m => m.name === '__init__');
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
904
|
+
const ctorSpec = (() => {
|
|
905
|
+
if (!init) {
|
|
906
|
+
return {
|
|
907
|
+
overloadDecl: '',
|
|
908
|
+
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
909
|
+
paramsDecl: `...args: unknown[]`,
|
|
910
|
+
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
911
|
+
hasKwargs: false,
|
|
912
|
+
guardLines: [] as string[],
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
917
|
+
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
918
|
+
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
919
|
+
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
920
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
921
|
+
const varArgsParam = fparams.find(p => p.varArgs);
|
|
922
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
923
|
+
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
924
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
925
|
+
const requiredPosCount =
|
|
926
|
+
firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
927
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
928
|
+
|
|
929
|
+
const renderPositionalParam = (
|
|
930
|
+
p: (typeof positionalParams)[number],
|
|
931
|
+
forceRequired = false
|
|
932
|
+
): string => {
|
|
933
|
+
const pname = this.escapeIdentifier(p.name);
|
|
934
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
935
|
+
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const kwargsType = (() => {
|
|
939
|
+
if (!needsKwargsParam) {
|
|
940
|
+
return '';
|
|
941
|
+
}
|
|
942
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
943
|
+
return 'Record<string, unknown>';
|
|
944
|
+
}
|
|
945
|
+
const props = keywordOnlyParams
|
|
946
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
947
|
+
.join(' ');
|
|
948
|
+
const obj = `{ ${props} }`;
|
|
949
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
950
|
+
})();
|
|
951
|
+
|
|
952
|
+
const paramsDeclParts: string[] = [];
|
|
953
|
+
positionalParams.forEach(p => {
|
|
954
|
+
paramsDeclParts.push(renderPositionalParam(p));
|
|
955
|
+
});
|
|
956
|
+
if (varArgsParam) {
|
|
957
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
958
|
+
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
959
|
+
}
|
|
960
|
+
if (needsKwargsParam) {
|
|
961
|
+
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
962
|
+
}
|
|
963
|
+
const paramsDecl = paramsDeclParts.join(', ');
|
|
964
|
+
|
|
965
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
966
|
+
const overloads: string[] = [];
|
|
967
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
968
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
969
|
+
const requiredPosCount =
|
|
970
|
+
firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
971
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
972
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
973
|
+
const rest: string[] = [];
|
|
974
|
+
if (varArgsParam) {
|
|
975
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
976
|
+
rest.push(
|
|
977
|
+
needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
rest.push(`kwargs: ${kwargsType}`);
|
|
981
|
+
overloads.push(
|
|
982
|
+
` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`
|
|
983
|
+
);
|
|
984
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
985
|
+
overloads.push(
|
|
986
|
+
` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
992
|
+
const declaration =
|
|
993
|
+
overloads.length > 0
|
|
994
|
+
? overloadDecl
|
|
995
|
+
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
996
|
+
|
|
997
|
+
const guardLines: string[] = [];
|
|
998
|
+
const callPreludeLines: string[] = [];
|
|
999
|
+
if (needsKwargsParam) {
|
|
1000
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
1001
|
+
}
|
|
1002
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
1003
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
1004
|
+
if (requiredPosCount < positionalParams.length) {
|
|
1005
|
+
callPreludeLines.push(
|
|
1006
|
+
` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`
|
|
1007
|
+
);
|
|
1008
|
+
callPreludeLines.push(` __args.pop();`);
|
|
1009
|
+
callPreludeLines.push(` }`);
|
|
1010
|
+
}
|
|
1011
|
+
if (needsKwargsParam && requiredPosCount < positionalParams.length) {
|
|
1012
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
1013
|
+
keywordOnlyNames,
|
|
1014
|
+
requiredKwOnlyNames,
|
|
1015
|
+
hasVarKwArgs,
|
|
1016
|
+
});
|
|
1017
|
+
callPreludeLines.push(
|
|
1018
|
+
` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`
|
|
1019
|
+
);
|
|
1020
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
1021
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
1022
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
1023
|
+
callPreludeLines.push(` __args.pop();`);
|
|
1024
|
+
callPreludeLines.push(` }`);
|
|
1025
|
+
callPreludeLines.push(` }`);
|
|
1026
|
+
}
|
|
1027
|
+
if (varArgsParam) {
|
|
1028
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
1029
|
+
if (needsVarArgsArray) {
|
|
1030
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
1031
|
+
keywordOnlyNames,
|
|
1032
|
+
requiredKwOnlyNames,
|
|
1033
|
+
hasVarKwArgs,
|
|
1034
|
+
});
|
|
1035
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
1036
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
1037
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
1038
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
1039
|
+
callPreludeLines.push(` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`);
|
|
1040
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
1041
|
+
callPreludeLines.push(` } else {`);
|
|
1042
|
+
callPreludeLines.push(
|
|
1043
|
+
` throw new Error(\`__init__ expected ${varArgsParam.name} to be an array\`);`
|
|
1044
|
+
);
|
|
1045
|
+
callPreludeLines.push(` }`);
|
|
1046
|
+
callPreludeLines.push(` }`);
|
|
1047
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
1048
|
+
} else {
|
|
1049
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
1053
|
+
|
|
1054
|
+
if (needsKwargsParam && positionalOnlyNames.length > 0) {
|
|
1055
|
+
guardLines.push(
|
|
1056
|
+
` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`
|
|
1057
|
+
);
|
|
1058
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
1059
|
+
guardLines.push(
|
|
1060
|
+
` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`
|
|
1061
|
+
);
|
|
1062
|
+
guardLines.push(
|
|
1063
|
+
` throw new Error(\`__init__ does not accept positional-only argument "\${key}" as a keyword argument\`);`
|
|
1064
|
+
);
|
|
1065
|
+
guardLines.push(` }`);
|
|
1066
|
+
guardLines.push(` }`);
|
|
1067
|
+
}
|
|
1068
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
1069
|
+
guardLines.push(
|
|
1070
|
+
` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`
|
|
1071
|
+
);
|
|
1072
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
1073
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
1074
|
+
guardLines.push(
|
|
1075
|
+
` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`
|
|
1076
|
+
);
|
|
1077
|
+
guardLines.push(` __missing.push(key);`);
|
|
1078
|
+
guardLines.push(` }`);
|
|
1079
|
+
guardLines.push(` }`);
|
|
1080
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
1081
|
+
guardLines.push(
|
|
1082
|
+
` throw new Error(\`Missing required keyword-only arguments for __init__: \${__missing.join(', ')}\`);`
|
|
1083
|
+
);
|
|
1084
|
+
guardLines.push(` }`);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return {
|
|
1088
|
+
overloadDecl,
|
|
1089
|
+
declaration,
|
|
1090
|
+
paramsDecl,
|
|
1091
|
+
callPrelude,
|
|
1092
|
+
hasKwargs: needsKwargsParam,
|
|
1093
|
+
guardLines,
|
|
1094
|
+
};
|
|
1095
|
+
})();
|
|
294
1096
|
|
|
295
|
-
const cname = this.escapeIdentifier(cls.name);
|
|
296
1097
|
const moduleId = moduleName ?? '__main__';
|
|
297
|
-
const methodsSection = methodBodies ? `\n${methodBodies}\n` : '\n';
|
|
298
|
-
const
|
|
1098
|
+
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
1099
|
+
const declarationMethodsSection =
|
|
1100
|
+
methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
1101
|
+
const ctorGuards = ctorSpec.guardLines.length > 0 ? `${ctorSpec.guardLines.join('\n')}\n` : '';
|
|
1102
|
+
const newClassExpr = `new ${cname}${classGenericContext.typeArguments}(handle)`;
|
|
1103
|
+
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
299
1104
|
private readonly __handle: string;
|
|
300
1105
|
private constructor(handle: string) { this.__handle = handle; }
|
|
301
|
-
static async create(${
|
|
302
|
-
const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}',
|
|
303
|
-
|
|
1106
|
+
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
1107
|
+
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${
|
|
1108
|
+
ctorSpec.hasKwargs ? ', __kwargs' : ''
|
|
1109
|
+
});
|
|
1110
|
+
return ${newClassExpr};
|
|
304
1111
|
}
|
|
305
|
-
static fromHandle(handle: string): ${
|
|
1112
|
+
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
306
1113
|
}
|
|
307
1114
|
`;
|
|
308
1115
|
|
|
309
|
-
|
|
1116
|
+
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
1117
|
+
private readonly __handle: string;
|
|
1118
|
+
private constructor(handle: string);
|
|
1119
|
+
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection} disposeHandle(): Promise<void>;
|
|
1120
|
+
}
|
|
1121
|
+
`;
|
|
1122
|
+
|
|
1123
|
+
return this.wrap(ts, declaration, [cls.name]);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
generateTypeAlias(alias: PythonTypeAlias, moduleName?: string): GeneratedCode {
|
|
1127
|
+
const genericContext = this.buildGenericRenderContext(
|
|
1128
|
+
this.getTypeParameters(alias.typeParameters),
|
|
1129
|
+
[alias.type],
|
|
1130
|
+
moduleName
|
|
1131
|
+
);
|
|
1132
|
+
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
1133
|
+
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
1134
|
+
const ts = `export type ${aliasName}${genericContext.declaration} = ${body}\n`;
|
|
1135
|
+
return this.wrap(ts, ts, [alias.name]);
|
|
310
1136
|
}
|
|
311
1137
|
|
|
312
1138
|
/**
|
|
@@ -335,16 +1161,22 @@ export class CodeGenerator {
|
|
|
335
1161
|
}
|
|
336
1162
|
|
|
337
1163
|
generateModuleDefinition(module: PythonModule, annotatedJSDoc = false): GeneratedCode {
|
|
338
|
-
const
|
|
1164
|
+
const functionResults = [...module.functions]
|
|
1165
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1166
|
+
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc));
|
|
1167
|
+
const classResults = [...module.classes]
|
|
339
1168
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
340
|
-
.map(
|
|
341
|
-
|
|
342
|
-
const classCodes = [...module.classes]
|
|
1169
|
+
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc));
|
|
1170
|
+
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
343
1171
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
344
|
-
.map(
|
|
345
|
-
|
|
1172
|
+
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
1173
|
+
|
|
1174
|
+
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
1175
|
+
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
1176
|
+
const typeAliasCodes = typeAliasResults.map(result => result.typescript).join('\n');
|
|
346
1177
|
|
|
347
1178
|
const header = `// Generated by tywrap\n// Module: ${module.name}\n// DO NOT EDIT MANUALLY\n\n`;
|
|
1179
|
+
const declarationHeader = `// Generated by tywrap\n// Type Declarations\n// DO NOT EDIT MANUALLY\n\n`;
|
|
348
1180
|
const hasRuntimeClasses = module.classes.some(c => {
|
|
349
1181
|
const kind = c.kind ?? 'class';
|
|
350
1182
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
@@ -352,8 +1184,13 @@ export class CodeGenerator {
|
|
|
352
1184
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
353
1185
|
const bridgeDecl = needsRuntime ? `import { getRuntimeBridge } from 'tywrap/runtime';\n\n` : '';
|
|
354
1186
|
|
|
355
|
-
const ts = `${header
|
|
356
|
-
|
|
1187
|
+
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
1188
|
+
const declaration = `${`${declarationHeader}${functionResults
|
|
1189
|
+
.map(result => result.declaration)
|
|
1190
|
+
.join('\n')}\n${classResults
|
|
1191
|
+
.map(result => result.declaration)
|
|
1192
|
+
.join('\n')}\n${typeAliasResults.map(result => result.declaration).join('\n')}`.trimEnd()}\n`;
|
|
1193
|
+
return this.wrap(ts, declaration, [module.name]);
|
|
357
1194
|
}
|
|
358
1195
|
|
|
359
1196
|
private generateJsDoc(doc?: string, paramAnnotations?: readonly string[]): string {
|
|
@@ -373,10 +1210,10 @@ export class CodeGenerator {
|
|
|
373
1210
|
return `/**\n${lines.join('\n')}\n */\n`;
|
|
374
1211
|
}
|
|
375
1212
|
|
|
376
|
-
private wrap(typescript: string, _sources: string[]): GeneratedCode {
|
|
1213
|
+
private wrap(typescript: string, declaration: string, _sources: string[]): GeneratedCode {
|
|
377
1214
|
return {
|
|
378
1215
|
typescript,
|
|
379
|
-
declaration
|
|
1216
|
+
declaration,
|
|
380
1217
|
sourceMap: undefined,
|
|
381
1218
|
metadata: {
|
|
382
1219
|
generatedAt: new Date(),
|
|
@@ -438,6 +1275,9 @@ export class CodeGenerator {
|
|
|
438
1275
|
}
|
|
439
1276
|
case 'custom': {
|
|
440
1277
|
const c = type as { kind: 'custom'; name: string };
|
|
1278
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
1279
|
+
return 'unknown';
|
|
1280
|
+
}
|
|
441
1281
|
return c.name;
|
|
442
1282
|
}
|
|
443
1283
|
case 'literal': {
|