tywrap 0.2.1 → 0.3.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 +65 -31
- package/dist/cli.js +49 -2
- 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 +43 -7
- 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 +47 -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/index.d.ts +7 -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 +170 -32
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +1 -1
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +1 -1
- 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 +12 -0
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +53 -5
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +51 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +227 -328
- 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 +40 -18
- package/runtime/python_bridge.py +78 -12
- package/src/cli.ts +61 -2
- package/src/config/index.ts +63 -17
- package/src/core/analyzer.ts +52 -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/index.ts +34 -5
- 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 +264 -44
- package/src/runtime/optimized-node.ts +4 -1
- 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 +67 -6
- package/src/types/global.d.ts +11 -1
- package/src/types/index.ts +63 -0
- package/src/tywrap.ts +279 -358
- 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/dist/core/generator.js
CHANGED
|
@@ -89,26 +89,243 @@ export class CodeGenerator {
|
|
|
89
89
|
return unicodeMap.get(char) ?? char.charCodeAt(0).toString(16);
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
+
getTypeParameters(typeParameters) {
|
|
93
|
+
return typeParameters ?? [];
|
|
94
|
+
}
|
|
95
|
+
buildGenericRenderContext(typeParameters, types, currentModule) {
|
|
96
|
+
const callableParamSpecs = new Set();
|
|
97
|
+
types.forEach(type => this.collectCallableParamSpecs(type, callableParamSpecs));
|
|
98
|
+
const emitted = [];
|
|
99
|
+
const emittedNames = new Set();
|
|
100
|
+
const emittedParamSpecs = new Set();
|
|
101
|
+
typeParameters.forEach(param => {
|
|
102
|
+
if (param.kind === 'typevar' &&
|
|
103
|
+
!param.bound &&
|
|
104
|
+
!(param.constraints && param.constraints.length > 0) &&
|
|
105
|
+
(!param.variance || param.variance === 'invariant')) {
|
|
106
|
+
emitted.push({ name: param.name, declaration: param.name });
|
|
107
|
+
emittedNames.add(param.name);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (param.kind === 'paramspec' && callableParamSpecs.has(param.name)) {
|
|
111
|
+
emitted.push({ name: param.name, declaration: `${param.name} extends unknown[]` });
|
|
112
|
+
emittedNames.add(param.name);
|
|
113
|
+
emittedParamSpecs.add(param.name);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
currentModule,
|
|
118
|
+
declaration: emitted.length > 0 ? `<${emitted.map(param => param.declaration).join(', ')}>` : '',
|
|
119
|
+
typeArguments: emitted.length > 0 ? `<${emitted.map(param => param.name).join(', ')}>` : '',
|
|
120
|
+
emittedNames,
|
|
121
|
+
emittedParamSpecs,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
mergeGenericRenderContexts(outer, inner) {
|
|
125
|
+
return {
|
|
126
|
+
currentModule: inner.currentModule ?? outer.currentModule,
|
|
127
|
+
declaration: inner.declaration,
|
|
128
|
+
typeArguments: inner.typeArguments,
|
|
129
|
+
emittedNames: new Set([...outer.emittedNames, ...inner.emittedNames]),
|
|
130
|
+
emittedParamSpecs: new Set([...outer.emittedParamSpecs, ...inner.emittedParamSpecs]),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
collectCallableParamSpecs(type, out) {
|
|
134
|
+
switch (type.kind) {
|
|
135
|
+
case 'collection':
|
|
136
|
+
type.itemTypes.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
137
|
+
break;
|
|
138
|
+
case 'paramspec':
|
|
139
|
+
out.add(type.name);
|
|
140
|
+
break;
|
|
141
|
+
case 'union':
|
|
142
|
+
type.types.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
143
|
+
break;
|
|
144
|
+
case 'optional':
|
|
145
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
146
|
+
break;
|
|
147
|
+
case 'generic':
|
|
148
|
+
type.typeArgs.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
149
|
+
break;
|
|
150
|
+
case 'callable':
|
|
151
|
+
if (type.parameterSpec?.kind === 'paramspec') {
|
|
152
|
+
out.add(type.parameterSpec.name);
|
|
153
|
+
}
|
|
154
|
+
type.parameters.forEach(item => this.collectCallableParamSpecs(item, out));
|
|
155
|
+
this.collectCallableParamSpecs(type.returnType, out);
|
|
156
|
+
break;
|
|
157
|
+
case 'annotated':
|
|
158
|
+
this.collectCallableParamSpecs(type.base, out);
|
|
159
|
+
break;
|
|
160
|
+
case 'final':
|
|
161
|
+
case 'classvar':
|
|
162
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
163
|
+
break;
|
|
164
|
+
case 'unpack':
|
|
165
|
+
this.collectCallableParamSpecs(type.type, out);
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
sanitizeType(type, ctx) {
|
|
172
|
+
const unknownType = () => ({ kind: 'custom', name: 'Any', module: 'typing' });
|
|
173
|
+
switch (type.kind) {
|
|
174
|
+
case 'primitive':
|
|
175
|
+
case 'literal':
|
|
176
|
+
return type;
|
|
177
|
+
case 'custom':
|
|
178
|
+
return type;
|
|
179
|
+
case 'collection':
|
|
180
|
+
return {
|
|
181
|
+
...type,
|
|
182
|
+
itemTypes: type.itemTypes.map(item => this.sanitizeType(item, ctx)),
|
|
183
|
+
};
|
|
184
|
+
case 'union':
|
|
185
|
+
return {
|
|
186
|
+
...type,
|
|
187
|
+
types: type.types.map(item => this.sanitizeType(item, ctx)),
|
|
188
|
+
};
|
|
189
|
+
case 'optional':
|
|
190
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
191
|
+
case 'generic':
|
|
192
|
+
return {
|
|
193
|
+
...type,
|
|
194
|
+
module: type.module === ctx.currentModule ? undefined : type.module,
|
|
195
|
+
typeArgs: type.typeArgs.map(item => this.sanitizeType(item, ctx)),
|
|
196
|
+
};
|
|
197
|
+
case 'callable':
|
|
198
|
+
if (type.parameterSpec && !ctx.emittedParamSpecs.has(type.parameterSpec.name)) {
|
|
199
|
+
return {
|
|
200
|
+
...type,
|
|
201
|
+
parameters: [{ kind: 'custom', name: '...' }],
|
|
202
|
+
parameterSpec: undefined,
|
|
203
|
+
returnType: this.sanitizeType(type.returnType, ctx),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
...type,
|
|
208
|
+
parameters: type.parameters.map(item => this.sanitizeType(item, ctx)),
|
|
209
|
+
parameterSpec: type.parameterSpec && ctx.emittedParamSpecs.has(type.parameterSpec.name)
|
|
210
|
+
? type.parameterSpec
|
|
211
|
+
: undefined,
|
|
212
|
+
returnType: this.sanitizeType(type.returnType, ctx),
|
|
213
|
+
};
|
|
214
|
+
case 'annotated':
|
|
215
|
+
return { ...type, base: this.sanitizeType(type.base, ctx) };
|
|
216
|
+
case 'typevar':
|
|
217
|
+
case 'paramspec':
|
|
218
|
+
return ctx.emittedNames.has(type.name) ? type : unknownType();
|
|
219
|
+
case 'paramspec_args':
|
|
220
|
+
return {
|
|
221
|
+
kind: 'collection',
|
|
222
|
+
name: 'list',
|
|
223
|
+
itemTypes: [unknownType()],
|
|
224
|
+
};
|
|
225
|
+
case 'paramspec_kwargs':
|
|
226
|
+
return {
|
|
227
|
+
kind: 'collection',
|
|
228
|
+
name: 'dict',
|
|
229
|
+
itemTypes: [{ kind: 'primitive', name: 'str' }, unknownType()],
|
|
230
|
+
};
|
|
231
|
+
case 'typevartuple':
|
|
232
|
+
return unknownType();
|
|
233
|
+
case 'unpack':
|
|
234
|
+
return unknownType();
|
|
235
|
+
case 'final':
|
|
236
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
237
|
+
case 'classvar':
|
|
238
|
+
return { ...type, type: this.sanitizeType(type.type, ctx) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
typeToTsFromPython(type, ctx, mappingContext) {
|
|
242
|
+
return this.typeToTs(this.mapper.mapPythonType(this.sanitizeType(type, ctx), mappingContext));
|
|
243
|
+
}
|
|
244
|
+
renderLooksLikeKwargsExpr(valueExpr, options) {
|
|
245
|
+
const base = `typeof ${valueExpr} === 'object' && ${valueExpr} !== null && !globalThis.Array.isArray(${valueExpr}) && (Object.getPrototypeOf(${valueExpr}) === Object.prototype || Object.getPrototypeOf(${valueExpr}) === null)`;
|
|
246
|
+
const keyCheck = (() => {
|
|
247
|
+
if (options.requiredKwOnlyNames.length > 0) {
|
|
248
|
+
return options.requiredKwOnlyNames
|
|
249
|
+
.map(k => `Object.prototype.hasOwnProperty.call(${valueExpr}, ${JSON.stringify(k)})`)
|
|
250
|
+
.join(' && ');
|
|
251
|
+
}
|
|
252
|
+
if (options.hasVarKwArgs) {
|
|
253
|
+
// With **kwargs, any plain object could be kwargs.
|
|
254
|
+
return 'true';
|
|
255
|
+
}
|
|
256
|
+
if (options.keywordOnlyNames.length > 0) {
|
|
257
|
+
return options.keywordOnlyNames
|
|
258
|
+
.map(k => `Object.prototype.hasOwnProperty.call(${valueExpr}, ${JSON.stringify(k)})`)
|
|
259
|
+
.join(' || ');
|
|
260
|
+
}
|
|
261
|
+
return 'false';
|
|
262
|
+
})();
|
|
263
|
+
return `(${base} && (${keyCheck}))`;
|
|
264
|
+
}
|
|
92
265
|
generateFunctionWrapper(func, moduleName, annotatedJSDoc = false) {
|
|
93
266
|
const jsdoc = this.generateJsDoc(func.docstring, annotatedJSDoc ? func.parameters.map(p => String(p.type)) : undefined);
|
|
94
267
|
const filteredParams = func.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
95
|
-
const
|
|
268
|
+
const keywordOnlyParams = filteredParams.filter(p => p.keywordOnly);
|
|
269
|
+
const positionalOnlyNames = filteredParams.filter(p => p.positionalOnly).map(p => p.name);
|
|
270
|
+
const hasVarKwArgs = filteredParams.some(p => p.kwArgs);
|
|
271
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
272
|
+
const varArgsParam = filteredParams.find(p => p.varArgs);
|
|
273
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
274
|
+
const positionalParams = filteredParams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
275
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(func.typeParameters), [func.returnType, ...filteredParams.map(param => param.type)], moduleName);
|
|
276
|
+
const typeParamDecl = genericContext.declaration;
|
|
277
|
+
const tsTypeForValue = (p) => this.typeToTsFromPython(p.type, genericContext, 'value');
|
|
278
|
+
const kwargsType = (() => {
|
|
279
|
+
if (!needsKwargsParam) {
|
|
280
|
+
return '';
|
|
281
|
+
}
|
|
282
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
283
|
+
return 'Record<string, unknown>';
|
|
284
|
+
}
|
|
285
|
+
const props = keywordOnlyParams
|
|
286
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsTypeForValue(p)};`)
|
|
287
|
+
.join(' ');
|
|
288
|
+
const obj = `{ ${props} }`;
|
|
289
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
290
|
+
})();
|
|
291
|
+
const renderPositionalParam = (p, forceRequired = false) => {
|
|
96
292
|
const pname = this.escapeIdentifier(p.name);
|
|
97
|
-
|
|
293
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
294
|
+
return `${pname}${opt}: ${tsTypeForValue(p)}`;
|
|
295
|
+
};
|
|
296
|
+
const renderVarArgsParam = (forceRequired = false) => {
|
|
297
|
+
if (!varArgsParam) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const pname = this.escapeIdentifier(varArgsParam.name);
|
|
301
|
+
if (!needsVarArgsArray) {
|
|
98
302
|
return `...${pname}: unknown[]`;
|
|
99
303
|
}
|
|
100
|
-
|
|
101
|
-
|
|
304
|
+
const opt = forceRequired ? '' : '?';
|
|
305
|
+
return `${pname}${opt}: unknown[]`;
|
|
306
|
+
};
|
|
307
|
+
const renderKwargsParam = (forceRequired = false) => {
|
|
308
|
+
if (!needsKwargsParam) {
|
|
309
|
+
return null;
|
|
102
310
|
}
|
|
103
|
-
|
|
311
|
+
const opt = forceRequired ? '' : '?';
|
|
312
|
+
return `kwargs${opt}: ${kwargsType}`;
|
|
104
313
|
};
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
314
|
+
const implParams = [];
|
|
315
|
+
for (const p of positionalParams) {
|
|
316
|
+
implParams.push(renderPositionalParam(p));
|
|
317
|
+
}
|
|
318
|
+
const varArgsDecl = renderVarArgsParam(false);
|
|
319
|
+
if (varArgsDecl) {
|
|
320
|
+
implParams.push(varArgsDecl);
|
|
321
|
+
}
|
|
322
|
+
const kwargsDecl = needsKwargsParam ? `kwargs?: ${kwargsType}` : null;
|
|
323
|
+
if (kwargsDecl) {
|
|
324
|
+
implParams.push(kwargsDecl);
|
|
325
|
+
}
|
|
326
|
+
const paramDecl = implParams.join(', ');
|
|
327
|
+
const hasKwArgs = needsKwargsParam;
|
|
328
|
+
const returnType = this.typeToTsFromPython(func.returnType, genericContext, 'return');
|
|
112
329
|
const fname = this.escapeIdentifier(func.name);
|
|
113
330
|
const moduleId = moduleName ?? '__main__';
|
|
114
331
|
// Overloads: generate trailing optional parameter drop variants (exclude *args/**kwargs).
|
|
@@ -116,136 +333,540 @@ export class CodeGenerator {
|
|
|
116
333
|
// `fn(a)`, `fn(a, b)`, ... all to typecheck. We emit a family of overloads that progressively
|
|
117
334
|
// "drop" optional tail args, but also include the full positional signature (<= length) so a
|
|
118
335
|
// call that supplies all args still matches an overload.
|
|
119
|
-
const
|
|
120
|
-
const
|
|
336
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
337
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
338
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
121
339
|
const overloads = [];
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
340
|
+
if (requiredKwOnlyNames.length > 0) {
|
|
341
|
+
// Required keyword-only params must be represented with a required `kwargs` parameter.
|
|
342
|
+
// Avoid "required after optional" by emitting overloads where all preceding parameters are required.
|
|
343
|
+
const requiredPosCount = firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
344
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
345
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
346
|
+
const rest = [];
|
|
347
|
+
const v = (() => {
|
|
348
|
+
if (!varArgsParam) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const pname = this.escapeIdentifier(varArgsParam.name);
|
|
352
|
+
if (!needsVarArgsArray) {
|
|
353
|
+
return `...${pname}: unknown[]`;
|
|
354
|
+
}
|
|
355
|
+
// In overloads where `kwargs` is required, keep the `args` surrogate parameter required,
|
|
356
|
+
// but allow `undefined` as a placeholder so callers can omit varargs while still passing kwargs.
|
|
357
|
+
return `${pname}: unknown[] | undefined`;
|
|
358
|
+
})();
|
|
359
|
+
if (v) {
|
|
360
|
+
rest.push(v);
|
|
361
|
+
}
|
|
362
|
+
const k = renderKwargsParam(true);
|
|
363
|
+
if (k) {
|
|
364
|
+
rest.push(k);
|
|
365
|
+
}
|
|
366
|
+
overloads.push(`export function ${fname}${typeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`);
|
|
367
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
368
|
+
// Also allow callers to omit the varargs surrogate parameter entirely (i.e. `fn(kwargs)`).
|
|
369
|
+
overloads.push(`export function ${fname}${typeParamDecl}(${[...head, renderKwargsParam(true)].join(', ')}): Promise<${returnType}>;`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (firstOptionalIndex >= 0 && !varArgsParam && !needsKwargsParam) {
|
|
374
|
+
for (let i = firstOptionalIndex; i <= positionalParams.length; i++) {
|
|
375
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p));
|
|
376
|
+
const rest = [];
|
|
377
|
+
const v = renderVarArgsParam(false);
|
|
378
|
+
if (v) {
|
|
379
|
+
rest.push(v);
|
|
380
|
+
}
|
|
381
|
+
const k = renderKwargsParam(false);
|
|
382
|
+
if (k) {
|
|
383
|
+
rest.push(k);
|
|
384
|
+
}
|
|
385
|
+
overloads.push(`export function ${fname}${typeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`);
|
|
128
386
|
}
|
|
129
387
|
}
|
|
130
388
|
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
131
|
-
const
|
|
132
|
-
|
|
389
|
+
const guardLines = [];
|
|
390
|
+
if (hasKwArgs && positionalOnlyNames.length > 0) {
|
|
391
|
+
guardLines.push(` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`);
|
|
392
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
393
|
+
guardLines.push(` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
394
|
+
guardLines.push(` throw new Error(\`${func.name} does not accept positional-only argument "\${key}" as a keyword argument\`);`);
|
|
395
|
+
guardLines.push(` }`);
|
|
396
|
+
guardLines.push(` }`);
|
|
397
|
+
}
|
|
398
|
+
if (hasKwArgs && requiredKwOnlyNames.length > 0) {
|
|
399
|
+
guardLines.push(` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`);
|
|
400
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
401
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
402
|
+
guardLines.push(` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
403
|
+
guardLines.push(` __missing.push(key);`);
|
|
404
|
+
guardLines.push(` }`);
|
|
405
|
+
guardLines.push(` }`);
|
|
406
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
407
|
+
guardLines.push(` throw new Error(\`Missing required keyword-only arguments for ${func.name}: \${__missing.join(', ')}\`);`);
|
|
408
|
+
guardLines.push(` }`);
|
|
409
|
+
}
|
|
410
|
+
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
411
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
412
|
+
const requiredPosCount = firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
413
|
+
const callPreludeLines = [];
|
|
414
|
+
if (hasKwArgs) {
|
|
415
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
416
|
+
}
|
|
417
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
418
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
419
|
+
if (requiredPosCount < positionalParams.length) {
|
|
420
|
+
callPreludeLines.push(` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`);
|
|
421
|
+
callPreludeLines.push(` __args.pop();`);
|
|
422
|
+
callPreludeLines.push(` }`);
|
|
423
|
+
}
|
|
424
|
+
if (hasKwArgs && requiredPosCount < positionalParams.length) {
|
|
425
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
426
|
+
keywordOnlyNames,
|
|
427
|
+
requiredKwOnlyNames,
|
|
428
|
+
hasVarKwArgs,
|
|
429
|
+
});
|
|
430
|
+
callPreludeLines.push(` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`);
|
|
431
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
432
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
433
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
434
|
+
callPreludeLines.push(` __args.pop();`);
|
|
435
|
+
callPreludeLines.push(` }`);
|
|
436
|
+
callPreludeLines.push(` }`);
|
|
437
|
+
}
|
|
438
|
+
if (varArgsParam) {
|
|
439
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
440
|
+
if (needsVarArgsArray) {
|
|
441
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
442
|
+
keywordOnlyNames,
|
|
443
|
+
requiredKwOnlyNames,
|
|
444
|
+
hasVarKwArgs,
|
|
445
|
+
});
|
|
446
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
447
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
448
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
449
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
450
|
+
callPreludeLines.push(` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`);
|
|
451
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
452
|
+
callPreludeLines.push(` } else {`);
|
|
453
|
+
callPreludeLines.push(` throw new Error(\`${func.name} expected ${varArgsParam.name} to be an array\`);`);
|
|
454
|
+
callPreludeLines.push(` }`);
|
|
455
|
+
callPreludeLines.push(` }`);
|
|
456
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
463
|
+
const ts = `${jsdoc}${overloadDecl}export async function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}> {
|
|
464
|
+
${callPrelude}${guards} return getRuntimeBridge().call('${moduleId}', '${func.name}', __args${hasKwArgs ? ', __kwargs' : ''});
|
|
133
465
|
}
|
|
134
466
|
`;
|
|
135
|
-
|
|
467
|
+
const declarationBody = overloads.length > 0
|
|
468
|
+
? overloadDecl
|
|
469
|
+
: `export function ${fname}${typeParamDecl}(${paramDecl}): Promise<${returnType}>;\n`;
|
|
470
|
+
return this.wrap(ts, `${jsdoc}${declarationBody}`, [func.name]);
|
|
136
471
|
}
|
|
137
472
|
generateClassWrapper(cls, moduleName, _annotatedJSDoc = false) {
|
|
138
473
|
const jsdoc = this.generateJsDoc(cls.docstring);
|
|
139
|
-
|
|
474
|
+
const classGenericContext = this.buildGenericRenderContext(this.getTypeParameters(cls.typeParameters), [
|
|
475
|
+
...cls.properties.map(property => property.type),
|
|
476
|
+
...cls.methods.flatMap(method => [
|
|
477
|
+
method.returnType,
|
|
478
|
+
...method.parameters.map(p => p.type),
|
|
479
|
+
]),
|
|
480
|
+
], moduleName);
|
|
481
|
+
const classTypeParamDecl = classGenericContext.declaration;
|
|
482
|
+
const cname = this.escapeIdentifier(cls.name);
|
|
483
|
+
const classSelfType = `${cname}${classGenericContext.typeArguments}`;
|
|
484
|
+
const tsValueType = (p) => this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
485
|
+
const wrapAlias = (body) => {
|
|
486
|
+
const ts = `${jsdoc}export type ${cname}${classTypeParamDecl} = ${body}\n`;
|
|
487
|
+
return this.wrap(ts, ts, [cls.name]);
|
|
488
|
+
};
|
|
140
489
|
if (cls.decorators.includes('__typed_dict__') || cls.kind === 'typed_dict') {
|
|
141
490
|
const props = cls.properties
|
|
142
491
|
.map(p => {
|
|
143
492
|
const pname = this.escapeIdentifier(p.name);
|
|
144
493
|
const opt = p.optional === true ? '?' : '';
|
|
145
|
-
const t = this.
|
|
494
|
+
const t = this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
146
495
|
return `${pname}${opt}: ${t};`;
|
|
147
496
|
})
|
|
148
497
|
.join(' ');
|
|
149
|
-
|
|
150
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
151
|
-
return this.wrap(ts, [cls.name]);
|
|
498
|
+
return wrapAlias(`{ ${props} }`);
|
|
152
499
|
}
|
|
153
500
|
if (cls.kind === 'namedtuple') {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const cname = this.escapeIdentifier(cls.name);
|
|
157
|
-
const ts = `${jsdoc}export type ${cname} = readonly [${elements.join(', ')}]\n`;
|
|
158
|
-
return this.wrap(ts, [cls.name]);
|
|
501
|
+
const elements = cls.properties.map(p => this.typeToTsFromPython(p.type, classGenericContext, 'value'));
|
|
502
|
+
return wrapAlias(`readonly [${elements.join(', ')}]`);
|
|
159
503
|
}
|
|
160
504
|
if (cls.kind === 'protocol') {
|
|
161
|
-
// Protocol -> structural interface-like type alias for attributes and callables (subset)
|
|
162
505
|
const props = cls.properties
|
|
163
|
-
.map(p => `${this.escapeIdentifier(p.name)}: ${this.
|
|
506
|
+
.map(p => `${this.escapeIdentifier(p.name)}: ${this.typeToTsFromPython(p.type, classGenericContext, 'value')};`)
|
|
164
507
|
.join(' ');
|
|
165
508
|
const methods = cls.methods
|
|
509
|
+
.filter(m => m.name !== '__init__')
|
|
166
510
|
.map(m => {
|
|
167
511
|
const fparams = m.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
512
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(m.typeParameters), [m.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
513
|
+
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
514
|
+
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
168
515
|
const paramsDecl = fparams
|
|
169
|
-
.map(p => `${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.
|
|
516
|
+
.map(p => `${this.escapeIdentifier(p.name)}${p.optional ? '?' : ''}: ${this.typeToTsFromPython(p.type, methodGenericContext, 'value')}`)
|
|
170
517
|
.join(', ');
|
|
171
|
-
const returnType = this.
|
|
172
|
-
return `${this.escapeIdentifier(m.name)}: (${paramsDecl}) => ${returnType};`;
|
|
518
|
+
const returnType = this.typeToTsFromPython(m.returnType, methodGenericContext, 'return');
|
|
519
|
+
return `${this.escapeIdentifier(m.name)}: ${methodTypeParamDecl}(${paramsDecl}) => ${returnType};`;
|
|
173
520
|
})
|
|
174
521
|
.join(' ');
|
|
175
|
-
|
|
176
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} ${methods} }\n`;
|
|
177
|
-
return this.wrap(ts, [cls.name]);
|
|
522
|
+
return wrapAlias(`{ ${props} ${methods} }`);
|
|
178
523
|
}
|
|
179
524
|
if (cls.kind === 'dataclass' || cls.kind === 'pydantic') {
|
|
180
|
-
// Data containers -> object type alias
|
|
181
525
|
const props = cls.properties
|
|
182
526
|
.map(p => {
|
|
183
527
|
const pname = this.escapeIdentifier(p.name);
|
|
184
528
|
const opt = p.optional === true ? '?' : '';
|
|
185
|
-
const t = this.
|
|
529
|
+
const t = this.typeToTsFromPython(p.type, classGenericContext, 'value');
|
|
186
530
|
return `${pname}${opt}: ${t};`;
|
|
187
531
|
})
|
|
188
532
|
.join(' ');
|
|
189
|
-
|
|
190
|
-
const ts = `${jsdoc}export type ${cname} = { ${props} }\n`;
|
|
191
|
-
return this.wrap(ts, [cls.name]);
|
|
533
|
+
return wrapAlias(`{ ${props} }`);
|
|
192
534
|
}
|
|
193
535
|
const sortedMethods = [...cls.methods].sort((a, b) => a.name.localeCompare(b.name));
|
|
194
|
-
const methodBodies =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.
|
|
214
|
-
|
|
536
|
+
const methodBodies = [];
|
|
537
|
+
const methodDeclarations = [];
|
|
538
|
+
sortedMethods
|
|
539
|
+
.filter(method => method.name !== '__init__')
|
|
540
|
+
.forEach(method => {
|
|
541
|
+
const fparams = method.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
542
|
+
const methodOwnGenericContext = this.buildGenericRenderContext(this.getTypeParameters(method.typeParameters), [method.returnType, ...fparams.map(param => param.type)], moduleName);
|
|
543
|
+
const methodGenericContext = this.mergeGenericRenderContexts(classGenericContext, methodOwnGenericContext);
|
|
544
|
+
const methodTypeParamDecl = methodOwnGenericContext.declaration;
|
|
545
|
+
const methodTsValueType = (p) => this.typeToTsFromPython(p.type, methodGenericContext, 'value');
|
|
546
|
+
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
547
|
+
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
548
|
+
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
549
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
550
|
+
const varArgsParam = fparams.find(p => p.varArgs);
|
|
551
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
552
|
+
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
553
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
554
|
+
const requiredPosCount = firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
555
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
556
|
+
const renderPositionalParam = (p, forceRequired = false) => {
|
|
557
|
+
const pname = this.escapeIdentifier(p.name);
|
|
558
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
559
|
+
return `${pname}${opt}: ${methodTsValueType(p)}`;
|
|
560
|
+
};
|
|
561
|
+
const kwargsType = (() => {
|
|
562
|
+
if (!needsKwargsParam) {
|
|
563
|
+
return '';
|
|
564
|
+
}
|
|
565
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
566
|
+
return 'Record<string, unknown>';
|
|
567
|
+
}
|
|
568
|
+
const props = keywordOnlyParams
|
|
569
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${methodTsValueType(p)};`)
|
|
570
|
+
.join(' ');
|
|
571
|
+
const obj = `{ ${props} }`;
|
|
572
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
573
|
+
})();
|
|
574
|
+
const paramsDeclParts = [];
|
|
575
|
+
positionalParams.forEach(p => {
|
|
576
|
+
paramsDeclParts.push(renderPositionalParam(p));
|
|
577
|
+
});
|
|
578
|
+
if (varArgsParam) {
|
|
579
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
580
|
+
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
581
|
+
}
|
|
582
|
+
if (needsKwargsParam) {
|
|
583
|
+
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
584
|
+
}
|
|
585
|
+
const paramsDecl = paramsDeclParts.join(', ');
|
|
586
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
587
|
+
const returnType = this.typeToTsFromPython(method.returnType, methodGenericContext, 'return');
|
|
588
|
+
const mname = this.escapeIdentifier(method.name);
|
|
589
|
+
const overloads = [];
|
|
590
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
591
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
592
|
+
const requiredPosCount = firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
593
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
594
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
595
|
+
const rest = [];
|
|
596
|
+
if (varArgsParam) {
|
|
597
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
598
|
+
rest.push(needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`);
|
|
599
|
+
}
|
|
600
|
+
rest.push(`kwargs: ${kwargsType}`);
|
|
601
|
+
overloads.push(` ${mname}${methodTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${returnType}>;`);
|
|
602
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
603
|
+
overloads.push(` ${mname}${methodTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${returnType}>;`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
608
|
+
const callPreludeLines = [];
|
|
609
|
+
if (needsKwargsParam) {
|
|
610
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
611
|
+
}
|
|
612
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
613
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
614
|
+
if (requiredPosCount < positionalParams.length) {
|
|
615
|
+
callPreludeLines.push(` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`);
|
|
616
|
+
callPreludeLines.push(` __args.pop();`);
|
|
617
|
+
callPreludeLines.push(` }`);
|
|
618
|
+
}
|
|
619
|
+
if (needsKwargsParam && requiredPosCount < positionalParams.length) {
|
|
620
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
621
|
+
keywordOnlyNames,
|
|
622
|
+
requiredKwOnlyNames,
|
|
623
|
+
hasVarKwArgs,
|
|
624
|
+
});
|
|
625
|
+
callPreludeLines.push(` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`);
|
|
626
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
627
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
628
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
629
|
+
callPreludeLines.push(` __args.pop();`);
|
|
630
|
+
callPreludeLines.push(` }`);
|
|
631
|
+
callPreludeLines.push(` }`);
|
|
632
|
+
}
|
|
633
|
+
if (varArgsParam) {
|
|
634
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
635
|
+
if (needsVarArgsArray) {
|
|
636
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
637
|
+
keywordOnlyNames,
|
|
638
|
+
requiredKwOnlyNames,
|
|
639
|
+
hasVarKwArgs,
|
|
640
|
+
});
|
|
641
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
642
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
643
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
644
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
645
|
+
callPreludeLines.push(` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`);
|
|
646
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
647
|
+
callPreludeLines.push(` } else {`);
|
|
648
|
+
callPreludeLines.push(` throw new Error(\`${method.name} expected ${varArgsParam.name} to be an array\`);`);
|
|
649
|
+
callPreludeLines.push(` }`);
|
|
650
|
+
callPreludeLines.push(` }`);
|
|
651
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
658
|
+
const guardLines = [];
|
|
659
|
+
if (needsKwargsParam && positionalOnlyNames.length > 0) {
|
|
660
|
+
guardLines.push(` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`);
|
|
661
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
662
|
+
guardLines.push(` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
663
|
+
guardLines.push(` throw new Error(\`${method.name} does not accept positional-only argument "\${key}" as a keyword argument\`);`);
|
|
664
|
+
guardLines.push(` }`);
|
|
665
|
+
guardLines.push(` }`);
|
|
666
|
+
}
|
|
667
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
668
|
+
guardLines.push(` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`);
|
|
669
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
670
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
671
|
+
guardLines.push(` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
672
|
+
guardLines.push(` __missing.push(key);`);
|
|
673
|
+
guardLines.push(` }`);
|
|
674
|
+
guardLines.push(` }`);
|
|
675
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
676
|
+
guardLines.push(` throw new Error(\`Missing required keyword-only arguments for ${method.name}: \${__missing.join(', ')}\`);`);
|
|
677
|
+
guardLines.push(` }`);
|
|
678
|
+
}
|
|
679
|
+
const guards = guardLines.length > 0 ? `${guardLines.join('\n')}\n` : '';
|
|
680
|
+
methodBodies.push(`${overloadDecl} async ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}> {
|
|
681
|
+
${callPrelude}${guards} return getRuntimeBridge().callMethod(this.__handle, '${method.name}', __args${needsKwargsParam ? ', __kwargs' : ''});
|
|
682
|
+
}`);
|
|
683
|
+
methodDeclarations.push(`${overloadDecl}${overloads.length > 0 ? '' : ` ${mname}${methodTypeParamDecl}(${paramsDecl}): Promise<${returnType}>;\n`}`);
|
|
684
|
+
});
|
|
215
685
|
const init = cls.methods.find(m => m.name === '__init__');
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
686
|
+
const ctorSpec = (() => {
|
|
687
|
+
if (!init) {
|
|
688
|
+
return {
|
|
689
|
+
overloadDecl: '',
|
|
690
|
+
declaration: ` static create${classTypeParamDecl}(...args: unknown[]): Promise<${classSelfType}>;\n`,
|
|
691
|
+
paramsDecl: `...args: unknown[]`,
|
|
692
|
+
callPrelude: ` const __args: unknown[] = [...args];\n`,
|
|
693
|
+
hasKwargs: false,
|
|
694
|
+
guardLines: [],
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const fparams = init.parameters.filter(p => p.name !== 'self' && p.name !== 'cls');
|
|
698
|
+
const keywordOnlyParams = fparams.filter(p => p.keywordOnly);
|
|
699
|
+
const positionalOnlyNames = fparams.filter(p => p.positionalOnly).map(p => p.name);
|
|
700
|
+
const hasVarKwArgs = fparams.some(p => p.kwArgs);
|
|
701
|
+
const needsKwargsParam = keywordOnlyParams.length > 0 || hasVarKwArgs;
|
|
702
|
+
const varArgsParam = fparams.find(p => p.varArgs);
|
|
703
|
+
const needsVarArgsArray = Boolean(varArgsParam) && needsKwargsParam;
|
|
704
|
+
const positionalParams = fparams.filter(p => !p.keywordOnly && !p.varArgs && !p.kwArgs);
|
|
705
|
+
const firstOptionalPosIndex = positionalParams.findIndex(p => p.optional);
|
|
706
|
+
const requiredPosCount = firstOptionalPosIndex >= 0 ? firstOptionalPosIndex : positionalParams.length;
|
|
707
|
+
const keywordOnlyNames = keywordOnlyParams.map(p => p.name);
|
|
708
|
+
const renderPositionalParam = (p, forceRequired = false) => {
|
|
709
|
+
const pname = this.escapeIdentifier(p.name);
|
|
710
|
+
const opt = !forceRequired && p.optional ? '?' : '';
|
|
711
|
+
return `${pname}${opt}: ${tsValueType(p)}`;
|
|
712
|
+
};
|
|
713
|
+
const kwargsType = (() => {
|
|
714
|
+
if (!needsKwargsParam) {
|
|
715
|
+
return '';
|
|
716
|
+
}
|
|
717
|
+
if (keywordOnlyParams.length === 0 && hasVarKwArgs) {
|
|
718
|
+
return 'Record<string, unknown>';
|
|
719
|
+
}
|
|
720
|
+
const props = keywordOnlyParams
|
|
721
|
+
.map(p => `${JSON.stringify(p.name)}${p.optional ? '?' : ''}: ${tsValueType(p)};`)
|
|
722
|
+
.join(' ');
|
|
723
|
+
const obj = `{ ${props} }`;
|
|
724
|
+
return hasVarKwArgs ? `(${obj} & Record<string, unknown>)` : obj;
|
|
725
|
+
})();
|
|
726
|
+
const paramsDeclParts = [];
|
|
727
|
+
positionalParams.forEach(p => {
|
|
728
|
+
paramsDeclParts.push(renderPositionalParam(p));
|
|
729
|
+
});
|
|
730
|
+
if (varArgsParam) {
|
|
731
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
732
|
+
paramsDeclParts.push(needsVarArgsArray ? `${vname}?: unknown[]` : `...${vname}: unknown[]`);
|
|
733
|
+
}
|
|
734
|
+
if (needsKwargsParam) {
|
|
735
|
+
paramsDeclParts.push(`kwargs?: ${kwargsType}`);
|
|
736
|
+
}
|
|
737
|
+
const paramsDecl = paramsDeclParts.join(', ');
|
|
738
|
+
const requiredKwOnlyNames = keywordOnlyParams.filter(p => !p.optional).map(p => p.name);
|
|
739
|
+
const overloads = [];
|
|
740
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
741
|
+
const firstOptionalIndex = positionalParams.findIndex(p => p.optional);
|
|
742
|
+
const requiredPosCount = firstOptionalIndex >= 0 ? firstOptionalIndex : positionalParams.length;
|
|
743
|
+
for (let i = requiredPosCount; i <= positionalParams.length; i++) {
|
|
744
|
+
const head = positionalParams.slice(0, i).map(p => renderPositionalParam(p, true));
|
|
745
|
+
const rest = [];
|
|
746
|
+
if (varArgsParam) {
|
|
747
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
748
|
+
rest.push(needsVarArgsArray ? `${vname}: unknown[] | undefined` : `...${vname}: unknown[]`);
|
|
749
|
+
}
|
|
750
|
+
rest.push(`kwargs: ${kwargsType}`);
|
|
751
|
+
overloads.push(` static create${classTypeParamDecl}(${[...head, ...rest].join(', ')}): Promise<${classSelfType}>;`);
|
|
752
|
+
if (varArgsParam && needsVarArgsArray) {
|
|
753
|
+
overloads.push(` static create${classTypeParamDecl}(${[...head, `kwargs: ${kwargsType}`].join(', ')}): Promise<${classSelfType}>;`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const overloadDecl = overloads.length > 0 ? `${overloads.join('\n')}\n` : '';
|
|
758
|
+
const declaration = overloads.length > 0
|
|
759
|
+
? overloadDecl
|
|
760
|
+
: ` static create${classTypeParamDecl}(${paramsDecl}): Promise<${classSelfType}>;\n`;
|
|
761
|
+
const guardLines = [];
|
|
762
|
+
const callPreludeLines = [];
|
|
763
|
+
if (needsKwargsParam) {
|
|
764
|
+
callPreludeLines.push(` let __kwargs = kwargs;`);
|
|
765
|
+
}
|
|
766
|
+
const positionalArgExprs = positionalParams.map(p => this.escapeIdentifier(p.name));
|
|
767
|
+
callPreludeLines.push(` const __args: unknown[] = [${positionalArgExprs.join(', ')}];`);
|
|
768
|
+
if (requiredPosCount < positionalParams.length) {
|
|
769
|
+
callPreludeLines.push(` while (__args.length > ${requiredPosCount} && __args[__args.length - 1] === undefined) {`);
|
|
770
|
+
callPreludeLines.push(` __args.pop();`);
|
|
771
|
+
callPreludeLines.push(` }`);
|
|
772
|
+
}
|
|
773
|
+
if (needsKwargsParam && requiredPosCount < positionalParams.length) {
|
|
774
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr('__candidate', {
|
|
775
|
+
keywordOnlyNames,
|
|
776
|
+
requiredKwOnlyNames,
|
|
777
|
+
hasVarKwArgs,
|
|
778
|
+
});
|
|
779
|
+
callPreludeLines.push(` if (__kwargs === undefined && __args.length > ${requiredPosCount}) {`);
|
|
780
|
+
callPreludeLines.push(` const __candidate = __args[__args.length - 1];`);
|
|
781
|
+
callPreludeLines.push(` if (${looksLikeKwargs}) {`);
|
|
782
|
+
callPreludeLines.push(` __kwargs = __candidate as any;`);
|
|
783
|
+
callPreludeLines.push(` __args.pop();`);
|
|
784
|
+
callPreludeLines.push(` }`);
|
|
785
|
+
callPreludeLines.push(` }`);
|
|
786
|
+
}
|
|
787
|
+
if (varArgsParam) {
|
|
788
|
+
const vname = this.escapeIdentifier(varArgsParam.name);
|
|
789
|
+
if (needsVarArgsArray) {
|
|
790
|
+
const looksLikeKwargs = this.renderLooksLikeKwargsExpr(vname, {
|
|
791
|
+
keywordOnlyNames,
|
|
792
|
+
requiredKwOnlyNames,
|
|
793
|
+
hasVarKwArgs,
|
|
794
|
+
});
|
|
795
|
+
callPreludeLines.push(` let __varargs: unknown[] = [];`);
|
|
796
|
+
callPreludeLines.push(` if (${vname} !== undefined) {`);
|
|
797
|
+
callPreludeLines.push(` if (globalThis.Array.isArray(${vname})) {`);
|
|
798
|
+
callPreludeLines.push(` __varargs = ${vname};`);
|
|
799
|
+
callPreludeLines.push(` } else if (__kwargs === undefined && ${looksLikeKwargs}) {`);
|
|
800
|
+
callPreludeLines.push(` __kwargs = ${vname} as any;`);
|
|
801
|
+
callPreludeLines.push(` } else {`);
|
|
802
|
+
callPreludeLines.push(` throw new Error(\`__init__ expected ${varArgsParam.name} to be an array\`);`);
|
|
803
|
+
callPreludeLines.push(` }`);
|
|
804
|
+
callPreludeLines.push(` }`);
|
|
805
|
+
callPreludeLines.push(` __args.push(...__varargs);`);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
callPreludeLines.push(` __args.push(...${vname});`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const callPrelude = callPreludeLines.length > 0 ? `${callPreludeLines.join('\n')}\n` : '';
|
|
812
|
+
if (needsKwargsParam && positionalOnlyNames.length > 0) {
|
|
813
|
+
guardLines.push(` const __positionalOnly = ${JSON.stringify(positionalOnlyNames)} as const;`);
|
|
814
|
+
guardLines.push(` for (const key of __positionalOnly) {`);
|
|
815
|
+
guardLines.push(` if (__kwargs && Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
816
|
+
guardLines.push(` throw new Error(\`__init__ does not accept positional-only argument "\${key}" as a keyword argument\`);`);
|
|
817
|
+
guardLines.push(` }`);
|
|
818
|
+
guardLines.push(` }`);
|
|
819
|
+
}
|
|
820
|
+
if (needsKwargsParam && requiredKwOnlyNames.length > 0) {
|
|
821
|
+
guardLines.push(` const __requiredKwOnly = ${JSON.stringify(requiredKwOnlyNames)} as const;`);
|
|
822
|
+
guardLines.push(` const __missing: string[] = [];`);
|
|
823
|
+
guardLines.push(` for (const key of __requiredKwOnly) {`);
|
|
824
|
+
guardLines.push(` if (!__kwargs || !Object.prototype.hasOwnProperty.call(__kwargs, key)) {`);
|
|
825
|
+
guardLines.push(` __missing.push(key);`);
|
|
826
|
+
guardLines.push(` }`);
|
|
827
|
+
guardLines.push(` }`);
|
|
828
|
+
guardLines.push(` if (__missing.length > 0) {`);
|
|
829
|
+
guardLines.push(` throw new Error(\`Missing required keyword-only arguments for __init__: \${__missing.join(', ')}\`);`);
|
|
830
|
+
guardLines.push(` }`);
|
|
831
|
+
}
|
|
832
|
+
return {
|
|
833
|
+
overloadDecl,
|
|
834
|
+
declaration,
|
|
835
|
+
paramsDecl,
|
|
836
|
+
callPrelude,
|
|
837
|
+
hasKwargs: needsKwargsParam,
|
|
838
|
+
guardLines,
|
|
839
|
+
};
|
|
840
|
+
})();
|
|
236
841
|
const moduleId = moduleName ?? '__main__';
|
|
237
|
-
const methodsSection = methodBodies ? `\n${methodBodies}\n` : '\n';
|
|
238
|
-
const
|
|
842
|
+
const methodsSection = methodBodies.length > 0 ? `\n${methodBodies.join('\n')}\n` : '\n';
|
|
843
|
+
const declarationMethodsSection = methodDeclarations.length > 0 ? `\n${methodDeclarations.join('')}\n` : '\n';
|
|
844
|
+
const ctorGuards = ctorSpec.guardLines.length > 0 ? `${ctorSpec.guardLines.join('\n')}\n` : '';
|
|
845
|
+
const newClassExpr = `new ${cname}${classGenericContext.typeArguments}(handle)`;
|
|
846
|
+
const ts = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
239
847
|
private readonly __handle: string;
|
|
240
848
|
private constructor(handle: string) { this.__handle = handle; }
|
|
241
|
-
static async create(${
|
|
242
|
-
const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}',
|
|
243
|
-
return
|
|
849
|
+
${ctorSpec.overloadDecl} static async create${classTypeParamDecl}(${ctorSpec.paramsDecl}): Promise<${classSelfType}> {
|
|
850
|
+
${ctorSpec.callPrelude}${ctorGuards} const handle = await getRuntimeBridge().instantiate<string>('${moduleId}', '${cls.name}', __args${ctorSpec.hasKwargs ? ', __kwargs' : ''});
|
|
851
|
+
return ${newClassExpr};
|
|
244
852
|
}
|
|
245
|
-
static fromHandle(handle: string): ${
|
|
853
|
+
static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType} { return ${newClassExpr}; }${methodsSection} async disposeHandle(): Promise<void> { await getRuntimeBridge().disposeInstance(this.__handle); }
|
|
246
854
|
}
|
|
247
855
|
`;
|
|
248
|
-
|
|
856
|
+
const declaration = `${jsdoc}export class ${cname}${classTypeParamDecl} {
|
|
857
|
+
private readonly __handle: string;
|
|
858
|
+
private constructor(handle: string);
|
|
859
|
+
${ctorSpec.declaration} static fromHandle${classTypeParamDecl}(handle: string): ${classSelfType};${declarationMethodsSection} disposeHandle(): Promise<void>;
|
|
860
|
+
}
|
|
861
|
+
`;
|
|
862
|
+
return this.wrap(ts, declaration, [cls.name]);
|
|
863
|
+
}
|
|
864
|
+
generateTypeAlias(alias, moduleName) {
|
|
865
|
+
const genericContext = this.buildGenericRenderContext(this.getTypeParameters(alias.typeParameters), [alias.type], moduleName);
|
|
866
|
+
const aliasName = this.escapeIdentifier(alias.name, { preserveCase: true });
|
|
867
|
+
const body = this.typeToTsFromPython(alias.type, genericContext, 'value');
|
|
868
|
+
const ts = `export type ${aliasName}${genericContext.declaration} = ${body}\n`;
|
|
869
|
+
return this.wrap(ts, ts, [alias.name]);
|
|
249
870
|
}
|
|
250
871
|
/**
|
|
251
872
|
* Generate TypeScript wrapper for Python module with caching
|
|
@@ -266,23 +887,33 @@ export class CodeGenerator {
|
|
|
266
887
|
return result;
|
|
267
888
|
}
|
|
268
889
|
generateModuleDefinition(module, annotatedJSDoc = false) {
|
|
269
|
-
const
|
|
890
|
+
const functionResults = [...module.functions]
|
|
891
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
892
|
+
.map(f => this.generateFunctionWrapper(f, module.name, annotatedJSDoc));
|
|
893
|
+
const classResults = [...module.classes]
|
|
270
894
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
271
|
-
.map(
|
|
272
|
-
|
|
273
|
-
const classCodes = [...module.classes]
|
|
895
|
+
.map(c => this.generateClassWrapper(c, module.name, annotatedJSDoc));
|
|
896
|
+
const typeAliasResults = [...(module.typeAliases ?? [])]
|
|
274
897
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
275
|
-
.map(
|
|
276
|
-
|
|
898
|
+
.map(alias => this.generateTypeAlias(alias, module.name));
|
|
899
|
+
const functionCodes = functionResults.map(result => result.typescript).join('\n');
|
|
900
|
+
const classCodes = classResults.map(result => result.typescript).join('\n');
|
|
901
|
+
const typeAliasCodes = typeAliasResults.map(result => result.typescript).join('\n');
|
|
277
902
|
const header = `// Generated by tywrap\n// Module: ${module.name}\n// DO NOT EDIT MANUALLY\n\n`;
|
|
903
|
+
const declarationHeader = `// Generated by tywrap\n// Type Declarations\n// DO NOT EDIT MANUALLY\n\n`;
|
|
278
904
|
const hasRuntimeClasses = module.classes.some(c => {
|
|
279
905
|
const kind = c.kind ?? 'class';
|
|
280
906
|
return kind === 'class' && !c.decorators.includes('__typed_dict__');
|
|
281
907
|
});
|
|
282
908
|
const needsRuntime = module.functions.length > 0 || hasRuntimeClasses;
|
|
283
909
|
const bridgeDecl = needsRuntime ? `import { getRuntimeBridge } from 'tywrap/runtime';\n\n` : '';
|
|
284
|
-
const ts = `${header
|
|
285
|
-
|
|
910
|
+
const ts = `${`${header}${bridgeDecl}${functionCodes}\n${classCodes}\n${typeAliasCodes}`.trimEnd()}\n`;
|
|
911
|
+
const declaration = `${`${declarationHeader}${functionResults
|
|
912
|
+
.map(result => result.declaration)
|
|
913
|
+
.join('\n')}\n${classResults
|
|
914
|
+
.map(result => result.declaration)
|
|
915
|
+
.join('\n')}\n${typeAliasResults.map(result => result.declaration).join('\n')}`.trimEnd()}\n`;
|
|
916
|
+
return this.wrap(ts, declaration, [module.name]);
|
|
286
917
|
}
|
|
287
918
|
generateJsDoc(doc, paramAnnotations) {
|
|
288
919
|
const lines = [];
|
|
@@ -300,10 +931,10 @@ export class CodeGenerator {
|
|
|
300
931
|
}
|
|
301
932
|
return `/**\n${lines.join('\n')}\n */\n`;
|
|
302
933
|
}
|
|
303
|
-
wrap(typescript, _sources) {
|
|
934
|
+
wrap(typescript, declaration, _sources) {
|
|
304
935
|
return {
|
|
305
936
|
typescript,
|
|
306
|
-
declaration
|
|
937
|
+
declaration,
|
|
307
938
|
sourceMap: undefined,
|
|
308
939
|
metadata: {
|
|
309
940
|
generatedAt: new Date(),
|
|
@@ -358,6 +989,9 @@ export class CodeGenerator {
|
|
|
358
989
|
}
|
|
359
990
|
case 'custom': {
|
|
360
991
|
const c = type;
|
|
992
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(c.name)) {
|
|
993
|
+
return 'unknown';
|
|
994
|
+
}
|
|
361
995
|
return c.name;
|
|
362
996
|
}
|
|
363
997
|
case 'literal': {
|