typenative 0.0.16 → 0.0.18

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/bin/transpiler.js CHANGED
@@ -9,6 +9,14 @@ let promiseResolveName = '';
9
9
  const dangerousNames = new Set(['main']);
10
10
  const renamedFunctions = new Map();
11
11
  const variableTypes = new Map();
12
+ const variableGoTypes = new Map();
13
+ const variableClassNames = new Map();
14
+ const classPropertyTypes = new Map();
15
+ const classMethodReturnTypes = new Map();
16
+ const interfacePropertyTypes = new Map();
17
+ const typeAliases = new Map();
18
+ const enumNames = new Set();
19
+ const enumBaseTypes = new Map();
12
20
  export function transpileToNative(code) {
13
21
  const sourceFile = ts.createSourceFile('main.ts', code, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
14
22
  TypeCheker = ts.createProgram(['main.ts'], {}).getTypeChecker();
@@ -18,16 +26,24 @@ export function transpileToNative(code) {
18
26
  promiseResolveName = '';
19
27
  renamedFunctions.clear();
20
28
  variableTypes.clear();
29
+ variableGoTypes.clear();
30
+ variableClassNames.clear();
31
+ classPropertyTypes.clear();
32
+ classMethodReturnTypes.clear();
33
+ interfacePropertyTypes.clear();
34
+ typeAliases.clear();
35
+ enumNames.clear();
36
+ enumBaseTypes.clear();
21
37
  const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
22
38
  const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
23
- return `package main
24
-
25
- ${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
26
-
27
- func main() {
28
- ${transpiledCode.trim()}
29
- }
30
-
39
+ return `package main
40
+
41
+ ${[...importedPackages].map((pkg) => `import "${pkg}"`).join('\n')}
42
+
43
+ func main() {
44
+ ${transpiledCode.trim()}
45
+ }
46
+
31
47
  ${transpiledCodeOutside.trim()}`;
32
48
  }
33
49
  export function visit(node, options = {}) {
@@ -43,8 +59,17 @@ export function visit(node, options = {}) {
43
59
  return 'nil';
44
60
  return getSafeName(node.text);
45
61
  }
46
- else if (ts.isStringLiteral(node)) {
47
- return `"${node.text}"`;
62
+ else if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
63
+ return toGoStringLiteral(node.text);
64
+ }
65
+ else if (ts.isAsExpression(node)) {
66
+ return visit(node.expression);
67
+ }
68
+ else if (ts.isTypeAssertionExpression(node)) {
69
+ return visit(node.expression);
70
+ }
71
+ else if (ts.isTemplateExpression(node)) {
72
+ return visitTemplateExpression(node);
48
73
  }
49
74
  else if (ts.isNumericLiteral(node)) {
50
75
  return `float64(${node.text})`;
@@ -73,12 +98,23 @@ export function visit(node, options = {}) {
73
98
  return `[]${type} {${node.elements.map((e) => visit(e)).join(', ')}}`;
74
99
  }
75
100
  else if (ts.isBlock(node)) {
76
- return `{\n\t\t${node.statements.map((n) => visit(n)).join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
101
+ return `{\n\t\t${options.prefixBlockContent ?? ''}${node.statements
102
+ .map((n) => visit(n))
103
+ .join('\t')}${options.extraBlockContent ?? ''}}${options.inline ? '' : '\n\t'}`;
77
104
  }
78
105
  else if (ts.isElementAccessExpression(node)) {
106
+ if (hasQuestionDot(node)) {
107
+ return visitOptionalElementAccess(node);
108
+ }
79
109
  return `${visit(node.expression)}[int(${visit(node.argumentExpression)})]`;
80
110
  }
81
111
  else if (ts.isPropertyAccessExpression(node)) {
112
+ if (hasQuestionDot(node)) {
113
+ return visitOptionalPropertyAccess(node);
114
+ }
115
+ if (ts.isIdentifier(node.expression) && enumNames.has(node.expression.text)) {
116
+ return `${getSafeName(node.expression.text)}_${getEnumMemberName(node.name)}`;
117
+ }
82
118
  const leftSide = visit(node.expression);
83
119
  const rightSide = visit(node.name);
84
120
  const objectType = resolveExpressionType(node.expression);
@@ -88,9 +124,18 @@ export function visit(node, options = {}) {
88
124
  const type = getType(node.type);
89
125
  // Track variable type for type-aware method dispatch
90
126
  if (ts.isIdentifier(node.name)) {
127
+ if (node.type) {
128
+ variableGoTypes.set(node.name.text, getType(node.type));
129
+ }
91
130
  const cat = node.type ? getTypeCategory(node.type) : undefined;
92
131
  if (cat) {
93
132
  variableTypes.set(node.name.text, cat);
133
+ if (cat === 'class' && node.type) {
134
+ const className = getClassNameFromTypeNode(node.type);
135
+ if (className) {
136
+ variableClassNames.set(node.name.text, className);
137
+ }
138
+ }
94
139
  }
95
140
  else if (node.initializer &&
96
141
  ts.isNewExpression(node.initializer) &&
@@ -100,11 +145,18 @@ export function visit(node, options = {}) {
100
145
  }
101
146
  else if (classNames.has(node.initializer.expression.text)) {
102
147
  variableTypes.set(node.name.text, 'class');
148
+ variableClassNames.set(node.name.text, node.initializer.expression.text);
103
149
  }
104
150
  }
105
151
  else if (node.initializer && ts.isRegularExpressionLiteral(node.initializer)) {
106
152
  variableTypes.set(node.name.text, 'RegExp');
107
153
  }
154
+ if (!variableGoTypes.has(node.name.text) && node.initializer) {
155
+ const inferredType = inferExpressionType(node.initializer);
156
+ if (inferredType) {
157
+ variableGoTypes.set(node.name.text, inferredType);
158
+ }
159
+ }
108
160
  }
109
161
  let initializer = node.initializer ? `= ${visit(node.initializer)}` : '';
110
162
  // Wrap non-nil values assigned to nullable primitive pointer types
@@ -119,6 +171,10 @@ export function visit(node, options = {}) {
119
171
  return `${type === ':' ? '' : 'var '}${visit(node.name)} ${type}${type === ':' ? '' : ' '}${initializer}`;
120
172
  }
121
173
  else if (ts.isCallExpression(node)) {
174
+ if (hasQuestionDot(node) ||
175
+ (ts.isPropertyAccessExpression(node.expression) && hasQuestionDot(node.expression))) {
176
+ return visitOptionalCall(node);
177
+ }
122
178
  // Handle setTimeout specially to get raw delay value
123
179
  if (ts.isIdentifier(node.expression) && node.expression.text === 'setTimeout') {
124
180
  importedPackages.add('time');
@@ -127,6 +183,10 @@ export function visit(node, options = {}) {
127
183
  const delay = ts.isNumericLiteral(delayNode) ? delayNode.text : visit(delayNode);
128
184
  return `time.AfterFunc(${delay} * time.Millisecond, ${callback.trimEnd()})`;
129
185
  }
186
+ const arrayHigherOrderCall = visitArrayHigherOrderCall(node);
187
+ if (arrayHigherOrderCall) {
188
+ return arrayHigherOrderCall;
189
+ }
130
190
  const caller = visit(node.expression);
131
191
  const safeCaller = getSafeName(caller);
132
192
  const typeArgs = getTypeArguments(node.typeArguments);
@@ -144,7 +204,13 @@ export function visit(node, options = {}) {
144
204
  else if (ts.isPostfixUnaryExpression(node)) {
145
205
  return `${visit(node.operand, { inline: true })}${getOperatorText(node.operator)}`;
146
206
  }
207
+ else if (ts.isConditionalExpression(node)) {
208
+ return visitConditionalExpression(node);
209
+ }
147
210
  else if (ts.isBinaryExpression(node)) {
211
+ if (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
212
+ return visitNullishCoalescingExpression(node);
213
+ }
148
214
  let op = node.operatorToken.getText();
149
215
  if (op === '===')
150
216
  op = '==';
@@ -171,9 +237,23 @@ export function visit(node, options = {}) {
171
237
  })}; ${visit(node.incrementor, { inline: true })}${visit(node.statement)}`;
172
238
  }
173
239
  else if (ts.isForOfStatement(node)) {
174
- return `for _,${visit(node.initializer, { inline: true })}= range ${visit(node.expression, {
175
- inline: true
176
- })}${visit(node.statement)}`;
240
+ const iterExpr = visit(node.expression, { inline: true });
241
+ const iterType = inferExpressionType(node.expression);
242
+ if (iterType && iterType.startsWith('map[')) {
243
+ const valueType = extractMapValueType(iterType);
244
+ const isSet = valueType === 'struct{}';
245
+ const varInfo = getForOfVarNames(node.initializer);
246
+ if (isSet) {
247
+ return `for ${varInfo[0]} := range ${iterExpr}${visit(node.statement)}`;
248
+ }
249
+ else if (varInfo.length >= 2) {
250
+ return `for ${varInfo[0]}, ${varInfo[1]} := range ${iterExpr}${visit(node.statement)}`;
251
+ }
252
+ else {
253
+ return `for ${varInfo[0]} := range ${iterExpr}${visit(node.statement)}`;
254
+ }
255
+ }
256
+ return `for _,${visit(node.initializer, { inline: true })}= range ${iterExpr}${visit(node.statement)}`;
177
257
  }
178
258
  else if (ts.isWhileStatement(node)) {
179
259
  return `for ${visit(node.expression, { inline: true })}${visit(node.statement)}`;
@@ -215,6 +295,20 @@ export function visit(node, options = {}) {
215
295
  else if (ts.isBreakStatement(node)) {
216
296
  return 'break';
217
297
  }
298
+ else if (ts.isThrowStatement(node)) {
299
+ const expr = node.expression;
300
+ if (ts.isNewExpression(expr) &&
301
+ ts.isIdentifier(expr.expression) &&
302
+ expr.expression.text === 'Error') {
303
+ const args = expr.arguments ?? [];
304
+ const msg = args.length > 0 ? visit(args[0]) : '""';
305
+ return `panic(${msg})` + (options.inline ? '' : ';\n\t');
306
+ }
307
+ return `panic(${visit(expr)})` + (options.inline ? '' : ';\n\t');
308
+ }
309
+ else if (ts.isTryStatement(node)) {
310
+ return visitTryStatement(node, options);
311
+ }
218
312
  else if (ts.isReturnStatement(node)) {
219
313
  // Handle return new Promise(...)
220
314
  if (node.expression &&
@@ -230,31 +324,74 @@ export function visit(node, options = {}) {
230
324
  outsideNodes.push(node);
231
325
  return '';
232
326
  }
233
- const name = visit(node.name, { inline: true });
234
- const safeName = getSafeName(name);
235
327
  const typeParams = getTypeParameters(node.typeParameters);
236
- const parameters = node.parameters
237
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
238
- .join(', ');
239
- const returnType = node.type ? ` ${getType(node.type)}` : '';
328
+ const parameterInfo = getFunctionParametersInfo(node.parameters);
329
+ if (node.body && ts.isBlock(node.body)) {
330
+ prescanVariableDeclarations(node.body);
331
+ }
332
+ const inferredRetType = inferFunctionBodyReturnType(node);
333
+ const returnType = inferredRetType ? ` ${inferredRetType}` : '';
240
334
  if (options.isOutside) {
241
- return `func ${safeName}${typeParams}(${parameters})${returnType} ${visit(node.body)}`;
335
+ const name = node.name ? visit(node.name, { inline: true }) : '';
336
+ const safeName = getSafeName(name);
337
+ return `func ${safeName}${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
338
+ prefixBlockContent: parameterInfo.prefixBlockContent
339
+ })}`;
242
340
  }
243
- return `${safeName} := func${typeParams}(${parameters})${returnType} ${visit(node.body)}`;
341
+ if (!node.name) {
342
+ return `func${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
343
+ prefixBlockContent: parameterInfo.prefixBlockContent
344
+ })}`;
345
+ }
346
+ const name = visit(node.name, { inline: true });
347
+ const safeName = getSafeName(name);
348
+ return `${safeName} := func${typeParams}(${parameterInfo.signature})${returnType} ${visit(node.body, {
349
+ prefixBlockContent: parameterInfo.prefixBlockContent
350
+ })}`;
244
351
  }
245
352
  else if (ts.isArrowFunction(node)) {
246
- const parameters = node.parameters
247
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
248
- .join(', ');
249
- const returnType = node.type ? ` ${getType(node.type)}` : '';
250
- return `func(${parameters})${returnType} ${visit(node.body)}`;
353
+ const parameterInfo = getFunctionParametersInfo(node.parameters);
354
+ const inferredRetType = inferFunctionBodyReturnType(node);
355
+ const returnType = inferredRetType ? ` ${inferredRetType}` : '';
356
+ if (parameterInfo.prefixBlockContent && !ts.isBlock(node.body)) {
357
+ return `func(${parameterInfo.signature})${returnType} {\n\t\t${parameterInfo.prefixBlockContent}return ${visit(node.body)};\n\t}`;
358
+ }
359
+ if (!ts.isBlock(node.body)) {
360
+ return `func(${parameterInfo.signature})${returnType} { return ${visit(node.body)}; }`;
361
+ }
362
+ return `func(${parameterInfo.signature})${returnType} ${visit(node.body, {
363
+ prefixBlockContent: parameterInfo.prefixBlockContent
364
+ })}`;
251
365
  }
252
366
  else if (node.kind === ts.SyntaxKind.ThisKeyword) {
253
367
  return 'self';
254
368
  }
369
+ else if (ts.isEnumDeclaration(node)) {
370
+ const enumName = node.name.text;
371
+ enumNames.add(enumName);
372
+ enumBaseTypes.set(enumName, getEnumBaseType(node));
373
+ if (options.addFunctionOutside) {
374
+ outsideNodes.push(node);
375
+ return '';
376
+ }
377
+ return visitEnumDeclaration(node);
378
+ }
379
+ else if (ts.isTypeAliasDeclaration(node)) {
380
+ typeAliases.set(node.name.text, node.type);
381
+ return '';
382
+ }
255
383
  else if (ts.isInterfaceDeclaration(node)) {
256
384
  if (options.addFunctionOutside) {
257
385
  outsideNodes.push(node);
386
+ const properties = new Map();
387
+ for (const member of node.members) {
388
+ if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
389
+ properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
390
+ }
391
+ }
392
+ if (properties.size > 0) {
393
+ interfacePropertyTypes.set(visit(node.name), properties);
394
+ }
258
395
  return '';
259
396
  }
260
397
  const name = visit(node.name);
@@ -270,6 +407,7 @@ export function visit(node, options = {}) {
270
407
  }
271
408
  }
272
409
  const methods = [];
410
+ const properties = [];
273
411
  for (const member of node.members) {
274
412
  if (ts.isMethodSignature(member)) {
275
413
  const methodName = visit(member.name);
@@ -279,6 +417,13 @@ export function visit(node, options = {}) {
279
417
  const returnType = member.type ? ` ${getType(member.type)}` : '';
280
418
  methods.push(`\t${methodName}(${params})${returnType}`);
281
419
  }
420
+ else if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
421
+ properties.push(`\t${member.name.text} ${getOptionalNodeType(member.type, !!member.questionToken)}`);
422
+ }
423
+ }
424
+ if (properties.length > 0 && methods.length === 0) {
425
+ const fields = [...extendedInterfaces.map((e) => `\t${e}`), ...properties];
426
+ return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
282
427
  }
283
428
  const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
284
429
  return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
@@ -286,7 +431,20 @@ export function visit(node, options = {}) {
286
431
  else if (ts.isClassDeclaration(node)) {
287
432
  if (options.addFunctionOutside) {
288
433
  outsideNodes.push(node);
289
- classNames.add(visit(node.name));
434
+ const className = visit(node.name);
435
+ classNames.add(className);
436
+ const properties = new Map();
437
+ const methods = new Map();
438
+ for (const member of node.members) {
439
+ if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
440
+ properties.set(member.name.text, getOptionalNodeType(member.type, !!member.questionToken));
441
+ }
442
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
443
+ methods.set(member.name.text, member.type ? getType(member.type) : 'interface{}');
444
+ }
445
+ }
446
+ classPropertyTypes.set(className, properties);
447
+ classMethodReturnTypes.set(className, methods);
290
448
  return '';
291
449
  }
292
450
  const name = visit(node.name);
@@ -312,7 +470,7 @@ export function visit(node, options = {}) {
312
470
  fieldType = `[]${getType(member.type, true)}`;
313
471
  }
314
472
  else {
315
- fieldType = member.type ? getType(member.type) : 'interface{}';
473
+ fieldType = getOptionalNodeType(member.type, !!member.questionToken);
316
474
  }
317
475
  fields.push(`\t${fieldName} ${fieldType}`);
318
476
  }
@@ -320,7 +478,7 @@ export function visit(node, options = {}) {
320
478
  let result = `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}\n\n`;
321
479
  const ctor = node.members.find((m) => ts.isConstructorDeclaration(m));
322
480
  if (ctor) {
323
- const params = ctor.parameters.map((p) => `${visit(p.name)} ${getType(p.type)}`).join(', ');
481
+ const ctorParameterInfo = getFunctionParametersInfo(ctor.parameters);
324
482
  const bodyStatements = ctor.body?.statements
325
483
  .filter((s) => {
326
484
  if (ts.isExpressionStatement(s) && ts.isCallExpression(s.expression)) {
@@ -330,7 +488,7 @@ export function visit(node, options = {}) {
330
488
  })
331
489
  .map((s) => visit(s))
332
490
  .join('\t') ?? '';
333
- result += `func New${name}${typeParams}(${params}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${bodyStatements}return self;\n\t}\n\n`;
491
+ result += `func New${name}${typeParams}(${ctorParameterInfo.signature}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${bodyStatements}return self;\n\t}\n\n`;
334
492
  }
335
493
  else {
336
494
  result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\treturn &${name}${typeParamNames}{}\n\t}\n\n`;
@@ -338,11 +496,11 @@ export function visit(node, options = {}) {
338
496
  for (const member of node.members) {
339
497
  if (ts.isMethodDeclaration(member)) {
340
498
  const methodName = visit(member.name);
341
- const params = member.parameters
342
- .map((p) => `${visit(p.name)} ${getType(p.type)}`)
343
- .join(', ');
499
+ const methodParameterInfo = getFunctionParametersInfo(member.parameters);
344
500
  const returnType = member.type ? ` ${getType(member.type)}` : '';
345
- result += `func (self *${name}${typeParamNames}) ${methodName}(${params})${returnType} ${visit(member.body)}\n\n`;
501
+ result += `func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, {
502
+ prefixBlockContent: methodParameterInfo.prefixBlockContent
503
+ })}\n\n`;
346
504
  }
347
505
  }
348
506
  return result.trim();
@@ -365,6 +523,12 @@ export function visit(node, options = {}) {
365
523
  }
366
524
  return `regexp.MustCompile("")`;
367
525
  }
526
+ if (className === 'Map') {
527
+ return visitNewMap(node);
528
+ }
529
+ if (className === 'Set') {
530
+ return visitNewSet(node);
531
+ }
368
532
  const typeArgs = getTypeArguments(node.typeArguments);
369
533
  const args = node.arguments ? node.arguments.map((a) => visit(a)) : [];
370
534
  return `New${className}${typeArgs}(${args.join(', ')})`;
@@ -408,9 +572,552 @@ function getTypeText(typeNode) {
408
572
  }
409
573
  return getType(typeNode);
410
574
  }
575
+ function toGoStringLiteral(value) {
576
+ return JSON.stringify(value);
577
+ }
578
+ function visitTemplateExpression(node) {
579
+ const parts = [];
580
+ if (node.head.text.length > 0) {
581
+ parts.push(toGoStringLiteral(node.head.text));
582
+ }
583
+ for (const span of node.templateSpans) {
584
+ importedPackages.add('fmt');
585
+ parts.push(`fmt.Sprintf("%v", ${visit(span.expression)})`);
586
+ if (span.literal.text.length > 0) {
587
+ parts.push(toGoStringLiteral(span.literal.text));
588
+ }
589
+ }
590
+ if (parts.length === 0) {
591
+ return '""';
592
+ }
593
+ return parts.join(' + ');
594
+ }
595
+ function hasQuestionDot(node) {
596
+ return ('questionDotToken' in node &&
597
+ !!node.questionDotToken);
598
+ }
599
+ function getTempName(prefix) {
600
+ return `__${prefix}_${goSafeId()}__`;
601
+ }
602
+ function inferExpectedTypeFromContext(node) {
603
+ const parent = node.parent;
604
+ if (ts.isVariableDeclaration(parent) && parent.initializer === node && parent.type) {
605
+ return getType(parent.type);
606
+ }
607
+ if (ts.isReturnStatement(parent)) {
608
+ let scope = parent.parent;
609
+ while (scope) {
610
+ if (ts.isFunctionDeclaration(scope) ||
611
+ ts.isMethodDeclaration(scope) ||
612
+ ts.isFunctionExpression(scope) ||
613
+ ts.isArrowFunction(scope)) {
614
+ if (scope.type)
615
+ return getType(scope.type);
616
+ break;
617
+ }
618
+ scope = scope.parent;
619
+ }
620
+ }
621
+ return undefined;
622
+ }
623
+ function prescanVariableDeclarations(block) {
624
+ for (const stmt of block.statements) {
625
+ if (ts.isVariableStatement(stmt)) {
626
+ for (const decl of stmt.declarationList.declarations) {
627
+ if (ts.isIdentifier(decl.name) && !variableGoTypes.has(decl.name.text)) {
628
+ if (decl.type) {
629
+ variableGoTypes.set(decl.name.text, getType(decl.type));
630
+ }
631
+ else if (decl.initializer) {
632
+ const inferredType = inferExpressionType(decl.initializer);
633
+ if (inferredType)
634
+ variableGoTypes.set(decl.name.text, inferredType);
635
+ }
636
+ }
637
+ }
638
+ }
639
+ }
640
+ }
641
+ function inferFunctionBodyReturnType(node) {
642
+ if (node.type)
643
+ return getType(node.type);
644
+ if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
645
+ return inferExpressionType(node.body);
646
+ }
647
+ if (node.body && ts.isBlock(node.body)) {
648
+ for (const stmt of node.body.statements) {
649
+ if (ts.isReturnStatement(stmt) && stmt.expression) {
650
+ const exprType = inferExpressionType(stmt.expression);
651
+ if (exprType)
652
+ return exprType;
653
+ }
654
+ }
655
+ }
656
+ return undefined;
657
+ }
658
+ function inferArrowFunctionGoType(node) {
659
+ const params = node.parameters
660
+ .map((p) => (p.type ? getType(p.type) : 'interface{}'))
661
+ .join(', ');
662
+ const retType = inferFunctionBodyReturnType(node);
663
+ return `func(${params})${retType ? ` ${retType}` : ''}`;
664
+ }
665
+ function inferExpressionType(expr) {
666
+ if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) {
667
+ return inferArrowFunctionGoType(expr);
668
+ }
669
+ if (ts.isParenthesizedExpression(expr))
670
+ return inferExpressionType(expr.expression);
671
+ if (ts.isNonNullExpression(expr))
672
+ return inferExpressionType(expr.expression);
673
+ if (ts.isAsExpression(expr))
674
+ return getType(expr.type);
675
+ if (ts.isTypeAssertionExpression(expr))
676
+ return getType(expr.type);
677
+ if (ts.isStringLiteral(expr) ||
678
+ ts.isNoSubstitutionTemplateLiteral(expr) ||
679
+ ts.isTemplateExpression(expr)) {
680
+ return 'string';
681
+ }
682
+ if (ts.isNumericLiteral(expr))
683
+ return 'float64';
684
+ if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword)
685
+ return 'bool';
686
+ if (expr.kind === ts.SyntaxKind.NullKeyword)
687
+ return 'nil';
688
+ if (ts.isIdentifier(expr))
689
+ return variableGoTypes.get(expr.text);
690
+ if (ts.isArrayLiteralExpression(expr)) {
691
+ if (expr.elements.length === 0)
692
+ return '[]interface{}';
693
+ const firstElementType = inferExpressionType(expr.elements[0]) ?? 'interface{}';
694
+ return `[]${firstElementType}`;
695
+ }
696
+ if (ts.isNewExpression(expr) && ts.isIdentifier(expr.expression)) {
697
+ const ctorName = expr.expression.text;
698
+ if (ctorName === 'Map' && expr.typeArguments && expr.typeArguments.length === 2) {
699
+ return `map[${getType(expr.typeArguments[0])}]${getType(expr.typeArguments[1])}`;
700
+ }
701
+ if (ctorName === 'Set' && expr.typeArguments && expr.typeArguments.length === 1) {
702
+ return `map[${getType(expr.typeArguments[0])}]struct{}`;
703
+ }
704
+ }
705
+ if (ts.isPropertyAccessExpression(expr)) {
706
+ if (ts.isIdentifier(expr.expression) && enumNames.has(expr.expression.text)) {
707
+ const enumType = getSafeName(expr.expression.text);
708
+ return enumType;
709
+ }
710
+ const leftType = inferExpressionType(expr.expression);
711
+ if (expr.name.text === 'length')
712
+ return 'float64';
713
+ const resolvedLeftType = leftType?.replace(/^\*/, '').replace(/\[.*\]$/, '');
714
+ const resolvedPropertyType = resolvedLeftType
715
+ ? (classPropertyTypes.get(resolvedLeftType)?.get(expr.name.text) ??
716
+ interfacePropertyTypes.get(resolvedLeftType)?.get(expr.name.text))
717
+ : undefined;
718
+ if (hasQuestionDot(expr)) {
719
+ if (leftType && leftType.startsWith('*')) {
720
+ const memberType = resolvedPropertyType ?? 'interface{}';
721
+ return makeNullableType(memberType);
722
+ }
723
+ return 'interface{}';
724
+ }
725
+ if (resolvedPropertyType) {
726
+ return resolvedPropertyType;
727
+ }
728
+ if (ts.isIdentifier(expr.expression)) {
729
+ const className = variableClassNames.get(expr.expression.text);
730
+ const memberType = className
731
+ ? classPropertyTypes.get(className)?.get(expr.name.text)
732
+ : undefined;
733
+ if (memberType)
734
+ return memberType;
735
+ }
736
+ }
737
+ if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
738
+ const methodName = expr.expression.name.text;
739
+ const ownerType = inferExpressionType(expr.expression.expression);
740
+ if (ownerType && ownerType.startsWith('map[')) {
741
+ if (methodName === 'has')
742
+ return 'bool';
743
+ if (methodName === 'get')
744
+ return extractMapValueType(ownerType);
745
+ }
746
+ if (isArrayLikeGoType(ownerType)) {
747
+ const elementType = getArrayElementTypeFromGoType(ownerType);
748
+ if (methodName === 'map') {
749
+ const callback = expr.arguments[0];
750
+ const mappedType = callback
751
+ ? inferArrayCallbackReturnType(callback, elementType, elementType)
752
+ : elementType;
753
+ return `[]${mappedType}`;
754
+ }
755
+ if (methodName === 'filter')
756
+ return `[]${elementType}`;
757
+ if (methodName === 'some')
758
+ return 'bool';
759
+ if (methodName === 'find')
760
+ return elementType;
761
+ if (methodName === 'join')
762
+ return 'string';
763
+ }
764
+ if (ownerType && ownerType.startsWith('*')) {
765
+ const className = ownerType.replace(/^\*/, '').replace(/\[.*\]$/, '');
766
+ const returnType = classMethodReturnTypes.get(className)?.get(methodName);
767
+ if (returnType) {
768
+ if (hasQuestionDot(expr) || hasQuestionDot(expr.expression)) {
769
+ return makeNullableType(returnType);
770
+ }
771
+ return returnType;
772
+ }
773
+ }
774
+ if (ts.isIdentifier(expr.expression.expression)) {
775
+ const className = variableClassNames.get(expr.expression.expression.text);
776
+ const returnType = className
777
+ ? classMethodReturnTypes.get(className)?.get(methodName)
778
+ : undefined;
779
+ if (returnType) {
780
+ if (hasQuestionDot(expr) || hasQuestionDot(expr.expression)) {
781
+ return makeNullableType(returnType);
782
+ }
783
+ return returnType;
784
+ }
785
+ }
786
+ }
787
+ if (ts.isConditionalExpression(expr)) {
788
+ const whenTrueType = inferExpressionType(expr.whenTrue);
789
+ const whenFalseType = inferExpressionType(expr.whenFalse);
790
+ if (whenTrueType && whenTrueType === whenFalseType)
791
+ return whenTrueType;
792
+ return whenTrueType ?? whenFalseType;
793
+ }
794
+ if (ts.isBinaryExpression(expr) &&
795
+ expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
796
+ const leftType = inferExpressionType(expr.left);
797
+ const rightType = inferExpressionType(expr.right);
798
+ if (leftType && leftType.startsWith('*') && rightType === leftType.slice(1)) {
799
+ return rightType;
800
+ }
801
+ return rightType ?? leftType;
802
+ }
803
+ return undefined;
804
+ }
805
+ function makeNullableType(typeName) {
806
+ if (!typeName || typeName === 'interface{}' || typeName.startsWith('*'))
807
+ return typeName || 'interface{}';
808
+ if (['string', 'float64', 'bool'].includes(typeName))
809
+ return `*${typeName}`;
810
+ return typeName;
811
+ }
812
+ function visitConditionalExpression(node) {
813
+ const whenTrue = visit(node.whenTrue);
814
+ const whenFalse = visit(node.whenFalse);
815
+ const resultType = inferExpectedTypeFromContext(node) ||
816
+ (() => {
817
+ const whenTrueType = inferExpressionType(node.whenTrue);
818
+ const whenFalseType = inferExpressionType(node.whenFalse);
819
+ if (whenTrueType && whenTrueType === whenFalseType)
820
+ return whenTrueType;
821
+ return whenTrueType ?? whenFalseType ?? 'interface{}';
822
+ })();
823
+ return `func() ${resultType} { if ${visit(node.condition)} { return ${whenTrue} }; return ${whenFalse} }()`;
824
+ }
825
+ function visitNullishCoalescingExpression(node) {
826
+ const leftType = inferExpressionType(node.left);
827
+ const rightType = inferExpressionType(node.right);
828
+ if (leftType && leftType.startsWith('*')) {
829
+ const leftValueType = leftType.slice(1);
830
+ const expectedType = inferExpectedTypeFromContext(node);
831
+ const resultType = expectedType || (rightType === leftValueType ? leftValueType : (rightType ?? leftType));
832
+ const tmp = getTempName('nullish');
833
+ const leftExpr = visit(node.left);
834
+ const rightExpr = visit(node.right);
835
+ const returnLeft = resultType === leftValueType ? `*${tmp}` : tmp;
836
+ return `func() ${resultType} { ${tmp} := ${leftExpr}; if ${tmp} == nil { return ${rightExpr} }; return ${returnLeft} }()`;
837
+ }
838
+ return visit(node.left);
839
+ }
840
+ function visitOptionalPropertyAccess(node) {
841
+ const baseExpr = visit(node.expression);
842
+ const baseType = inferExpressionType(node.expression);
843
+ if (!baseType || !baseType.startsWith('*')) {
844
+ const objectType = resolveExpressionType(node.expression);
845
+ return getAcessString(baseExpr, visit(node.name), objectType);
846
+ }
847
+ const className = baseType.replace(/^\*/, '').replace(/\[.*\]$/, '');
848
+ const propertyType = classPropertyTypes.get(className)?.get(node.name.text) ?? 'interface{}';
849
+ const nullableType = makeNullableType(propertyType);
850
+ const tmp = getTempName('opt');
851
+ const propertyAccess = `${tmp}.${visit(node.name)}`;
852
+ if (nullableType.startsWith('*') && nullableType.slice(1) === propertyType) {
853
+ const valueTemp = getTempName('optv');
854
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${propertyAccess}; return &${valueTemp} }()`;
855
+ }
856
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${propertyAccess} }()`;
857
+ }
858
+ function visitOptionalElementAccess(node) {
859
+ const baseExpr = visit(node.expression);
860
+ const baseType = inferExpressionType(node.expression);
861
+ if (!baseType || !baseType.startsWith('*')) {
862
+ return `${baseExpr}[int(${visit(node.argumentExpression)})]`;
863
+ }
864
+ const valueType = inferExpectedTypeFromContext(node) ?? 'interface{}';
865
+ const nullableType = makeNullableType(valueType);
866
+ const tmp = getTempName('opte');
867
+ const elementExpr = `${tmp}[int(${visit(node.argumentExpression)})]`;
868
+ if (nullableType.startsWith('*') && nullableType.slice(1) === valueType) {
869
+ const valueTemp = getTempName('optev');
870
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${elementExpr}; return &${valueTemp} }()`;
871
+ }
872
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${elementExpr} }()`;
873
+ }
874
+ function visitOptionalCall(node) {
875
+ if (!ts.isPropertyAccessExpression(node.expression)) {
876
+ return `${visit(node.expression)}(${node.arguments.map((a) => visit(a)).join(', ')})`;
877
+ }
878
+ const baseNode = node.expression.expression;
879
+ const methodName = node.expression.name.text;
880
+ const baseExpr = visit(baseNode);
881
+ const baseType = inferExpressionType(baseNode);
882
+ const args = node.arguments.map((a) => visit(a)).join(', ');
883
+ if (!baseType || !baseType.startsWith('*')) {
884
+ return `${baseExpr}.${methodName}(${args})`;
885
+ }
886
+ const className = baseType.replace(/^\*/, '').replace(/\[.*\]$/, '');
887
+ const returnType = classMethodReturnTypes.get(className)?.get(methodName) ?? 'interface{}';
888
+ const nullableType = makeNullableType(returnType);
889
+ const tmp = getTempName('optc');
890
+ const callExpr = `${tmp}.${methodName}(${args})`;
891
+ if (nullableType.startsWith('*') && nullableType.slice(1) === returnType) {
892
+ const valueTemp = getTempName('optcv');
893
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; ${valueTemp} := ${callExpr}; return &${valueTemp} }()`;
894
+ }
895
+ return `func() ${nullableType} { ${tmp} := ${baseExpr}; if ${tmp} == nil { var __zero ${nullableType}; return __zero }; return ${callExpr} }()`;
896
+ }
897
+ function isArrayLikeGoType(goType) {
898
+ return !!goType && goType.startsWith('[]');
899
+ }
900
+ function getArrayElementTypeFromGoType(goType) {
901
+ if (!goType.startsWith('[]'))
902
+ return 'interface{}';
903
+ const elementType = goType.slice(2);
904
+ return elementType || 'interface{}';
905
+ }
906
+ function inferArrayCallbackReturnType(callback, elementType, fallbackType) {
907
+ if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
908
+ if (callback.type) {
909
+ const explicitType = getType(callback.type);
910
+ return explicitType || fallbackType;
911
+ }
912
+ if (ts.isBlock(callback.body)) {
913
+ return fallbackType;
914
+ }
915
+ const inferred = inferExpressionType(callback.body);
916
+ return inferred ?? fallbackType;
917
+ }
918
+ if (ts.isIdentifier(callback)) {
919
+ const knownType = variableGoTypes.get(callback.text);
920
+ if (knownType)
921
+ return knownType;
922
+ }
923
+ return fallbackType;
924
+ }
925
+ function buildArrayCallbackInfo(callback, elementType, forcedReturnType) {
926
+ if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
927
+ const paramCount = callback.parameters.length;
928
+ const callbackReturnType = forcedReturnType ?? inferArrayCallbackReturnType(callback, elementType, 'interface{}');
929
+ const params = [];
930
+ if (paramCount > 0) {
931
+ params.push(`${visit(callback.parameters[0].name)} ${elementType}`);
932
+ }
933
+ if (paramCount > 1) {
934
+ params.push(`${visit(callback.parameters[1].name)} float64`);
935
+ }
936
+ if (paramCount > 2) {
937
+ params.push(`${visit(callback.parameters[2].name)} []${elementType}`);
938
+ }
939
+ const body = ts.isBlock(callback.body)
940
+ ? visit(callback.body)
941
+ : `{\n\t\treturn ${visit(callback.body)};\n\t}`;
942
+ return {
943
+ fnExpr: `func(${params.join(', ')}) ${callbackReturnType} ${body}`,
944
+ paramCount,
945
+ returnType: callbackReturnType
946
+ };
947
+ }
948
+ return {
949
+ fnExpr: visit(callback),
950
+ paramCount: 1,
951
+ returnType: forcedReturnType ?? 'interface{}'
952
+ };
953
+ }
954
+ function buildArrayCallbackInvocation(callbackInfo, itemVar, indexVar, arrayVar) {
955
+ const args = [];
956
+ if (callbackInfo.paramCount > 0)
957
+ args.push(itemVar);
958
+ if (callbackInfo.paramCount > 1)
959
+ args.push(`float64(${indexVar})`);
960
+ if (callbackInfo.paramCount > 2)
961
+ args.push(arrayVar);
962
+ return `(${callbackInfo.fnExpr})(${args.join(', ')})`;
963
+ }
964
+ function visitArrayHigherOrderCall(node) {
965
+ if (!ts.isPropertyAccessExpression(node.expression))
966
+ return undefined;
967
+ const methodName = node.expression.name.text;
968
+ if (!['map', 'filter', 'some', 'find', 'join'].includes(methodName)) {
969
+ return undefined;
970
+ }
971
+ const arrayExprNode = node.expression.expression;
972
+ const arrayExpr = visit(arrayExprNode);
973
+ const ownerType = inferExpressionType(arrayExprNode);
974
+ const elementType = isArrayLikeGoType(ownerType)
975
+ ? getArrayElementTypeFromGoType(ownerType)
976
+ : 'interface{}';
977
+ if (methodName === 'join') {
978
+ importedPackages.add('strings');
979
+ importedPackages.add('fmt');
980
+ const separator = node.arguments[0] ? visit(node.arguments[0]) : '""';
981
+ const arrVar = getTempName('arrjoin');
982
+ const partsVar = getTempName('parts');
983
+ return `func() string { ${arrVar} := ${arrayExpr}; ${partsVar} := make([]string, len(${arrVar})); for i, v := range ${arrVar} { ${partsVar}[i] = fmt.Sprintf("%v", v) }; return strings.Join(${partsVar}, ${separator}) }()`;
984
+ }
985
+ const callback = node.arguments[0];
986
+ if (!callback) {
987
+ return undefined;
988
+ }
989
+ const arrVar = getTempName('arrhof');
990
+ const idxVar = getTempName('i');
991
+ const itemVar = getTempName('item');
992
+ if (methodName === 'map') {
993
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, elementType);
994
+ const mappedType = callbackInfo.returnType || 'interface{}';
995
+ const resultVar = getTempName('mapres');
996
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
997
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
998
+ return `func() []${mappedType} { ${arrVar} := ${arrayExpr}; ${resultVar} := make([]${mappedType}, 0, len(${arrVar})); for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { ${resultVar} = append(${resultVar}, ${callbackCall}) }; return ${resultVar} }()`;
999
+ }
1000
+ if (methodName === 'filter') {
1001
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1002
+ const resultVar = getTempName('filterres');
1003
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1004
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1005
+ return `func() []${elementType} { ${arrVar} := ${arrayExpr}; ${resultVar} := make([]${elementType}, 0, len(${arrVar})); for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { ${resultVar} = append(${resultVar}, ${itemVar}) } }; return ${resultVar} }()`;
1006
+ }
1007
+ if (methodName === 'some') {
1008
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1009
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1010
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1011
+ return `func() bool { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return true } }; return false }()`;
1012
+ }
1013
+ const callbackInfo = buildArrayCallbackInfo(callback, elementType, 'bool');
1014
+ const rangeIndexVar = callbackInfo.paramCount > 1 ? idxVar : '_';
1015
+ const callbackCall = buildArrayCallbackInvocation(callbackInfo, itemVar, idxVar, arrVar);
1016
+ return `func() ${elementType} { ${arrVar} := ${arrayExpr}; for ${rangeIndexVar}, ${itemVar} := range ${arrVar} { if ${callbackCall} { return ${itemVar} } }; var __zero ${elementType}; return __zero }()`;
1017
+ }
1018
+ function getAliasType(name, seen = new Set()) {
1019
+ const aliasType = typeAliases.get(name);
1020
+ if (!aliasType)
1021
+ return undefined;
1022
+ if (seen.has(name))
1023
+ return undefined;
1024
+ if (ts.isTypeReferenceNode(aliasType) && ts.isIdentifier(aliasType.typeName)) {
1025
+ const nestedName = aliasType.typeName.text;
1026
+ if (typeAliases.has(nestedName)) {
1027
+ seen.add(name);
1028
+ return getAliasType(nestedName, seen) ?? aliasType;
1029
+ }
1030
+ }
1031
+ return aliasType;
1032
+ }
1033
+ function getOptionalNodeType(typeNode, isOptional) {
1034
+ const baseType = typeNode ? getType(typeNode) : 'interface{}';
1035
+ if (!isOptional)
1036
+ return baseType;
1037
+ if (baseType === 'interface{}' || baseType.startsWith('*'))
1038
+ return baseType;
1039
+ if (['string', 'float64', 'bool'].includes(baseType))
1040
+ return `*${baseType}`;
1041
+ return baseType;
1042
+ }
1043
+ function getEnumMemberName(name) {
1044
+ if (ts.isIdentifier(name)) {
1045
+ return getSafeName(name.text);
1046
+ }
1047
+ if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
1048
+ const sanitized = name.text.replace(/[^a-zA-Z0-9_]/g, '_');
1049
+ return sanitized.length > 0 ? sanitized : 'Member';
1050
+ }
1051
+ return 'Member';
1052
+ }
1053
+ function getEnumBaseType(node) {
1054
+ for (const member of node.members) {
1055
+ const initializer = member.initializer;
1056
+ if (!initializer)
1057
+ continue;
1058
+ if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
1059
+ return 'string';
1060
+ }
1061
+ }
1062
+ return 'float64';
1063
+ }
1064
+ function readNumericEnumInitializer(initializer) {
1065
+ if (ts.isNumericLiteral(initializer)) {
1066
+ return Number(initializer.text);
1067
+ }
1068
+ if (ts.isPrefixUnaryExpression(initializer) &&
1069
+ initializer.operator === ts.SyntaxKind.MinusToken &&
1070
+ ts.isNumericLiteral(initializer.operand)) {
1071
+ return -Number(initializer.operand.text);
1072
+ }
1073
+ return undefined;
1074
+ }
1075
+ function visitEnumDeclaration(node) {
1076
+ const enumName = getSafeName(node.name.text);
1077
+ const baseType = enumBaseTypes.get(node.name.text) ?? getEnumBaseType(node);
1078
+ let nextNumericValue = 0;
1079
+ let canAutoIncrement = true;
1080
+ const members = [];
1081
+ for (const member of node.members) {
1082
+ const memberName = getEnumMemberName(member.name);
1083
+ const symbolName = `${enumName}_${memberName}`;
1084
+ let valueExpr;
1085
+ if (member.initializer) {
1086
+ if (baseType === 'float64') {
1087
+ const numericValue = readNumericEnumInitializer(member.initializer);
1088
+ if (numericValue !== undefined) {
1089
+ valueExpr = `${numericValue}`;
1090
+ nextNumericValue = numericValue + 1;
1091
+ canAutoIncrement = true;
1092
+ }
1093
+ else {
1094
+ valueExpr = `float64(${visit(member.initializer)})`;
1095
+ canAutoIncrement = false;
1096
+ }
1097
+ }
1098
+ else {
1099
+ valueExpr = visit(member.initializer);
1100
+ }
1101
+ }
1102
+ else if (baseType === 'float64') {
1103
+ const currentValue = canAutoIncrement ? nextNumericValue : 0;
1104
+ valueExpr = `${currentValue}`;
1105
+ nextNumericValue = currentValue + 1;
1106
+ }
1107
+ else {
1108
+ valueExpr = toGoStringLiteral(memberName);
1109
+ }
1110
+ members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
1111
+ }
1112
+ return `type ${enumName} ${baseType}\n\nvar (\n${members.join('\n')}\n)`;
1113
+ }
411
1114
  function getType(typeNode, getArrayType = false) {
412
1115
  if (!typeNode)
413
1116
  return ':';
1117
+ if (ts.isArrayTypeNode(typeNode)) {
1118
+ const elementType = getType(typeNode.elementType);
1119
+ return getArrayType ? elementType : `[]${elementType}`;
1120
+ }
414
1121
  // Handle union types (e.g. string | null, number | undefined)
415
1122
  if (ts.isUnionTypeNode(typeNode)) {
416
1123
  const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
@@ -428,14 +1135,37 @@ function getType(typeNode, getArrayType = false) {
428
1135
  // Non-nullable union or multi-type union → interface{}
429
1136
  return 'interface{}';
430
1137
  }
1138
+ if (ts.isFunctionTypeNode(typeNode)) {
1139
+ const params = typeNode.parameters
1140
+ .map((p) => (p.type ? getType(p.type) : 'interface{}'))
1141
+ .join(', ');
1142
+ const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
1143
+ return `func(${params})${ret}`;
1144
+ }
431
1145
  if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
432
1146
  const name = typeNode.typeName.text;
1147
+ if (enumNames.has(name)) {
1148
+ return getSafeName(name);
1149
+ }
1150
+ const aliasType = getAliasType(name);
1151
+ if (aliasType) {
1152
+ return getType(aliasType, getArrayType);
1153
+ }
433
1154
  if (name === 'Promise' && typeNode.typeArguments && typeNode.typeArguments.length > 0) {
434
1155
  return `chan ${getType(typeNode.typeArguments[0])}`;
435
1156
  }
436
1157
  if (name === 'RegExp') {
437
1158
  return '*regexp.Regexp';
438
1159
  }
1160
+ if (name === 'Map' && typeNode.typeArguments && typeNode.typeArguments.length === 2) {
1161
+ const keyType = getType(typeNode.typeArguments[0]);
1162
+ const valueType = getType(typeNode.typeArguments[1]);
1163
+ return `map[${keyType}]${valueType}`;
1164
+ }
1165
+ if (name === 'Set' && typeNode.typeArguments && typeNode.typeArguments.length === 1) {
1166
+ const elementType = getType(typeNode.typeArguments[0]);
1167
+ return `map[${elementType}]struct{}`;
1168
+ }
439
1169
  const typeArgs = getTypeArguments(typeNode.typeArguments);
440
1170
  if (classNames.has(name)) {
441
1171
  return `*${name}${typeArgs}`;
@@ -486,12 +1216,30 @@ function getTypeCategory(typeNode) {
486
1216
  }
487
1217
  if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
488
1218
  const name = typeNode.typeName.text;
1219
+ if (name === 'Map')
1220
+ return 'Map';
1221
+ if (name === 'Set')
1222
+ return 'Set';
489
1223
  if (classNames.has(name))
490
1224
  return 'class';
491
1225
  return name;
492
1226
  }
493
1227
  return undefined;
494
1228
  }
1229
+ function getClassNameFromTypeNode(typeNode) {
1230
+ if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) {
1231
+ return classNames.has(typeNode.typeName.text) ? typeNode.typeName.text : undefined;
1232
+ }
1233
+ if (ts.isUnionTypeNode(typeNode)) {
1234
+ const nonNullTypes = typeNode.types.filter((t) => t.kind !== ts.SyntaxKind.NullKeyword &&
1235
+ t.kind !== ts.SyntaxKind.UndefinedKeyword &&
1236
+ !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword));
1237
+ if (nonNullTypes.length === 1) {
1238
+ return getClassNameFromTypeNode(nonNullTypes[0]);
1239
+ }
1240
+ }
1241
+ return undefined;
1242
+ }
495
1243
  function resolveExpressionType(expr) {
496
1244
  if (ts.isIdentifier(expr)) {
497
1245
  return variableTypes.get(expr.text);
@@ -512,6 +1260,9 @@ function getAcessString(leftSide, rightSide, objectType) {
512
1260
  if (rightSide === 'length' && objectType !== 'class') {
513
1261
  return `float64(len(${leftSide}))`;
514
1262
  }
1263
+ if (rightSide === 'size' && (objectType === 'Map' || objectType === 'Set')) {
1264
+ return `float64(len(${leftSide}))`;
1265
+ }
515
1266
  return `${leftSide}.${rightSide}`;
516
1267
  }
517
1268
  const callHandlers = {
@@ -667,6 +1418,25 @@ const arrayMethodHandlers = {
667
1418
  return `fmt.Sprintf("%v", ${obj})`;
668
1419
  }
669
1420
  };
1421
+ const mapMethodHandlers = {
1422
+ set: (obj, args) => `${obj}[${args[0]}] = ${args[1]}`,
1423
+ get: (obj, args) => `${obj}[${args[0]}]`,
1424
+ has: (obj, args) => {
1425
+ const tmp = getTempName('ok');
1426
+ return `func() bool { _, ${tmp} := ${obj}[${args[0]}]; return ${tmp} }()`;
1427
+ },
1428
+ delete: (obj, args) => `delete(${obj}, ${args[0]})`,
1429
+ clear: (obj) => `clear(${obj})`
1430
+ };
1431
+ const setMethodHandlers = {
1432
+ add: (obj, args) => `${obj}[${args[0]}] = struct{}{}`,
1433
+ has: (obj, args) => {
1434
+ const tmp = getTempName('ok');
1435
+ return `func() bool { _, ${tmp} := ${obj}[${args[0]}]; return ${tmp} }()`;
1436
+ },
1437
+ delete: (obj, args) => `delete(${obj}, ${args[0]})`,
1438
+ clear: (obj) => `clear(${obj})`
1439
+ };
670
1440
  function getDynamicCallHandler(caller, objectType) {
671
1441
  if (promiseResolveName && caller === promiseResolveName) {
672
1442
  return (_caller, args) => `ch <- ${args[0]}`;
@@ -695,6 +1465,12 @@ function getDynamicCallHandler(caller, objectType) {
695
1465
  else if (objectType === 'RegExp') {
696
1466
  handler = regexpMethodHandlers[methodName];
697
1467
  }
1468
+ else if (objectType === 'Map') {
1469
+ handler = mapMethodHandlers[methodName];
1470
+ }
1471
+ else if (objectType === 'Set') {
1472
+ handler = setMethodHandlers[methodName];
1473
+ }
698
1474
  else {
699
1475
  // Unknown type: try both maps for backward compatibility
700
1476
  handler =
@@ -772,6 +1548,59 @@ function getTypeArguments(typeArguments) {
772
1548
  const args = typeArguments.map((ta) => getType(ta));
773
1549
  return `[${args.join(', ')}]`;
774
1550
  }
1551
+ function getParameterGoType(param) {
1552
+ if (param.type) {
1553
+ const explicitType = getType(param.type);
1554
+ return explicitType === ':' ? 'interface{}' : explicitType;
1555
+ }
1556
+ if (param.initializer) {
1557
+ const inferredType = inferExpressionType(param.initializer);
1558
+ if (inferredType && inferredType !== 'nil' && inferredType !== ':') {
1559
+ return inferredType;
1560
+ }
1561
+ }
1562
+ return 'interface{}';
1563
+ }
1564
+ function getFunctionParametersInfo(parameters) {
1565
+ if (parameters.length === 0) {
1566
+ return { signature: '', prefixBlockContent: '' };
1567
+ }
1568
+ const firstDefaultIndex = parameters.findIndex((p) => !!p.initializer);
1569
+ if (firstDefaultIndex === -1) {
1570
+ return {
1571
+ signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
1572
+ prefixBlockContent: ''
1573
+ };
1574
+ }
1575
+ const hasRequiredAfterDefault = parameters
1576
+ .slice(firstDefaultIndex)
1577
+ .some((p) => !p.initializer);
1578
+ if (hasRequiredAfterDefault) {
1579
+ return {
1580
+ signature: parameters.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`).join(', '),
1581
+ prefixBlockContent: ''
1582
+ };
1583
+ }
1584
+ const requiredParams = parameters.slice(0, firstDefaultIndex);
1585
+ const defaultedParams = parameters.slice(firstDefaultIndex);
1586
+ const signatureParts = requiredParams.map((p) => `${visit(p.name)} ${getParameterGoType(p)}`);
1587
+ signatureParts.push('__defaultArgs ...interface{}');
1588
+ const prefixBlockContent = defaultedParams
1589
+ .map((param, index) => {
1590
+ const paramName = visit(param.name);
1591
+ const paramType = getParameterGoType(param);
1592
+ const defaultValue = visit(param.initializer);
1593
+ if (paramType === 'interface{}') {
1594
+ return `var ${paramName} interface{}\n\t\tif len(__defaultArgs) > ${index} {\n\t\t\t${paramName} = __defaultArgs[${index}]\n\t\t} else {\n\t\t\t${paramName} = ${defaultValue}\n\t\t}\n\t\t`;
1595
+ }
1596
+ return `var ${paramName} ${paramType}\n\t\tif len(__defaultArgs) > ${index} {\n\t\t\t${paramName} = __defaultArgs[${index}].(${paramType})\n\t\t} else {\n\t\t\t${paramName} = ${defaultValue}\n\t\t}\n\t\t`;
1597
+ })
1598
+ .join('');
1599
+ return {
1600
+ signature: signatureParts.join(', '),
1601
+ prefixBlockContent
1602
+ };
1603
+ }
775
1604
  function getSafeName(name) {
776
1605
  if (!dangerousNames.has(name)) {
777
1606
  return name;
@@ -831,3 +1660,89 @@ function visitNewPromise(node) {
831
1660
  promiseResolveName = prevResolveName;
832
1661
  return `func() chan ${channelType} {\n\t\tch := make(chan ${channelType})\n\t\tgo func() ${body.trimEnd()}()\n\t\treturn ch;\n\t}()`;
833
1662
  }
1663
+ function extractMapValueType(mapType) {
1664
+ // mapType is "map[K]V" — find the closing bracket of K accounting for nesting
1665
+ if (!mapType.startsWith('map['))
1666
+ return 'interface{}';
1667
+ let depth = 0;
1668
+ for (let i = 4; i < mapType.length; i++) {
1669
+ if (mapType[i] === '[')
1670
+ depth++;
1671
+ else if (mapType[i] === ']') {
1672
+ if (depth === 0)
1673
+ return mapType.substring(i + 1);
1674
+ depth--;
1675
+ }
1676
+ }
1677
+ return 'interface{}';
1678
+ }
1679
+ function visitNewMap(node) {
1680
+ let keyType = 'interface{}';
1681
+ let valueType = 'interface{}';
1682
+ if (node.typeArguments && node.typeArguments.length === 2) {
1683
+ keyType = getType(node.typeArguments[0]);
1684
+ valueType = getType(node.typeArguments[1]);
1685
+ }
1686
+ const mapType = `map[${keyType}]${valueType}`;
1687
+ const args = node.arguments;
1688
+ if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
1689
+ return `make(${mapType})`;
1690
+ }
1691
+ const initArg = args[0];
1692
+ const tmp = getTempName('map');
1693
+ const entries = initArg.elements
1694
+ .filter((el) => ts.isArrayLiteralExpression(el) && el.elements.length >= 2)
1695
+ .map((el) => {
1696
+ const pair = el;
1697
+ return `${tmp}[${visit(pair.elements[0])}] = ${visit(pair.elements[1])}`;
1698
+ })
1699
+ .join('; ');
1700
+ return `func() ${mapType} { ${tmp} := make(${mapType}); ${entries}; return ${tmp} }()`;
1701
+ }
1702
+ function visitNewSet(node) {
1703
+ let elementType = 'interface{}';
1704
+ if (node.typeArguments && node.typeArguments.length === 1) {
1705
+ elementType = getType(node.typeArguments[0]);
1706
+ }
1707
+ const setType = `map[${elementType}]struct{}`;
1708
+ const args = node.arguments;
1709
+ if (!args || args.length === 0 || !ts.isArrayLiteralExpression(args[0])) {
1710
+ return `make(${setType})`;
1711
+ }
1712
+ const initArg = args[0];
1713
+ const tmp = getTempName('set');
1714
+ const values = initArg.elements.map((el) => `${tmp}[${visit(el)}] = struct{}{}`).join('; ');
1715
+ return `func() ${setType} { ${tmp} := make(${setType}); ${values}; return ${tmp} }()`;
1716
+ }
1717
+ function getForOfVarNames(initializer) {
1718
+ if (!ts.isVariableDeclarationList(initializer) || initializer.declarations.length === 0) {
1719
+ return ['_'];
1720
+ }
1721
+ const decl = initializer.declarations[0];
1722
+ if (ts.isArrayBindingPattern(decl.name)) {
1723
+ return decl.name.elements.map((el) => {
1724
+ if (ts.isOmittedExpression(el))
1725
+ return '_';
1726
+ return visit(el.name);
1727
+ });
1728
+ }
1729
+ return [visit(decl.name)];
1730
+ }
1731
+ function visitTryStatement(node, options) {
1732
+ const deferreds = [];
1733
+ // Register finally first (LIFO: runs last after catch)
1734
+ if (node.finallyBlock) {
1735
+ const finallyBody = node.finallyBlock.statements.map((s) => visit(s)).join('\t');
1736
+ deferreds.push(`defer func() {\n\t\t\t${finallyBody}\t\t\t}()`);
1737
+ }
1738
+ // Register catch second (LIFO: runs first, handles panic via recover)
1739
+ if (node.catchClause) {
1740
+ const varDecl = node.catchClause.variableDeclaration;
1741
+ const catchVar = varDecl ? visit(varDecl.name) : '_r';
1742
+ const catchBody = node.catchClause.block.statements.map((s) => visit(s)).join('\t');
1743
+ deferreds.push(`defer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\t${catchVar} := r\n\t\t\t\t_ = ${catchVar}\n\t\t\t\t${catchBody}\t\t\t}\n\t\t\t}()`);
1744
+ }
1745
+ const tryBody = node.tryBlock.statements.map((s) => visit(s)).join('\t');
1746
+ const body = [...deferreds, tryBody].join('\n\t\t\t');
1747
+ return `func() {\n\t\t\t${body}\n\t\t\t}()` + (options.inline ? '' : ';\n\t');
1748
+ }